diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..013dd2021d --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[report] +show_missing = True diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md index d07ca9062d..91a2472d87 100644 --- a/docs/about/release-notes.md +++ b/docs/about/release-notes.md @@ -25,6 +25,131 @@ The current and past members of the MkDocs team. ### Major Additions to Development Version +#### Internal Refactor of Pages, Files, and Navigation + +Internal handling of pages, files and navigation has been completely refactored. +The changes included in the refactor are summarized below. + +* Support for hidden pages. All Markdown pages are now included in the build + regardless of whether they are included in the navigation configuration + (#699). +* The navigation can now include links to external sites (#989 #1373 & #1406). +* Page data (including titles) is properly determined for all pages before any + page is rendered (#1347). +* Automatically populated navigation now sorts index pages to the top. In other + words, The index page will be listed as the first child of a directory, while + all other documents are sorted alphanumerically by file name after the index + page (#73 & #1042). +* A `README.md` file is now treated as an index file within a directory and + will be rendered to `index.html` (#608). +* The URLs for all files are computed once and stored in a files collection. + This ensures all internal links are always computed correctly regardless of + the configuration. This also allows all internal links to be validated, not + just links to other Markdown pages. (#842 & #872). +* An [on_files] plugin event has been added, which could be used to include + files not in the `docs_dir`, exclude files, redefine page URLs (i.e. + implement extensionless URLs), or to manipulate files in various other ways. + + [on_files]: ../user-guide/plugins.md#on_files + +##### Backward Incompatible Changes + +As part of the internal refactor, a number of backward incompatible changes have +been introduced, which are summarized below. + +###### URLS have changed when `use_directory_urls` is `False` + +Previously, all Markdown pages would be have their filenames altered to be index +pages regardless of how the [use_directory_urls] setting was configured. +However, the path munging is only needed when `use_directory_urls` is set to +`True` (the default). The path mungling no longer happens when +`use_directory_urls` is set to `False`, which will result in different URLs for +all pages that were not already index files. As this behavior only effects a +non-default configuration, and the most common user-case for setting the option +to `False` is for local file system (`file://`) browsing, its not likely to +effect most users. However, if you have `use_directory_urls` set to `False` +for a MkDocs site hosted on a web server, most of your URLs will now be broken. +As you can see below, the new URLs are much more sensible. + +| Markdown file | Old URL | New URL | +| --------------- | -------------------- | -------------- | +| `index.md` | `index.html` | `index.html` | +| `foo.md` | `foo/index.html` | `foo.html` | +| `foo/bar.md` | `foo/bar/index.html` | `foo/bar.html` | + +Note that there has been no change to URLs or file paths when +`use_directory_urls` is set to `True` (the default), except that MkDocs more +consistently includes an ending slash on all internally generated URLs. + +[use_directory_urls]: ../user-guide/configuration.md#use_directory_urls + +###### The `pages` configuration setting has been renamed to `nav` + +The `pages` configuration setting is deprecated and will issue a warning if set +in the configuration file. The setting has been renamed `nav`. To update your +configuration, simply rename the setting to `nav`. In other words, if your +configuration looked like this: + +```yaml +pages: + - Home: index.md + - User Guide: user-guide.md +``` + +Simply edit the configuration as follows: + +```yaml +nav: + - Home: index.md + - User Guide: user-guide.md +``` + +In the current release, any configuration which includes a `pages` setting, but +no `nav` setting, the `pages` configuration will be copied to `nav` and a +warning will be issued. However, in a future release, that may no longer happen. +If both `pages` and `nav` are defined, the `pages` setting will be ignored. + +###### Template variables and `base_url` + +In previous versions of MkDocs some URLs expected the [base_url] template +variable to be prepended to the URL and others did not. That inconsistency has +been removed. All URLs must now be joined with the `base_url`. As previously, a +slash must be included between the `base_url` and the URL variable. For example, +a theme template might have previously included a link to the `site_name` as: + +```django +{{ config.site_name }} +``` + +And MkDocs would magically return a URL for the homepage which was relative to +the current page. That "magic" has been removed and the `base_url` must now be +explicitly included: + +```django +{{ config.site_name }} +``` + +This change applies to any navigation items and pages, as well as the +`page.next_page` and `page.previous_page` attributes. For the time being, the +`extra_javascript` and `extra_css` variables continue to work as previously +(without `base_url`), but they have been deprecated and the corresponding +configuration values (`config.extra_javascript` and `config.extra_css` +respectively) should be used with `base_url` instead. + +Note that navigation can now include links to external sites. Obviously, the +`base_url` should not be prepended to these items. Therefore, all navigation +items contain a `is_link` attribute which can be used to alter the behavior for +external links. + +```django +{{ nav_item.title }} +``` + +Any other URL variables which should not be used with `base_url` are explicitly +documented as such. + +[base_url]: ../user-guide/custom-themes.md#base_url + #### Path Based Settings are Relative to Configuration File (#543) Previously any relative paths in the various configuration options were @@ -181,7 +306,7 @@ template exists. ##### Context Variables Page specific variable names in the template context have been refactored as -defined in [Custom Themes](../user-guide/custom-themes/#page). The +defined in [Custom Themes](../user-guide/custom-themes.md#page). The old variable names issued a warning in version 0.16, but have been removed in version 1.0. @@ -199,14 +324,14 @@ user created and third-party templates: | previous_page | [page.previous_page]| | next_page | [page.next_page] | -[page]: ../user-guide/custom-themes/#page -[page.title]: ../user-guide/custom-themes/#pagetitle -[page.content]: ../user-guide/custom-themes/#pagecontent -[page.toc]: ../user-guide/custom-themes/#pagetoc -[page.meta]: ../user-guide/custom-themes/#pagemeta -[page.canonical_url]: ../user-guide/custom-themes/#pagecanonical_url -[page.previous_page]: ../user-guide/custom-themes/#pageprevious_page -[page.next_page]: ../user-guide/custom-themes/#pagenext_page +[page]: ../user-guide/custom-themes.md#page +[page.title]: ../user-guide/custom-themes.md#pagetitle +[page.content]: ../user-guide/custom-themes.md#pagecontent +[page.toc]: ../user-guide/custom-themes.md#pagetoc +[page.meta]: ../user-guide/custom-themes.md#pagemeta +[page.canonical_url]: ../user-guide/custom-themes.md#pagecanonical_url +[page.previous_page]: ../user-guide/custom-themes.md#pageprevious_page +[page.next_page]: ../user-guide/custom-themes.md#pagenext_page Additionally, a number of global variables have been altered and/or removed and user created and third-party templates should be updated as outlined below: @@ -286,7 +411,7 @@ the `extra_css` or `extra_javascript` config settings going forward. ##### Page Context Page specific variable names in the template context have been refactored as -defined in [Custom Themes](../user-guide/custom-themes/#page). The +defined in [Custom Themes](../user-guide/custom-themes.md#page). The old variable names will issue a warning but continue to work for version 0.16, but may be removed in a future version. @@ -304,14 +429,14 @@ user created and third-party templates: | previous_page | [page.previous_page]| | next_page | [page.next_page] | -[page]: ../user-guide/custom-themes/#page -[page.title]: ../user-guide/custom-themes/#pagetitle -[page.content]: ../user-guide/custom-themes/#pagecontent -[page.toc]: ../user-guide/custom-themes/#pagetoc -[page.meta]: ../user-guide/custom-themes/#pagemeta -[page.canonical_url]: ../user-guide/custom-themes/#pagecanonical_url -[page.previous_page]: ../user-guide/custom-themes/#pageprevious_page -[page.next_page]: ../user-guide/custom-themes/#pagenext_page +[page]: ../user-guide/custom-themes.md#page +[page.title]: ../user-guide/custom-themes.md#pagetitle +[page.content]: ../user-guide/custom-themes.md#pagecontent +[page.toc]: ../user-guide/custom-themes.md#pagetoc +[page.meta]: ../user-guide/custom-themes.md#pagemeta +[page.canonical_url]: ../user-guide/custom-themes.md#pagecanonical_url +[page.previous_page]: ../user-guide/custom-themes.md#pageprevious_page +[page.next_page]: ../user-guide/custom-themes.md#pagenext_page ##### Global Context @@ -400,7 +525,7 @@ overriding blocks in the same manner as the built-in themes. Third party themes are encouraged to wrap the various pieces of their templates in blocks in order to support such customization. -[blocks]: ../user-guide/styling-your-docs/#overriding-template-blocks +[blocks]: ../user-guide/styling-your-docs.md#overriding-template-blocks #### Auto-Populated `extra_css` and `extra_javascript` Deprecated. (#986) @@ -444,7 +569,7 @@ the `docs_dir` is set to the directory which contains your config file rather than a child directory. You will need to rearrange you directory structure to better conform with the documented [layout]. -[layout]: ../user-guide/writing-your-docs/#file-layout +[layout]: ../user-guide/writing-your-docs.md#file-layout ### Other Changes and Additions to Version 0.16.0 @@ -522,8 +647,8 @@ See the documentation for [Styling your docs] for more information about using and customizing themes and [Custom themes] for creating and distributing new themes -[Styling your docs]: /user-guide/styling-your-docs.md -[Custom themes]: /user-guide/custom-themes.md +[Styling your docs]: ../user-guide/styling-your-docs.md +[Custom themes]: ../user-guide/custom-themes.md ### Other Changes and Additions to Version 0.15.0 @@ -544,9 +669,9 @@ themes * Bugfix: Provide filename to Read the Docs. (#721 and RTD#1480) * Bugfix: Silence Click's unicode_literals warning. (#708) -[site_description]: /user-guide/configuration.md#site_description -[site_author]: /user-guide/configuration.md#site_author -[ReadTheDocs]: /user-guide/styling-your-docs.md#readthedocs +[site_description]: ../user-guide/configuration.md#site_description +[site_author]: ../user-guide/configuration.md#site_author +[ReadTheDocs]: ../user-guide/styling-your-docs.md#readthedocs ## Version 0.14.0 (2015-06-09) @@ -604,7 +729,7 @@ This new file is created on every MkDocs build (with `mkdocs build`) and no configuration is needed to enable it. [future release]: https://github.com/mkdocs/mkdocs/pull/481 -[site_dir]: /user-guide/configuration.md#site_dir +[site_dir]: ../user-guide/configuration.md#site_dir #### Change the pages configuration @@ -612,8 +737,8 @@ Provide a [new way] to define pages, and specifically [nested pages], in the mkdocs.yml file and deprecate the existing approach, support will be removed with MkDocs 1.0. -[new way]: /user-guide/writing-your-docs.md#configure-pages-and-navigation -[nested pages]: /user-guide/writing-your-docs.md#multilevel-documentation +[new way]: ../user-guide/writing-your-docs.md#configure-pages-and-navigation +[nested pages]: ../user-guide/writing-your-docs.md#multilevel-documentation #### Warn users about the removal of builtin themes @@ -631,7 +756,7 @@ JavaScript library [lunr.js]. It has been added to both the `mkdocs` and for adding it to your own themes. [lunr.js]: http://lunrjs.com/ -[supporting search]: /user-guide/styling-your-docs.md#search-and-themes +[supporting search]: ../user-guide/styling-your-docs.md#search-and-themes #### New Command Line Interface @@ -659,10 +784,10 @@ can also use Jinja2 syntax and take advantage of the [global variables]. By default MkDocs will use this approach to create a sitemap for the documentation. -[extra_javascript]: /user-guide/configuration.md#extra_javascript -[extra_css]: /user-guide/configuration.md#extra_css -[extra_templates]: /user-guide/configuration.md#extra_templates -[global variables]: /user-guide/styling-your-docs.md#global-context +[extra_javascript]: ../user-guide/configuration.md#extra_javascript +[extra_css]: ../user-guide/configuration.md#extra_css +[extra_templates]: ../user-guide/configuration.md#extra_templates +[global variables]: ../user-guide/styling-your-docs.md#global-context ### Other Changes and Additions to Version 0.13.0 @@ -679,8 +804,8 @@ documentation. called index.md (#535) * Bugfix: Fix errors with Unicode filenames (#542). -[extra config]: /user-guide/configuration.md#extra -[Markdown extension configuration options]: /user-guide/configuration.md#markdown_extensions +[extra config]: ../user-guide/configuration.md#extra +[Markdown extension configuration options]: ../user-guide/configuration.md#markdown_extensions [wheels]: http://pythonwheels.com/ ## Version 0.12.2 (2015-04-22) diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index 8cac62f917..c9a16b30af 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -162,24 +162,60 @@ This option can be overridden by a command line option in `gh-deploy`. ## Documentation layout -### pages +### nav -This setting is used to determine the set of pages that should be built for the -documentation. For example, the following would create Introduction, User Guide -and About pages, given the three source files `index.md`, `user-guide.md` and -`about.md`, respectively. +This setting is used to determine the format and layout of the global navigation +for the site. For example, the following would create "Introduction", "User +Guide" and "About" navigation items. ```yaml -pages: +nav: - 'Introduction': 'index.md' - 'User Guide': 'user-guide.md' - 'About': 'about.md' ``` -See the section on [configuring pages and navigation] for a more detailed -breakdown, including how to create sub-sections. +All paths must be relative to the `mkdocs.yml` configuration file. See the +section on [configuring pages and navigation] for a more detailed breakdown, +including how to create sub-sections. -**default**: By default `pages` will contain an alphanumerically sorted, nested +Navigation items may also include links to external sites. While titles are +optional for internal links, they are required for external links. An external +link may be a full URL or a relative URL. Any path which is not found in the +files is assumed to be an external link. + +```yaml +nav: + - Home: index.md + - User Guide: user-guide.md + - Bug Tracker: https://example.com/ +``` + +In the above example, the first two items point to local files while the third +points to an external site. + +However, sometimes the MkDocs site is hosted in a subdirectory of a project's +site and you may want to link to other parts of the same site without including +the full domain. In that case, you may use and appropriate relative URL. + +```yaml +site_url: http://example.com/foo/ + +nav: + - Home: ../ + - User Guide: user-guide.md + - Bug Tracker: /bugs/ +``` + +In the above example, two different styles of external links are used. First +note that the `site_url` indicates that the MkDocs site is hosted in the `/foo/` +subdirectory of the domain. Therefore, the `Home` navigation item is a relative +link which steps up one level to the server root and effectively points to +`http://example.com/`. The `Bug Tracker` item uses an absolute path from the +server root and effectively points to `http://example.com/bugs/`. Of course, the +`User Guide` points to a local MkDocs page. + +**default**: By default `nav` will contain an alphanumerically sorted, nested list of all the Markdown files found within the `docs_dir` and its sub-directories. If none are found it will be `[]` (an empty list). @@ -324,27 +360,26 @@ documentation. The following table demonstrates how the URLs used on the site differ when setting `use_directory_urls` to `true` or `false`. -Source file | Generated HTML | use_directory_urls: true | use_directory_urls: false ------------- | -------------------- | ------------------------ | ------------------------ -index.md | index.html | / | /index.html -api-guide.md | api-guide/index.html | /api-guide/ | /api-guide/index.html -about.md | about/index.html | /about/ | /about/index.html +Source file | use_directory_urls: true | use_directory_urls: false +---------------- | ------------------------- | ------------------------- +index.md | / | /index.html +api-guide.md | /api-guide/ | /api-guide.html +about/license.md | /about/license/ | /about/license.html The default style of `use_directory_urls: true` creates more user friendly URLs, and is usually what you'll want to use. The alternate style can occasionally be useful if you want your documentation to remain properly linked when opening pages directly from the file system, because -it create links that point directly to the target *file* rather than the target +it creates links that point directly to the target *file* rather than the target *directory*. **default**: `true` ### strict -Determines if a broken link to a page within the documentation is considered a -warning or an error (link to a page not listed in the pages setting). Set to -true to halt processing when a broken link is found, false prints a warning. +Determines how warnings are handled. Set to `true` to halt processing when a +warning is raised. Set to `false` to print a warning and continue processing. **default**: `false` @@ -519,7 +554,7 @@ You may [contribute additional languages]. any reason, a warning is issued. You may use the `--strict` flag when building to cause such a failure to raise an error instead. - !!! Note +!!! Note On smaller sites, using a pre-built index is not recommended as it creates a significant increase is bandwidth requirements with little to no noticeable @@ -545,3 +580,4 @@ You may [contribute additional languages]. [ISO 639-1]: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes [Lunr Languages]: https://github.com/MihaiValentin/lunr-languages#lunr-languages----- [contribute additional languages]: https://github.com/MihaiValentin/lunr-languages/blob/master/CONTRIBUTING.md +[Node.js]: https://nodejs.org/ diff --git a/docs/user-guide/custom-themes.md b/docs/user-guide/custom-themes.md index 18f72b282a..dc1c971f3e 100644 --- a/docs/user-guide/custom-themes.md +++ b/docs/user-guide/custom-themes.md @@ -126,6 +126,8 @@ used options include: * [config.site_url](./configuration.md#site_url) * [config.site_author](./configuration.md#site_author) * [config.site_description](./configuration.md#site_description) +* [config.extra_javascript](./configuration.md#extra_javascript) +* [config.extra_css](./configuration.md#extra_css) * [config.repo_url](./configuration.md#repo_url) * [config.repo_name](./configuration.md#repo_name) * [config.copyright](./configuration.md#copyright) @@ -133,7 +135,29 @@ used options include: #### nav -The `nav` variable is used to create the navigation for the documentation. +The `nav` variable is used to create the navigation for the documentation. The +`nav` object is an iterable of [navigation objects](#navigation-objects) as +defined by the [nav] configuration setting. + +[nav]: configuration.md#nav + +In addition to the iterable of [navigation objects](#navigation-objects), the +`nav` object contains the following attributes: + +##### nav.homepage + +The [page](#page) object for the homepage of the site. + +##### nav.pages + +A flat list of all [page](#page) objects contained in the navigation. This list +is not necessarily a complete list of all site pages as it does not contain +pages which are not included in the navigation. This list does match the list +and order of pages used for all "next page" and "previous page" links. For a +list of all pages, use the [pages](#pages) template variable. + +##### Nav Example + Following is a basic usage example which outputs the first and second level navigation as a nested list. @@ -145,15 +169,15 @@ navigation as a nested list.
This is some text.
-And some more text.
- """) - - expected_toc = dedent(""" - Heading 1 - #heading-1 - Heading 2 - #heading-2 - """) - - expected_meta = {'title': 'custom title'} - - self.assertEqual(page.content.strip(), expected_html) - self.assertEqual(str(page.toc).strip(), expected_toc) - self.assertEqual(page.meta, expected_meta) - self.assertEqual(page.title, 'custom title') - - def test_convert_internal_link(self): - md_text = 'An [internal link](internal.md) to another document.' - expected = 'An internal link to another document.
' - config = load_config(pages=['index.md', 'internal.md']) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_convert_multiple_internal_links(self): - md_text = '[First link](first.md) [second link](second.md).' - expected = '' - config = load_config(pages=['index.md', 'first.md', 'second.md']) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_convert_internal_link_differing_directory(self): - md_text = 'An [internal link](../internal.md) to another document.' - expected = 'An internal link to another document.
' - config = load_config(pages=['foo/bar.md', 'internal.md']) - page, nav = build_page(None, 'foo/bar.md', config, md_text) - page.render(config) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_convert_internal_link_with_anchor(self): - md_text = 'An [internal link](internal.md#section1.1) to another document.' - expected = 'An internal link to another document.
' - config = load_config(pages=['index.md', 'internal.md']) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_convert_internal_media(self): - """Test relative image URL's are the same for different base_urls""" - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - ] - - config = load_config(pages=pages) - site_navigation = nav.SiteNavigation(config) + 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.markdown, page.meta = meta.get_data(md_src) + return page, files - expected_results = ( - './img/initial-layout.png', - '../img/initial-layout.png', - '../img/initial-layout.png', - ) - template = '' +class BuildTests(PathAssertionMixin, LogTestCase): - for (page, expected) in zip(site_navigation.walk_pages(), expected_results): - page.markdown = '![The initial MkDocs layout](img/initial-layout.png)' - page.render(config, site_navigation) - self.assertEqual(page.content, template % expected) + # Test build.get_context - def test_convert_internal_asbolute_media(self): - """Test absolute image URL's are correct for different base_urls""" - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', + def test_context_base_url_homepage(self): + nav_cfg = [ + {'Home': 'index.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']), + ]) + nav = get_navigation(files, cfg) + context = build.get_context(nav, files, cfg, nav.pages[0]) + self.assertEqual(context['base_url'], '.') - config = load_config(pages=pages) - site_navigation = nav.SiteNavigation(config) - - expected_results = ( - './img/initial-layout.png', - '../img/initial-layout.png', - '../../img/initial-layout.png', - ) - - template = '' - - for (page, expected) in zip(site_navigation.walk_pages(), expected_results): - page.markdown = '![The initial MkDocs layout](/img/initial-layout.png)' - page.render(config, site_navigation) - self.assertEqual(page.content, template % expected) - - def test_dont_convert_code_block_urls(self): - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', + def test_context_base_url_homepage_use_directory_urls(self): + nav_cfg = [ + {'Home': 'index.md'} ] - - config = load_config(pages=pages) - site_navigation = nav.SiteNavigation(config) - - expected = dedent(""" -An HTML Anchor::
-<a href="index.md">My example link</a>
-
- """)
-
- for page in site_navigation.walk_pages():
- page.markdown = 'An HTML Anchor::\n\n My example link\n'
- page.render(config, site_navigation)
- self.assertEqual(page.content, expected)
-
- def test_anchor_only_link(self):
- pages = [
- 'index.md',
- 'internal.md',
- 'sub/internal.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'], '.')
+
+ def test_context_base_url_nested_page(self):
+ nav_cfg = [
+ {'Home': 'index.md'},
+ {'Nested': 'foo/bar.md'}
]
-
- config = load_config(pages=pages)
- site_navigation = nav.SiteNavigation(config)
-
- for page in site_navigation.walk_pages():
- page.markdown = '[test](#test)'
- page.render(config, site_navigation)
- self.assertEqual(page.content, '')
-
- def test_ignore_external_link(self):
- md_text = 'An [external link](http://example.com/external.md).'
- expected = 'An external link.
' - config = load_config(pages=[{'Home': 'index.md'}]) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_not_use_directory_urls(self): - md_text = 'An [internal link](internal.md) to another document.' - expected = 'An internal link to another document.
' - config = load_config(pages=['index.md', 'internal.md'], use_directory_urls=False) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_ignore_email_links(self): - md_text = 'AA autolin', - 'k@example.com', - ' and an link.
' + 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']) ]) - config = load_config(pages=[{'Home': 'index.md'}]) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected.strip()) - - def test_markdown_table_extension(self): - """ - Ensure that the table extension is supported. - """ - md_text = dedent(""" - First Header | Second Header - -------------- | -------------- - Content Cell 1 | Content Cell 2 - Content Cell 3 | Content Cell 4 - """) - - expected_html = dedent(""" -First Header | -Second Header | -
---|---|
Content Cell 1 | -Content Cell 2 | -
Content Cell 3 | -Content Cell 4 | -
print 'foo'\n
- """)
-
- config = load_config(pages=[{'Home': 'index.md'}])
- page, nav = build_page(None, 'index.md', config, md_text)
- page.render(config, nav)
- self.assertEqual(page.content.strip(), expected_html)
-
- def test_markdown_custom_extension(self):
- """
- Check that an extension applies when requested in the arguments to
- `convert_markdown`.
- """
- md_text = "foo__bar__baz"
-
- # Check that the plugin is not active when not requested.
- expected_without_smartstrong = "foobarbaz
" - config = load_config(pages=[{'Home': 'index.md'}]) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected_without_smartstrong) - - # Check that the plugin is active when requested. - expected_with_smartstrong = "foo__bar__baz
" - config = load_config(pages=[{'Home': 'index.md'}], markdown_extensions=['smart_strong']) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), expected_with_smartstrong) - - def test_markdown_duplicate_custom_extension(self): - """ - Duplicated extension names should not cause problems. - """ - md_text = "foo" - config = load_config(pages=[{'Home': 'index.md'}], markdown_extensions=['toc']) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - self.assertEqual(page.content.strip(), 'foo
') - - def test_copying_media(self): - with TemporaryDirectory() as docs_dir, TemporaryDirectory() as site_dir: - # Create a non-empty markdown file, image, html file, dot file and dot directory. - f = open(os.path.join(docs_dir, 'index.md'), 'w') - f.write(dedent(""" - page_title: custom title - - # Heading 1 - - This is some text. - - # Heading 2 - - And some more text. - """)) - f.close() - open(os.path.join(docs_dir, 'img.jpg'), 'w').close() - open(os.path.join(docs_dir, 'example.html'), 'w').close() - open(os.path.join(docs_dir, '.hidden'), 'w').close() - os.mkdir(os.path.join(docs_dir, '.git')) - open(os.path.join(docs_dir, '.git/hidden'), 'w').close() - - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) - build.build(cfg) - - # Verify only the markdown (coverted to html) and the image are copied. - self.assertTrue(os.path.isfile(os.path.join(site_dir, 'index.html'))) - self.assertTrue(os.path.isfile(os.path.join(site_dir, 'img.jpg'))) - self.assertTrue(os.path.isfile(os.path.join(site_dir, 'example.html'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, '.hidden'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, '.git/hidden'))) - - def test_copy_theme_files(self): - with TemporaryDirectory() as docs_dir, TemporaryDirectory() as site_dir: - # Create a non-empty markdown file. - f = open(os.path.join(docs_dir, 'index.md'), 'w') - f.write(dedent(""" - page_title: custom title - - # Heading 1 - - This is some text. - """)) - f.close() - - cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) - build.build(cfg) - - # Verify only theme media are copied, not templates or Python files. - self.assertTrue(os.path.isfile(os.path.join(site_dir, 'index.html'))) - self.assertTrue(os.path.isdir(os.path.join(site_dir, 'js'))) - self.assertTrue(os.path.isdir(os.path.join(site_dir, 'css'))) - self.assertTrue(os.path.isdir(os.path.join(site_dir, 'img'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, '__init__.py'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, '__init__.pyc'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, 'base.html'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, 'content.html'))) - self.assertFalse(os.path.isfile(os.path.join(site_dir, 'nav.html'))) - - def test_strict_mode_valid(self): - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', + nav = get_navigation(files, cfg) + context = build.get_context(nav, files, cfg, nav.pages[1]) + self.assertEqual(context['base_url'], '..') + + def test_context_base_url_nested_page_use_directory_urls(self): + nav_cfg = [ + {'Home': 'index.md'}, + {'Nested': 'foo/bar.md'} ] - - md_text = "[test](internal.md)" - - config = load_config(pages=pages, strict=False) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - - config = load_config(pages=pages, strict=True) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - - def test_strict_mode_invalid(self): - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', + cfg = load_config(nav=nav_cfg) + 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'], '../..') + + def test_context_base_url_relative_no_page(self): + cfg = load_config(use_directory_urls=False) + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='..') + self.assertEqual(context['base_url'], '..') + + def test_context_base_url_relative_no_page_use_directory_urls(self): + cfg = load_config() + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='..') + self.assertEqual(context['base_url'], '..') + + def test_context_base_url_absolute_no_page(self): + cfg = load_config(use_directory_urls=False) + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='/') + self.assertEqual(context['base_url'], '') + + def test_context_base_url__absolute_no_page_use_directory_urls(self): + cfg = load_config() + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='/') + self.assertEqual(context['base_url'], '') + + def test_context_base_url_absolute_nested_no_page(self): + cfg = load_config(use_directory_urls=False) + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='/foo/') + self.assertEqual(context['base_url'], '/foo') + + def test_context_base_url__absolute_nested_no_page_use_directory_urls(self): + cfg = load_config() + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='/foo/') + self.assertEqual(context['base_url'], '/foo') + + 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']) + + def test_context_extra_css_js_from_nested_page(self): + nav_cfg = [ + {'Home': 'index.md'}, + {'Nested': 'foo/bar.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']), + 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']) + + 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']) - md_text = "[test](bad_link.md)" - - config = load_config(pages=pages, strict=False) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) + def test_context_extra_css_js_no_page(self): + cfg = load_config(extra_css=['style.css'], extra_javascript=['script.js']) + context = build.get_context(mock.Mock(), mock.Mock(), cfg, base_url='..') + self.assertEqual(context['extra_css'], ['../style.css']) + self.assertEqual(context['extra_javascript'], ['../script.js']) - config = load_config(pages=pages, strict=True) - page, nav = build_page(None, 'index.md', config, md_text) - self.assertRaises( - MarkdownNotFound, - page.render, config, nav) + def test_extra_context(self): + cfg = load_config(extra={'a': 1}) + context = build.get_context(mock.Mock(), mock.Mock(), cfg) + self.assertEqual(context['config']['extra']['a'], 1) - def test_absolute_link(self): - pages = [ - 'index.md', - 'sub/index.md', - ] + # Test build._build_theme_template + + @mock.patch('mkdocs.utils.write_file') + @mock.patch('mkdocs.commands.build._build_template', return_value='some content') + def test_build_theme_template(self, mock_build_template, mock_write_file): + cfg = load_config() + env = cfg['theme'].get_env() + build._build_theme_template('main.html', env, mock.Mock(), cfg, mock.Mock()) + mock_write_file.assert_called_once() + mock_build_template.assert_called_once() + + @mock.patch('mkdocs.utils.write_file') + @mock.patch('mkdocs.commands.build._build_template', return_value='some content') + @mock.patch('gzip.open') + def test_build_sitemap_template(self, mock_gzip_open, mock_build_template, mock_write_file): + cfg = load_config() + env = cfg['theme'].get_env() + build._build_theme_template('sitemap.xml', env, mock.Mock(), cfg, mock.Mock()) + mock_write_file.assert_called_once() + mock_build_template.assert_called_once() + mock_gzip_open.assert_called_once() + + @mock.patch('mkdocs.utils.write_file') + @mock.patch('mkdocs.commands.build._build_template', return_value='') + def test_skip_missing_theme_template(self, mock_build_template, mock_write_file): + cfg = load_config() + env = cfg['theme'].get_env() + with self.assertLogs('mkdocs', level='WARN') as cm: + build._build_theme_template('missing.html', env, mock.Mock(), cfg, mock.Mock()) + self.assertEqual( + cm.output, + ["WARNING:mkdocs.commands.build:Template skipped: 'missing.html' not found in theme directories."] + ) + mock_write_file.assert_not_called() + mock_build_template.assert_not_called() + + @mock.patch('mkdocs.utils.write_file') + @mock.patch('mkdocs.commands.build._build_template', return_value='') + def test_skip_theme_template_empty_output(self, mock_build_template, mock_write_file): + cfg = load_config() + env = cfg['theme'].get_env() + with self.assertLogs('mkdocs', level='INFO') as cm: + build._build_theme_template('main.html', env, mock.Mock(), cfg, mock.Mock()) + self.assertEqual( + cm.output, + ["INFO:mkdocs.commands.build:Template skipped: 'main.html' generated empty output."] + ) + mock_write_file.assert_not_called() + mock_build_template.assert_called_once() - md_text = "[test 1](/index.md) [test 2](/sub/index.md)" - config = load_config(pages=pages, strict=True) - page, nav = build_page(None, 'index.md', config, md_text) - page.render(config, nav) - - def test_extension_config(self): - """ - Test that a dictionary of 'markdown_extensions' is recognized as - both a list of extensions and a dictionary of extnesion configs. - """ - md_text = dedent(""" - # A Header - """) - - expected_html = dedent(""" -\ufeff# An UTF-8 encoded file with a BOM
`. - f = io.open(os.path.join(site_dir, 'index.html'), 'r', encoding='utf-8') - output = f.read() - f.close() - self.assertTrue( - 'page content
') + + @tempdir(files={'testing.html': 'page content
'}) + 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.markdown.startswith('# Welcome to MkDocs')) + self.assertTrue(page.content.startswith('page content
'}) + 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.markdown, None) + self.assertEqual(page.content, None) + + @tempdir(files={'index.md': 'new page content'}) + @mock.patch('io.open', side_effect=IOError('Error message.')) + 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(IOError, 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." + ] + ) + mock_open.assert_called_once() + + # Test build._build_page + + @tempdir() + 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.markdown = 'page content' + page.content = 'page content
' + build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) + self.assertPathIsFile(site_dir, 'index.html') + + # TODO: fix this. It seems that jinja2 chokes on the mock object. Not sure how to resolve. + # @tempdir() + # @mock.patch('jinja2.environment.Template') + # def test_build_page_empty(self, site_dir, mock_template): + # mock_template.render = mock.Mock(return_value='') + # 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 = '' + # page.markdown = '' + # page.content = '' + # with self.assertLogs('mkdocs', level='INFO') as cm: + # build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) + # self.assertEqual( + # cm.output, + # ["INFO:mkdocs.commands.build:Page skipped: 'index.md'. Generated empty output."] + # ) + # mock_template.render.assert_called_once() + # self.assertPathNotFile(site_dir, 'index.html') + + @tempdir(files={'index.md': 'page content'}) + @tempdir(files={'index.html': 'page content
'}) + @mock.patch('mkdocs.utils.write_file') + def test_build_page_dirty_modified(self, site_dir, docs_dir, mock_write_file): + cfg = load_config(docs_dir=docs_dir, 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.markdown = 'new page content' + page.content = 'new page content
' + build._build_page(page, cfg, files, nav, cfg['theme'].get_env(), dirty=True) + mock_write_file.assert_not_called() + + @tempdir(files={'testing.html': 'page content
'}) + @mock.patch('mkdocs.utils.write_file') + def test_build_page_dirty_not_modified(self, site_dir, mock_write_file): + cfg = load_config(site_dir=site_dir, nav=['index.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.markdown = 'page content' + page.content = 'page content
' + build._build_page(page, cfg, files, nav, cfg['theme'].get_env(), dirty=True) + mock_write_file.assert_called_once() + + @tempdir() + def test_build_page_custom_template(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.meta = {'template': '404.html'} + page.markdown = 'page content' + page.content = 'page content
' + build._build_page(page, cfg, files, nav, cfg['theme'].get_env()) + self.assertPathIsFile(site_dir, 'index.html') + + @tempdir() + @mock.patch('mkdocs.utils.write_file', side_effect=IOError('Error message.')) + 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.markdown = 'page content' + page.content = 'page content
' + with self.assertLogs('mkdocs', level='ERROR') as cm: + self.assertRaises(IOError, 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."] + ) + mock_write_file.assert_called_once() + + # Test build.build + + @tempdir(files={ + 'index.md': 'page content', + 'empty.md': '', + 'img.jpg': '', + 'static.html': 'content', + '.hidden': 'content', + '.git/hidden': 'content' + }) + @tempdir() + def test_copying_media(self, site_dir, docs_dir): + cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) + build.build(cfg) + + # Verify that only non-empty md file (coverted to html), static HTML file and image are copied. + self.assertPathIsFile(site_dir, 'index.html') + self.assertPathIsFile(site_dir, 'img.jpg') + self.assertPathIsFile(site_dir, 'static.html') + self.assertPathNotExists(site_dir, 'empty.md') + self.assertPathNotExists(site_dir, '.hidden') + self.assertPathNotExists(site_dir, '.git/hidden') + + @tempdir(files={'index.md': 'page content'}) + @tempdir() + def test_copy_theme_files(self, site_dir, docs_dir): + cfg = load_config(docs_dir=docs_dir, site_dir=site_dir) + build.build(cfg) + + # Verify only theme media are copied, not templates or Python files. + self.assertPathIsFile(site_dir, 'index.html') + self.assertPathIsFile(site_dir, '404.html') + self.assertPathIsDir(site_dir, 'js') + self.assertPathIsDir(site_dir, 'css') + self.assertPathIsDir(site_dir, 'img') + self.assertPathIsDir(site_dir, 'fonts') + self.assertPathNotExists(site_dir, '__init__.py') + self.assertPathNotExists(site_dir, '__init__.pyc') + self.assertPathNotExists(site_dir, 'base.html') + self.assertPathNotExists(site_dir, 'content.html') + self.assertPathNotExists(site_dir, 'main.html') + + # Test build.site_directory_contains_stale_files + + @tempdir(files=['index.html']) + def test_site_dir_contains_stale_files(self, site_dir): + self.assertTrue(build.site_directory_contains_stale_files(site_dir)) + + @tempdir() + def test_not_site_dir_contains_stale_files(self, site_dir): + self.assertFalse(build.site_directory_contains_stale_files(site_dir)) diff --git a/mkdocs/tests/config/base_tests.py b/mkdocs/tests/config/base_tests.py index 5118279fe7..0d5e27fafa 100644 --- a/mkdocs/tests/config/base_tests.py +++ b/mkdocs/tests/config/base_tests.py @@ -9,7 +9,7 @@ except ImportError: from backports.tempfile import TemporaryDirectory -from mkdocs import exceptions +from mkdocs import exceptions, utils from mkdocs.config import base, defaults from mkdocs.config.config_options import BaseConfigOption @@ -273,5 +273,7 @@ def test_load_from_file_with_relative_paths(self): self.assertTrue(isinstance(cfg, base.Config)) self.assertEqual(cfg['site_name'], 'MkDocs Test') self.assertEqual(cfg['docs_dir'], docs_dir) + self.assertEqual(cfg.config_file_path, config_fname) + self.assertIsInstance(cfg.config_file_path, utils.text_type) finally: config_dir.cleanup() diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py index 1d573ba244..cd828e4add 100644 --- a/mkdocs/tests/config/config_options_tests.py +++ b/mkdocs/tests/config/config_options_tests.py @@ -1,6 +1,9 @@ +# coding=UTF-8 + from __future__ import unicode_literals import os +import sys import unittest from mock import patch @@ -273,14 +276,99 @@ def test_incorrect_type_type_error(self): self.assertRaises(config_options.ValidationError, option.validate, []) - def test_doc_dir_is_config_dir(self): + def test_dir_unicode(self): + cfg = Config( + [('dir', config_options.Dir())], + config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), + ) + + test_config = { + 'dir': 'юникод' + } + + cfg.load_dict(test_config) + + fails, warns = cfg.validate() + + self.assertEqual(len(fails), 0) + self.assertEqual(len(warns), 0) + self.assertIsInstance(cfg['dir'], utils.text_type) + + def test_dir_filesystemencoding(self): + cfg = Config( + [('dir', config_options.Dir())], + config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), + ) + + test_config = { + 'dir': 'Übersicht'.encode(encoding=sys.getfilesystemencoding()) + } + + cfg.load_dict(test_config) + + fails, warns = cfg.validate() + + if utils.PY3: + # In PY3 string_types does not include byte strings so validation fails + self.assertEqual(len(fails), 1) + self.assertEqual(len(warns), 0) + else: + # In PY2 string_types includes byte strings so validation passes + # This test confirms that the byte string is properly decoded + self.assertEqual(len(fails), 0) + self.assertEqual(len(warns), 0) + self.assertIsInstance(cfg['dir'], utils.text_type) + + def test_dir_bad_encoding_fails(self): + cfg = Config( + [('dir', config_options.Dir())], + config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), + ) + + test_config = { + 'dir': 'юникод'.encode(encoding='ISO 8859-5') + } + + cfg.load_dict(test_config) + + fails, warns = cfg.validate() + + if sys.platform.startswith('win') and not utils.PY3: + # PY2 on Windows seems to be able to decode anything we give it. + # But that just means less possable errors for those users so we allow it. + self.assertEqual(len(fails), 0) + else: + self.assertEqual(len(fails), 1) + self.assertEqual(len(warns), 0) + + def test_config_dir_prepended(self): + base_path = os.path.abspath('.') + cfg = Config( + [('dir', config_options.Dir())], + config_file_path=os.path.join(base_path, 'mkdocs.yml'), + ) + + test_config = { + 'dir': 'foo' + } + + cfg.load_dict(test_config) + + fails, warns = cfg.validate() + + self.assertEqual(len(fails), 0) + self.assertEqual(len(warns), 0) + self.assertIsInstance(cfg['dir'], utils.text_type) + self.assertEqual(cfg['dir'], os.path.join(base_path, 'foo')) + + def test_dir_is_config_dir_fails(self): cfg = Config( - [('docs_dir', config_options.Dir())], + [('dir', config_options.Dir())], config_file_path=os.path.join(os.path.abspath('.'), 'mkdocs.yml'), ) test_config = { - 'docs_dir': '.' + 'dir': '.' } cfg.load_dict(test_config) @@ -438,11 +526,11 @@ def test_theme_invalid_type(self): option.validate, config) -class PagesTest(unittest.TestCase): +class NavTest(unittest.TestCase): def test_old_format(self): - option = config_options.Pages() + option = config_options.Nav() self.assertRaises( config_options.ValidationError, option.validate, @@ -451,7 +539,7 @@ def test_old_format(self): def test_provided_dict(self): - option = config_options.Pages() + option = config_options.Nav() value = option.validate([ 'index.md', {"Page": "page.md"} @@ -462,7 +550,7 @@ def test_provided_dict(self): def test_provided_empty(self): - option = config_options.Pages() + option = config_options.Nav() value = option.validate([]) self.assertEqual(None, value) @@ -470,13 +558,13 @@ def test_provided_empty(self): def test_invalid_type(self): - option = config_options.Pages() + option = config_options.Nav() self.assertRaises(config_options.ValidationError, option.validate, {}) def test_invalid_config(self): - option = config_options.Pages() + option = config_options.Nav() self.assertRaises(config_options.ValidationError, option.validate, [[], 1]) diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py index 68c76c9484..2ad0fb54b3 100644 --- a/mkdocs/tests/config/config_tests.py +++ b/mkdocs/tests/config/config_tests.py @@ -178,56 +178,37 @@ def test_theme(self): self.assertEqual(c['theme'].static_templates, set(result['static_templates'])) self.assertEqual(dict([(k, c['theme'][k]) for k in iter(c['theme'])]), result['vars']) - def test_default_pages(self): - with TemporaryDirectory() as tmp_dir: - open(os.path.join(tmp_dir, 'index.md'), 'w').close() - open(os.path.join(tmp_dir, 'about.md'), 'w').close() - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict({ - 'site_name': 'Example', - 'docs_dir': tmp_dir, - 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') - }) - conf.validate() - self.assertEqual(['index.md', 'about.md'], conf['pages']) - - def test_default_pages_nested(self): - with TemporaryDirectory() as tmp_dir: - open(os.path.join(tmp_dir, 'index.md'), 'w').close() - open(os.path.join(tmp_dir, 'getting-started.md'), 'w').close() - open(os.path.join(tmp_dir, 'about.md'), 'w').close() - os.makedirs(os.path.join(tmp_dir, 'subA')) - open(os.path.join(tmp_dir, 'subA', 'index.md'), 'w').close() - os.makedirs(os.path.join(tmp_dir, 'subA', 'subA1')) - open(os.path.join(tmp_dir, 'subA', 'subA1', 'index.md'), 'w').close() - os.makedirs(os.path.join(tmp_dir, 'subC')) - open(os.path.join(tmp_dir, 'subC', 'index.md'), 'w').close() - os.makedirs(os.path.join(tmp_dir, 'subB')) - open(os.path.join(tmp_dir, 'subB', 'index.md'), 'w').close() - conf = config.Config(schema=config.DEFAULT_SCHEMA) - conf.load_dict({ - 'site_name': 'Example', - 'docs_dir': tmp_dir, - 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') - }) - conf.validate() - self.assertEqual([ - 'index.md', - 'about.md', - 'getting-started.md', - {'subA': [ - os.path.join('subA', 'index.md'), - {'subA1': [ - os.path.join('subA', 'subA1', 'index.md') - ]} - ]}, - {'subB': [ - os.path.join('subB', 'index.md') - ]}, - {'subC': [ - os.path.join('subC', 'index.md') - ]} - ], conf['pages']) + def test_empty_nav(self): + conf = config.Config(schema=config.DEFAULT_SCHEMA) + conf.load_dict({ + 'site_name': 'Example', + 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') + }) + conf.validate() + self.assertEqual(conf['nav'], None) + + def test_copy_pages_to_nav(self): + # TODO: remove this when pages config setting is fully deprecated. + conf = config.Config(schema=config.DEFAULT_SCHEMA) + conf.load_dict({ + 'site_name': 'Example', + 'pages': ['index.md', 'about.md'], + 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') + }) + conf.validate() + self.assertEqual(conf['nav'], ['index.md', 'about.md']) + + def test_dont_overwrite_nav_with_pages(self): + # TODO: remove this when pages config setting is fully deprecated. + conf = config.Config(schema=config.DEFAULT_SCHEMA) + conf.load_dict({ + 'site_name': 'Example', + 'pages': ['index.md', 'about.md'], + 'nav': ['foo.md', 'bar.md'], + 'config_file_path': os.path.join(os.path.abspath('.'), 'mkdocs.yml') + }) + conf.validate() + self.assertEqual(conf['nav'], ['foo.md', 'bar.md']) def test_doc_dir_in_site_dir(self): diff --git a/mkdocs/tests/integration/complicated_config/mkdocs.yml b/mkdocs/tests/integration/complicated_config/mkdocs.yml index f84936cc25..5c20379555 100644 --- a/mkdocs/tests/integration/complicated_config/mkdocs.yml +++ b/mkdocs/tests/integration/complicated_config/mkdocs.yml @@ -1,6 +1,6 @@ site_name: My Docs -pages: +nav: - Home: index.md - User Guide: - Writing your docs: index.md diff --git a/mkdocs/tests/integration/minimal/mkdocs.yml b/mkdocs/tests/integration/minimal/mkdocs.yml index f4d5b08c94..ff21753366 100644 --- a/mkdocs/tests/integration/minimal/mkdocs.yml +++ b/mkdocs/tests/integration/minimal/mkdocs.yml @@ -1,6 +1,6 @@ site_name: MyTest -pages: +nav: - 'testing.md' site_author: "Tom Christie & Dougal Matthews" diff --git a/mkdocs/tests/integration/subpages/docs/index.md b/mkdocs/tests/integration/subpages/docs/index.md index e4801fa6f4..9700881581 100644 --- a/mkdocs/tests/integration/subpages/docs/index.md +++ b/mkdocs/tests/integration/subpages/docs/index.md @@ -1,4 +1,4 @@ -# Test sub pages and referencing images +## Test sub pages and referencing images ## Reference an image in: / diff --git a/mkdocs/tests/integration/subpages/docs/metadata.md b/mkdocs/tests/integration/subpages/docs/metadata.md new file mode 100644 index 0000000000..d871ea26db --- /dev/null +++ b/mkdocs/tests/integration/subpages/docs/metadata.md @@ -0,0 +1,5 @@ +title: A Page Title + +# Welcome to MkDocs + +Some page content goes here. diff --git a/mkdocs/tests/integration/subpages/docs/page-title.md b/mkdocs/tests/integration/subpages/docs/page-title.md new file mode 100644 index 0000000000..26075f1ca2 --- /dev/null +++ b/mkdocs/tests/integration/subpages/docs/page-title.md @@ -0,0 +1 @@ +Page content. diff --git a/mkdocs/tests/integration/subpages/docs/pageTitle.md b/mkdocs/tests/integration/subpages/docs/pageTitle.md new file mode 100644 index 0000000000..26075f1ca2 --- /dev/null +++ b/mkdocs/tests/integration/subpages/docs/pageTitle.md @@ -0,0 +1 @@ +Page content. diff --git "a/mkdocs/tests/integration/unicode/docs/\303\234bersicht.md" "b/mkdocs/tests/integration/unicode/docs/\303\234bersicht.md" index da37213adb..e410983437 100644 --- "a/mkdocs/tests/integration/unicode/docs/\303\234bersicht.md" +++ "b/mkdocs/tests/integration/unicode/docs/\303\234bersicht.md" @@ -1,4 +1,4 @@ -# Welcome to MkDocs +Welcome to MkDocs For full documentation visit [mkdocs.org](http://mkdocs.org). diff --git "a/mkdocs/tests/integration/unicode/docs/\342\231\252.md" "b/mkdocs/tests/integration/unicode/docs/\342\231\252.md" index da37213adb..e410983437 100644 --- "a/mkdocs/tests/integration/unicode/docs/\342\231\252.md" +++ "b/mkdocs/tests/integration/unicode/docs/\342\231\252.md" @@ -1,4 +1,4 @@ -# Welcome to MkDocs +Welcome to MkDocs For full documentation visit [mkdocs.org](http://mkdocs.org). diff --git a/mkdocs/tests/nav_tests.py b/mkdocs/tests/nav_tests.py deleted file mode 100644 index d8648b1ca3..0000000000 --- a/mkdocs/tests/nav_tests.py +++ /dev/null @@ -1,850 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -from __future__ import unicode_literals -import mock -import os -import unittest - -from mkdocs import nav -from mkdocs.exceptions import ConfigurationError -from mkdocs.tests.base import dedent, load_config - - -class SiteNavigationTests(unittest.TestCase): - def test_simple_toc(self): - pages = [ - {'Home': 'index.md'}, - {'About': 'about.md'} - ] - expected = dedent(""" - Home - / - About - /about/ - """) - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 2) - self.assertEqual(len(site_navigation.pages), 2) - - def test_empty_toc_item(self): - pages = [ - 'index.md', - {'About': 'about.md'} - ] - expected = dedent(""" - Home - / - About - /about/ - """) - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 2) - self.assertEqual(len(site_navigation.pages), 2) - - def test_indented_toc(self): - pages = [ - {'Home': 'index.md'}, - {'API Guide': [ - {'Running': 'api-guide/running.md'}, - {'Testing': 'api-guide/testing.md'}, - {'Debugging': 'api-guide/debugging.md'}, - ]}, - {'About': [ - {'Release notes': 'about/release-notes.md'}, - {'License': 'about/license.md'} - ]} - ] - expected = dedent(""" - Home - / - API Guide - Running - /api-guide/running/ - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ - About - Release notes - /about/release-notes/ - License - /about/license/ - """) - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 3) - self.assertEqual(len(site_navigation.pages), 6) - - def test_nested_ungrouped(self): - pages = [ - {'Home': 'index.md'}, - {'Contact': 'about/contact.md'}, - {'License Title': 'about/sub/license.md'}, - ] - expected = dedent(""" - Home - / - Contact - /about/contact/ - License Title - /about/sub/license/ - """) - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 3) - self.assertEqual(len(site_navigation.pages), 3) - - def test_nested_ungrouped_no_titles(self): - pages = [ - 'index.md', - 'about/contact.md', - 'about/sub/license.md' - ] - expected = dedent(""" - Home - / - Contact - /about/contact/ - License - /about/sub/license/ - """) - - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 3) - self.assertEqual(len(site_navigation.pages), 3) - - @mock.patch.object(os.path, 'sep', '\\') - def test_nested_ungrouped_no_titles_windows(self): - pages = [ - 'index.md', - 'about\\contact.md', - 'about\\sub\\license.md', - ] - expected = dedent(""" - Home - / - Contact - /about/contact/ - License - /about/sub/license/ - """) - - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - self.assertEqual(str(site_navigation).strip(), expected) - self.assertEqual(len(site_navigation.nav_items), 3) - self.assertEqual(len(site_navigation.pages), 3) - - def test_walk_simple_toc(self): - pages = [ - {'Home': 'index.md'}, - {'About': 'about.md'} - ] - expected = [ - dedent(""" - Home - / [*] - About - /about/ - """), - dedent(""" - Home - / - About - /about/ [*] - """) - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - for index, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(str(site_navigation).strip(), expected[index]) - - def test_walk_empty_toc(self): - pages = [ - 'index.md', - {'About': 'about.md'} - ] - expected = [ - dedent(""" - Home - / [*] - About - /about/ - """), - dedent(""" - Home - / - About - /about/ [*] - """) - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - for index, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(str(site_navigation).strip(), expected[index]) - - def test_walk_indented_toc(self): - pages = [ - {'Home': 'index.md'}, - {'API Guide': [ - {'Running': 'api-guide/running.md'}, - {'Testing': 'api-guide/testing.md'}, - {'Debugging': 'api-guide/debugging.md'}, - ]}, - {'About': [ - {'Release notes': 'about/release-notes.md'}, - {'License': 'about/license.md'} - ]} - ] - expected = [ - dedent(""" - Home - / [*] - API Guide - Running - /api-guide/running/ - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ - About - Release notes - /about/release-notes/ - License - /about/license/ - """), - dedent(""" - Home - / - API Guide [*] - Running - /api-guide/running/ [*] - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ - About - Release notes - /about/release-notes/ - License - /about/license/ - """), - dedent(""" - Home - / - API Guide [*] - Running - /api-guide/running/ - Testing - /api-guide/testing/ [*] - Debugging - /api-guide/debugging/ - About - Release notes - /about/release-notes/ - License - /about/license/ - """), - dedent(""" - Home - / - API Guide [*] - Running - /api-guide/running/ - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ [*] - About - Release notes - /about/release-notes/ - License - /about/license/ - """), - dedent(""" - Home - / - API Guide - Running - /api-guide/running/ - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ - About [*] - Release notes - /about/release-notes/ [*] - License - /about/license/ - """), - dedent(""" - Home - / - API Guide - Running - /api-guide/running/ - Testing - /api-guide/testing/ - Debugging - /api-guide/debugging/ - About [*] - Release notes - /about/release-notes/ - License - /about/license/ [*] - """) - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - for index, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(str(site_navigation).strip(), expected[index]) - - def test_base_url(self): - pages = [ - 'index.md' - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages, use_directory_urls=False)) - base_url = site_navigation.url_context.make_relative('/') - self.assertEqual(base_url, '.') - - def test_relative_md_links_have_slash(self): - pages = [ - 'index.md', - 'user-guide/styling-your-docs.md' - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages, use_directory_urls=False)) - site_navigation.url_context.base_path = "/user-guide/configuration" - url = site_navigation.url_context.make_relative('/user-guide/styling-your-docs/') - self.assertEqual(url, '../styling-your-docs/') - - def test_generate_site_navigation(self): - """ - Verify inferring page titles based on the filename - """ - - pages = [ - 'index.md', - 'api-guide/running.md', - 'about/notes.md', - 'about/sub/license.md', - ] - - url_context = nav.URLContext() - nav_items, pages = nav._generate_site_navigation(load_config(pages=pages), url_context) - - self.assertEqual([n.title for n in nav_items], - ['Home', 'Running', 'Notes', 'License']) - self.assertEqual([n.url for n in nav_items], [ - '.', - 'api-guide/running/', - 'about/notes/', - 'about/sub/license/' - ]) - self.assertEqual([p.title for p in pages], - ['Home', 'Running', 'Notes', 'License']) - - @mock.patch.object(os.path, 'sep', '\\') - def test_generate_site_navigation_windows(self): - """ - Verify inferring page titles based on the filename with a windows path - """ - pages = [ - 'index.md', - 'api-guide\\running.md', - 'about\\notes.md', - 'about\\sub\\license.md', - ] - - url_context = nav.URLContext() - nav_items, pages = nav._generate_site_navigation(load_config(pages=pages), url_context) - - self.assertEqual([n.title for n in nav_items], - ['Home', 'Running', 'Notes', 'License']) - self.assertEqual([n.url for n in nav_items], [ - '.', - 'api-guide/running/', - 'about/notes/', - 'about/sub/license/' - ]) - self.assertEqual([p.title for p in pages], - ['Home', 'Running', 'Notes', 'License']) - - def test_force_abs_urls(self): - """ - Verify force absolute URLs - """ - - pages = [ - 'index.md', - 'api-guide/running.md', - 'about/notes.md', - 'about/sub/license.md', - ] - - url_context = nav.URLContext() - url_context.force_abs_urls = True - nav_items, pages = nav._generate_site_navigation(load_config(pages=pages), url_context) - - self.assertEqual([n.title for n in nav_items], - ['Home', 'Running', 'Notes', 'License']) - self.assertEqual([n.url for n in nav_items], [ - '/', - '/api-guide/running/', - '/about/notes/', - '/about/sub/license/' - ]) - - def test_force_abs_urls_with_base(self): - """ - Verify force absolute URLs - """ - - pages = [ - 'index.md', - 'api-guide/running.md', - 'about/notes.md', - 'about/sub/license.md', - ] - - url_context = nav.URLContext() - url_context.force_abs_urls = True - url_context.base_path = '/foo/' - nav_items, pages = nav._generate_site_navigation(load_config(pages=pages), url_context) - - self.assertEqual([n.title for n in nav_items], - ['Home', 'Running', 'Notes', 'License']) - self.assertEqual([n.url for n in nav_items], [ - '/foo/', - '/foo/api-guide/running/', - '/foo/about/notes/', - '/foo/about/sub/license/' - ]) - - def test_invalid_pages_config(self): - - bad_page = {"a": "index.md", "b": "index.md"} # extra key - - def _test(): - return nav._generate_site_navigation(load_config(pages=[bad_page, ]), None) - - self.assertRaises(ConfigurationError, _test) - - def test_pages_config(self): - - bad_page = {} # empty - - def _test(): - return nav._generate_site_navigation(load_config(pages=[bad_page, ]), None) - - self.assertRaises(ConfigurationError, _test) - - def test_ancestors(self): - - pages = [ - {'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'} - ]} - ] - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - - ancestors = ( - [], - [site_navigation.nav_items[1]], - [site_navigation.nav_items[1]], - [site_navigation.nav_items[1]], - [site_navigation.nav_items[1], - site_navigation.pages[4].ancestors[-1]], - [site_navigation.nav_items[2]], - [site_navigation.nav_items[2]], - ) - - self.assertEqual(len(site_navigation.pages), len(ancestors)) - - for i, (page, expected_ancestor) in enumerate( - zip(site_navigation.pages, ancestors)): - self.assertEqual(page.ancestors, expected_ancestor, - "Failed on ancestor test {0}".format(i)) - - def test_nesting(self): - - pages = [ - {'Home': 'index.md'}, - {'Install': [ - {'Pre-install': 'install/install-pre.md'}, - {'The install': 'install/install-actual.md'}, - {'Post install': 'install/install-post.md'}, - ]}, - {'Guide': [ - {'Tutorial': [ - {'Getting Started': 'guide/tutorial/running.md'}, - {'Advanced Features': 'guide/tutorial/testing.md'}, - {'Further Reading': 'guide/tutorial/debugging.md'}, - ]}, - {'API Reference': [ - {'Feature 1': 'guide/api-ref/running.md'}, - {'Feature 2': 'guide/api-ref/testing.md'}, - {'Feature 3': 'guide/api-ref/debugging.md'}, - ]}, - {'Testing': 'guide/testing.md'}, - {'Deploying': 'guide/deploying.md'}, - ]} - ] - - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - - self.assertEqual([n.title for n in site_navigation.nav_items], - ['Home', 'Install', 'Guide']) - self.assertEqual(len(site_navigation.pages), 12) - - expected = dedent(""" - Home - / - Install - Pre-install - /install/install-pre/ - The install - /install/install-actual/ - Post install - /install/install-post/ - Guide - Tutorial - Getting Started - /guide/tutorial/running/ - Advanced Features - /guide/tutorial/testing/ - Further Reading - /guide/tutorial/debugging/ - API Reference - Feature 1 - /guide/api-ref/running/ - Feature 2 - /guide/api-ref/testing/ - Feature 3 - /guide/api-ref/debugging/ - Testing - /guide/testing/ - Deploying - /guide/deploying/ - """) - - self.maxDiff = None - self.assertEqual(str(site_navigation).strip(), expected) - - def test_edit_uri(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Basic test - repo_url = 'http://example.com/' - edit_uri = 'edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2], - repo_url + edit_uri + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_sub_dir(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Basic test - repo_url = 'http://example.com/foo/' - edit_uri = 'edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2], - repo_url + edit_uri + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_missing_slash(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Ensure the '/' is added to the repo_url and edit_uri - repo_url = 'http://example.com' - edit_uri = 'edit/master/docs' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + '/' + edit_uri + '/' + pages[0], - repo_url + '/' + edit_uri + '/' + pages[1], - repo_url + '/' + edit_uri + '/' + pages[2], - repo_url + '/' + edit_uri + '/' + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_sub_dir_missing_slash(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Basic test - repo_url = 'http://example.com/foo' - edit_uri = 'edit/master/docs' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + '/' + edit_uri + '/' + pages[0], - repo_url + '/' + edit_uri + '/' + pages[1], - repo_url + '/' + edit_uri + '/' + pages[2], - repo_url + '/' + edit_uri + '/' + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_query_string(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Ensure query strings are supported - repo_url = 'http://example.com' - edit_uri = '?query=edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2], - repo_url + edit_uri + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_fragment(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub/internal.md', - 'sub1/sub2/internal.md', - ] - - # Ensure fragment strings are supported - repo_url = 'http://example.com' - edit_uri = '#fragment/edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2], - repo_url + edit_uri + pages[3], - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Basic test - repo_url = 'http://example.com/' - edit_uri = 'edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2].replace('\\', '/'), - repo_url + edit_uri + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_sub_dir_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Basic test - repo_url = 'http://example.com/foo/' - edit_uri = 'edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2].replace('\\', '/'), - repo_url + edit_uri + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_missing_slash_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Ensure the '/' is added to the repo_url and edit_uri - repo_url = 'http://example.com' - edit_uri = 'edit/master/docs' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + '/' + edit_uri + '/' + pages[0], - repo_url + '/' + edit_uri + '/' + pages[1], - repo_url + '/' + edit_uri + '/' + pages[2].replace('\\', '/'), - repo_url + '/' + edit_uri + '/' + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_sub_dir_missing_slash_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Ensure the '/' is added to the repo_url and edit_uri - repo_url = 'http://example.com/foo' - edit_uri = 'edit/master/docs' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + '/' + edit_uri + '/' + pages[0], - repo_url + '/' + edit_uri + '/' + pages[1], - repo_url + '/' + edit_uri + '/' + pages[2].replace('\\', '/'), - repo_url + '/' + edit_uri + '/' + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_query_string_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Ensure query strings are supported - repo_url = 'http://example.com' - edit_uri = '?query=edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2].replace('\\', '/'), - repo_url + edit_uri + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) - - def test_edit_uri_fragment_windows(self): - - pages = [ - 'index.md', - 'internal.md', - 'sub\\internal.md', - 'sub1\\sub2\\internal.md', - ] - - # Ensure fragment strings are supported - repo_url = 'http://example.com' - edit_uri = '#fragment/edit/master/docs/' - - site_navigation = nav.SiteNavigation(load_config( - pages=pages, - repo_url=repo_url, - edit_uri=edit_uri, - site_dir='site', - site_url='', - use_directory_urls=True - )) - - expected_results = ( - repo_url + edit_uri + pages[0], - repo_url + edit_uri + pages[1], - repo_url + edit_uri + pages[2].replace('\\', '/'), - repo_url + edit_uri + pages[3].replace('\\', '/'), - ) - - for idx, page in enumerate(site_navigation.walk_pages()): - self.assertEqual(page.edit_url, expected_results[idx]) diff --git a/mkdocs/tests/search_tests.py b/mkdocs/tests/search_tests.py index 2444b8efed..8441e61bdb 100644 --- a/mkdocs/tests/search_tests.py +++ b/mkdocs/tests/search_tests.py @@ -6,11 +6,13 @@ import mock import json -from mkdocs import nav +from mkdocs.structure.files import File +from mkdocs.structure.pages import Page +from mkdocs.structure.toc import get_toc from mkdocs.contrib import search from mkdocs.contrib.search import search_index from mkdocs.config.config_options import ValidationError -from mkdocs.tests.base import dedent, markdown_to_toc, load_config +from mkdocs.tests.base import dedent, get_markdown_toc, load_config def strip_whitespace(string): @@ -248,7 +250,7 @@ def test_find_toc_by_id(self): ## Heading 2 ### Heading 3 """) - toc = markdown_to_toc(md) + toc = get_toc(get_markdown_toc(md)) toc_item = index._find_toc_by_id(toc, "heading-1") self.assertEqual(toc_item.url, "#heading-1") @@ -273,23 +275,22 @@ def test_create_search_index(self):Content 3
""" + cfg = load_config() pages = [ - {'Home': 'index.md'}, - {'About': 'about.md'}, + Page('Home', File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg), + Page('About', File('about.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']), cfg) ] - site_navigation = nav.SiteNavigation(load_config(pages=pages)) - md = dedent(""" # Heading 1 ## Heading 2 ### Heading 3 """) - toc = markdown_to_toc(md) + toc = get_toc(get_markdown_toc(md)) full_content = ''.join("""Heading{0}Content{0}""".format(i) for i in range(1, 4)) - for page in site_navigation: + for page in pages: # Fake page.read_source() and page.render() page.markdown = md page.toc = toc @@ -300,7 +301,7 @@ def test_create_search_index(self): self.assertEqual(len(index._entries), 4) - loc = page.abs_url + loc = page.url self.assertEqual(index._entries[0]['title'], page.title) self.assertEqual(strip_whitespace(index._entries[0]['text']), full_content) diff --git a/mkdocs/tests/structure/__init__.py b/mkdocs/tests/structure/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mkdocs/tests/structure/file_tests.py b/mkdocs/tests/structure/file_tests.py new file mode 100644 index 0000000000..3e56bea1da --- /dev/null +++ b/mkdocs/tests/structure/file_tests.py @@ -0,0 +1,576 @@ +import unittest +import os +import io +import mock + +from mkdocs.structure.files import Files, File, get_files, _sort_files, _filter_paths +from mkdocs.tests.base import load_config, tempdir, PathAssertionMixin + + +class TestFiles(PathAssertionMixin, unittest.TestCase): + + def test_file_eq(self): + file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertTrue(file == File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)) + + def test_file_ne(self): + file = File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + # Different filename + self.assertTrue(file != File('b.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)) + # Different src_path + self.assertTrue(file != File('a.md', '/path/to/other', '/path/to/site', use_directory_urls=False)) + # Different URL + self.assertTrue(file != File('a.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)) + + def test_sort_files(self): + self.assertEqual( + _sort_files(['b.md', 'bb.md', 'a.md', 'index.md', 'aa.md']), + ['index.md', 'a.md', 'aa.md', 'b.md', 'bb.md'] + ) + + self.assertEqual( + _sort_files(['b.md', 'index.html', 'a.md', 'index.md']), + ['index.html', 'index.md', 'a.md', 'b.md'] + ) + + self.assertEqual( + _sort_files(['a.md', 'index.md', 'b.md', 'index.html']), + ['index.md', 'index.html', 'a.md', 'b.md'] + ) + + self.assertEqual( + _sort_files(['.md', '_.md', 'a.md', 'index.md', '1.md']), + ['index.md', '.md', '1.md', '_.md', 'a.md'] + ) + + self.assertEqual( + _sort_files(['a.md', 'b.md', 'a.md']), + ['a.md', 'a.md', 'b.md'] + ) + + def test_md_file(self): + f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md') + self.assertPathsEqual(f.dest_path, 'foo.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo.html') + self.assertEqual(f.url, 'foo.html') + self.assertEqual(f.name, 'foo') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_file_use_directory_urls(self): + f = File('foo.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md') + self.assertPathsEqual(f.dest_path, 'foo/index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') + self.assertEqual(f.url, 'foo/') + self.assertEqual(f.name, 'foo') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_file_nested(self): + f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/bar.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md') + self.assertPathsEqual(f.dest_path, 'foo/bar.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') + self.assertEqual(f.url, 'foo/bar.html') + self.assertEqual(f.name, 'bar') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_file_nested_use_directory_urls(self): + f = File('foo/bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/bar.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md') + self.assertPathsEqual(f.dest_path, 'foo/bar/index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar/index.html') + self.assertEqual(f.url, 'foo/bar/') + self.assertEqual(f.name, 'bar') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_index_file(self): + f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'index.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md') + self.assertPathsEqual(f.dest_path, 'index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html') + self.assertEqual(f.url, 'index.html') + self.assertEqual(f.name, 'index') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_index_file_use_directory_urls(self): + f = File('index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'index.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md') + self.assertPathsEqual(f.dest_path, 'index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/index.html') + self.assertEqual(f.url, '.') + self.assertEqual(f.name, 'index') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_index_file_nested(self): + f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/index.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md') + self.assertPathsEqual(f.dest_path, 'foo/index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') + self.assertEqual(f.url, 'foo/index.html') + self.assertEqual(f.name, 'index') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_md_index_file_nested_use_directory_urls(self): + f = File('foo/index.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/index.md') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md') + self.assertPathsEqual(f.dest_path, 'foo/index.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/index.html') + self.assertEqual(f.url, 'foo/') + self.assertEqual(f.name, 'index') + self.assertTrue(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_static_file(self): + f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/bar.html') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html') + self.assertPathsEqual(f.dest_path, 'foo/bar.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') + self.assertEqual(f.url, 'foo/bar.html') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertTrue(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_static_file_use_directory_urls(self): + f = File('foo/bar.html', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/bar.html') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html') + self.assertPathsEqual(f.dest_path, 'foo/bar.html') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.html') + self.assertEqual(f.url, 'foo/bar.html') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertTrue(f.is_static_page()) + self.assertFalse(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_media_file(self): + f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/bar.jpg') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg') + self.assertPathsEqual(f.dest_path, 'foo/bar.jpg') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg') + self.assertEqual(f.url, 'foo/bar.jpg') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_media_file_use_directory_urls(self): + f = File('foo/bar.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/bar.jpg') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg') + self.assertPathsEqual(f.dest_path, 'foo/bar.jpg') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.jpg') + self.assertEqual(f.url, 'foo/bar.jpg') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_javascript_file(self): + f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/bar.js') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js') + self.assertPathsEqual(f.dest_path, 'foo/bar.js') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js') + self.assertEqual(f.url, 'foo/bar.js') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertTrue(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_javascript_file_use_directory_urls(self): + f = File('foo/bar.js', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/bar.js') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js') + self.assertPathsEqual(f.dest_path, 'foo/bar.js') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.js') + self.assertEqual(f.url, 'foo/bar.js') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertTrue(f.is_javascript()) + self.assertFalse(f.is_css()) + + def test_css_file(self): + f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertPathsEqual(f.src_path, 'foo/bar.css') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css') + self.assertPathsEqual(f.dest_path, 'foo/bar.css') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css') + self.assertEqual(f.url, 'foo/bar.css') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertTrue(f.is_css()) + + def test_css_file_use_directory_urls(self): + f = File('foo/bar.css', '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertPathsEqual(f.src_path, 'foo/bar.css') + self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css') + self.assertPathsEqual(f.dest_path, 'foo/bar.css') + self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo/bar.css') + self.assertEqual(f.url, 'foo/bar.css') + self.assertEqual(f.name, 'bar') + self.assertFalse(f.is_documentation_page()) + self.assertFalse(f.is_static_page()) + self.assertTrue(f.is_media_file()) + self.assertFalse(f.is_javascript()) + self.assertTrue(f.is_css()) + + 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]) + + def test_filter_paths(self): + # Root level file + self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['bar.md'])) + self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['foo.md'])) + + # Nested file + self.assertFalse(_filter_paths('foo.md', 'baz/foo.md', False, ['bar.md'])) + self.assertTrue(_filter_paths('foo.md', 'baz/foo.md', False, ['foo.md'])) + + # Wildcard + self.assertFalse(_filter_paths('foo.md', 'foo.md', False, ['*.txt'])) + self.assertTrue(_filter_paths('foo.md', 'foo.md', False, ['*.md'])) + + # Root level dir + self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz'])) + self.assertFalse(_filter_paths('bar', 'bar', True, ['/baz/'])) + self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar'])) + self.assertTrue(_filter_paths('bar', 'bar', True, ['/bar/'])) + + # Nested dir + self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar'])) + self.assertFalse(_filter_paths('bar', 'foo/bar', True, ['/bar/'])) + self.assertTrue(_filter_paths('bar', 'foo/bar', True, ['bar/'])) + + # Files that look like dirs (no extension). Note that `is_dir` is `False`. + self.assertFalse(_filter_paths('bar', 'bar', False, ['bar/'])) + self.assertFalse(_filter_paths('bar', 'foo/bar', False, ['bar/'])) + + def test_get_relative_url_use_directory_urls(self): + to_files = [ + 'index.md', + 'foo/index.md', + 'foo/bar/index.md', + 'foo/bar/baz/index.md', + 'foo.md', + 'foo/bar.md', + 'foo/bar/baz.md' + ] + + to_file_urls = [ + '.', + 'foo/', + 'foo/bar/', + 'foo/bar/baz/', + 'foo/', + 'foo/bar/', + 'foo/bar/baz/' + ] + + from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) + expected = [ + 'img.jpg', # img.jpg relative to . + '../img.jpg', # img.jpg relative to foo/ + '../../img.jpg', # img.jpg relative to foo/bar/ + '../../../img.jpg', # img.jpg relative to foo/bar/baz/ + '../img.jpg', # img.jpg relative to foo + '../../img.jpg', # img.jpg relative to foo/bar + '../../../img.jpg' # img.jpg relative to foo/bar/baz + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertEqual(from_file.url, 'img.jpg') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=True) + expected = [ + 'foo/img.jpg', # foo/img.jpg relative to . + 'img.jpg', # foo/img.jpg relative to foo/ + '../img.jpg', # foo/img.jpg relative to foo/bar/ + '../../img.jpg', # foo/img.jpg relative to foo/bar/baz/ + 'img.jpg', # foo/img.jpg relative to foo + '../img.jpg', # foo/img.jpg relative to foo/bar + '../../img.jpg' # foo/img.jpg relative to foo/bar/baz + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertEqual(from_file.url, 'foo/img.jpg') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=True) + expected = [ + '.', # . relative to . + '..', # . relative to foo/ + '../..', # . relative to foo/bar/ + '../../..', # . relative to foo/bar/baz/ + '..', # . relative to foo + '../..', # . relative to foo/bar + '../../..' # . relative to foo/bar/baz + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertEqual(from_file.url, '.') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('file.md', '/path/to/docs', '/path/to/site', use_directory_urls=True) + expected = [ + 'file/', # file relative to . + '../file/', # file relative to foo/ + '../../file/', # file relative to foo/bar/ + '../../../file/', # file relative to foo/bar/baz/ + '../file/', # file relative to foo + '../../file/', # file relative to foo/bar + '../../../file/' # file relative to foo/bar/baz + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=True) + self.assertEqual(from_file.url, 'file/') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + def test_get_relative_url(self): + to_files = [ + 'index.md', + 'foo/index.md', + 'foo/bar/index.md', + 'foo/bar/baz/index.md', + 'foo.md', + 'foo/bar.md', + 'foo/bar/baz.md' + ] + + to_file_urls = [ + 'index.html', + 'foo/index.html', + 'foo/bar/index.html', + 'foo/bar/baz/index.html', + 'foo.html', + 'foo/bar.html', + 'foo/bar/baz.html' + ] + + from_file = File('img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) + expected = [ + 'img.jpg', # img.jpg relative to . + '../img.jpg', # img.jpg relative to foo/ + '../../img.jpg', # img.jpg relative to foo/bar/ + '../../../img.jpg', # img.jpg relative to foo/bar/baz/ + 'img.jpg', # img.jpg relative to foo.html + '../img.jpg', # img.jpg relative to foo/bar.html + '../../img.jpg' # img.jpg relative to foo/bar/baz.html + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertEqual(from_file.url, 'img.jpg') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('foo/img.jpg', '/path/to/docs', '/path/to/site', use_directory_urls=False) + expected = [ + 'foo/img.jpg', # foo/img.jpg relative to . + 'img.jpg', # foo/img.jpg relative to foo/ + '../img.jpg', # foo/img.jpg relative to foo/bar/ + '../../img.jpg', # foo/img.jpg relative to foo/bar/baz/ + 'foo/img.jpg', # foo/img.jpg relative to foo.html + 'img.jpg', # foo/img.jpg relative to foo/bar.html + '../img.jpg' # foo/img.jpg relative to foo/bar/baz.html + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertEqual(from_file.url, 'foo/img.jpg') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('index.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) + expected = [ + 'index.html', # index.html relative to . + '../index.html', # index.html relative to foo/ + '../../index.html', # index.html relative to foo/bar/ + '../../../index.html', # index.html relative to foo/bar/baz/ + 'index.html', # index.html relative to foo.html + '../index.html', # index.html relative to foo/bar.html + '../../index.html' # index.html relative to foo/bar/baz.html + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertEqual(from_file.url, 'index.html') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + from_file = File('file.html', '/path/to/docs', '/path/to/site', use_directory_urls=False) + expected = [ + 'file.html', # file.html relative to . + '../file.html', # file.html relative to foo/ + '../../file.html', # file.html relative to foo/bar/ + '../../../file.html', # file.html relative to foo/bar/baz/ + 'file.html', # file.html relative to foo.html + '../file.html', # file.html relative to foo/bar.html + '../../file.html' # file.html relative to foo/bar/baz.html + ] + + for i, filename in enumerate(to_files): + file = File(filename, '/path/to/docs', '/path/to/site', use_directory_urls=False) + self.assertEqual(from_file.url, 'file.html') + self.assertEqual(file.url, to_file_urls[i]) + self.assertEqual(from_file.url_relative_to(file.url), expected[i]) + self.assertEqual(from_file.url_relative_to(file), expected[i]) + + @tempdir(files=[ + 'index.md', + 'bar.css', + 'bar.html', + 'bar.jpg', + 'bar.js', + 'bar.md', + '.dotfile', + 'templates/foo.html' + ]) + def test_get_files(self, tdir): + config = load_config(docs_dir=tdir, extra_css=['bar.css'], extra_javascript=['bar.js']) + files = get_files(config) + expected = ['index.md', 'bar.css', 'bar.html', 'bar.jpg', 'bar.js', 'bar.md'] + self.assertIsInstance(files, Files) + self.assertEqual(len(files), len(expected)) + self.assertEqual([f.src_path for f in files], expected) + + @tempdir() + @tempdir(files={'test.txt': 'source content'}) + def test_copy_file(self, src_dir, dest_dir): + file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) + dest_path = os.path.join(dest_dir, 'test.txt') + self.assertPathNotExists(dest_path) + file.copy_file() + self.assertPathIsFile(dest_path) + + @tempdir(files={'test.txt': 'destination content'}) + @tempdir(files={'test.txt': 'source content'}) + def test_copy_file_clean_modified(self, src_dir, dest_dir): + file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) + file.is_modified = mock.Mock(return_value=True) + dest_path = os.path.join(dest_dir, 'test.txt') + file.copy_file(dirty=False) + self.assertPathIsFile(dest_path) + with io.open(dest_path, 'r', encoding='utf-8') as f: + self.assertEqual(f.read(), 'source content') + + @tempdir(files={'test.txt': 'destination content'}) + @tempdir(files={'test.txt': 'source content'}) + def test_copy_file_dirty_modified(self, src_dir, dest_dir): + file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) + file.is_modified = mock.Mock(return_value=True) + dest_path = os.path.join(dest_dir, 'test.txt') + file.copy_file(dirty=True) + self.assertPathIsFile(dest_path) + with io.open(dest_path, 'r', encoding='utf-8') as f: + self.assertEqual(f.read(), 'source content') + + @tempdir(files={'test.txt': 'destination content'}) + @tempdir(files={'test.txt': 'source content'}) + def test_copy_file_dirty_not_modified(self, src_dir, dest_dir): + file = File('test.txt', src_dir, dest_dir, use_directory_urls=False) + file.is_modified = mock.Mock(return_value=False) + dest_path = os.path.join(dest_dir, 'test.txt') + file.copy_file(dirty=True) + self.assertPathIsFile(dest_path) + with io.open(dest_path, 'r', encoding='utf-8') as f: + self.assertEqual(f.read(), 'destination content') diff --git a/mkdocs/tests/structure/nav_tests.py b/mkdocs/tests/structure/nav_tests.py new file mode 100644 index 0000000000..3596fb6f85 --- /dev/null +++ b/mkdocs/tests/structure/nav_tests.py @@ -0,0 +1,343 @@ +#!/usr/bin/env python +# coding: utf-8 + +from __future__ import unicode_literals +import sys +import unittest + +from mkdocs.structure.nav import get_navigation +from mkdocs.structure.files import File, Files +from mkdocs.structure.pages import Page +from mkdocs.tests.base import dedent, load_config + + +class SiteNavigationTests(unittest.TestCase): + + maxDiff = None + + def test_simple_nav(self): + nav_cfg = [ + {'Home': 'index.md'}, + {'About': 'about.md'} + ] + expected = dedent(""" + Page(title='Home', url='/') + Page(title='About', url='/about/') + """) + 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), 2) + self.assertEqual(len(site_navigation.pages), 2) + self.assertEqual(repr(site_navigation.homepage), "Page(title='Home', url='/')") + + 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) + + 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) + + 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) + + 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'])]) + 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), 1) + + 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) + + 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) + + 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='/')") + + @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") + def test_nested_ungrouped_no_titles_windows(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) + + 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='/')") + + 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='/')") + + 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) diff --git a/mkdocs/tests/structure/page_tests.py b/mkdocs/tests/structure/page_tests.py new file mode 100644 index 0000000000..1557badbc8 --- /dev/null +++ b/mkdocs/tests/structure/page_tests.py @@ -0,0 +1,789 @@ +from __future__ import unicode_literals + +import unittest +import os +import sys +import mock +import io + +try: + # py>=3.2 + from tempfile import TemporaryDirectory +except ImportError: + from backports.tempfile import TemporaryDirectory + +from mkdocs.structure.pages import Page +from mkdocs.structure.files import File, Files +from mkdocs.tests.base import load_config, dedent, LogTestCase +from mkdocs.exceptions import MarkdownNotFound + + +class PageTests(unittest.TestCase): + + DOCS_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../integration/subpages/docs') + + def test_homepage(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + self.assertIsNone(fl.page) + pg = Page('Foo', fl, cfg) + self.assertEqual(fl.page, pg) + self.assertEqual(pg.url, '') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertTrue(pg.is_homepage) + self.assertTrue(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_nested_index_page(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + pg.parent = 'foo' + self.assertEqual(pg.url, 'sub1/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertTrue(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertFalse(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, 'foo') + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_nested_nonindex_page(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + pg.parent = 'foo' + self.assertEqual(pg.url, 'sub1/non-index/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertFalse(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, 'foo') + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_page_defaults(self): + cfg = load_config() + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertRegexpMatches(pg.update_date, r'\d{4}-\d{2}-\d{2}') + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_page_no_directory_url(self): + cfg = load_config(use_directory_urls=False) + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'testing.html') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_page_canonical_url(self): + cfg = load_config(site_url='http://example.com') + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, '/testing/') + self.assertEqual(pg.canonical_url, 'http://example.com/testing/') + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_page_canonical_url_nested(self): + cfg = load_config(site_url='http://example.com/foo/') + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, '/foo/testing/') + self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/') + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_page_canonical_url_nested_no_slash(self): + cfg = load_config(site_url='http://example.com/foo') + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, '/foo/testing/') + self.assertEqual(pg.canonical_url, 'http://example.com/foo/testing/') + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertEqual(pg.markdown, None) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Foo') + self.assertEqual(pg.toc, []) + + def test_predefined_page_title(self): + cfg = load_config() + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Page Title', fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Page Title') + self.assertEqual(pg.toc, []) + + def test_page_title_from_markdown(self): + cfg = load_config() + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Welcome to MkDocs') + self.assertEqual(pg.toc, []) + + def test_page_title_from_meta(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('metadata.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, 'metadata/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('# Welcome to MkDocs\n')) + self.assertEqual(pg.meta, {'title': 'A Page Title'}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'A Page Title') + self.assertEqual(pg.toc, []) + + def test_page_title_from_filename(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('page-title.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, 'page-title/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('Page content.\n')) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Page title') + self.assertEqual(pg.toc, []) + + def test_page_title_from_capitalized_filename(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('pageTitle.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, 'pageTitle/') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertFalse(pg.is_homepage) + self.assertFalse(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('Page content.\n')) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'pageTitle') + self.assertEqual(pg.toc, []) + + def test_page_title_from_homepage_filename(self): + cfg = load_config(docs_dir=self.DOCS_DIR) + fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.url, '') + self.assertEqual(pg.abs_url, None) + self.assertEqual(pg.canonical_url, None) + self.assertEqual(pg.edit_url, None) + self.assertEqual(pg.file, fl) + self.assertEqual(pg.content, None) + self.assertTrue(pg.is_homepage) + self.assertTrue(pg.is_index) + self.assertTrue(pg.is_page) + self.assertFalse(pg.is_section) + self.assertTrue(pg.is_top_level) + self.assertTrue(pg.markdown.startswith('## Test')) + self.assertEqual(pg.meta, {}) + self.assertEqual(pg.next_page, None) + self.assertEqual(pg.parent, None) + self.assertEqual(pg.previous_page, None) + self.assertEqual(pg.title, 'Home') + self.assertEqual(pg.toc, []) + + def test_page_eq(self): + cfg = load_config() + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertTrue(pg == Page('Foo', fl, cfg)) + + def test_page_ne(self): + cfg = load_config() + f1 = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + f2 = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', f1, cfg) + # Different Title + self.assertTrue(pg != Page('Bar', f1, cfg)) + # Different File + self.assertTrue(pg != Page('Foo', f2, cfg)) + + def test_BOM(self): + md_src = '# An UTF-8 encoded file with a BOM' + with TemporaryDirectory() as docs_dir: + # We don't use mkdocs.tests.base.tempdir decorator here due to uniqueness of this test. + cfg = load_config(docs_dir=docs_dir) + fl = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page(None, fl, cfg) + # Create an UTF-8 Encoded file with BOM (as Micorsoft editors do). See #1186 + with io.open(fl.abs_src_path, 'w', encoding='utf-8-sig') as f: + f.write(md_src) + # Now read the file. + pg.read_source(cfg) + # Ensure the BOM (`\ufeff`) is removed + self.assertNotIn('\ufeff', pg.markdown) + self.assertEqual(pg.markdown, md_src) + self.assertEqual(pg.meta, {}) + + def test_page_edit_url(self): + configs = [ + { + 'repo_url': 'http://github.com/mkdocs/mkdocs' + }, + { + 'repo_url': 'https://github.com/mkdocs/mkdocs/' + }, { + 'repo_url': 'http://example.com' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': 'edit/master' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '?query=edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '?query=edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '#edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '#edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '' # Set to blank value + }, { + # Nothing defined + } + ] + + expected = [ + 'http://github.com/mkdocs/mkdocs/edit/master/docs/testing.md', + 'https://github.com/mkdocs/mkdocs/edit/master/docs/testing.md', + None, + 'http://example.com/edit/master/testing.md', + 'http://example.com/edit/master/testing.md', + 'http://example.com/edit/master/testing.md', + 'http://example.com/edit/master/testing.md', + 'http://example.com/edit/master/testing.md', + 'http://example.com/foo/edit/master/testing.md', + 'http://example.com/foo/edit/master/testing.md', + 'http://example.com?query=edit/master/testing.md', + 'http://example.com/?query=edit/master/testing.md', + 'http://example.com#edit/master/testing.md', + 'http://example.com/#edit/master/testing.md', + None, + None + ] + + for i, c in enumerate(configs): + cfg = load_config(**c) + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'testing/') + self.assertEqual(pg.edit_url, expected[i]) + + def test_nested_page_edit_url(self): + configs = [ + { + 'repo_url': 'http://github.com/mkdocs/mkdocs' + }, + { + 'repo_url': 'https://github.com/mkdocs/mkdocs/' + }, { + 'repo_url': 'http://example.com' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': 'edit/master' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '?query=edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '?query=edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '#edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '#edit/master/' + } + ] + + expected = [ + 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', + 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', + None, + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/foo/edit/master/sub1/non-index.md', + 'http://example.com/foo/edit/master/sub1/non-index.md', + 'http://example.com?query=edit/master/sub1/non-index.md', + 'http://example.com/?query=edit/master/sub1/non-index.md', + 'http://example.com#edit/master/sub1/non-index.md', + 'http://example.com/#edit/master/sub1/non-index.md' + ] + + for i, c in enumerate(configs): + c['docs_dir'] = self.DOCS_DIR + cfg = load_config(**c) + fl = File('sub1/non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'sub1/non-index/') + self.assertEqual(pg.edit_url, expected[i]) + + @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") + def test_nested_page_edit_url_windows(self): + configs = [ + { + 'repo_url': 'http://github.com/mkdocs/mkdocs' + }, + { + 'repo_url': 'https://github.com/mkdocs/mkdocs/' + }, { + 'repo_url': 'http://example.com' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': 'edit/master' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': '/edit/master/' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': '/edit/master' + }, { + 'repo_url': 'http://example.com/foo/', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com/foo', + 'edit_uri': 'edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '?query=edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '?query=edit/master/' + }, { + 'repo_url': 'http://example.com', + 'edit_uri': '#edit/master' + }, { + 'repo_url': 'http://example.com/', + 'edit_uri': '#edit/master/' + } + ] + + expected = [ + 'http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', + 'https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md', + None, + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/edit/master/sub1/non-index.md', + 'http://example.com/foo/edit/master/sub1/non-index.md', + 'http://example.com/foo/edit/master/sub1/non-index.md', + 'http://example.com?query=edit/master/sub1/non-index.md', + 'http://example.com/?query=edit/master/sub1/non-index.md', + 'http://example.com#edit/master/sub1/non-index.md', + 'http://example.com/#edit/master/sub1/non-index.md' + ] + + for i, c in enumerate(configs): + c['docs_dir'] = self.DOCS_DIR + cfg = load_config(**c) + fl = File('sub1\\non-index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + self.assertEqual(pg.url, 'sub1/non-index/') + self.assertEqual(pg.edit_url, expected[i]) + + def test_page_render(self): + cfg = load_config() + fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) + pg = Page('Foo', fl, cfg) + pg.read_source(cfg) + self.assertEqual(pg.content, None) + self.assertEqual(pg.toc, []) + pg.render(cfg, [fl]) + self.assertTrue(pg.content.startswith( + 'not a link.
' + ) + + @mock.patch('io.open', mock.mock_open(read_data='[link](non-existant.md)')) + def test_bad_relative_html_link(self): + with self.assertLogs('mkdocs', level='WARNING') as cm: + self.assertEqual( + self.get_rendered_result(['index.md']), + '' + ) + self.assertEqual( + cm.output, + ["WARNING:mkdocs.structure.pages:Documentation file 'index.md' contains a link " + "to 'non-existant.md' which does not exist in the documentation directory."] + ) + + @mock.patch('io.open', mock.mock_open(read_data='[link](non-existant.md)')) + def test_bad_relative_html_link_strict(self): + self.assertRaises(MarkdownNotFound, self.get_rendered_result, ['index.md'], strict=True) + + @mock.patch('io.open', mock.mock_open(read_data='[external link](http://example.com/index.md)')) + def test_external_link(self): + self.assertEqual( + self.get_rendered_result(['index.md'], strict=True), + '' + ) + + @mock.patch('io.open', mock.mock_open(read_data='