Beispiel #1
0
 def test_build_page_error(self, site_dir, mock_write_file):
     cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[])
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     page = files.documentation_pages()[0].page
     # Fake populate page
     page.title = 'Title'
     page.source = 'page content'
     page.content = '<p>page content</p>'
     with self.assertLogs('mkdocs', level='ERROR') as cm:
         self.assertRaises(
             OSError,
             build._build_page,
             page,
             cfg,
             files,
             nav,
             cfg['theme'].get_env(),
         )
     self.assertEqual(
         cm.output,
         [
             "ERROR:mkdocs.commands.build:Error building page 'index.md': Error message."
         ],
     )
     self.assert_mock_called_once(mock_write_file)
Beispiel #2
0
 def test_build_page_dirty_not_modified(self, site_dir, mock_write_file):
     cfg = load_config(site_dir=site_dir, nav=['testing.md'], plugins=[])
     files = Files([
         File(
             'testing.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     page = files.documentation_pages()[0].page
     # Fake populate page
     page.title = 'Title'
     page.source = 'page content'
     page.content = '<p>page content</p>'
     build._build_page(
         page,
         cfg,
         files,
         nav,
         cfg['theme'].get_env(),
         dirty=True,
     )
     self.assert_mock_called_once(mock_write_file)
Beispiel #3
0
 def test_populate_page_read_error(self, docs_dir, mock_open):
     cfg = load_config(docs_dir=docs_dir)
     file = File(
         'missing.md',
         cfg['docs_dir'],
         cfg['site_dir'],
         cfg['use_directory_urls'],
     )
     page = Page('Foo', file, cfg)
     with self.assertLogs('mkdocs', level='ERROR') as cm:
         self.assertRaises(
             OSError,
             build._populate_page,
             page,
             cfg,
             Files([file]),
         )
     self.assertEqual(
         cm.output,
         [
             'ERROR:mkdocs.structure.pages:File not found: missing.md',
             "ERROR:mkdocs.commands.build:Error reading page 'missing.md': Error message.",
         ],
     )
     self.assert_mock_called_once(mock_open)
Beispiel #4
0
 def test_nav_bad_links(self):
     nav_cfg = [{
         'Home': 'index.md'
     }, {
         'Missing': 'missing.html'
     }, {
         'Bad External': 'example.com'
     }]
     expected = dedent("""
     Page(title='Home', url='/')
     Link(title='Missing', url='missing.html')
     Link(title='Bad External', url='example.com')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     with self.assertLogs('mkdocs', level='WARNING') as cm:
         site_navigation = get_navigation(files, cfg)
     self.assertEqual(cm.output, [
         "WARNING:mkdocs.structure.nav:A relative path to 'missing.html' is included "
         "in the 'nav' configuration, which is not found in the documentation files",
         "WARNING:mkdocs.structure.nav:A relative path to 'example.com' is included "
         "in the 'nav' configuration, which is not found in the documentation files"
     ])
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 3)
     self.assertEqual(len(site_navigation.pages), 1)
Beispiel #5
0
 def test_nested_ungrouped_nav(self):
     nav_cfg = [
         {
             'Home': 'index.md'
         },
         {
             'Contact': 'about/contact.md'
         },
         {
             'License Title': 'about/sub/license.md'
         },
     ]
     expected = dedent("""
     Page(title='Home', url='/')
     Page(title='Contact', url='/about/contact/')
     Page(title='License Title', url='/about/sub/license/')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File(
             list(item.values())[0], cfg['docs_dir'], cfg['site_dir'],
             cfg['use_directory_urls']) for item in nav_cfg
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 3)
     self.assertEqual(len(site_navigation.pages), 3)
Beispiel #6
0
 def test_nav_external_links(self):
     nav_cfg = [{
         'Home': 'index.md'
     }, {
         'Local': '/local.html'
     }, {
         'External': 'http://example.com/external.html'
     }]
     expected = dedent("""
     Page(title='Home', url='/')
     Link(title='Local', url='/local.html')
     Link(title='External', url='http://example.com/external.html')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     with self.assertLogs('mkdocs', level='DEBUG') as cm:
         site_navigation = get_navigation(files, cfg)
     self.assertEqual(cm.output, [
         "DEBUG:mkdocs.structure.nav:An absolute path to '/local.html' is included in the "
         "'nav' configuration, which presumably points to an external resource.",
         "DEBUG:mkdocs.structure.nav:An external link to 'http://example.com/external.html' "
         "is included in the 'nav' configuration."
     ])
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 3)
     self.assertEqual(len(site_navigation.pages), 1)
Beispiel #7
0
 def test_context_extra_css_js_from_nested_page_use_directory_urls(self):
     nav_cfg = [
         {
             'Home': 'index.md'
         },
         {
             'Nested': 'foo/bar.md'
         },
     ]
     cfg = load_config(
         nav=nav_cfg,
         extra_css=['style.css'],
         extra_javascript=['script.js'],
     )
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
         File(
             'foo/bar.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     context = build.get_context(nav, files, cfg, nav.pages[1])
     self.assertEqual(context['extra_css'], ['../../style.css'])
     self.assertEqual(context['extra_javascript'], ['../../script.js'])
Beispiel #8
0
 def test_context_base_url_nested_page(self):
     nav_cfg = [
         {
             'Home': 'index.md'
         },
         {
             'Nested': 'foo/bar.md'
         },
     ]
     cfg = load_config(nav=nav_cfg, use_directory_urls=False)
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
         File(
             'foo/bar.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     context = build.get_context(nav, files, cfg, nav.pages[1])
     self.assertEqual(context['base_url'], '..')
Beispiel #9
0
 def test_build_page(self, site_dir):
     cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[])
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     page = files.documentation_pages()[0].page
     # Fake populate page
     page.title = 'Title'
     page.source = 'page content'
     page.content = '<p>page content</p>'
     build._build_page(page, cfg, files, nav, cfg['theme'].get_env())
     self.assertPathIsFile(site_dir, 'index.html')
Beispiel #10
0
 def test_get_by_type_nested_sections(self):
     nav_cfg = [{'Section 1': [{'Section 2': [{'Page': 'page.md'}]}]}]
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('page.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(len(_get_by_type(site_navigation, Section)), 2)
Beispiel #11
0
 def test_populate_page(self, docs_dir):
     cfg = load_config(docs_dir=docs_dir)
     file = File(
         'index.md',
         cfg['docs_dir'],
         cfg['site_dir'],
         cfg['use_directory_urls'],
     )
     page = Page('Foo', file, cfg)
     build._populate_page(page, cfg, Files([file]))
     self.assertEqual(page.content, '<p>page content</p>')
Beispiel #12
0
 def test_build_extra_template(self):
     cfg = load_config()
     files = Files([
         File(
             'foo.html',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     build._build_extra_template('foo.html', files, cfg, mock.Mock())
Beispiel #13
0
 def get_rendered_result(self, files):
     cfg = load_config(docs_dir=self.DOCS_DIR)
     fs = []
     for f in files:
         fs.append(
             File(f.replace('/', os.sep), cfg['docs_dir'], cfg['site_dir'],
                  cfg['use_directory_urls']))
     pg = Page('Foo', fs[0], cfg)
     pg.read_source(cfg)
     pg.render(cfg, Files(fs))
     return pg.content
Beispiel #14
0
 def test_populate_page_dirty_not_modified(self, site_dir, docs_dir):
     cfg = load_config(docs_dir=docs_dir, site_dir=site_dir)
     file = File(
         'index.md',
         cfg['docs_dir'],
         cfg['site_dir'],
         cfg['use_directory_urls'],
     )
     page = Page('Foo', file, cfg)
     build._populate_page(page, cfg, Files([file]), dirty=True)
     # Content is empty as file read was skipped
     self.assertEqual(page.source, None)
     self.assertEqual(page.content, None)
Beispiel #15
0
 def test_populate_page_dirty_modified(self, site_dir):
     cfg = load_config(site_dir=site_dir)
     file = File(
         'testing.md',
         cfg['docs_dir'],
         cfg['site_dir'],
         cfg['use_directory_urls'],
     )
     page = Page('Foo', file, cfg)
     build._populate_page(page, cfg, Files([file]), dirty=True)
     self.assertTrue(page.source.startswith('# Welcome to MkDocs'))
     self.assertTrue(
         page.content.startswith(
             '<h1 id="welcome-to-mkdocs">Welcome to MkDocs</h1>', ), )
Beispiel #16
0
def build_page(title, path, config, md_src=''):
    """ Helper which returns a Page object. """

    files = Files([
        File(
            path,
            config['docs_dir'],
            config['site_dir'],
            config['use_directory_urls'],
        ),
    ])
    page = Page(title, list(files)[0], config)
    # Fake page.read_source()
    page.source, page.meta = meta.get_data(md_src)
    return page, files
Beispiel #17
0
 def test_nav_no_title(self):
     nav_cfg = ['index.md', {'About': 'about.md'}]
     expected = dedent("""
     Page(title=[blank], url='/')
     Page(title='About', url='/about/')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File(nav_cfg[0], cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File(nav_cfg[1]['About'], cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 2)
     self.assertEqual(len(site_navigation.pages), 2)
Beispiel #18
0
 def test_nav_from_files(self):
     expected = dedent("""
     Page(title=[blank], url='/')
     Page(title=[blank], url='/about/')
     """)
     cfg = load_config(site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('about.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 2)
     self.assertEqual(len(site_navigation.pages), 2)
     self.assertEqual(repr(site_navigation.homepage),
                      "Page(title=[blank], url='/')")
Beispiel #19
0
 def test_context_base_url_homepage_use_directory_urls(self):
     nav_cfg = [
         {
             'Home': 'index.md'
         },
     ]
     cfg = load_config(nav=nav_cfg)
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     context = build.get_context(nav, files, cfg, nav.pages[0])
     self.assertEqual(context['base_url'], '.')
Beispiel #20
0
 def test_skip_ioerror_extra_template(self, mock_open):
     cfg = load_config()
     files = Files([
         File(
             'foo.html',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     with self.assertLogs('mkdocs', level='INFO') as cm:
         build._build_extra_template('foo.html', files, cfg, mock.Mock())
     self.assertEqual(
         cm.output,
         [
             "WARNING:mkdocs.commands.build:Error reading template 'foo.html': Error message."
         ],
     )
Beispiel #21
0
 def test_nav_missing_page(self):
     nav_cfg = [{'Home': 'index.md'}]
     expected = dedent("""
     Page(title='Home', url='/')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('page_not_in_nav.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls'])
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 1)
     self.assertEqual(len(site_navigation.pages), 1)
     for file in files:
         self.assertIsInstance(file.page, Page)
Beispiel #22
0
 def test_nav_no_directory_urls(self):
     nav_cfg = [{'Home': 'index.md'}, {'About': 'about.md'}]
     expected = dedent("""
     Page(title='Home', url='/index.html')
     Page(title='About', url='/about.html')
     """)
     cfg = load_config(nav=nav_cfg,
                       use_directory_urls=False,
                       site_url='http://example.com/')
     files = Files([
         File(
             list(item.values())[0], cfg['docs_dir'], cfg['site_dir'],
             cfg['use_directory_urls']) for item in nav_cfg
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 2)
     self.assertEqual(len(site_navigation.pages), 2)
Beispiel #23
0
 def test_skip_extra_template_empty_output(self):
     cfg = load_config()
     files = Files([
         File(
             'foo.html',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     with self.assertLogs('mkdocs', level='INFO') as cm:
         build._build_extra_template('foo.html', files, cfg, mock.Mock())
     self.assertEqual(
         cm.output,
         [
             "INFO:mkdocs.commands.build:Template skipped: 'foo.html' generated empty output."
         ],
     )
Beispiel #24
0
    def test_nested_ungrouped_nav_no_titles(self):
        nav_cfg = ['index.md', 'about/contact.md', 'about/sub/license.md']
        expected = dedent("""
        Page(title=[blank], url='/')
        Page(title=[blank], url='/about/contact/')
        Page(title=[blank], url='/about/sub/license/')
        """)

        cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
        files = Files([
            File(item, cfg['docs_dir'], cfg['site_dir'],
                 cfg['use_directory_urls']) for item in nav_cfg
        ])
        site_navigation = get_navigation(files, cfg)
        self.assertEqual(str(site_navigation).strip(), expected)
        self.assertEqual(len(site_navigation.items), 3)
        self.assertEqual(len(site_navigation.pages), 3)
        self.assertEqual(repr(site_navigation.homepage),
                         "Page(title=[blank], url='/')")
Beispiel #25
0
 def test_skip_missing_extra_template(self):
     cfg = load_config()
     files = Files([
         File(
             'foo.html',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     with self.assertLogs('mkdocs', level='INFO') as cm:
         build._build_extra_template(
             'missing.html',
             files,
             cfg,
             mock.Mock(),
         )
     self.assertEqual(
         cm.output,
         [
             "WARNING:mkdocs.commands.build:Template skipped: 'missing.html' not found in docs_dir."
         ],
     )
Beispiel #26
0
 def test_context_extra_css_js_from_homepage(self):
     nav_cfg = [
         {
             'Home': 'index.md'
         },
     ]
     cfg = load_config(
         nav=nav_cfg,
         extra_css=['style.css'],
         extra_javascript=['script.js'],
         use_directory_urls=False,
     )
     files = Files([
         File(
             'index.md',
             cfg['docs_dir'],
             cfg['site_dir'],
             cfg['use_directory_urls'],
         ),
     ])
     nav = get_navigation(files, cfg)
     context = build.get_context(nav, files, cfg, nav.pages[0])
     self.assertEqual(context['extra_css'], ['style.css'])
     self.assertEqual(context['extra_javascript'], ['script.js'])
Beispiel #27
0
 def test_nav_from_nested_files(self):
     expected = dedent("""
     Page(title=[blank], url='/')
     Section(title='About')
         Page(title=[blank], url='/about/license/')
         Page(title=[blank], url='/about/release-notes/')
     Section(title='Api guide')
         Page(title=[blank], url='/api-guide/debugging/')
         Page(title=[blank], url='/api-guide/running/')
         Page(title=[blank], url='/api-guide/testing/')
         Section(title='Advanced')
             Page(title=[blank], url='/api-guide/advanced/part-1/')
     """)
     cfg = load_config(site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('about/license.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/advanced/part-1.md', cfg['docs_dir'],
              cfg['site_dir'], cfg['use_directory_urls']),
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 3)
     self.assertEqual(len(site_navigation.pages), 7)
     self.assertEqual(repr(site_navigation.homepage),
                      "Page(title=[blank], url='/')")
Beispiel #28
0
 def test_active(self):
     nav_cfg = [{
         'Home': 'index.md'
     }, {
         'API Guide': [
             {
                 'Running': 'api-guide/running.md'
             },
             {
                 'Testing': 'api-guide/testing.md'
             },
             {
                 'Debugging': 'api-guide/debugging.md'
             },
             {
                 'Advanced': [
                     {
                         'Part 1': 'api-guide/advanced/part-1.md'
                     },
                 ]
             },
         ]
     }, {
         'About': [{
             'Release notes': 'about/release-notes.md'
         }, {
             'License': 'about/license.md'
         }]
     }]
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/advanced/part-1.md', cfg['docs_dir'],
              cfg['site_dir'], cfg['use_directory_urls']),
         File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('about/license.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
     ])
     site_navigation = get_navigation(files, cfg)
     # Confirm nothing is active
     self.assertTrue(
         all(page.active is False for page in site_navigation.pages))
     self.assertTrue(
         all(item.active is False for item in site_navigation.items))
     # Activate
     site_navigation.items[1].children[3].children[0].active = True
     # Confirm ancestors are activated
     self.assertTrue(
         site_navigation.items[1].children[3].children[0].active)
     self.assertTrue(site_navigation.items[1].children[3].active)
     self.assertTrue(site_navigation.items[1].active)
     # Confirm non-ancestors are not activated
     self.assertFalse(site_navigation.items[0].active)
     self.assertFalse(site_navigation.items[1].children[0].active)
     self.assertFalse(site_navigation.items[1].children[1].active)
     self.assertFalse(site_navigation.items[1].children[2].active)
     self.assertFalse(site_navigation.items[2].active)
     self.assertFalse(site_navigation.items[2].children[0].active)
     self.assertFalse(site_navigation.items[2].children[1].active)
     # Deactivate
     site_navigation.items[1].children[3].children[0].active = False
     # Confirm ancestors are deactivated
     self.assertFalse(
         site_navigation.items[1].children[3].children[0].active)
     self.assertFalse(site_navigation.items[1].children[3].active)
     self.assertFalse(site_navigation.items[1].active)
Beispiel #29
0
 def test_files(self):
     fs = [
         File('index.md',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True),
         File('foo/bar.md',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True),
         File('foo/bar.html',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True),
         File('foo/bar.jpg',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True),
         File('foo/bar.js',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True),
         File('foo/bar.css',
              '/path/to/docs',
              '/path/to/site',
              use_directory_urls=True)
     ]
     files = Files(fs)
     self.assertEqual([f for f in files], fs)
     self.assertEqual(len(files), 6)
     self.assertEqual(files.documentation_pages(), [fs[0], fs[1]])
     self.assertEqual(files.static_pages(), [fs[2]])
     self.assertEqual(files.media_files(), [fs[3], fs[4], fs[5]])
     self.assertEqual(files.javascript_files(), [fs[4]])
     self.assertEqual(files.css_files(), [fs[5]])
     self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3])
     self.assertEqual(files.get_file_from_path('foo/bar.jpg'), fs[3])
     self.assertEqual(files.get_file_from_path('missing.jpg'), None)
     self.assertTrue(fs[2].src_path in files)
     self.assertTrue(fs[2].src_path in files)
     extra_file = File('extra.md',
                       '/path/to/docs',
                       '/path/to/site',
                       use_directory_urls=True)
     self.assertFalse(extra_file.src_path in files)
     files.append(extra_file)
     self.assertEqual(len(files), 7)
     self.assertTrue(extra_file.src_path in files)
     self.assertEqual(files.documentation_pages(),
                      [fs[0], fs[1], extra_file])
Beispiel #30
0
 def test_indented_nav(self):
     nav_cfg = [{
         'Home': 'index.md'
     }, {
         'API Guide': [
             {
                 'Running': 'api-guide/running.md'
             },
             {
                 'Testing': 'api-guide/testing.md'
             },
             {
                 'Debugging': 'api-guide/debugging.md'
             },
             {
                 'Advanced': [
                     {
                         'Part 1': 'api-guide/advanced/part-1.md'
                     },
                 ]
             },
         ]
     }, {
         'About': [{
             'Release notes': 'about/release-notes.md'
         }, {
             'License': '/license.html'
         }]
     }, {
         'External': 'https://example.com/'
     }]
     expected = dedent("""
     Page(title='Home', url='/')
     Section(title='API Guide')
         Page(title='Running', url='/api-guide/running/')
         Page(title='Testing', url='/api-guide/testing/')
         Page(title='Debugging', url='/api-guide/debugging/')
         Section(title='Advanced')
             Page(title='Part 1', url='/api-guide/advanced/part-1/')
     Section(title='About')
         Page(title='Release notes', url='/about/release-notes/')
         Link(title='License', url='/license.html')
     Link(title='External', url='https://example.com/')
     """)
     cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
     files = Files([
         File('index.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/running.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/testing.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/debugging.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
         File('api-guide/advanced/part-1.md', cfg['docs_dir'],
              cfg['site_dir'], cfg['use_directory_urls']),
         File('about/release-notes.md', cfg['docs_dir'], cfg['site_dir'],
              cfg['use_directory_urls']),
     ])
     site_navigation = get_navigation(files, cfg)
     self.assertEqual(str(site_navigation).strip(), expected)
     self.assertEqual(len(site_navigation.items), 4)
     self.assertEqual(len(site_navigation.pages), 6)
     self.assertEqual(repr(site_navigation.homepage),
                      "Page(title='Home', url='/')")
     self.assertIsNone(site_navigation.items[0].parent)
     self.assertEqual(site_navigation.items[0].ancestors, [])
     self.assertIsNone(site_navigation.items[1].parent)
     self.assertEqual(site_navigation.items[1].ancestors, [])
     self.assertEqual(len(site_navigation.items[1].children), 4)
     self.assertEqual(repr(site_navigation.items[1].children[0].parent),
                      "Section(title='API Guide')")
     self.assertEqual(site_navigation.items[1].children[0].ancestors,
                      [site_navigation.items[1]])
     self.assertEqual(repr(site_navigation.items[1].children[1].parent),
                      "Section(title='API Guide')")
     self.assertEqual(site_navigation.items[1].children[1].ancestors,
                      [site_navigation.items[1]])
     self.assertEqual(repr(site_navigation.items[1].children[2].parent),
                      "Section(title='API Guide')")
     self.assertEqual(site_navigation.items[1].children[2].ancestors,
                      [site_navigation.items[1]])
     self.assertEqual(repr(site_navigation.items[1].children[3].parent),
                      "Section(title='API Guide')")
     self.assertEqual(site_navigation.items[1].children[3].ancestors,
                      [site_navigation.items[1]])
     self.assertEqual(len(site_navigation.items[1].children[3].children), 1)
     self.assertEqual(
         repr(site_navigation.items[1].children[3].children[0].parent),
         "Section(title='Advanced')")
     self.assertEqual(
         site_navigation.items[1].children[3].children[0].ancestors,
         [site_navigation.items[1].children[3], site_navigation.items[1]])
     self.assertIsNone(site_navigation.items[2].parent)
     self.assertEqual(len(site_navigation.items[2].children), 2)
     self.assertEqual(repr(site_navigation.items[2].children[0].parent),
                      "Section(title='About')")
     self.assertEqual(site_navigation.items[2].children[0].ancestors,
                      [site_navigation.items[2]])
     self.assertEqual(repr(site_navigation.items[2].children[1].parent),
                      "Section(title='About')")
     self.assertEqual(site_navigation.items[2].children[1].ancestors,
                      [site_navigation.items[2]])
     self.assertIsNone(site_navigation.items[3].parent)
     self.assertEqual(site_navigation.items[3].ancestors, [])
     self.assertIsNone(site_navigation.items[3].children)