added podman, json and yaml

This commit is contained in:
2022-11-27 19:11:46 +01:00
parent 01135dea09
commit 5226e858bb
790 changed files with 114578 additions and 16 deletions

View File

@ -0,0 +1,13 @@
import logging
class DisallowLogsHandler(logging.Handler):
def __init__(self, level=logging.WARNING):
super().__init__(level=level)
self.formatter = logging.Formatter("%(levelname)s:%(name)s:%(message)s")
def emit(self, record):
raise AssertionError(f'Unexpected log: {self.format(record)!r}')
logging.lastResort = DisallowLogsHandler() # type: ignore

View File

@ -0,0 +1,140 @@
import contextlib
import os
import textwrap
from functools import wraps
from tempfile import TemporaryDirectory
import markdown
from mkdocs import utils
from mkdocs.config.defaults import MkDocsConfig
def dedent(text):
return textwrap.dedent(text).strip()
def get_markdown_toc(markdown_source):
"""Return TOC generated by Markdown parser from Markdown source text."""
md = markdown.Markdown(extensions=['toc'])
md.convert(markdown_source)
return md.toc_tokens
def load_config(**cfg) -> MkDocsConfig:
"""Helper to build a simple config for testing."""
path_base = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'integration', 'minimal')
cfg = cfg or {}
if 'site_name' not in cfg:
cfg['site_name'] = 'Example'
if 'config_file_path' not in cfg:
cfg['config_file_path'] = os.path.join(path_base, 'mkdocs.yml')
if 'docs_dir' not in cfg:
# Point to an actual dir to avoid a 'does not exist' error on validation.
cfg['docs_dir'] = os.path.join(path_base, 'docs')
conf = MkDocsConfig(config_file_path=cfg['config_file_path'])
conf.load_dict(cfg)
errors_warnings = conf.validate()
assert errors_warnings == ([], []), errors_warnings
return conf
def tempdir(files=None, **kw):
"""
A decorator for building a temporary directory with prepopulated files.
The temporary directory and files are created just before the wrapped function is called and are destroyed
immediately after the wrapped function returns.
The `files` keyword should be a dict of file paths as keys and strings of file content as values.
If `files` is a list, then each item is assumed to be a path of an empty file. All other
keywords are passed to `tempfile.TemporaryDirectory` to create the parent directory.
In the following example, two files are created in the temporary directory and then are destroyed when
the function exits:
@tempdir(files={
'foo.txt': 'foo content',
'bar.txt': 'bar content'
})
def example(self, tdir):
assert os.path.isfile(os.path.join(tdir, 'foo.txt'))
pth = os.path.join(tdir, 'bar.txt')
assert os.path.isfile(pth)
with open(pth, 'r', encoding='utf-8') as f:
assert f.read() == 'bar content'
"""
files = {f: '' for f in files} if isinstance(files, (list, tuple)) else files or {}
kw['prefix'] = 'mkdocs_test-' + kw.get('prefix', '')
def decorator(fn):
@wraps(fn)
def wrapper(self, *args):
with TemporaryDirectory(**kw) as td:
for path, content in files.items():
pth = os.path.join(td, path)
utils.write_file(content.encode(encoding='utf-8'), pth)
return fn(self, td, *args)
return wrapper
return decorator
@contextlib.contextmanager
def change_dir(path):
old_cwd = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_cwd)
class PathAssertionMixin:
"""
Assertion methods for testing paths.
Each method accepts one or more strings, which are first joined using os.path.join.
"""
def assertPathsEqual(self, a, b, msg=None):
self.assertEqual(a.replace(os.sep, '/'), b.replace(os.sep, '/'))
def assertPathExists(self, *parts):
path = os.path.join(*parts)
if not os.path.exists(path):
msg = self._formatMessage(None, f"The path '{path}' does not exist")
raise self.failureException(msg)
def assertPathNotExists(self, *parts):
path = os.path.join(*parts)
if os.path.exists(path):
msg = self._formatMessage(None, f"The path '{path}' does exist")
raise self.failureException(msg)
def assertPathIsFile(self, *parts):
path = os.path.join(*parts)
if not os.path.isfile(path):
msg = self._formatMessage(None, f"The path '{path}' is not a file that exists")
raise self.failureException(msg)
def assertPathNotFile(self, *parts):
path = os.path.join(*parts)
if os.path.isfile(path):
msg = self._formatMessage(None, f"The path '{path}' is a file that exists")
raise self.failureException(msg)
def assertPathIsDir(self, *parts):
path = os.path.join(*parts)
if not os.path.isdir(path):
msg = self._formatMessage(None, f"The path '{path}' is not a directory that exists")
raise self.failureException(msg)
def assertPathNotDir(self, *parts):
path = os.path.join(*parts)
if os.path.isfile(path):
msg = self._formatMessage(None, f"The path '{path}' is a directory that exists")
raise self.failureException(msg)

View File

@ -0,0 +1,571 @@
#!/usr/bin/env python
import unittest
from unittest import mock
from mkdocs.commands import build
from mkdocs.exceptions import PluginError
from mkdocs.structure.files import File, Files
from mkdocs.structure.nav import get_navigation
from mkdocs.structure.pages import Page
from mkdocs.tests.base import PathAssertionMixin, load_config, tempdir
from mkdocs.utils import meta
def build_page(title, path, config, md_src=''):
"""Helper which returns a Page object."""
files = Files([File(path, config.docs_dir, config.site_dir, config.use_directory_urls)])
page = Page(title, list(files)[0], config)
# Fake page.read_source()
page.markdown, page.meta = meta.get_data(md_src)
return page, files
class BuildTests(PathAssertionMixin, unittest.TestCase):
def _get_env_with_null_translations(self, config):
env = config.theme.get_env()
env.add_extension('jinja2.ext.i18n')
env.install_null_translations()
return env
# Test build.get_context
def test_context_base_url_homepage(self):
nav_cfg = [
{'Home': 'index.md'},
]
cfg = load_config(nav=nav_cfg, use_directory_urls=False)
fs = [
File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
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_homepage_use_directory_urls(self):
nav_cfg = [
{'Home': 'index.md'},
]
cfg = load_config(nav=nav_cfg)
fs = [
File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
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'},
]
cfg = load_config(nav=nav_cfg, use_directory_urls=False)
fs = [
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']),
]
files = Files(fs)
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'},
]
cfg = load_config(nav=nav_cfg)
fs = [
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']),
]
files = Files(fs)
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,
)
fs = [
File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
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,
)
fs = [
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']),
]
files = Files(fs)
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'],
)
fs = [
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']),
]
files = Files(fs)
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'])
# TODO: This shouldn't pass on Linux
# @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_context_extra_css_path_warning(self):
nav_cfg = [
{'Home': 'index.md'},
]
cfg = load_config(
nav=nav_cfg,
extra_css=['assets\\style.css'],
use_directory_urls=False,
)
fs = [
File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
nav = get_navigation(files, cfg)
with self.assertLogs('mkdocs') as cm:
context = build.get_context(nav, files, cfg, nav.pages[0])
self.assertEqual(context['extra_css'], ['assets/style.css'])
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.utils:Path 'assets\\style.css' uses OS-specific separator '\\'. "
"That will be unsupported in a future release. Please change it to '/'.",
)
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'])
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)
# 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.GzipFile')
@tempdir()
def test_build_sitemap_template(
self, site_dir, mock_gzip_gzipfile, mock_build_template, mock_write_file
):
cfg = load_config(site_dir=site_dir)
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_gzipfile.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') as cm:
build._build_theme_template('missing.html', env, mock.Mock(), cfg, mock.Mock())
self.assertEqual(
'\n'.join(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') as cm:
build._build_theme_template('main.html', env, mock.Mock(), cfg, mock.Mock())
self.assertEqual(
'\n'.join(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()
# Test build._build_extra_template
@tempdir()
@mock.patch('mkdocs.commands.build.open', mock.mock_open(read_data='template content'))
def test_build_extra_template(self, site_dir):
cfg = load_config(site_dir=site_dir)
fs = [
File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
build._build_extra_template('foo.html', files, cfg, mock.Mock())
@mock.patch('mkdocs.commands.build.open', mock.mock_open(read_data='template content'))
def test_skip_missing_extra_template(self):
cfg = load_config()
fs = [
File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
with self.assertLogs('mkdocs') as cm:
build._build_extra_template('missing.html', files, cfg, mock.Mock())
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.commands.build:Template skipped: 'missing.html' not found in docs_dir.",
)
@mock.patch('mkdocs.commands.build.open', side_effect=OSError('Error message.'))
def test_skip_ioerror_extra_template(self, mock_open):
cfg = load_config()
fs = [
File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
with self.assertLogs('mkdocs') as cm:
build._build_extra_template('foo.html', files, cfg, mock.Mock())
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.commands.build:Error reading template 'foo.html': Error message.",
)
@mock.patch('mkdocs.commands.build.open', mock.mock_open(read_data=''))
def test_skip_extra_template_empty_output(self):
cfg = load_config()
fs = [
File('foo.html', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
]
files = Files(fs)
with self.assertLogs('mkdocs') as cm:
build._build_extra_template('foo.html', files, cfg, mock.Mock())
self.assertEqual(
'\n'.join(cm.output),
"INFO:mkdocs.commands.build:Template skipped: 'foo.html' generated empty output.",
)
# Test build._populate_page
@tempdir(files={'index.md': 'page content'})
def test_populate_page(self, docs_dir):
cfg = load_config(docs_dir=docs_dir)
file = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
page = Page('Foo', file, cfg)
build._populate_page(page, cfg, Files([file]))
self.assertEqual(page.content, '<p>page content</p>')
@tempdir(files={'testing.html': '<p>page content</p>'})
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('<h1 id="welcome-to-mkdocs">Welcome to MkDocs</h1>')
)
@tempdir(files={'index.md': 'page content'})
@tempdir(files={'index.html': '<p>page content</p>'})
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('mkdocs.structure.pages.open', side_effect=OSError('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') as cm:
with self.assertRaises(OSError):
build._populate_page(page, cfg, Files([file]))
self.assertEqual(
cm.output,
[
'ERROR:mkdocs.structure.pages:File not found: missing.md',
"ERROR:mkdocs.commands.build:Error reading page 'missing.md': Error message.",
],
)
mock_open.assert_called_once()
@tempdir(files={'index.md': 'page content'})
@mock.patch(
'mkdocs.plugins.PluginCollection.run_event', side_effect=PluginError('Error message.')
)
def test_populate_page_read_plugin_error(self, docs_dir, mock_open):
cfg = load_config(docs_dir=docs_dir)
file = File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
page = Page('Foo', file, cfg)
with self.assertLogs('mkdocs') as cm:
with self.assertRaises(PluginError):
build._populate_page(page, cfg, Files([file]))
self.assertEqual(
'\n'.join(cm.output),
"ERROR:mkdocs.commands.build:Error reading page 'index.md':",
)
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=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
page = files.documentation_pages()[0].page
# Fake populate page
page.title = 'Title'
page.markdown = 'page content'
page.content = '<p>page content</p>'
build._build_page(page, cfg, files, nav, self._get_env_with_null_translations(cfg))
self.assertPathIsFile(site_dir, 'index.html')
@tempdir()
@mock.patch('jinja2.environment.Template.render', return_value='')
def test_build_page_empty(self, site_dir, render_mock):
cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
with self.assertLogs('mkdocs') as cm:
build._build_page(
files.documentation_pages()[0].page, cfg, files, nav, cfg['theme'].get_env()
)
self.assertEqual(
'\n'.join(cm.output),
"INFO:mkdocs.commands.build:Page skipped: 'index.md'. Generated empty output.",
)
self.assertPathNotFile(site_dir, 'index.html')
render_mock.assert_called_once()
@tempdir(files={'index.md': 'page content'})
@tempdir(files={'index.html': '<p>page content</p>'})
@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=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
page = files.documentation_pages()[0].page
# Fake populate page
page.title = 'Title'
page.markdown = 'new page content'
page.content = '<p>new page content</p>'
build._build_page(
page, cfg, files, nav, self._get_env_with_null_translations(cfg), dirty=True
)
mock_write_file.assert_not_called()
@tempdir(files={'testing.html': '<p>page content</p>'})
@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=['testing.md'], plugins=[])
fs = [File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
page = files.documentation_pages()[0].page
# Fake populate page
page.title = 'Title'
page.markdown = 'page content'
page.content = '<p>page content</p>'
build._build_page(
page, cfg, files, nav, self._get_env_with_null_translations(cfg), 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=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
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 = '<p>page content</p>'
build._build_page(page, cfg, files, nav, self._get_env_with_null_translations(cfg))
self.assertPathIsFile(site_dir, 'index.html')
@tempdir()
@mock.patch('mkdocs.utils.write_file', side_effect=OSError('Error message.'))
def test_build_page_error(self, site_dir, mock_write_file):
cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
page = files.documentation_pages()[0].page
# Fake populate page
page.title = 'Title'
page.markdown = 'page content'
page.content = '<p>page content</p>'
with self.assertLogs('mkdocs') as cm:
with self.assertRaises(OSError):
build._build_page(page, cfg, files, nav, self._get_env_with_null_translations(cfg))
self.assertEqual(
'\n'.join(cm.output),
"ERROR:mkdocs.commands.build:Error building page 'index.md': Error message.",
)
mock_write_file.assert_called_once()
@tempdir()
@mock.patch(
'mkdocs.plugins.PluginCollection.run_event', side_effect=PluginError('Error message.')
)
def test_build_page_plugin_error(self, site_dir, mock_write_file):
cfg = load_config(site_dir=site_dir, nav=['index.md'], plugins=[])
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
nav = get_navigation(files, cfg)
page = files.documentation_pages()[0].page
# Fake populate page
page.title = 'Title'
page.markdown = 'page content'
page.content = '<p>page content</p>'
with self.assertLogs('mkdocs') as cm:
with self.assertRaises(PluginError):
build._build_page(page, cfg, files, nav, cfg['theme'].get_env())
self.assertEqual(
'\n'.join(cm.output),
"ERROR:mkdocs.commands.build:Error building page 'index.md':",
)
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 (converted 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, Python or localization 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')
self.assertPathNotExists(site_dir, 'locales')
# 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))

View File

@ -0,0 +1,626 @@
#!/usr/bin/env python
import io
import logging
import unittest
from unittest import mock
from click.testing import CliRunner
from mkdocs import __main__ as cli
class CLITests(unittest.TestCase):
def setUp(self):
self.runner = CliRunner()
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_default(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve"], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_config_file(self, mock_serve):
result = self.runner.invoke(
cli.cli, ["serve", "--config-file", "mkdocs.yml"], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_serve.call_count, 1)
args, kwargs = mock_serve.call_args
self.assertTrue('config_file' in kwargs)
self.assertIsInstance(kwargs['config_file'], io.BufferedReader)
self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml')
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_dev_addr(self, mock_serve):
result = self.runner.invoke(
cli.cli, ["serve", '--dev-addr', '0.0.0.0:80'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr='0.0.0.0:80',
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_strict(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve", '--strict'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=True,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_theme(self, mock_serve):
result = self.runner.invoke(
cli.cli, ["serve", '--theme', 'readthedocs'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme='readthedocs',
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_use_directory_urls(self, mock_serve):
result = self.runner.invoke(
cli.cli, ["serve", '--use-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=True,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_no_directory_urls(self, mock_serve):
result = self.runner.invoke(
cli.cli, ["serve", '--no-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=False,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_livereload(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve", '--livereload'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_no_livereload(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve", '--no-livereload'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='no-livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_dirtyreload(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve", '--dirtyreload'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='dirty',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=False,
watch=(),
)
@mock.patch('mkdocs.commands.serve.serve', autospec=True)
def test_serve_watch_theme(self, mock_serve):
result = self.runner.invoke(cli.cli, ["serve", '--watch-theme'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_serve.assert_called_once_with(
dev_addr=None,
livereload='livereload',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
watch_theme=True,
watch=(),
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_defaults(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
args, kwargs = mock_build.call_args
self.assertTrue('dirty' in kwargs)
self.assertFalse(kwargs['dirty'])
mock_load_config.assert_called_once_with(
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir=None,
)
handler = logging._handlers.get('MkDocsStreamHandler')
self.assertEqual(handler.level, logging.INFO)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_clean(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build', '--clean'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
args, kwargs = mock_build.call_args
self.assertTrue('dirty' in kwargs)
self.assertFalse(kwargs['dirty'])
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_dirty(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build', '--dirty'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
args, kwargs = mock_build.call_args
self.assertTrue('dirty' in kwargs)
self.assertTrue(kwargs['dirty'])
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_config_file(self, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['build', '--config-file', 'mkdocs.yml'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
self.assertEqual(mock_load_config.call_count, 1)
args, kwargs = mock_load_config.call_args
self.assertTrue('config_file' in kwargs)
self.assertIsInstance(kwargs['config_file'], io.BufferedReader)
self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml')
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_strict(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build', '--strict'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
config_file=None,
strict=True,
theme=None,
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_theme(self, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['build', '--theme', 'readthedocs'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
config_file=None,
strict=None,
theme='readthedocs',
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_use_directory_urls(self, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['build', '--use-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
config_file=None,
strict=None,
theme=None,
use_directory_urls=True,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_no_directory_urls(self, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['build', '--no-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
config_file=None,
strict=None,
theme=None,
use_directory_urls=False,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_site_dir(self, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['build', '--site-dir', 'custom'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir='custom',
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_verbose(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build', '--verbose'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
handler = logging._handlers.get('MkDocsStreamHandler')
self.assertEqual(handler.level, logging.DEBUG)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
def test_build_quiet(self, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['build', '--quiet'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_build.call_count, 1)
handler = logging._handlers.get('MkDocsStreamHandler')
self.assertEqual(handler.level, logging.ERROR)
@mock.patch('mkdocs.commands.new.new', autospec=True)
def test_new(self, mock_new):
result = self.runner.invoke(cli.cli, ["new", "project"], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
mock_new.assert_called_once_with('project')
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_defaults(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['gh-deploy'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
g_args, g_kwargs = mock_gh_deploy.call_args
self.assertTrue('message' in g_kwargs)
self.assertEqual(g_kwargs['message'], None)
self.assertTrue('force' in g_kwargs)
self.assertEqual(g_kwargs['force'], False)
self.assertTrue('ignore_version' in g_kwargs)
self.assertEqual(g_kwargs['ignore_version'], False)
self.assertEqual(mock_build.call_count, 1)
b_args, b_kwargs = mock_build.call_args
self.assertTrue('dirty' in b_kwargs)
self.assertFalse(b_kwargs['dirty'])
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_clean(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['gh-deploy', '--clean'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
args, kwargs = mock_build.call_args
self.assertTrue('dirty' in kwargs)
self.assertFalse(kwargs['dirty'])
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_dirty(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['gh-deploy', '--dirty'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
args, kwargs = mock_build.call_args
self.assertTrue('dirty' in kwargs)
self.assertTrue(kwargs['dirty'])
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_config_file(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--config-file', 'mkdocs.yml'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
self.assertEqual(mock_load_config.call_count, 1)
args, kwargs = mock_load_config.call_args
self.assertTrue('config_file' in kwargs)
self.assertIsInstance(kwargs['config_file'], io.BufferedReader)
self.assertEqual(kwargs['config_file'].name, 'mkdocs.yml')
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_message(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--message', 'A commit message'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
g_args, g_kwargs = mock_gh_deploy.call_args
self.assertTrue('message' in g_kwargs)
self.assertEqual(g_kwargs['message'], 'A commit message')
self.assertEqual(mock_build.call_count, 1)
self.assertEqual(mock_load_config.call_count, 1)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_remote_branch(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--remote-branch', 'foo'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch='foo',
remote_name=None,
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_remote_name(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--remote-name', 'foo'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name='foo',
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_force(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['gh-deploy', '--force'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
g_args, g_kwargs = mock_gh_deploy.call_args
self.assertTrue('force' in g_kwargs)
self.assertEqual(g_kwargs['force'], True)
self.assertEqual(mock_build.call_count, 1)
self.assertEqual(mock_load_config.call_count, 1)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_ignore_version(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--ignore-version'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
g_args, g_kwargs = mock_gh_deploy.call_args
self.assertTrue('ignore_version' in g_kwargs)
self.assertEqual(g_kwargs['ignore_version'], True)
self.assertEqual(mock_build.call_count, 1)
self.assertEqual(mock_load_config.call_count, 1)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_strict(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(cli.cli, ['gh-deploy', '--strict'], catch_exceptions=False)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=True,
theme=None,
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_theme(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--theme', 'readthedocs'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=None,
theme='readthedocs',
use_directory_urls=None,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_use_directory_urls(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--use-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=None,
theme=None,
use_directory_urls=True,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_no_directory_urls(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--no-directory-urls'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=None,
theme=None,
use_directory_urls=False,
site_dir=None,
)
@mock.patch('mkdocs.config.load_config', autospec=True)
@mock.patch('mkdocs.commands.build.build', autospec=True)
@mock.patch('mkdocs.commands.gh_deploy.gh_deploy', autospec=True)
def test_gh_deploy_site_dir(self, mock_gh_deploy, mock_build, mock_load_config):
result = self.runner.invoke(
cli.cli, ['gh-deploy', '--site-dir', 'custom'], catch_exceptions=False
)
self.assertEqual(result.exit_code, 0)
self.assertEqual(mock_gh_deploy.call_count, 1)
self.assertEqual(mock_build.call_count, 1)
mock_load_config.assert_called_once_with(
remote_branch=None,
remote_name=None,
config_file=None,
strict=None,
theme=None,
use_directory_urls=None,
site_dir='custom',
)

View File

@ -0,0 +1,284 @@
import os
import unittest
from mkdocs import exceptions
from mkdocs.config import base
from mkdocs.config import config_options as c
from mkdocs.config import defaults
from mkdocs.config.base import ValidationError
from mkdocs.tests.base import change_dir, tempdir
class ConfigBaseTests(unittest.TestCase):
def test_unrecognised_keys(self):
conf = defaults.MkDocsConfig()
conf.load_dict(
{
'not_a_valid_config_option': "test",
}
)
failed, warnings = conf.validate()
self.assertEqual(
warnings,
[
(
'not_a_valid_config_option',
'Unrecognised configuration name: not_a_valid_config_option',
)
],
)
def test_missing_required(self):
conf = defaults.MkDocsConfig()
errors, warnings = conf.validate()
self.assertEqual(
errors, [('site_name', ValidationError('Required configuration not provided.'))]
)
self.assertEqual(warnings, [])
@tempdir()
def test_load_from_file(self, temp_dir):
"""
Users can explicitly set the config file using the '--config' option.
Allows users to specify a config other than the default `mkdocs.yml`.
"""
with open(os.path.join(temp_dir, 'mkdocs.yml'), 'w') as config_file:
config_file.write("site_name: MkDocs Test\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
cfg = base.load_config(config_file=config_file.name)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test')
@tempdir()
def test_load_default_file(self, temp_dir):
"""
test that `mkdocs.yml` will be loaded when '--config' is not set.
"""
with open(os.path.join(temp_dir, 'mkdocs.yml'), 'w') as config_file:
config_file.write("site_name: MkDocs Test\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
with change_dir(temp_dir):
cfg = base.load_config(config_file=None)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test')
@tempdir
def test_load_default_file_with_yaml(self, temp_dir):
"""
test that `mkdocs.yml` will be loaded when '--config' is not set.
"""
with open(os.path.join(temp_dir, 'mkdocs.yaml'), 'w') as config_file:
config_file.write("site_name: MkDocs Test\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
with change_dir(temp_dir):
cfg = base.load_config(config_file=None)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test')
@tempdir()
def test_load_default_file_prefer_yml(self, temp_dir):
"""
test that `mkdocs.yml` will be loaded when '--config' is not set.
"""
with open(os.path.join(temp_dir, 'mkdocs.yml'), 'w') as config_file1:
config_file1.write("site_name: MkDocs Test1\n")
with open(os.path.join(temp_dir, 'mkdocs.yaml'), 'w') as config_file2:
config_file2.write("site_name: MkDocs Test2\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
with change_dir(temp_dir):
cfg = base.load_config(config_file=None)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test1')
def test_load_from_missing_file(self):
with self.assertRaisesRegex(
exceptions.ConfigurationError, "Config file 'missing_file.yml' does not exist."
):
base.load_config(config_file='missing_file.yml')
@tempdir()
def test_load_from_open_file(self, temp_path):
"""
`load_config` can accept an open file descriptor.
"""
config_fname = os.path.join(temp_path, 'mkdocs.yml')
config_file = open(config_fname, 'w+')
config_file.write("site_name: MkDocs Test\n")
config_file.flush()
os.mkdir(os.path.join(temp_path, 'docs'))
cfg = base.load_config(config_file=config_file)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test')
# load_config will always close the file
self.assertTrue(config_file.closed)
@tempdir()
def test_load_from_closed_file(self, temp_dir):
"""
The `serve` command with auto-reload may pass in a closed file descriptor.
Ensure `load_config` reloads the closed file.
"""
with open(os.path.join(temp_dir, 'mkdocs.yml'), 'w') as config_file:
config_file.write("site_name: MkDocs Test\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
cfg = base.load_config(config_file=config_file)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
self.assertEqual(cfg['site_name'], 'MkDocs Test')
@tempdir
def test_load_missing_required(self, temp_dir):
"""
`site_name` is a required setting.
"""
with open(os.path.join(temp_dir, 'mkdocs.yml'), 'w') as config_file:
config_file.write("site_dir: output\nsite_url: https://www.mkdocs.org\n")
os.mkdir(os.path.join(temp_dir, 'docs'))
with self.assertLogs('mkdocs') as cm:
with self.assertRaises(exceptions.Abort):
base.load_config(config_file=config_file.name)
self.assertEqual(
'\n'.join(cm.output),
"ERROR:mkdocs.config:Config value 'site_name': Required configuration not provided.",
)
def test_pre_validation_error(self):
class InvalidConfigOption(c.BaseConfigOption):
def pre_validation(self, config, key_name):
raise ValidationError('pre_validation error')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(errors, [('invalid_option', ValidationError('pre_validation error'))])
self.assertEqual(warnings, [])
def test_run_validation_error(self):
class InvalidConfigOption(c.BaseConfigOption):
def run_validation(self, value):
raise ValidationError('run_validation error')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(errors, [('invalid_option', ValidationError('run_validation error'))])
self.assertEqual(warnings, [])
def test_post_validation_error(self):
class InvalidConfigOption(c.BaseConfigOption):
def post_validation(self, config, key_name):
raise ValidationError('post_validation error')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(errors, [('invalid_option', ValidationError('post_validation error'))])
self.assertEqual(warnings, [])
def test_pre_and_run_validation_errors(self):
"""A pre_validation error does not stop run_validation from running."""
class InvalidConfigOption(c.BaseConfigOption):
def pre_validation(self, config, key_name):
raise ValidationError('pre_validation error')
def run_validation(self, value):
raise ValidationError('run_validation error')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(
errors,
[
('invalid_option', ValidationError('pre_validation error')),
('invalid_option', ValidationError('run_validation error')),
],
)
self.assertEqual(warnings, [])
def test_run_and_post_validation_errors(self):
"""A run_validation error stops post_validation from running."""
class InvalidConfigOption(c.BaseConfigOption):
def run_validation(self, value):
raise ValidationError('run_validation error')
def post_validation(self, config, key_name):
raise ValidationError('post_validation error')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(errors, [('invalid_option', ValidationError('run_validation error'))])
self.assertEqual(warnings, [])
def test_validation_warnings(self):
class InvalidConfigOption(c.BaseConfigOption):
def pre_validation(self, config, key_name):
self.warnings.append('pre_validation warning')
def run_validation(self, value):
self.warnings.append('run_validation warning')
def post_validation(self, config, key_name):
self.warnings.append('post_validation warning')
conf = base.Config(schema=(('invalid_option', InvalidConfigOption()),))
errors, warnings = conf.validate()
self.assertEqual(errors, [])
self.assertEqual(
warnings,
[
('invalid_option', 'pre_validation warning'),
('invalid_option', 'run_validation warning'),
('invalid_option', 'post_validation warning'),
],
)
@tempdir()
def test_load_from_file_with_relative_paths(self, config_dir):
"""
When explicitly setting a config file, paths should be relative to the
config file, not the working directory.
"""
config_fname = os.path.join(config_dir, 'mkdocs.yml')
with open(config_fname, 'w') as config_file:
config_file.write("docs_dir: src\nsite_name: MkDocs Test\n")
docs_dir = os.path.join(config_dir, 'src')
os.mkdir(docs_dir)
cfg = base.load_config(config_file=config_file)
self.assertTrue(isinstance(cfg, defaults.MkDocsConfig))
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, str)
def test_get_schema(self):
class FooConfig:
z = c.URL()
aa = c.Type(int)
self.assertEqual(
base.get_schema(FooConfig),
(
('z', FooConfig.z),
('aa', FooConfig.aa),
),
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,278 @@
#!/usr/bin/env python
import os
import unittest
import mkdocs
from mkdocs import config
from mkdocs.config import config_options as c
from mkdocs.config import defaults
from mkdocs.config.base import ValidationError
from mkdocs.exceptions import ConfigurationError
from mkdocs.localization import parse_locale
from mkdocs.tests.base import dedent, tempdir
class ConfigTests(unittest.TestCase):
def test_missing_config_file(self):
with self.assertRaises(ConfigurationError):
config.load_config(config_file='bad_filename.yaml')
def test_missing_site_name(self):
conf = defaults.MkDocsConfig()
conf.load_dict({})
errors, warnings = conf.validate()
self.assertEqual(
errors, [('site_name', ValidationError("Required configuration not provided."))]
)
self.assertEqual(warnings, [])
def test_empty_config(self):
with self.assertRaises(ConfigurationError):
config.load_config(config_file='/dev/null')
def test_nonexistant_config(self):
with self.assertRaises(ConfigurationError):
config.load_config(config_file='/path/that/is/not/real')
@tempdir()
def test_invalid_config(self, temp_path):
file_contents = dedent(
"""
- ['index.md', 'Introduction']
- ['index.md', 'Introduction']
- ['index.md', 'Introduction']
"""
)
config_path = os.path.join(temp_path, 'foo.yml')
with open(config_path, 'w') as config_file:
config_file.write(file_contents)
with self.assertRaises(ConfigurationError):
config.load_config(config_file=open(config_file.name, 'rb'))
@tempdir()
def test_config_option(self, temp_path):
"""
Users can explicitly set the config file using the '--config' option.
Allows users to specify a config other than the default `mkdocs.yml`.
"""
expected_result = {
'site_name': 'Example',
'nav': [{'Introduction': 'index.md'}],
}
file_contents = dedent(
"""
site_name: Example
nav:
- 'Introduction': 'index.md'
"""
)
config_path = os.path.join(temp_path, 'mkdocs.yml')
with open(config_path, 'w') as config_file:
config_file.write(file_contents)
os.mkdir(os.path.join(temp_path, 'docs'))
result = config.load_config(config_file=config_file.name)
self.assertEqual(result['site_name'], expected_result['site_name'])
self.assertEqual(result['nav'], expected_result['nav'])
@tempdir()
@tempdir()
def test_theme(self, mytheme, custom):
configs = [
dict(), # default theme
{"theme": "readthedocs"}, # builtin theme
{"theme": {'name': 'readthedocs'}}, # builtin as complex
{"theme": {'name': None, 'custom_dir': mytheme}}, # custom only as complex
{
"theme": {'name': 'readthedocs', 'custom_dir': custom}
}, # builtin and custom as complex
{ # user defined variables
'theme': {
'name': 'mkdocs',
'locale': 'fr',
'static_templates': ['foo.html'],
'show_sidebar': False,
'some_var': 'bar',
}
},
]
mkdocs_dir = os.path.abspath(os.path.dirname(mkdocs.__file__))
mkdocs_templates_dir = os.path.join(mkdocs_dir, 'templates')
theme_dir = os.path.abspath(os.path.join(mkdocs_dir, 'themes'))
results = (
{
'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir],
'static_templates': ['404.html', 'sitemap.xml'],
'vars': {
'name': 'mkdocs',
'locale': parse_locale('en'),
'include_search_page': False,
'search_index_only': False,
'analytics': {'gtag': None},
'highlightjs': True,
'hljs_style': 'github',
'hljs_languages': [],
'navigation_depth': 2,
'nav_style': 'primary',
'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83},
},
},
{
'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir],
'static_templates': ['404.html', 'sitemap.xml'],
'vars': {
'name': 'readthedocs',
'locale': parse_locale('en'),
'include_search_page': True,
'search_index_only': False,
'analytics': {'anonymize_ip': False, 'gtag': None},
'highlightjs': True,
'hljs_languages': [],
'include_homepage_in_sidebar': True,
'prev_next_buttons_location': 'bottom',
'navigation_depth': 4,
'sticky_navigation': True,
'logo': None,
'titles_only': False,
'collapse_navigation': True,
},
},
{
'dirs': [os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir],
'static_templates': ['404.html', 'sitemap.xml'],
'vars': {
'name': 'readthedocs',
'locale': parse_locale('en'),
'include_search_page': True,
'search_index_only': False,
'analytics': {'anonymize_ip': False, 'gtag': None},
'highlightjs': True,
'hljs_languages': [],
'include_homepage_in_sidebar': True,
'prev_next_buttons_location': 'bottom',
'navigation_depth': 4,
'sticky_navigation': True,
'logo': None,
'titles_only': False,
'collapse_navigation': True,
},
},
{
'dirs': [mytheme, mkdocs_templates_dir],
'static_templates': ['sitemap.xml'],
'vars': {'name': None, 'locale': parse_locale('en')},
},
{
'dirs': [custom, os.path.join(theme_dir, 'readthedocs'), mkdocs_templates_dir],
'static_templates': ['404.html', 'sitemap.xml'],
'vars': {
'name': 'readthedocs',
'locale': parse_locale('en'),
'include_search_page': True,
'search_index_only': False,
'analytics': {'anonymize_ip': False, 'gtag': None},
'highlightjs': True,
'hljs_languages': [],
'include_homepage_in_sidebar': True,
'prev_next_buttons_location': 'bottom',
'navigation_depth': 4,
'sticky_navigation': True,
'logo': None,
'titles_only': False,
'collapse_navigation': True,
},
},
{
'dirs': [os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir],
'static_templates': ['404.html', 'sitemap.xml', 'foo.html'],
'vars': {
'name': 'mkdocs',
'locale': parse_locale('fr'),
'show_sidebar': False,
'some_var': 'bar',
'include_search_page': False,
'search_index_only': False,
'analytics': {'gtag': None},
'highlightjs': True,
'hljs_style': 'github',
'hljs_languages': [],
'navigation_depth': 2,
'nav_style': 'primary',
'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83},
},
},
)
for config_contents, result in zip(configs, results):
with self.subTest(config_contents):
conf = config.Config(schema=(('theme', c.Theme(default='mkdocs')),))
conf.load_dict(config_contents)
errors, warnings = conf.validate()
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
self.assertEqual(conf['theme'].dirs, result['dirs'])
self.assertEqual(conf['theme'].static_templates, set(result['static_templates']))
self.assertEqual({k: conf['theme'][k] for k in iter(conf['theme'])}, result['vars'])
def test_empty_nav(self):
conf = defaults.MkDocsConfig()
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_error_on_pages(self):
conf = defaults.MkDocsConfig()
conf.load_dict(
{
'site_name': 'Example',
'pages': ['index.md', 'about.md'],
}
)
errors, warnings = conf.validate()
exp_error = "The configuration option 'pages' was removed from MkDocs. Use 'nav' instead."
self.assertEqual(errors, [('pages', ValidationError(exp_error))])
self.assertEqual(warnings, [])
def test_doc_dir_in_site_dir(self):
j = os.path.join
test_configs = (
{'docs_dir': j('site', 'docs'), 'site_dir': 'site'},
{'docs_dir': 'docs', 'site_dir': '.'},
{'docs_dir': '.', 'site_dir': '.'},
{'docs_dir': 'docs', 'site_dir': ''},
{'docs_dir': '', 'site_dir': ''},
{'docs_dir': 'docs', 'site_dir': 'docs'},
)
cfg = {
'config_file_path': j(os.path.abspath('..'), 'mkdocs.yml'),
}
for test_config in test_configs:
with self.subTest(test_config):
patch = {**cfg, **test_config}
# Same as the default schema, but don't verify the docs_dir exists.
conf = config.Config(
schema=(
('docs_dir', c.Dir(default='docs')),
('site_dir', c.SiteDir(default='site')),
('config_file_path', c.Type(str)),
)
)
conf.load_dict(patch)
errors, warnings = conf.validate()
self.assertEqual(len(errors), 1)
self.assertEqual(warnings, [])

View File

@ -0,0 +1,190 @@
import unittest
from unittest import mock
from ghp_import import GhpError
from mkdocs import __version__
from mkdocs.commands import gh_deploy
from mkdocs.exceptions import Abort
from mkdocs.tests.base import load_config
class TestGitHubDeploy(unittest.TestCase):
@mock.patch('subprocess.Popen')
def test_is_cwd_git_repo(self, mock_popeno):
mock_popeno().wait.return_value = 0
self.assertTrue(gh_deploy._is_cwd_git_repo())
@mock.patch('subprocess.Popen')
def test_is_cwd_not_git_repo(self, mock_popeno):
mock_popeno().wait.return_value = 1
self.assertFalse(gh_deploy._is_cwd_git_repo())
@mock.patch('subprocess.Popen')
def test_get_current_sha(self, mock_popeno):
mock_popeno().communicate.return_value = (b'6d98394\n', b'')
self.assertEqual(gh_deploy._get_current_sha('.'), '6d98394')
@mock.patch('subprocess.Popen')
def test_get_remote_url_ssh(self, mock_popeno):
mock_popeno().communicate.return_value = (
b'git@github.com:mkdocs/mkdocs.git\n',
b'',
)
expected = ('git@', 'mkdocs/mkdocs.git')
self.assertEqual(expected, gh_deploy._get_remote_url('origin'))
@mock.patch('subprocess.Popen')
def test_get_remote_url_http(self, mock_popeno):
mock_popeno().communicate.return_value = (
b'https://github.com/mkdocs/mkdocs.git\n',
b'',
)
expected = ('https://', 'mkdocs/mkdocs.git')
self.assertEqual(expected, gh_deploy._get_remote_url('origin'))
@mock.patch('subprocess.Popen')
def test_get_remote_url_enterprise(self, mock_popeno):
mock_popeno().communicate.return_value = (
b'https://notgh.com/mkdocs/mkdocs.git\n',
b'',
)
expected = (None, None)
self.assertEqual(expected, gh_deploy._get_remote_url('origin'))
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
def test_deploy(self, mock_import, check_version, get_remote, get_sha, is_repo):
config = load_config(
remote_branch='test',
)
gh_deploy.gh_deploy(config)
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
@mock.patch('os.path.isfile', return_value=False)
def test_deploy_no_cname(
self, mock_isfile, mock_import, check_version, get_remote, get_sha, is_repo
):
config = load_config(
remote_branch='test',
)
gh_deploy.gh_deploy(config)
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch(
'mkdocs.commands.gh_deploy._get_remote_url', return_value=('git@', 'mkdocs/mkdocs.git')
)
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
def test_deploy_hostname(self, mock_import, check_version, get_remote, get_sha, is_repo):
config = load_config(
remote_branch='test',
)
gh_deploy.gh_deploy(config)
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
def test_deploy_ignore_version_default(
self, mock_import, check_version, get_remote, get_sha, is_repo
):
config = load_config(
remote_branch='test',
)
gh_deploy.gh_deploy(config)
check_version.assert_called_once()
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch('mkdocs.commands.gh_deploy._get_remote_url', return_value=(None, None))
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
def test_deploy_ignore_version(self, mock_import, check_version, get_remote, get_sha, is_repo):
config = load_config(
remote_branch='test',
)
gh_deploy.gh_deploy(config, ignore_version=True)
check_version.assert_not_called()
@mock.patch('mkdocs.commands.gh_deploy._is_cwd_git_repo', return_value=True)
@mock.patch('mkdocs.commands.gh_deploy._get_current_sha', return_value='shashas')
@mock.patch('mkdocs.commands.gh_deploy._check_version')
@mock.patch('ghp_import.ghp_import')
def test_deploy_error(self, mock_import, check_version, get_sha, is_repo):
mock_import.side_effect = GhpError('TestError123')
config = load_config(
remote_branch='test',
)
with self.assertLogs('mkdocs', level='ERROR') as cm:
with self.assertRaises(Abort):
gh_deploy.gh_deploy(config)
self.assertEqual(
cm.output,
[
'ERROR:mkdocs.commands.gh_deploy:Failed to deploy to GitHub with error: \n'
'TestError123'
],
)
class TestGitHubDeployLogs(unittest.TestCase):
@mock.patch('subprocess.Popen')
def test_mkdocs_newer(self, mock_popeno):
mock_popeno().communicate.return_value = (
b'Deployed 12345678 with MkDocs version: 0.1.2\n',
b'',
)
with self.assertLogs('mkdocs') as cm:
gh_deploy._check_version('gh-pages')
self.assertEqual(
'\n'.join(cm.output),
f'INFO:mkdocs.commands.gh_deploy:Previous deployment was done with MkDocs '
f'version 0.1.2; you are deploying with a newer version ({__version__})',
)
@mock.patch('subprocess.Popen')
def test_mkdocs_older(self, mock_popeno):
mock_popeno().communicate.return_value = (
b'Deployed 12345678 with MkDocs version: 10.1.2\n',
b'',
)
with self.assertLogs('mkdocs', level='ERROR') as cm:
with self.assertRaises(Abort):
gh_deploy._check_version('gh-pages')
self.assertEqual(
'\n'.join(cm.output),
f'ERROR:mkdocs.commands.gh_deploy:Deployment terminated: Previous deployment was made with '
f'MkDocs version 10.1.2; you are attempting to deploy with an older version ({__version__}).'
f' Use --ignore-version to deploy anyway.',
)
@mock.patch('subprocess.Popen')
def test_version_unknown(self, mock_popeno):
mock_popeno().communicate.return_value = (b'No version specified\n', b'')
with self.assertLogs('mkdocs') as cm:
gh_deploy._check_version('gh-pages')
self.assertEqual(
'\n'.join(cm.output),
'WARNING:mkdocs.commands.gh_deploy:Version check skipped: No version specified in previous deployment.',
)

View File

@ -0,0 +1,73 @@
"""
# MkDocs Integration tests
This is a simple integration test that builds the MkDocs
documentation against all of the builtin themes.
From the root of the MkDocs git repo, use:
python -m mkdocs.tests.integration --help
TODOs
- Build with different configuration options.
- Build documentation other than just MkDocs as it is relatively simple.
"""
import logging
import os
import subprocess
import tempfile
import click
log = logging.getLogger('mkdocs')
DIR = os.path.dirname(__file__)
MKDOCS_CONFIG = os.path.abspath(os.path.join(DIR, '../../mkdocs.yml'))
MKDOCS_THEMES = ['mkdocs', 'readthedocs']
TEST_PROJECTS = os.path.abspath(os.path.join(DIR, 'integration'))
@click.command()
@click.option(
'--output',
help="The output directory to use when building themes",
type=click.Path(file_okay=False, writable=True),
)
def main(output=None):
if output is None:
directory = tempfile.TemporaryDirectory(prefix='mkdocs_integration-')
output = directory.name
log.propagate = False
stream = logging.StreamHandler()
formatter = logging.Formatter("\033[1m\033[1;32m *** %(message)s *** \033[0m")
stream.setFormatter(formatter)
log.addHandler(stream)
log.setLevel(logging.DEBUG)
base_cmd = ['mkdocs', 'build', '-q', '-s', '--site-dir']
log.debug("Building installed themes.")
for theme in sorted(MKDOCS_THEMES):
log.debug(f"Building theme: {theme}")
project_dir = os.path.dirname(MKDOCS_CONFIG)
out = os.path.join(output, theme)
command = base_cmd + [out, '--theme', theme]
subprocess.check_call(command, cwd=project_dir)
log.debug("Building test projects.")
for project in os.listdir(TEST_PROJECTS):
log.debug(f"Building test project: {project}")
project_dir = os.path.join(TEST_PROJECTS, project)
out = os.path.join(output, project)
command = base_cmd + [out]
subprocess.check_call(command, cwd=project_dir)
log.debug(f"Theme and integration builds are in {output}")
if __name__ == '__main__':
main()

View File

@ -0,0 +1,610 @@
#!/usr/bin/env python
import contextlib
import email
import io
import sys
import threading
import time
import unittest
from pathlib import Path
from unittest import mock
from mkdocs.livereload import LiveReloadServer
from mkdocs.tests.base import change_dir, tempdir
class FakeRequest:
def __init__(self, content):
self.in_file = io.BytesIO(content.encode())
self.out_file = io.BytesIO()
self.out_file.close = lambda: None
def makefile(self, *args, **kwargs):
return self.in_file
def sendall(self, data):
self.out_file.write(data)
@contextlib.contextmanager
def testing_server(root, builder=lambda: None, mount_path="/"):
"""Create the server and start most of its parts, but don't listen on a socket."""
with mock.patch("socket.socket"):
server = LiveReloadServer(
builder,
host="localhost",
port=0,
root=root,
mount_path=mount_path,
polling_interval=0.2,
bind_and_activate=False,
)
server.setup_environ()
server.observer.start()
thread = threading.Thread(target=server._build_loop, daemon=True)
thread.start()
yield server
server.shutdown()
thread.join()
def do_request(server, content):
request = FakeRequest(content + " HTTP/1.1")
server.RequestHandlerClass(request, ("127.0.0.1", 0), server)
response = request.out_file.getvalue()
headers, _, content = response.partition(b"\r\n\r\n")
status, _, headers = headers.partition(b"\r\n")
status = status.split(None, 1)[1].decode()
headers = email.message_from_bytes(headers)
headers["_status"] = status
return headers, content.decode()
SCRIPT_REGEX = r'<script>[\S\s]+?livereload\([0-9]+, [0-9]+\);\s*</script>'
class BuildTests(unittest.TestCase):
@tempdir({"test.css": "div { color: red; }"})
def test_serves_normal_file(self, site_dir):
with testing_server(site_dir) as server:
headers, output = do_request(server, "GET /test.css")
self.assertEqual(output, "div { color: red; }")
self.assertEqual(headers["_status"], "200 OK")
self.assertEqual(headers.get("content-length"), str(len(output)))
@tempdir({"docs/foo.docs": "docs1", "mkdocs.yml": "yml1"})
@tempdir({"foo.site": "original"})
def test_basic_rebuild(self, site_dir, origin_dir):
docs_dir = Path(origin_dir, "docs")
started_building = threading.Event()
def rebuild():
started_building.set()
Path(site_dir, "foo.site").write_text(
Path(docs_dir, "foo.docs").read_text() + Path(origin_dir, "mkdocs.yml").read_text()
)
with testing_server(site_dir, rebuild) as server:
server.watch(docs_dir, rebuild)
server.watch(Path(origin_dir, "mkdocs.yml"), rebuild)
time.sleep(0.01)
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "original")
Path(docs_dir, "foo.docs").write_text("docs2")
self.assertTrue(started_building.wait(timeout=10))
started_building.clear()
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "docs2yml1")
Path(origin_dir, "mkdocs.yml").write_text("yml2")
self.assertTrue(started_building.wait(timeout=10))
started_building.clear()
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "docs2yml2")
@tempdir({"foo.docs": "a"})
@tempdir({"foo.site": "original"})
def test_rebuild_after_delete(self, site_dir, docs_dir):
started_building = threading.Event()
def rebuild():
started_building.set()
Path(site_dir, "foo.site").unlink()
with testing_server(site_dir, rebuild) as server:
server.watch(docs_dir, rebuild)
time.sleep(0.01)
Path(docs_dir, "foo.docs").write_text("b")
self.assertTrue(started_building.wait(timeout=10))
with self.assertLogs("mkdocs.livereload"):
_, output = do_request(server, "GET /foo.site")
self.assertIn("404", output)
@tempdir({"aaa": "something"})
def test_rebuild_after_rename(self, site_dir):
started_building = threading.Event()
with testing_server(site_dir, started_building.set) as server:
server.watch(site_dir)
time.sleep(0.01)
Path(site_dir, "aaa").rename(Path(site_dir, "bbb"))
self.assertTrue(started_building.wait(timeout=10))
@tempdir()
def test_rebuild_on_edit(self, site_dir):
started_building = threading.Event()
with open(Path(site_dir, "test"), "wb") as f:
time.sleep(0.01)
with testing_server(site_dir, started_building.set) as server:
server.watch(site_dir)
time.sleep(0.01)
f.write(b"hi\n")
f.flush()
self.assertTrue(started_building.wait(timeout=10))
@tempdir()
def test_unwatch(self, site_dir):
started_building = threading.Event()
with testing_server(site_dir, started_building.set) as server:
with self.assertRaises(KeyError):
server.unwatch(site_dir)
server.watch(site_dir)
server.watch(site_dir)
server.unwatch(site_dir)
time.sleep(0.01)
Path(site_dir, "foo").write_text("foo")
self.assertTrue(started_building.wait(timeout=10))
started_building.clear()
server.unwatch(site_dir)
Path(site_dir, "foo").write_text("bar")
self.assertFalse(started_building.wait(timeout=0.5))
with self.assertRaises(KeyError):
server.unwatch(site_dir)
@tempdir({"foo.docs": "a"})
@tempdir({"foo.site": "original"})
def test_custom_action_warns(self, site_dir, docs_dir):
started_building = threading.Event()
def rebuild():
started_building.set()
content = Path(docs_dir, "foo.docs").read_text()
Path(site_dir, "foo.site").write_text(content * 5)
with testing_server(site_dir) as server:
with self.assertWarnsRegex(DeprecationWarning, "func") as cm:
server.watch(docs_dir, rebuild)
time.sleep(0.01)
self.assertIn("livereload_tests.py", cm.filename)
Path(docs_dir, "foo.docs").write_text("b")
self.assertTrue(started_building.wait(timeout=10))
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "bbbbb")
@tempdir({"foo.docs": "docs1"})
@tempdir({"foo.extra": "extra1"})
@tempdir({"foo.site": "original"})
def test_multiple_dirs_can_cause_rebuild(self, site_dir, extra_dir, docs_dir):
started_building = threading.Barrier(2)
def rebuild():
started_building.wait(timeout=10)
content1 = Path(docs_dir, "foo.docs").read_text()
content2 = Path(extra_dir, "foo.extra").read_text()
Path(site_dir, "foo.site").write_text(content1 + content2)
with testing_server(site_dir, rebuild) as server:
server.watch(docs_dir)
server.watch(extra_dir)
time.sleep(0.01)
Path(docs_dir, "foo.docs").write_text("docs2")
started_building.wait(timeout=10)
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "docs2extra1")
Path(extra_dir, "foo.extra").write_text("extra2")
started_building.wait(timeout=10)
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "docs2extra2")
@tempdir({"foo.docs": "docs1"})
@tempdir({"foo.extra": "extra1"})
@tempdir({"foo.site": "original"})
def test_multiple_dirs_changes_rebuild_only_once(self, site_dir, extra_dir, docs_dir):
started_building = threading.Event()
def rebuild():
self.assertFalse(started_building.is_set())
started_building.set()
content1 = Path(docs_dir, "foo.docs").read_text()
content2 = Path(extra_dir, "foo.extra").read_text()
Path(site_dir, "foo.site").write_text(content1 + content2)
with testing_server(site_dir, rebuild) as server:
server.watch(docs_dir)
server.watch(extra_dir)
time.sleep(0.01)
_, output = do_request(server, "GET /foo.site")
Path(docs_dir, "foo.docs").write_text("docs2")
Path(extra_dir, "foo.extra").write_text("extra2")
self.assertTrue(started_building.wait(timeout=10))
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "docs2extra2")
@tempdir({"foo.docs": "a"})
@tempdir({"foo.site": "original"})
def test_change_is_detected_while_building(self, site_dir, docs_dir):
before_finished_building = threading.Barrier(2)
can_finish_building = threading.Event()
def rebuild():
content = Path(docs_dir, "foo.docs").read_text()
Path(site_dir, "foo.site").write_text(content * 5)
before_finished_building.wait(timeout=10)
self.assertTrue(can_finish_building.wait(timeout=10))
with testing_server(site_dir, rebuild) as server:
server.watch(docs_dir)
time.sleep(0.01)
Path(docs_dir, "foo.docs").write_text("b")
before_finished_building.wait(timeout=10)
Path(docs_dir, "foo.docs").write_text("c")
can_finish_building.set()
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "bbbbb")
before_finished_building.wait(timeout=10)
_, output = do_request(server, "GET /foo.site")
self.assertEqual(output, "ccccc")
@tempdir(
{
"normal.html": "<html><body>hello</body></html>",
"no_body.html": "<p>hi",
"empty.html": "",
"multi_body.html": "<body>foo</body><body>bar</body>",
}
)
def test_serves_modified_html(self, site_dir):
with testing_server(site_dir) as server:
server.watch(site_dir)
headers, output = do_request(server, "GET /normal.html")
self.assertRegex(output, fr"^<html><body>hello{SCRIPT_REGEX}</body></html>$")
self.assertEqual(headers.get("content-type"), "text/html")
self.assertEqual(headers.get("content-length"), str(len(output)))
_, output = do_request(server, "GET /no_body.html")
self.assertRegex(output, fr"^<p>hi{SCRIPT_REGEX}$")
headers, output = do_request(server, "GET /empty.html")
self.assertRegex(output, fr"^{SCRIPT_REGEX}$")
self.assertEqual(headers.get("content-length"), str(len(output)))
_, output = do_request(server, "GET /multi_body.html")
self.assertRegex(output, fr"^<body>foo</body><body>bar{SCRIPT_REGEX}</body>$")
@tempdir({"index.html": "<body>aaa</body>", "foo/index.html": "<body>bbb</body>"})
def test_serves_directory_index(self, site_dir):
with testing_server(site_dir) as server:
headers, output = do_request(server, "GET /")
self.assertRegex(output, r"^<body>aaa</body>$")
self.assertEqual(headers["_status"], "200 OK")
self.assertEqual(headers.get("content-type"), "text/html")
self.assertEqual(headers.get("content-length"), str(len(output)))
for path in "/foo/", "/foo/index.html":
_, output = do_request(server, "GET /foo/")
self.assertRegex(output, r"^<body>bbb</body>$")
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /foo/index.html/")
self.assertEqual(headers["_status"], "404 Not Found")
@tempdir(
{
"foo/bar/index.html": "<body>aaa</body>",
"foo/測試/index.html": "<body>bbb</body>",
}
)
def test_redirects_to_directory(self, site_dir):
with testing_server(site_dir, mount_path="/sub") as server:
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /sub/foo/bar")
self.assertEqual(headers["_status"], "302 Found")
self.assertEqual(headers.get("location"), "/sub/foo/bar/")
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /sub/foo/測試")
self.assertEqual(headers["_status"], "302 Found")
self.assertEqual(headers.get("location"), "/sub/foo/%E6%B8%AC%E8%A9%A6/")
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /sub/foo/%E6%B8%AC%E8%A9%A6")
self.assertEqual(headers["_status"], "302 Found")
self.assertEqual(headers.get("location"), "/sub/foo/%E6%B8%AC%E8%A9%A6/")
@tempdir({"я.html": "<body>aaa</body>", "测试2/index.html": "<body>bbb</body>"})
def test_serves_with_unicode_characters(self, site_dir):
with testing_server(site_dir) as server:
_, output = do_request(server, "GET /я.html")
self.assertRegex(output, r"^<body>aaa</body>$")
_, output = do_request(server, "GET /%D1%8F.html")
self.assertRegex(output, r"^<body>aaa</body>$")
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /%D1.html")
self.assertEqual(headers["_status"], "404 Not Found")
_, output = do_request(server, "GET /测试2/")
self.assertRegex(output, r"^<body>bbb</body>$")
_, output = do_request(server, "GET /%E6%B5%8B%E8%AF%952/index.html")
self.assertRegex(output, r"^<body>bbb</body>$")
@tempdir()
def test_serves_polling_instantly(self, site_dir):
with testing_server(site_dir) as server:
_, output = do_request(server, "GET /livereload/0/0")
self.assertTrue(output.isdigit())
@tempdir()
def test_serves_polling_with_mount_path(self, site_dir):
with testing_server(site_dir, mount_path="/test/f*o") as server:
_, output = do_request(server, "GET /livereload/0/0")
self.assertTrue(output.isdigit())
@tempdir()
@tempdir()
def test_serves_polling_after_event(self, site_dir, docs_dir):
with testing_server(site_dir) as server:
initial_epoch = server._visible_epoch
server.watch(docs_dir)
time.sleep(0.01)
Path(docs_dir, "foo.docs").write_text("b")
_, output = do_request(server, f"GET /livereload/{initial_epoch}/0")
self.assertNotEqual(server._visible_epoch, initial_epoch)
self.assertEqual(output, str(server._visible_epoch))
@tempdir()
def test_serves_polling_with_timeout(self, site_dir):
with testing_server(site_dir) as server:
server.poll_response_timeout = 0.2
initial_epoch = server._visible_epoch
start_time = time.monotonic()
_, output = do_request(server, f"GET /livereload/{initial_epoch}/0")
self.assertGreaterEqual(time.monotonic(), start_time + 0.2)
self.assertEqual(output, str(initial_epoch))
@tempdir()
def test_error_handler(self, site_dir):
with testing_server(site_dir) as server:
server.error_handler = lambda code: b"[%d]" % code
with self.assertLogs("mkdocs.livereload") as cm:
headers, output = do_request(server, "GET /missing")
self.assertEqual(headers["_status"], "404 Not Found")
self.assertEqual(output, "[404]")
self.assertRegex(
"\n".join(cm.output),
r'^WARNING:mkdocs.livereload:.*"GET /missing HTTP/1.1" code 404',
)
@tempdir()
def test_bad_error_handler(self, site_dir):
self.maxDiff = None
with testing_server(site_dir) as server:
server.error_handler = lambda code: 0 / 0
with self.assertLogs("mkdocs.livereload") as cm:
headers, output = do_request(server, "GET /missing")
self.assertEqual(headers["_status"], "404 Not Found")
self.assertIn("404", output)
self.assertRegex(
"\n".join(cm.output), r"Failed to render an error message[\s\S]+/missing.+code 404"
)
@tempdir(
{
"test.html": "<!DOCTYPE html>\nhi",
"test.xml": '<?xml version="1.0" encoding="UTF-8"?>\n<foo></foo>',
"test.css": "div { color: red; }",
"test.js": "use strict;",
"test.json": '{"a": "b"}',
}
)
def test_mime_types(self, site_dir):
with testing_server(site_dir) as server:
headers, _ = do_request(server, "GET /test.html")
self.assertEqual(headers.get("content-type"), "text/html")
headers, _ = do_request(server, "GET /test.xml")
self.assertIn(headers.get("content-type"), ["text/xml", "application/xml"])
headers, _ = do_request(server, "GET /test.css")
self.assertEqual(headers.get("content-type"), "text/css")
headers, _ = do_request(server, "GET /test.js")
self.assertEqual(headers.get("content-type"), "application/javascript")
headers, _ = do_request(server, "GET /test.json")
self.assertEqual(headers.get("content-type"), "application/json")
@tempdir({"index.html": "<body>aaa</body>", "sub/sub.html": "<body>bbb</body>"})
def test_serves_from_mount_path(self, site_dir):
with testing_server(site_dir, mount_path="/sub") as server:
headers, output = do_request(server, "GET /sub/")
self.assertRegex(output, r"^<body>aaa</body>$")
self.assertEqual(headers.get("content-type"), "text/html")
_, output = do_request(server, "GET /sub/sub/sub.html")
self.assertRegex(output, r"^<body>bbb</body>$")
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /sub/sub.html")
self.assertEqual(headers["_status"], "404 Not Found")
@tempdir()
def test_redirects_to_mount_path(self, site_dir):
with testing_server(site_dir, mount_path="/mount/path") as server:
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /")
self.assertEqual(headers["_status"], "302 Found")
self.assertEqual(headers.get("location"), "/mount/path/")
@tempdir()
def test_redirects_to_unicode_mount_path(self, site_dir):
with testing_server(site_dir, mount_path="/mount/測試") as server:
with self.assertLogs("mkdocs.livereload"):
headers, _ = do_request(server, "GET /")
self.assertEqual(headers["_status"], "302 Found")
self.assertEqual(headers.get("location"), "/mount/%E6%B8%AC%E8%A9%A6/")
@tempdir({"mkdocs.yml": "original", "mkdocs2.yml": "original"}, prefix="tmp_dir")
@tempdir(prefix="origin_dir")
@tempdir({"subdir/foo.md": "original"}, prefix="dest_docs_dir")
def test_watches_direct_symlinks(self, dest_docs_dir, origin_dir, tmp_dir):
try:
Path(origin_dir, "docs").symlink_to(dest_docs_dir, target_is_directory=True)
Path(origin_dir, "mkdocs.yml").symlink_to(Path(tmp_dir, "mkdocs.yml"))
except NotImplementedError: # PyPy on Windows
self.skipTest("Creating symlinks not supported")
started_building = threading.Event()
def wait_for_build():
result = started_building.wait(timeout=10)
started_building.clear()
with self.assertLogs("mkdocs.livereload"):
do_request(server, "GET /")
return result
with testing_server(tmp_dir, started_building.set) as server:
server.watch(Path(origin_dir, "docs"))
server.watch(Path(origin_dir, "mkdocs.yml"))
time.sleep(0.01)
Path(origin_dir, "unrelated.md").write_text("foo")
self.assertFalse(started_building.wait(timeout=0.5))
Path(tmp_dir, "mkdocs.yml").write_text("edited")
self.assertTrue(wait_for_build())
Path(dest_docs_dir, "subdir", "foo.md").write_text("edited")
self.assertTrue(wait_for_build())
@tempdir(["file_dest_1.md", "file_dest_2.md", "file_dest_unused.md"], prefix="tmp_dir")
@tempdir(["file_under.md"], prefix="dir_to_link_to")
@tempdir()
def test_watches_through_symlinks(self, docs_dir, dir_to_link_to, tmp_dir):
try:
Path(docs_dir, "link1.md").symlink_to(Path(tmp_dir, "file_dest_1.md"))
Path(docs_dir, "linked_dir").symlink_to(dir_to_link_to, target_is_directory=True)
Path(dir_to_link_to, "sublink.md").symlink_to(Path(tmp_dir, "file_dest_2.md"))
except NotImplementedError: # PyPy on Windows
self.skipTest("Creating symlinks not supported")
started_building = threading.Event()
def wait_for_build():
result = started_building.wait(timeout=10)
started_building.clear()
with self.assertLogs("mkdocs.livereload"):
do_request(server, "GET /")
return result
with testing_server(docs_dir, started_building.set) as server:
server.watch(docs_dir)
time.sleep(0.01)
Path(tmp_dir, "file_dest_1.md").write_text("edited")
self.assertTrue(wait_for_build())
Path(dir_to_link_to, "file_under.md").write_text("edited")
self.assertTrue(wait_for_build())
Path(tmp_dir, "file_dest_2.md").write_text("edited")
self.assertTrue(wait_for_build())
Path(docs_dir, "link1.md").unlink()
self.assertTrue(wait_for_build())
Path(tmp_dir, "file_dest_unused.md").write_text("edited")
self.assertFalse(started_building.wait(timeout=0.5))
@tempdir(prefix="site_dir")
@tempdir(["docs/unused.md", "README.md"], prefix="origin_dir")
def test_watches_through_relative_symlinks(self, origin_dir, site_dir):
docs_dir = Path(origin_dir, "docs")
with change_dir(docs_dir):
try:
Path(docs_dir, "README.md").symlink_to(Path("..", "README.md"))
except NotImplementedError: # PyPy on Windows
self.skipTest("Creating symlinks not supported")
started_building = threading.Event()
with testing_server(docs_dir, started_building.set) as server:
server.watch(docs_dir)
time.sleep(0.01)
Path(origin_dir, "README.md").write_text("edited")
self.assertTrue(started_building.wait(timeout=10))
@tempdir()
def test_watch_with_broken_symlinks(self, docs_dir):
Path(docs_dir, "subdir").mkdir()
try:
if sys.platform != "win32":
Path(docs_dir, "subdir", "circular").symlink_to(Path(docs_dir))
Path(docs_dir, "broken_1").symlink_to(Path(docs_dir, "oh no"))
Path(docs_dir, "broken_2").symlink_to(Path(docs_dir, "oh no"), target_is_directory=True)
Path(docs_dir, "broken_3").symlink_to(Path(docs_dir, "broken_2"))
except NotImplementedError: # PyPy on Windows
self.skipTest("Creating symlinks not supported")
started_building = threading.Event()
with testing_server(docs_dir, started_building.set) as server:
server.watch(docs_dir)
time.sleep(0.01)
Path(docs_dir, "subdir", "test").write_text("test")
self.assertTrue(started_building.wait(timeout=10))

View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
import unittest
from unittest import mock
from mkdocs.config.base import ValidationError
from mkdocs.localization import install_translations, parse_locale
from mkdocs.tests.base import tempdir
class LocalizationTests(unittest.TestCase):
def setUp(self):
self.env = mock.Mock()
def test_jinja_extension_installed(self):
install_translations(self.env, parse_locale('en'), [])
self.env.add_extension.assert_called_once_with('jinja2.ext.i18n')
def test_valid_language(self):
locale = parse_locale('en')
self.assertEqual(locale.language, 'en')
def test_valid_language_territory(self):
locale = parse_locale('en_US')
self.assertEqual(locale.language, 'en')
self.assertEqual(locale.territory, 'US')
self.assertEqual(str(locale), 'en_US')
def test_unknown_locale(self):
self.assertRaises(ValidationError, parse_locale, 'foo')
def test_invalid_locale(self):
self.assertRaises(ValidationError, parse_locale, '42')
@tempdir()
def test_no_translations_found(self, dir_without_translations):
with self.assertLogs('mkdocs') as cm:
install_translations(self.env, parse_locale('fr_CA'), [dir_without_translations])
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.localization:No translations could be found for the locale 'fr_CA'. "
"Defaulting to English.",
)
self.env.install_null_translations.assert_called_once()
@tempdir
def test_translations_found(self, tdir):
translations = mock.Mock()
with mock.patch('mkdocs.localization.Translations.load', return_value=translations):
install_translations(self.env, parse_locale('en'), [tdir])
self.env.install_gettext_translations.assert_called_once_with(translations)
@tempdir()
@tempdir()
def test_merge_translations(self, custom_dir, theme_dir):
custom_dir_translations = mock.Mock()
theme_dir_translations = mock.Mock()
def side_effet(*args, **kwargs):
dirname = args[0]
if dirname.startswith(custom_dir):
return custom_dir_translations
elif dirname.startswith(theme_dir):
return theme_dir_translations
else:
self.fail()
with mock.patch('mkdocs.localization.Translations.load', side_effect=side_effet):
install_translations(self.env, parse_locale('en'), [custom_dir, theme_dir])
theme_dir_translations.merge.assert_called_once_with(custom_dir_translations)

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python
import os
import unittest
from mkdocs.commands import new
from mkdocs.tests.base import change_dir, tempdir
class NewTests(unittest.TestCase):
@tempdir()
def test_new(self, temp_dir):
with change_dir(temp_dir):
new.new("myproject")
expected_paths = [
os.path.join(temp_dir, "myproject"),
os.path.join(temp_dir, "myproject", "mkdocs.yml"),
os.path.join(temp_dir, "myproject", "docs"),
os.path.join(temp_dir, "myproject", "docs", "index.md"),
]
for expected_path in expected_paths:
self.assertTrue(os.path.exists(expected_path))

View File

@ -0,0 +1,299 @@
#!/usr/bin/env python
import os
import unittest
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from typing_extensions import assert_type
else:
def assert_type(val, typ):
return None
from mkdocs import plugins
from mkdocs.commands import build
from mkdocs.config import base
from mkdocs.config import config_options as c
from mkdocs.config.base import ValidationError
from mkdocs.exceptions import Abort, BuildError, PluginError
from mkdocs.tests.base import load_config, tempdir
class _DummyPluginConfig(base.Config):
foo = c.Type(str, default='default foo')
bar = c.Type(int, default=0)
dir = c.Optional(c.Dir(exists=False))
class DummyPlugin(plugins.BasePlugin[_DummyPluginConfig]):
def on_pre_page(self, content, **kwargs):
"""modify page content by prepending `foo` config value."""
return f'{self.config.foo} {content}'
def on_nav(self, item, **kwargs):
"""do nothing (return None) to not modify item."""
return None
def on_page_read_source(self, **kwargs):
"""create new source by prepending `foo` config value to 'source'."""
return f'{self.config.foo} source'
def on_pre_build(self, **kwargs):
"""do nothing (return None)."""
return None
class TestPluginClass(unittest.TestCase):
def test_valid_plugin_options(self) -> None:
test_dir = 'test'
options = {
'foo': 'some value',
'dir': test_dir,
}
cfg_fname = os.path.join('tmp', 'test', 'fname.yml')
cfg_fname = os.path.abspath(cfg_fname)
cfg_dirname = os.path.dirname(cfg_fname)
expected = {
'foo': 'some value',
'bar': 0,
'dir': os.path.join(cfg_dirname, test_dir),
}
plugin = DummyPlugin()
errors, warnings = plugin.load_config(options, config_file_path=cfg_fname)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
assert_type(plugin.config, _DummyPluginConfig)
self.assertEqual(plugin.config, expected)
assert_type(plugin.config.bar, int)
self.assertEqual(plugin.config.bar, 0)
assert_type(plugin.config.dir, Optional[str])
def test_invalid_plugin_options(self):
plugin = DummyPlugin()
errors, warnings = plugin.load_config({'foo': 42})
self.assertEqual(
errors,
[('foo', ValidationError("Expected type: <class 'str'> but received: <class 'int'>"))],
)
self.assertEqual(warnings, [])
errors, warnings = plugin.load_config({'bar': 'a string'})
self.assertEqual(
errors,
[('bar', ValidationError("Expected type: <class 'int'> but received: <class 'str'>"))],
)
self.assertEqual(warnings, [])
errors, warnings = plugin.load_config({'invalid_key': 'value'})
self.assertEqual(errors, [])
self.assertEqual(
warnings, [('invalid_key', "Unrecognised configuration name: invalid_key")]
)
class TestPluginCollection(unittest.TestCase):
def test_correct_events_registered(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
collection['foo'] = plugin
self.assertEqual(
collection.events,
{
'startup': [],
'shutdown': [],
'serve': [],
'config': [],
'pre_build': [plugin.on_pre_build],
'files': [],
'nav': [plugin.on_nav],
'env': [],
'post_build': [],
'build_error': [],
'pre_template': [],
'template_context': [],
'post_template': [],
'pre_page': [plugin.on_pre_page],
'page_read_source': [plugin.on_page_read_source],
'page_markdown': [],
'page_content': [],
'page_context': [],
'post_page': [],
},
)
def test_event_priorities(self):
class PrioPlugin(plugins.BasePlugin):
config_scheme = base.get_schema(_DummyPluginConfig)
@plugins.event_priority(100)
def on_pre_page(self, content, **kwargs):
pass
@plugins.event_priority(-100)
def on_nav(self, item, **kwargs):
pass
def on_page_read_source(self, **kwargs):
pass
@plugins.event_priority(-50)
def on_post_build(self, **kwargs):
pass
collection = plugins.PluginCollection()
collection['dummy'] = dummy = DummyPlugin()
collection['prio'] = prio = PrioPlugin()
self.assertEqual(
collection.events['pre_page'],
[prio.on_pre_page, dummy.on_pre_page],
)
self.assertEqual(
collection.events['nav'],
[dummy.on_nav, prio.on_nav],
)
self.assertEqual(
collection.events['page_read_source'],
[dummy.on_page_read_source, prio.on_page_read_source],
)
self.assertEqual(
collection.events['post_build'],
[prio.on_post_build],
)
def test_set_plugin_on_collection(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
collection['foo'] = plugin
self.assertEqual([(k, v) for k, v in collection.items()], [('foo', plugin)])
def test_set_multiple_plugins_on_collection(self):
collection = plugins.PluginCollection()
plugin1 = DummyPlugin()
collection['foo'] = plugin1
plugin2 = DummyPlugin()
collection['bar'] = plugin2
self.assertEqual(
[(k, v) for k, v in collection.items()], [('foo', plugin1), ('bar', plugin2)]
)
def test_run_event_on_collection(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
plugin.load_config({'foo': 'new'})
collection['foo'] = plugin
self.assertEqual(collection.run_event('pre_page', 'page content'), 'new page content')
def test_run_event_twice_on_collection(self):
collection = plugins.PluginCollection()
plugin1 = DummyPlugin()
plugin1.load_config({'foo': 'new'})
collection['foo'] = plugin1
plugin2 = DummyPlugin()
plugin2.load_config({'foo': 'second'})
collection['bar'] = plugin2
self.assertEqual(
collection.run_event('pre_page', 'page content'), 'second new page content'
)
def test_event_returns_None(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
plugin.load_config({'foo': 'new'})
collection['foo'] = plugin
self.assertEqual(collection.run_event('nav', 'nav item'), 'nav item')
def test_event_empty_item(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
plugin.load_config({'foo': 'new'})
collection['foo'] = plugin
self.assertEqual(collection.run_event('page_read_source'), 'new source')
def test_event_empty_item_returns_None(self):
collection = plugins.PluginCollection()
plugin = DummyPlugin()
plugin.load_config({'foo': 'new'})
collection['foo'] = plugin
self.assertEqual(collection.run_event('pre_build'), None)
def test_run_undefined_event_on_collection(self):
collection = plugins.PluginCollection()
self.assertEqual(collection.run_event('pre_page', 'page content'), 'page content')
def test_run_unknown_event_on_collection(self):
collection = plugins.PluginCollection()
with self.assertRaises(KeyError):
collection.run_event('unknown', 'page content')
@tempdir()
def test_run_build_error_event(self, site_dir):
build_errors = []
class PluginRaisingError(plugins.BasePlugin):
def __init__(self, error_on):
self.error_on = error_on
def on_pre_page(self, page, **kwargs):
if self.error_on == 'pre_page':
raise BuildError('pre page error')
return page
def on_page_markdown(self, markdown, **kwargs):
if self.error_on == 'page_markdown':
raise BuildError('page markdown error')
return markdown
def on_page_content(self, html, **kwargs):
if self.error_on == 'page_content':
raise PluginError('page content error')
return html
def on_post_page(self, html, **kwargs):
if self.error_on == 'post_page':
raise ValueError('post page error')
def on_build_error(self, error, **kwargs):
build_errors.append(error)
cfg = load_config(site_dir=site_dir)
cfg['plugins']['errorplugin'] = PluginRaisingError(error_on='pre_page')
with self.assertLogs('mkdocs', level='ERROR'):
self.assertRaises(Abort, build.build, cfg)
cfg = load_config(site_dir=site_dir)
cfg['plugins']['errorplugin'] = PluginRaisingError(error_on='page_markdown')
with self.assertLogs('mkdocs', level='ERROR'):
self.assertRaises(Abort, build.build, cfg)
cfg = load_config(site_dir=site_dir)
cfg['plugins']['errorplugin'] = PluginRaisingError(error_on='page_content')
with self.assertLogs('mkdocs', level='ERROR'):
self.assertRaises(Abort, build.build, cfg)
cfg = load_config(site_dir=site_dir)
cfg['plugins']['errorplugin'] = PluginRaisingError(error_on='post_page')
with self.assertLogs('mkdocs', level='ERROR'):
self.assertRaises(ValueError, build.build, cfg)
cfg = load_config(site_dir=site_dir)
cfg['plugins']['errorplugin'] = PluginRaisingError(error_on='')
build.build(cfg)
self.assertEqual(len(build_errors), 4)
self.assertIs(build_errors[0].__class__, BuildError)
self.assertEqual(str(build_errors[0]), 'pre page error')
self.assertIs(build_errors[1].__class__, BuildError)
self.assertEqual(str(build_errors[1]), 'page markdown error')
self.assertIs(build_errors[2].__class__, PluginError)
self.assertEqual(str(build_errors[2]), 'page content error')
self.assertIs(build_errors[3].__class__, ValueError)
self.assertEqual(str(build_errors[3]), 'post page error')

View File

@ -0,0 +1,645 @@
#!/usr/bin/env python
import json
import unittest
from unittest import mock
from mkdocs.config.config_options import ValidationError
from mkdocs.contrib import search
from mkdocs.contrib.search import search_index
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from mkdocs.structure.toc import get_toc
from mkdocs.tests.base import dedent, get_markdown_toc, load_config
def strip_whitespace(string):
return string.replace("\n", "").replace(" ", "")
class SearchConfigTests(unittest.TestCase):
def test_lang_default(self):
option = search.LangOption(default=['en'])
value = option.validate(None)
self.assertEqual(['en'], value)
def test_lang_str(self):
option = search.LangOption()
value = option.validate('en')
self.assertEqual(['en'], value)
def test_lang_list(self):
option = search.LangOption()
value = option.validate(['en'])
self.assertEqual(['en'], value)
def test_lang_multi_list(self):
option = search.LangOption()
value = option.validate(['en', 'es', 'fr'])
self.assertEqual(['en', 'es', 'fr'], value)
def test_lang_no_default_none(self):
option = search.LangOption()
value = option.validate(None)
self.assertIsNone(value)
def test_lang_no_default_str(self):
option = search.LangOption(default=[])
value = option.validate('en')
self.assertEqual(['en'], value)
def test_lang_no_default_list(self):
option = search.LangOption(default=[])
value = option.validate(['en'])
self.assertEqual(['en'], value)
def test_lang_bad_type(self):
option = search.LangOption()
with self.assertRaises(ValidationError):
option.validate({})
def test_lang_bad_code(self):
option = search.LangOption()
value = option.validate(['foo'])
self.assertEqual(['en'], value)
def test_lang_good_and_bad_code(self):
option = search.LangOption()
value = option.validate(['en', 'foo'])
self.assertEqual(['en'], value)
def test_lang_missing_and_with_territory(self):
option = search.LangOption()
value = option.validate(['zh_CN', 'pt_BR', 'fr'])
self.assertEqual(['fr', 'en', 'pt'], value)
class SearchPluginTests(unittest.TestCase):
def test_plugin_config_defaults(self):
expected = {
'lang': None,
'separator': r'[\s\-]+',
'min_search_length': 3,
'prebuild_index': False,
'indexing': 'full',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_plugin_config_lang(self):
expected = {
'lang': ['es'],
'separator': r'[\s\-]+',
'min_search_length': 3,
'prebuild_index': False,
'indexing': 'full',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({'lang': 'es'})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_plugin_config_separator(self):
expected = {
'lang': None,
'separator': r'[\s\-\.]+',
'min_search_length': 3,
'prebuild_index': False,
'indexing': 'full',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({'separator': r'[\s\-\.]+'})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_plugin_config_min_search_length(self):
expected = {
'lang': None,
'separator': r'[\s\-]+',
'min_search_length': 2,
'prebuild_index': False,
'indexing': 'full',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({'min_search_length': 2})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_plugin_config_prebuild_index(self):
expected = {
'lang': None,
'separator': r'[\s\-]+',
'min_search_length': 3,
'prebuild_index': True,
'indexing': 'full',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({'prebuild_index': True})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_plugin_config_indexing(self):
expected = {
'lang': None,
'separator': r'[\s\-]+',
'min_search_length': 3,
'prebuild_index': False,
'indexing': 'titles',
}
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({'indexing': 'titles'})
self.assertEqual(plugin.config, expected)
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
def test_event_on_config_defaults(self):
plugin = search.SearchPlugin()
plugin.load_config({})
result = plugin.on_config(load_config(theme='mkdocs', extra_javascript=[]))
self.assertFalse(result['theme']['search_index_only'])
self.assertFalse(result['theme']['include_search_page'])
self.assertEqual(result['theme'].static_templates, {'404.html', 'sitemap.xml'})
self.assertEqual(len(result['theme'].dirs), 3)
self.assertEqual(result['extra_javascript'], ['search/main.js'])
self.assertEqual(plugin.config.lang, [result['theme']['locale'].language])
def test_event_on_config_lang(self):
plugin = search.SearchPlugin()
plugin.load_config({'lang': 'es'})
result = plugin.on_config(load_config(theme='mkdocs', extra_javascript=[]))
self.assertFalse(result['theme']['search_index_only'])
self.assertFalse(result['theme']['include_search_page'])
self.assertEqual(result['theme'].static_templates, {'404.html', 'sitemap.xml'})
self.assertEqual(len(result['theme'].dirs), 3)
self.assertEqual(result['extra_javascript'], ['search/main.js'])
self.assertEqual(plugin.config.lang, ['es'])
def test_event_on_config_theme_locale(self):
plugin = search.SearchPlugin()
plugin.load_config({})
result = plugin.on_config(
load_config(theme={'name': 'mkdocs', 'locale': 'fr'}, extra_javascript=[])
)
self.assertFalse(result['theme']['search_index_only'])
self.assertFalse(result['theme']['include_search_page'])
self.assertEqual(result['theme'].static_templates, {'404.html', 'sitemap.xml'})
self.assertEqual(len(result['theme'].dirs), 3)
self.assertEqual(result['extra_javascript'], ['search/main.js'])
self.assertEqual(plugin.config.lang, [result['theme']['locale'].language])
def test_event_on_config_include_search_page(self):
plugin = search.SearchPlugin()
plugin.load_config({})
config = load_config(
theme={'name': 'mkdocs', 'include_search_page': True}, extra_javascript=[]
)
result = plugin.on_config(config)
self.assertFalse(result['theme']['search_index_only'])
self.assertTrue(result['theme']['include_search_page'])
self.assertEqual(
result['theme'].static_templates, {'404.html', 'sitemap.xml', 'search.html'}
)
self.assertEqual(len(result['theme'].dirs), 3)
self.assertEqual(result['extra_javascript'], ['search/main.js'])
def test_event_on_config_search_index_only(self):
plugin = search.SearchPlugin()
plugin.load_config({})
config = load_config(
theme={'name': 'mkdocs', 'search_index_only': True}, extra_javascript=[]
)
result = plugin.on_config(config)
self.assertTrue(result['theme']['search_index_only'])
self.assertFalse(result['theme']['include_search_page'])
self.assertEqual(result['theme'].static_templates, {'404.html', 'sitemap.xml'})
self.assertEqual(len(result['theme'].dirs), 2)
self.assertEqual(len(result['extra_javascript']), 0)
@mock.patch('mkdocs.utils.write_file', autospec=True)
@mock.patch('mkdocs.utils.copy_file', autospec=True)
def test_event_on_post_build_defaults(self, mock_copy_file, mock_write_file):
plugin = search.SearchPlugin()
plugin.load_config({})
config = load_config(theme='mkdocs')
plugin.on_config(config)
plugin.on_pre_build(config)
plugin.on_post_build(config)
self.assertEqual(mock_copy_file.call_count, 0)
self.assertEqual(mock_write_file.call_count, 1)
@mock.patch('mkdocs.utils.write_file', autospec=True)
@mock.patch('mkdocs.utils.copy_file', autospec=True)
def test_event_on_post_build_single_lang(self, mock_copy_file, mock_write_file):
plugin = search.SearchPlugin()
plugin.load_config({'lang': ['es']})
config = load_config(theme='mkdocs')
plugin.on_pre_build(config)
plugin.on_post_build(config)
self.assertEqual(mock_copy_file.call_count, 2)
self.assertEqual(mock_write_file.call_count, 1)
@mock.patch('mkdocs.utils.write_file', autospec=True)
@mock.patch('mkdocs.utils.copy_file', autospec=True)
def test_event_on_post_build_multi_lang(self, mock_copy_file, mock_write_file):
plugin = search.SearchPlugin()
plugin.load_config({'lang': ['es', 'fr']})
config = load_config(theme='mkdocs')
plugin.on_pre_build(config)
plugin.on_post_build(config)
self.assertEqual(mock_copy_file.call_count, 4)
self.assertEqual(mock_write_file.call_count, 1)
@mock.patch('mkdocs.utils.write_file', autospec=True)
@mock.patch('mkdocs.utils.copy_file', autospec=True)
def test_event_on_post_build_search_index_only(self, mock_copy_file, mock_write_file):
plugin = search.SearchPlugin()
plugin.load_config({'lang': ['es']})
config = load_config(theme={'name': 'mkdocs', 'search_index_only': True})
plugin.on_pre_build(config)
plugin.on_post_build(config)
self.assertEqual(mock_copy_file.call_count, 0)
self.assertEqual(mock_write_file.call_count, 1)
class SearchIndexTests(unittest.TestCase):
def test_html_stripping(self):
stripper = search_index.ContentParser()
stripper.feed("<h1>Testing</h1><p>Content</p>")
self.assertEqual(stripper.stripped_html, "Testing\nContent")
def test_content_parser(self):
parser = search_index.ContentParser()
parser.feed('<h1 id="title">Title</h1>TEST')
parser.close()
self.assertEqual(
parser.data, [search_index.ContentSection(text=["TEST"], id_="title", title="Title")]
)
def test_content_parser_no_id(self):
parser = search_index.ContentParser()
parser.feed("<h1>Title</h1>TEST")
parser.close()
self.assertEqual(
parser.data, [search_index.ContentSection(text=["TEST"], id_=None, title="Title")]
)
def test_content_parser_content_before_header(self):
parser = search_index.ContentParser()
parser.feed("Content Before H1 <h1>Title</h1>TEST")
parser.close()
self.assertEqual(
parser.data, [search_index.ContentSection(text=["TEST"], id_=None, title="Title")]
)
def test_content_parser_no_sections(self):
parser = search_index.ContentParser()
parser.feed("No H1 or H2<span>Title</span>TEST")
self.assertEqual(parser.data, [])
def test_find_toc_by_id(self):
"""
Test finding the relevant TOC item by the tag ID.
"""
index = search_index.SearchIndex()
md = dedent(
"""
# Heading 1
## Heading 2
### Heading 3
"""
)
toc = get_toc(get_markdown_toc(md))
toc_item = index._find_toc_by_id(toc, "heading-1")
self.assertEqual(toc_item.url, "#heading-1")
self.assertEqual(toc_item.title, "Heading 1")
toc_item2 = index._find_toc_by_id(toc, "heading-2")
self.assertEqual(toc_item2.url, "#heading-2")
self.assertEqual(toc_item2.title, "Heading 2")
toc_item3 = index._find_toc_by_id(toc, "heading-3")
self.assertEqual(toc_item3.url, "#heading-3")
self.assertEqual(toc_item3.title, "Heading 3")
def test_create_search_index(self):
html_content = """
<h1 id="heading-1">Heading 1</h1>
<p>Content 1</p>
<h2 id="heading-2">Heading 2</h1>
<p>Content 2</p>
<h3 id="heading-3">Heading 3</h1>
<p>Content 3</p>
"""
base_cfg = load_config()
pages = [
Page(
'Home',
File(
'index.md',
base_cfg['docs_dir'],
base_cfg['site_dir'],
base_cfg['use_directory_urls'],
),
base_cfg,
),
Page(
'About',
File(
'about.md',
base_cfg['docs_dir'],
base_cfg['site_dir'],
base_cfg['use_directory_urls'],
),
base_cfg,
),
]
md = dedent(
"""
# Heading 1
## Heading 2
### Heading 3
"""
)
toc = get_toc(get_markdown_toc(md))
full_content = ''.join(f"Heading{i}Content{i}" for i in range(1, 4))
plugin = search.SearchPlugin()
errors, warnings = plugin.load_config({})
for page in pages:
# Fake page.read_source() and page.render()
page.markdown = md
page.toc = toc
page.content = html_content
index = search_index.SearchIndex(**plugin.config)
index.add_entry_from_context(page)
self.assertEqual(len(index._entries), 4)
loc = page.url
self.assertEqual(index._entries[0]['title'], page.title)
self.assertEqual(strip_whitespace(index._entries[0]['text']), full_content)
self.assertEqual(index._entries[0]['location'], loc)
self.assertEqual(index._entries[1]['title'], "Heading 1")
self.assertEqual(index._entries[1]['text'], "Content 1")
self.assertEqual(index._entries[1]['location'], f"{loc}#heading-1")
self.assertEqual(index._entries[2]['title'], "Heading 2")
self.assertEqual(strip_whitespace(index._entries[2]['text']), "Content2")
self.assertEqual(index._entries[2]['location'], f"{loc}#heading-2")
self.assertEqual(index._entries[3]['title'], "Heading 3")
self.assertEqual(strip_whitespace(index._entries[3]['text']), "Content3")
self.assertEqual(index._entries[3]['location'], f"{loc}#heading-3")
def test_search_indexing_options(self):
def test_page(title, filename, config):
test_page = Page(
title,
File(filename, config.docs_dir, config.site_dir, config.use_directory_urls),
config,
)
test_page.content = """
<h1 id="heading-1">Heading 1</h1>
<p>Content 1</p>
<h2 id="heading-2">Heading 2</h1>
<p>Content 2</p>
<h3 id="heading-3">Heading 3</h1>
<p>Content 3</p>"""
test_page.markdown = dedent(
"""
# Heading 1
## Heading 2
### Heading 3"""
)
test_page.toc = get_toc(get_markdown_toc(test_page.markdown))
return test_page
def validate_full(data, page):
self.assertEqual(len(data), 4)
for x in data:
self.assertTrue(x['title'])
self.assertTrue(x['text'])
def validate_sections(data, page):
# Sanity
self.assertEqual(len(data), 4)
# Page
self.assertEqual(data[0]['title'], page.title)
self.assertFalse(data[0]['text'])
# Headings
for x in data[1:]:
self.assertTrue(x['title'])
self.assertFalse(x['text'])
def validate_titles(data, page):
# Sanity
self.assertEqual(len(data), 1)
for x in data:
self.assertFalse(x['text'])
for option, validate in {
'full': validate_full,
'sections': validate_sections,
'titles': validate_titles,
}.items():
with self.subTest(option):
plugin = search.SearchPlugin()
# Load plugin config, overriding indexing for test case
errors, warnings = plugin.load_config({'indexing': option})
self.assertEqual(errors, [])
self.assertEqual(warnings, [])
base_cfg = load_config()
base_cfg['plugins']['search'].config.indexing = option
pages = [
test_page('Home', 'index.md', base_cfg),
test_page('About', 'about.md', base_cfg),
]
for page in pages:
index = search_index.SearchIndex(**plugin.config)
index.add_entry_from_context(page)
data = index.generate_search_index()
validate(json.loads(data)['docs'], page)
@mock.patch('subprocess.Popen', autospec=True)
def test_prebuild_index(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ('{"mock": "index"}', None)
mock_popen_obj.returncode = 0
index = search_index.SearchIndex(prebuild_index=True)
expected = {
'docs': [],
'config': {'prebuild_index': True},
'index': {'mock': 'index'},
}
result = json.loads(index.generate_search_index())
self.assertEqual(mock_popen.call_count, 1)
self.assertEqual(mock_popen_obj.communicate.call_count, 1)
self.assertEqual(result, expected)
@mock.patch('subprocess.Popen', autospec=True)
def test_prebuild_index_returns_error(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ('', 'Some Error')
mock_popen_obj.returncode = 0
index = search_index.SearchIndex(prebuild_index=True)
expected = {
'docs': [],
'config': {'prebuild_index': True},
}
with self.assertLogs('mkdocs') as cm:
result = json.loads(index.generate_search_index())
self.assertEqual(
'\n'.join(cm.output),
'WARNING:mkdocs.contrib.search.search_index:Failed to pre-build search index. Error: Some Error',
)
self.assertEqual(mock_popen.call_count, 1)
self.assertEqual(mock_popen_obj.communicate.call_count, 1)
self.assertEqual(result, expected)
@mock.patch('subprocess.Popen', autospec=True)
def test_prebuild_index_raises_ioerror(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.side_effect = OSError
mock_popen_obj.returncode = 1
index = search_index.SearchIndex(prebuild_index=True)
expected = {
'docs': [],
'config': {'prebuild_index': True},
}
with self.assertLogs('mkdocs') as cm:
result = json.loads(index.generate_search_index())
self.assertEqual(
'\n'.join(cm.output),
'WARNING:mkdocs.contrib.search.search_index:Failed to pre-build search index. Error: ',
)
self.assertEqual(mock_popen.call_count, 1)
self.assertEqual(mock_popen_obj.communicate.call_count, 1)
self.assertEqual(result, expected)
@mock.patch('subprocess.Popen', autospec=True, side_effect=OSError)
def test_prebuild_index_raises_oserror(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ('foo', 'bar')
mock_popen_obj.returncode = 0
index = search_index.SearchIndex(prebuild_index=True)
expected = {
'docs': [],
'config': {'prebuild_index': True},
}
with self.assertLogs('mkdocs') as cm:
result = json.loads(index.generate_search_index())
self.assertEqual(
'\n'.join(cm.output),
'WARNING:mkdocs.contrib.search.search_index:Failed to pre-build search index. Error: ',
)
self.assertEqual(mock_popen.call_count, 1)
self.assertEqual(mock_popen_obj.communicate.call_count, 0)
self.assertEqual(result, expected)
@mock.patch('subprocess.Popen', autospec=True)
def test_prebuild_index_false(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ('', '')
mock_popen_obj.returncode = 0
index = search_index.SearchIndex(prebuild_index=False)
expected = {
'docs': [],
'config': {'prebuild_index': False},
}
result = json.loads(index.generate_search_index())
self.assertEqual(mock_popen.call_count, 0)
self.assertEqual(mock_popen_obj.communicate.call_count, 0)
self.assertEqual(result, expected)
@unittest.skipUnless(search_index.haslunrpy, 'lunr.py is not installed')
@mock.patch('mkdocs.contrib.search.search_index.lunr', autospec=True)
def test_prebuild_index_python(self, mock_lunr):
mock_lunr.return_value.serialize.return_value = {'mock': 'index'}
index = search_index.SearchIndex(prebuild_index='python', lang='en')
expected = {
'docs': [],
'config': {'prebuild_index': 'python', 'lang': 'en'},
'index': {'mock': 'index'},
}
result = json.loads(index.generate_search_index())
self.assertEqual(mock_lunr.call_count, 1)
self.assertEqual(result, expected)
@unittest.skipIf(search_index.haslunrpy, 'lunr.py is installed')
def test_prebuild_index_python_missing_lunr(self):
# When the lunr.py dependencies are not installed no prebuilt index is created.
index = search_index.SearchIndex(prebuild_index='python', lang='en')
expected = {
'docs': [],
'config': {'prebuild_index': 'python', 'lang': 'en'},
}
with self.assertLogs('mkdocs', level='WARNING'):
result = json.loads(index.generate_search_index())
self.assertEqual(result, expected)
@mock.patch('subprocess.Popen', autospec=True)
def test_prebuild_index_node(self, mock_popen):
# See https://stackoverflow.com/a/36501078/866026
mock_popen.return_value = mock.Mock()
mock_popen_obj = mock_popen.return_value
mock_popen_obj.communicate.return_value = ('{"mock": "index"}', None)
mock_popen_obj.returncode = 0
index = search_index.SearchIndex(prebuild_index='node')
expected = {
'docs': [],
'config': {'prebuild_index': 'node'},
'index': {'mock': 'index'},
}
result = json.loads(index.generate_search_index())
self.assertEqual(mock_popen.call_count, 1)
self.assertEqual(mock_popen_obj.communicate.call_count, 1)
self.assertEqual(result, expected)

View File

@ -0,0 +1,754 @@
import os
import sys
import unittest
from unittest import mock
from mkdocs.structure.files import File, Files, _filter_paths, _sort_files, get_files
from mkdocs.tests.base import PathAssertionMixin, load_config, tempdir
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)
)
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_src_path_windows(self):
f = File('foo\\a.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
self.assertEqual(f.src_uri, 'foo/a.md')
self.assertEqual(f.src_path, 'foo\\a.md')
f.src_uri = 'foo/b.md'
self.assertEqual(f.src_uri, 'foo/b.md')
self.assertEqual(f.src_path, 'foo\\b.md')
f.src_path = 'foo/c.md'
self.assertEqual(f.src_uri, 'foo/c.md')
self.assertEqual(f.src_path, 'foo\\c.md')
f.src_path = 'foo\\d.md'
self.assertEqual(f.src_uri, 'foo/d.md')
self.assertEqual(f.src_path, 'foo\\d.md')
f.src_uri = 'foo\\e.md'
self.assertEqual(f.src_uri, 'foo\\e.md')
self.assertEqual(f.src_path, 'foo\\e.md')
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'],
)
self.assertEqual(
_sort_files(['A.md', 'B.md', 'README.md']),
['README.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.assertEqual(f.src_uri, 'foo.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'index.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md')
self.assertEqual(f.dest_uri, '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_readme_index_file(self):
f = File('README.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
self.assertEqual(f.src_uri, 'README.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/README.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'index.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/index.md')
self.assertEqual(f.dest_uri, '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_readme_index_file_use_directory_urls(self):
f = File('README.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
self.assertEqual(f.src_uri, 'README.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/README.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/index.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/index.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/index.md')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.html')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.html')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.html')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.jpg')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.jpg')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.jpg')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.js')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.js')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.js')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.css')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css')
self.assertEqual(f.dest_uri, '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.assertEqual(f.src_uri, 'foo/bar.css')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo/bar.css')
self.assertEqual(f.dest_uri, '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_file_name_with_space(self):
f = File('foo bar.md', '/path/to/docs', '/path/to/site', use_directory_urls=False)
self.assertEqual(f.src_uri, 'foo bar.md')
self.assertPathsEqual(f.abs_src_path, '/path/to/docs/foo bar.md')
self.assertEqual(f.dest_uri, 'foo bar.html')
self.assertPathsEqual(f.abs_dest_path, '/path/to/site/foo bar.html')
self.assertEqual(f.url, 'foo%20bar.html')
self.assertEqual(f.name, 'foo bar')
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_uri in files.src_uris)
self.assertTrue(fs[2].src_uri in files.src_uris)
extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
self.assertFalse(extra_file.src_uri in files.src_uris)
files.append(extra_file)
self.assertEqual(len(files), 7)
self.assertTrue(extra_file.src_uri in files.src_uris)
self.assertEqual(files.documentation_pages(), [fs[0], fs[1], extra_file])
@tempdir(
files=[
'favicon.ico',
'index.md',
]
)
@tempdir(
files=[
'base.html',
'favicon.ico',
'style.css',
'foo.md',
'README',
'.ignore.txt',
'.ignore/file.txt',
'foo/.ignore.txt',
'foo/.ignore/file.txt',
]
)
def test_add_files_from_theme(self, tdir, ddir):
config = load_config(docs_dir=ddir, theme={'name': None, 'custom_dir': tdir})
env = config.theme.get_env()
files = get_files(config)
self.assertEqual(
[file.src_path for file in files],
['index.md', 'favicon.ico'],
)
files.add_files_from_theme(env, config)
self.assertEqual(
[file.src_path for file in files],
['index.md', 'favicon.ico', 'style.css'],
)
# Ensure theme file does not override docs_dir file
self.assertEqual(
files.get_file_from_path('favicon.ico').abs_src_path,
os.path.normpath(os.path.join(ddir, 'favicon.ico')),
)
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):
with self.subTest(from_file=from_file.src_path, to_file=filename):
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):
with self.subTest(from_file=from_file.src_path, to_file=filename):
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):
with self.subTest(from_file=from_file.src_path, to_file=filename):
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):
with self.subTest(from_file=from_file.src_path, to_file=filename):
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',
'readme.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', 'readme.md']
self.assertIsInstance(files, Files)
self.assertEqual(len(files), len(expected))
self.assertEqual([f.src_path for f in files], expected)
@tempdir(
files=[
'README.md',
'foo.md',
]
)
def test_get_files_include_readme_without_index(self, tdir):
config = load_config(docs_dir=tdir)
files = get_files(config)
expected = ['README.md', 'foo.md']
self.assertIsInstance(files, Files)
self.assertEqual(len(files), len(expected))
self.assertEqual([f.src_path for f in files], expected)
@tempdir(
files=[
'index.md',
'README.md',
'foo.md',
]
)
def test_get_files_exclude_readme_with_index(self, tdir):
config = load_config(docs_dir=tdir)
with self.assertLogs('mkdocs') as cm:
files = get_files(config)
self.assertRegex(
'\n'.join(cm.output),
r"^WARNING:mkdocs.structure.files:Both index.md and README.md found. Skipping README.md .+$",
)
expected = ['index.md', 'foo.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': 'source content'})
def test_copy_file_same_file(self, dest_dir):
file = File('test.txt', dest_dir, dest_dir, use_directory_urls=False)
dest_path = os.path.join(dest_dir, 'test.txt')
file.copy_file()
self.assertPathIsFile(dest_path)
with open(dest_path, 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_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 open(dest_path, 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 open(dest_path, 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 open(dest_path, encoding='utf-8') as f:
self.assertEqual(f.read(), 'destination content')
def test_files_append_remove_src_paths(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(len(files), 6)
self.assertEqual(len(files.src_uris), 6)
extra_file = File('extra.md', '/path/to/docs', '/path/to/site', use_directory_urls=True)
self.assertFalse(extra_file.src_uri in files.src_uris)
files.append(extra_file)
self.assertEqual(len(files), 7)
self.assertEqual(len(files.src_uris), 7)
self.assertTrue(extra_file.src_uri in files.src_uris)
files.remove(extra_file)
self.assertEqual(len(files), 6)
self.assertEqual(len(files.src_uris), 6)
self.assertFalse(extra_file.src_uri in files.src_uris)

View File

@ -0,0 +1,477 @@
#!/usr/bin/env python
import sys
import unittest
from mkdocs.structure.files import File, Files
from mkdocs.structure.nav import Section, _get_by_type, get_navigation
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/')
fs = [
File(
list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']
)
for item in nav_cfg
]
files = Files(fs)
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/')
fs = [
File(
list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']
)
for item in nav_cfg
]
files = Files(fs)
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='/index.html')")
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/')
fs = [
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']),
]
files = Files(fs)
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/')
fs = [
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']),
]
files = Files(fs)
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/')
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
with self.assertLogs('mkdocs', level='DEBUG') as cm:
site_navigation = get_navigation(files, cfg)
self.assertEqual(
cm.output,
[
"DEBUG:mkdocs.structure.nav:An absolute path to '/local.html' is included in the "
"'nav' configuration, which presumably points to an external resource.",
"DEBUG:mkdocs.structure.nav:An external link to 'http://example.com/external.html' "
"is included in the 'nav' configuration.",
],
)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.items), 3)
self.assertEqual(len(site_navigation.pages), 1)
def test_nav_bad_links(self):
nav_cfg = [
{'Home': 'index.md'},
{'Missing': 'missing.html'},
{'Bad External': 'example.com'},
]
expected = dedent(
"""
Page(title='Home', url='/')
Link(title='Missing', url='missing.html')
Link(title='Bad External', url='example.com')
"""
)
cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
fs = [File('index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
with self.assertLogs('mkdocs') as cm:
site_navigation = get_navigation(files, cfg)
self.assertEqual(
cm.output,
[
"WARNING:mkdocs.structure.nav:A relative path to 'missing.html' is included "
"in the 'nav' configuration, which is not found in the documentation files",
"WARNING:mkdocs.structure.nav:A relative path to 'example.com' is included "
"in the 'nav' configuration, which is not found in the documentation files",
],
)
self.assertEqual(str(site_navigation).strip(), expected)
self.assertEqual(len(site_navigation.items), 3)
self.assertEqual(len(site_navigation.pages), 1)
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/')
fs = [
'index.md',
'api-guide/running.md',
'api-guide/testing.md',
'api-guide/debugging.md',
'api-guide/advanced/part-1.md',
'about/release-notes.md',
]
files = Files(
[File(s, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for s in fs]
)
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/')
fs = [
File(
list(item.values())[0], cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']
)
for item in nav_cfg
]
files = Files(fs)
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/')
fs = [
File(item, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
for item in nav_cfg
]
files = Files(fs)
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/')
fs = [
File(item, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
for item in nav_cfg
]
files = Files(fs)
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/')
fs = [
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']),
]
files = Files(fs)
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/')
fs = [
'index.md',
'about/license.md',
'about/release-notes.md',
'api-guide/debugging.md',
'api-guide/running.md',
'api-guide/testing.md',
'api-guide/advanced/part-1.md',
]
files = Files(
[File(s, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for s in fs]
)
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/')
fs = [
'index.md',
'api-guide/running.md',
'api-guide/testing.md',
'api-guide/debugging.md',
'api-guide/advanced/part-1.md',
'about/release-notes.md',
'about/license.md',
]
files = Files(
[File(s, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for s in fs]
)
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)
def test_get_by_type_nested_sections(self):
nav_cfg = [
{
'Section 1': [
{
'Section 2': [
{'Page': 'page.md'},
]
},
]
},
]
cfg = load_config(nav=nav_cfg, site_url='http://example.com/')
fs = [File('page.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])]
files = Files(fs)
site_navigation = get_navigation(files, cfg)
self.assertEqual(len(_get_by_type(site_navigation, Section)), 2)

View File

@ -0,0 +1,815 @@
import functools
import os
import sys
import unittest
from unittest import mock
from mkdocs.structure.files import File, Files
from mkdocs.structure.pages import Page
from mkdocs.tests.base import dedent, load_config, tempdir
load_config = functools.lru_cache(maxsize=None)(load_config)
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_index_page_no_parent(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 = None # non-homepage at nav root level; see #1919.
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.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_no_parent_no_directory_urls(self):
cfg = load_config(docs_dir=self.DOCS_DIR, use_directory_urls=False)
fl = File('sub1/index.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
pg = Page('Foo', fl, cfg)
pg.parent = None # non-homepage at nav root level; see #1919.
self.assertEqual(pg.url, 'sub1/index.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.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_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.assertRegex(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))
@tempdir()
def test_BOM(self, docs_dir):
md_src = '# An UTF-8 encoded file with a BOM'
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 Microsoft editors do). See #1186
with 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, paths={'testing.md': 'testing/', 'sub1/non-index.md': 'sub1/non-index/'}
):
for case in [
dict(
config={'repo_url': 'http://github.com/mkdocs/mkdocs'},
edit_url='http://github.com/mkdocs/mkdocs/edit/master/docs/testing.md',
edit_url2='http://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
),
dict(
config={'repo_url': 'https://github.com/mkdocs/mkdocs/'},
edit_url='https://github.com/mkdocs/mkdocs/edit/master/docs/testing.md',
edit_url2='https://github.com/mkdocs/mkdocs/edit/master/docs/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com'},
edit_url=None,
edit_url2=None,
),
dict(
config={'repo_url': 'http://example.com', 'edit_uri': 'edit/master'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com', 'edit_uri': '/edit/master'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/foo/', 'edit_uri': '/edit/master/'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/foo', 'edit_uri': '/edit/master/'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/foo/', 'edit_uri': '/edit/master'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/foo/', 'edit_uri': 'edit/master/'},
edit_url='http://example.com/foo/edit/master/testing.md',
edit_url2='http://example.com/foo/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/foo', 'edit_uri': 'edit/master/'},
edit_url='http://example.com/foo/edit/master/testing.md',
edit_url2='http://example.com/foo/edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com', 'edit_uri': '?query=edit/master'},
edit_url='http://example.com?query=edit/master/testing.md',
edit_url2='http://example.com?query=edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/', 'edit_uri': '?query=edit/master/'},
edit_url='http://example.com/?query=edit/master/testing.md',
edit_url2='http://example.com/?query=edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com', 'edit_uri': '#edit/master'},
edit_url='http://example.com#edit/master/testing.md',
edit_url2='http://example.com#edit/master/sub1/non-index.md',
),
dict(
config={'repo_url': 'http://example.com/', 'edit_uri': '#edit/master/'},
edit_url='http://example.com/#edit/master/testing.md',
edit_url2='http://example.com/#edit/master/sub1/non-index.md',
),
dict(
config={'edit_uri': 'http://example.com/edit/master'},
edit_url='http://example.com/edit/master/testing.md',
edit_url2='http://example.com/edit/master/sub1/non-index.md',
),
dict(
config={'edit_uri_template': 'https://github.com/project/repo/wiki/{path_noext}'},
edit_url='https://github.com/project/repo/wiki/testing',
edit_url2='https://github.com/project/repo/wiki/sub1/non-index',
),
dict(
config={
'repo_url': 'https://github.com/project/repo/wiki',
'edit_uri_template': '{path_noext}/_edit',
},
edit_url='https://github.com/project/repo/wiki/testing/_edit',
edit_url2='https://github.com/project/repo/wiki/sub1/non-index/_edit',
),
dict(
config={
'repo_url': 'https://gitlab.com/project/repo',
'edit_uri_template': '-/sse/master/docs%2F{path!q}',
},
edit_url='https://gitlab.com/project/repo/-/sse/master/docs%2Ftesting.md',
edit_url2='https://gitlab.com/project/repo/-/sse/master/docs%2Fsub1%2Fnon-index.md',
),
dict(
config={
'repo_url': 'https://bitbucket.org/project/repo/',
'edit_uri_template': 'src/master/docs/{path}?mode=edit',
},
edit_url='https://bitbucket.org/project/repo/src/master/docs/testing.md?mode=edit',
edit_url2='https://bitbucket.org/project/repo/src/master/docs/sub1/non-index.md?mode=edit',
),
dict(
config={
'repo_url': 'http://example.com',
'edit_uri': '',
'edit_uri_template': '',
}, # Set to blank value
edit_url=None,
edit_url2=None,
),
dict(config={}, edit_url=None, edit_url2=None), # Nothing defined
]:
for i, path in enumerate(paths, 1):
edit_url_key = f'edit_url{i}' if i > 1 else 'edit_url'
with self.subTest(case['config'], path=path):
cfg = load_config(**case['config'])
fl = File(path, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
pg = Page('Foo', fl, cfg)
self.assertEqual(pg.url, paths[path])
self.assertEqual(pg.edit_url, case[edit_url_key])
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_page_edit_url_windows(self):
self.test_page_edit_url(
paths={'testing.md': 'testing/', 'sub1\\non-index.md': 'sub1/non-index/'}
)
def test_page_edit_url_warning(self):
for case in [
dict(
config={'edit_uri': 'edit/master'},
edit_url='edit/master/testing.md',
warning="WARNING:mkdocs.structure.pages:edit_uri: "
"'edit/master/testing.md' is not a valid URL, it should include the http:// (scheme)",
),
]:
with self.subTest(case['config']):
with self.assertLogs('mkdocs') as cm:
cfg = load_config(**case['config'])
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, case['edit_url'])
self.assertEqual(cm.output, [case['warning']])
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('<h1 id="welcome-to-mkdocs">Welcome to MkDocs</h1>\n')
)
self.assertEqual(
str(pg.toc).strip(),
dedent(
"""
Welcome to MkDocs - #welcome-to-mkdocs
Commands - #commands
Project layout - #project-layout
"""
),
)
def test_missing_page(self):
cfg = load_config()
fl = File('missing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
pg = Page('Foo', fl, cfg)
with self.assertLogs('mkdocs') as cm:
with self.assertRaises(OSError):
pg.read_source(cfg)
self.assertEqual(
'\n'.join(cm.output), 'ERROR:mkdocs.structure.pages:File not found: missing.md'
)
class SourceDateEpochTests(unittest.TestCase):
def setUp(self):
self.default = os.environ.get('SOURCE_DATE_EPOCH', None)
os.environ['SOURCE_DATE_EPOCH'] = '0'
def test_source_date_epoch(self):
cfg = load_config()
fl = File('testing.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls'])
pg = Page('Foo', fl, cfg)
self.assertEqual(pg.update_date, '1970-01-01')
def tearDown(self):
if self.default is not None:
os.environ['SOURCE_DATE_EPOCH'] = self.default
else:
del os.environ['SOURCE_DATE_EPOCH']
class RelativePathExtensionTests(unittest.TestCase):
DOCS_DIR = os.path.join(
os.path.abspath(os.path.dirname(__file__)), '../integration/subpages/docs'
)
def get_rendered_result(self, files):
cfg = load_config(docs_dir=self.DOCS_DIR)
fs = [File(f, cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']) for f in files]
pg = Page('Foo', fs[0], cfg)
pg.read_source(cfg)
pg.render(cfg, Files(fs))
return pg.content
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-index.md)'))
def test_relative_html_link(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'non-index.md']),
'<p><a href="non-index/">link</a></p>', # No trailing /
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](index.md)'))
def test_relative_html_link_index(self):
self.assertEqual(
self.get_rendered_result(['non-index.md', 'index.md']),
'<p><a href="../">link</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/index.md)'))
def test_relative_html_link_sub_index(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'sub2/index.md']),
'<p><a href="sub2/">link</a></p>', # No trailing /
)
@mock.patch(
'mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/non-index.md)')
)
def test_relative_html_link_sub_page(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'sub2/non-index.md']),
'<p><a href="sub2/non-index/">link</a></p>', # No trailing /
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](file%20name.md)'))
def test_relative_html_link_with_encoded_space(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'file name.md']),
'<p><a href="file%20name/">link</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](file name.md)'))
def test_relative_html_link_with_unencoded_space(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'file name.md']),
'<p><a href="file%20name/">link</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](../index.md)'))
def test_relative_html_link_parent_index(self):
self.assertEqual(
self.get_rendered_result(['sub2/non-index.md', 'index.md']),
'<p><a href="../../">link</a></p>',
)
@mock.patch(
'mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-index.md#hash)')
)
def test_relative_html_link_hash(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'non-index.md']),
'<p><a href="non-index/#hash">link</a></p>',
)
@mock.patch(
'mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/index.md#hash)')
)
def test_relative_html_link_sub_index_hash(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'sub2/index.md']),
'<p><a href="sub2/#hash">link</a></p>',
)
@mock.patch(
'mkdocs.structure.pages.open', mock.mock_open(read_data='[link](sub2/non-index.md#hash)')
)
def test_relative_html_link_sub_page_hash(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'sub2/non-index.md']),
'<p><a href="sub2/non-index/#hash">link</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](#hash)'))
def test_relative_html_link_hash_only(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><a href="#hash">link</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](image.png)'))
def test_relative_image_link_from_homepage(self):
self.assertEqual(
self.get_rendered_result(['index.md', 'image.png']),
'<p><img alt="image" src="image.png" /></p>', # no opening ./
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](../image.png)'))
def test_relative_image_link_from_subpage(self):
self.assertEqual(
self.get_rendered_result(['sub2/non-index.md', 'image.png']),
'<p><img alt="image" src="../../image.png" /></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='![image](image.png)'))
def test_relative_image_link_from_sibling(self):
self.assertEqual(
self.get_rendered_result(['non-index.md', 'image.png']),
'<p><img alt="image" src="../image.png" /></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='*__not__ a link*.'))
def test_no_links(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><em><strong>not</strong> a link</em>.</p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='[link](non-existent.md)'))
def test_bad_relative_html_link(self):
with self.assertLogs('mkdocs') as cm:
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><a href="non-existent.md">link</a></p>',
)
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.structure.pages:Documentation file 'index.md' contains a link "
"to 'non-existent.md' which is not found in the documentation files.",
)
@mock.patch(
'mkdocs.structure.pages.open',
mock.mock_open(read_data='[external](http://example.com/index.md)'),
)
def test_external_link(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><a href="http://example.com/index.md">external</a></p>',
)
@mock.patch(
'mkdocs.structure.pages.open', mock.mock_open(read_data='[absolute link](/path/to/file.md)')
)
def test_absolute_link(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><a href="/path/to/file.md">absolute link</a></p>',
)
@mock.patch(
'mkdocs.structure.pages.open',
mock.mock_open(read_data='[absolute local path](\\image.png)'),
)
def test_absolute_win_local_path(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
'<p><a href="\\image.png">absolute local path</a></p>',
)
@mock.patch('mkdocs.structure.pages.open', mock.mock_open(read_data='<mail@example.com>'))
def test_email_link(self):
self.assertEqual(
self.get_rendered_result(['index.md']),
# Markdown's default behavior is to obscure email addresses by entity-encoding them.
# The following is equivalent to: '<p><a href="mailto:mail@example.com">mail@example.com</a></p>'
'<p><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#109;&#97;&#105;&#108;&#64;&#101;'
'&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;">&#109;&#97;&#105;&#108;&#64;'
'&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;</a></p>',
)

View File

@ -0,0 +1,198 @@
#!/usr/bin/env python
import unittest
from mkdocs.structure.toc import get_toc
from mkdocs.tests.base import dedent, get_markdown_toc
class TableOfContentsTests(unittest.TestCase):
def test_indented_toc(self):
md = dedent(
"""
# Heading 1
## Heading 2
### Heading 3
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 1)
def test_indented_toc_html(self):
md = dedent(
"""
# Heading 1
## <code>Heading</code> 2
## Heading 3
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 1)
def test_flat_toc(self):
md = dedent(
"""
# Heading 1
# Heading 2
# Heading 3
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 3)
def test_flat_h2_toc(self):
md = dedent(
"""
## Heading 1
## Heading 2
## Heading 3
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 3)
def test_mixed_toc(self):
md = dedent(
"""
# Heading 1
## Heading 2
# Heading 3
### Heading 4
### Heading 5
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
Heading 4 - #heading-4
Heading 5 - #heading-5
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 2)
def test_mixed_html(self):
md = dedent(
"""
# Heading 1
## Heading 2
# Heading 3
### Heading 4
### <a>Heading 5</a>
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
Heading 4 - #heading-4
Heading 5 - #heading-5
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 2)
def test_nested_anchor(self):
md = dedent(
"""
# Heading 1
## Heading 2
# Heading 3
### Heading 4
### <a href="/">Heading 5</a>
"""
)
expected = dedent(
"""
Heading 1 - #heading-1
Heading 2 - #heading-2
Heading 3 - #heading-3
Heading 4 - #heading-4
Heading 5 - #heading-5
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 2)
def test_entityref(self):
md = dedent(
"""
# Heading & 1
## Heading > 2
### Heading < 3
"""
)
expected = dedent(
"""
Heading &amp; 1 - #heading-1
Heading &gt; 2 - #heading-2
Heading &lt; 3 - #heading-3
"""
)
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 1)
def test_charref(self):
md = '# &#64;Header'
expected = '&#64;Header - #header'
toc = get_toc(get_markdown_toc(md))
self.assertEqual(str(toc).strip(), expected)
self.assertEqual(len(toc), 1)
def test_level(self):
md = dedent(
"""
# Heading 1
## Heading 1.1
### Heading 1.1.1
### Heading 1.1.2
## Heading 1.2
"""
)
toc = get_toc(get_markdown_toc(md))
def get_level_sequence(items):
for item in items:
yield item.level
yield from get_level_sequence(item.children)
self.assertEqual(tuple(get_level_sequence(toc)), (1, 2, 3, 3, 2))

View File

@ -0,0 +1,108 @@
import os
import unittest
from unittest import mock
import mkdocs
from mkdocs.localization import parse_locale
from mkdocs.tests.base import tempdir
from mkdocs.theme import Theme
abs_path = os.path.abspath(os.path.dirname(__file__))
mkdocs_dir = os.path.abspath(os.path.dirname(mkdocs.__file__))
mkdocs_templates_dir = os.path.join(mkdocs_dir, 'templates')
theme_dir = os.path.abspath(os.path.join(mkdocs_dir, 'themes'))
def get_vars(theme):
"""Return dict of theme vars."""
return {k: theme[k] for k in iter(theme)}
class ThemeTests(unittest.TestCase):
def test_simple_theme(self):
theme = Theme(name='mkdocs')
self.assertEqual(
theme.dirs,
[os.path.join(theme_dir, 'mkdocs'), mkdocs_templates_dir],
)
self.assertEqual(theme.static_templates, {'404.html', 'sitemap.xml'})
self.assertEqual(
get_vars(theme),
{
'name': 'mkdocs',
'locale': parse_locale('en'),
'include_search_page': False,
'search_index_only': False,
'analytics': {'gtag': None},
'highlightjs': True,
'hljs_style': 'github',
'hljs_languages': [],
'navigation_depth': 2,
'nav_style': 'primary',
'shortcuts': {'help': 191, 'next': 78, 'previous': 80, 'search': 83},
},
)
@tempdir()
def test_custom_dir(self, custom):
theme = Theme(name='mkdocs', custom_dir=custom)
self.assertEqual(
theme.dirs,
[
custom,
os.path.join(theme_dir, 'mkdocs'),
mkdocs_templates_dir,
],
)
@tempdir()
def test_custom_dir_only(self, custom):
theme = Theme(name=None, custom_dir=custom)
self.assertEqual(
theme.dirs,
[custom, mkdocs_templates_dir],
)
def static_templates(self):
theme = Theme(name='mkdocs', static_templates='foo.html')
self.assertEqual(
theme.static_templates,
{'404.html', 'sitemap.xml', 'foo.html'},
)
def test_vars(self):
theme = Theme(name='mkdocs', foo='bar', baz=True)
self.assertEqual(theme['foo'], 'bar')
self.assertEqual(theme['baz'], True)
self.assertTrue('new' not in theme)
with self.assertRaises(KeyError):
theme['new']
theme['new'] = 42
self.assertTrue('new' in theme)
self.assertEqual(theme['new'], 42)
@mock.patch('mkdocs.utils.yaml_load', return_value=None)
def test_no_theme_config(self, m):
theme = Theme(name='mkdocs')
self.assertEqual(m.call_count, 1)
self.assertEqual(theme.static_templates, {'sitemap.xml'})
def test_inherited_theme(self):
m = mock.Mock(
side_effect=[
{'extends': 'readthedocs', 'static_templates': ['child.html']},
{'static_templates': ['parent.html']},
]
)
with mock.patch('mkdocs.utils.yaml_load', m) as m:
theme = Theme(name='mkdocs')
self.assertEqual(m.call_count, 2)
self.assertEqual(
theme.dirs,
[
os.path.join(theme_dir, 'mkdocs'),
os.path.join(theme_dir, 'readthedocs'),
mkdocs_templates_dir,
],
)
self.assertEqual(theme.static_templates, {'sitemap.xml', 'child.html', 'parent.html'})

View File

@ -0,0 +1,55 @@
import unittest
from mkdocs.utils.babel_stub import Locale, UnknownLocaleError
class BabelStubTests(unittest.TestCase):
def test_locale_language_only(self):
locale = Locale('es')
self.assertEqual(locale.language, 'es')
self.assertEqual(locale.territory, '')
self.assertEqual(str(locale), 'es')
def test_locale_language_territory(self):
locale = Locale('es', 'ES')
self.assertEqual(locale.language, 'es')
self.assertEqual(locale.territory, 'ES')
self.assertEqual(str(locale), 'es_ES')
def test_parse_locale_language_only(self):
locale = Locale.parse('fr', '_')
self.assertEqual(locale.language, 'fr')
self.assertEqual(locale.territory, '')
self.assertEqual(str(locale), 'fr')
def test_parse_locale_language_territory(self):
locale = Locale.parse('fr_FR', '_')
self.assertEqual(locale.language, 'fr')
self.assertEqual(locale.territory, 'FR')
self.assertEqual(str(locale), 'fr_FR')
def test_parse_locale_language_territory_sep(self):
locale = Locale.parse('fr-FR', '-')
self.assertEqual(locale.language, 'fr')
self.assertEqual(locale.territory, 'FR')
self.assertEqual(str(locale), 'fr_FR')
def test_parse_locale_bad_type(self):
with self.assertRaises(TypeError):
Locale.parse(['list'], '_')
def test_parse_locale_invalid_characters(self):
with self.assertRaises(ValueError):
Locale.parse('42', '_')
def test_parse_locale_bad_format(self):
with self.assertRaises(ValueError):
Locale.parse('en-GB', '_')
def test_parse_locale_bad_format_sep(self):
with self.assertRaises(ValueError):
Locale.parse('en_GB', '-')
def test_parse_locale_unknown_locale(self):
with self.assertRaises(UnknownLocaleError):
Locale.parse('foo', '_')

View File

@ -0,0 +1,691 @@
#!/usr/bin/env python
import datetime
import logging
import os
import posixpath
import stat
import unittest
from unittest import mock
from mkdocs import exceptions, utils
from mkdocs.structure.files import File
from mkdocs.structure.pages import Page
from mkdocs.tests.base import dedent, load_config, tempdir
from mkdocs.utils import meta
BASEYML = """
INHERIT: parent.yml
foo: bar
baz:
sub1: replaced
sub3: new
deep1:
deep2-1:
deep3-1: replaced
"""
PARENTYML = """
foo: foo
baz:
sub1: 1
sub2: 2
deep1:
deep2-1:
deep3-1: foo
deep3-2: bar
deep2-2: baz
"""
class UtilsTests(unittest.TestCase):
def test_is_markdown_file(self):
expected_results = {
'index.md': True,
'index.markdown': True,
'index.MARKDOWN': False,
'index.txt': False,
'indexmd': False,
}
for path, expected_result in expected_results.items():
with self.subTest(path):
is_markdown = utils.is_markdown_file(path)
self.assertEqual(is_markdown, expected_result)
def test_get_relative_url(self):
for case in [
dict(url='foo/bar', other='foo', expected='bar'),
dict(url='foo/bar.txt', other='foo', expected='bar.txt'),
dict(url='foo', other='foo/bar', expected='..'),
dict(url='foo', other='foo/bar.txt', expected='.'),
dict(url='foo/../../bar', other='.', expected='bar'),
dict(url='foo/../../bar', other='foo', expected='../bar'),
dict(url='foo//./bar/baz', other='foo/bar/baz', expected='.'),
dict(url='a/b/.././../c', other='.', expected='c'),
dict(url='a/b/c/d/ee', other='a/b/c/d/e', expected='../ee'),
dict(url='a/b/c/d/ee', other='a/b/z/d/e', expected='../../../c/d/ee'),
dict(url='foo', other='bar.', expected='foo'),
dict(url='foo', other='bar./', expected='../foo'),
dict(url='foo', other='foo/bar./', expected='..'),
dict(url='foo', other='foo/bar./.', expected='..'),
dict(url='foo', other='foo/bar././', expected='..'),
dict(url='foo/', other='foo/bar././', expected='../'),
dict(url='foo', other='foo', expected='.'),
dict(url='.foo', other='.foo', expected='.foo'),
dict(url='.foo/', other='.foo', expected='.foo/'),
dict(url='.foo', other='.foo/', expected='.'),
dict(url='.foo/', other='.foo/', expected='./'),
dict(url='///', other='', expected='./'),
dict(url='a///', other='', expected='a/'),
dict(url='a///', other='a', expected='./'),
dict(url='.', other='here', expected='..'),
dict(url='..', other='here', expected='..'),
dict(url='../..', other='here', expected='..'),
dict(url='../../a', other='here', expected='../a'),
dict(url='..', other='here.txt', expected='.'),
dict(url='a', other='', expected='a'),
dict(url='a', other='..', expected='a'),
dict(url='a', other='b', expected='../a'),
# The dots are considered a file. Documenting a long-standing bug:
dict(url='a', other='b/..', expected='../a'),
dict(url='a', other='b/../..', expected='a'),
dict(url='a/..../b', other='a/../b', expected='../a/..../b'),
dict(url='a/я/b', other='a/я/c', expected='../b'),
dict(url='a/я/b', other='a/яя/c', expected='../../я/b'),
]:
url, other, expected = case['url'], case['other'], case['expected']
with self.subTest(url=url, other=other):
# Leading slash intentionally ignored
self.assertEqual(utils.get_relative_url(url, other), expected)
self.assertEqual(utils.get_relative_url('/' + url, other), expected)
self.assertEqual(utils.get_relative_url(url, '/' + other), expected)
self.assertEqual(utils.get_relative_url('/' + url, '/' + other), expected)
def test_get_relative_url_empty(self):
for url in ['', '.', '/.']:
for other in ['', '.', '/', '/.']:
with self.subTest(url=url, other=other):
self.assertEqual(utils.get_relative_url(url, other), '.')
self.assertEqual(utils.get_relative_url('/', ''), './')
self.assertEqual(utils.get_relative_url('/', '/'), './')
self.assertEqual(utils.get_relative_url('/', '.'), './')
self.assertEqual(utils.get_relative_url('/', '/.'), './')
def test_create_media_urls(self):
expected_results = {
'https://media.cdn.org/jq.js': [
'https://media.cdn.org/jq.js',
'https://media.cdn.org/jq.js',
'https://media.cdn.org/jq.js',
],
'http://media.cdn.org/jquery.js': [
'http://media.cdn.org/jquery.js',
'http://media.cdn.org/jquery.js',
'http://media.cdn.org/jquery.js',
],
'//media.cdn.org/jquery.js': [
'//media.cdn.org/jquery.js',
'//media.cdn.org/jquery.js',
'//media.cdn.org/jquery.js',
],
'media.cdn.org/jquery.js': [
'media.cdn.org/jquery.js',
'media.cdn.org/jquery.js',
'../media.cdn.org/jquery.js',
],
'local/file/jquery.js': [
'local/file/jquery.js',
'local/file/jquery.js',
'../local/file/jquery.js',
],
'image.png': [
'image.png',
'image.png',
'../image.png',
],
'style.css?v=20180308c': [
'style.css?v=20180308c',
'style.css?v=20180308c',
'../style.css?v=20180308c',
],
'#some_id': [
'#some_id',
'#some_id',
'#some_id',
],
}
cfg = load_config(use_directory_urls=False)
pages = [
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,
),
Page(
'FooBar',
File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
cfg,
),
]
for i, page in enumerate(pages):
urls = utils.create_media_urls(expected_results.keys(), page)
self.assertEqual([v[i] for v in expected_results.values()], urls)
def test_create_media_urls_use_directory_urls(self):
expected_results = {
'https://media.cdn.org/jq.js': [
'https://media.cdn.org/jq.js',
'https://media.cdn.org/jq.js',
'https://media.cdn.org/jq.js',
],
'http://media.cdn.org/jquery.js': [
'http://media.cdn.org/jquery.js',
'http://media.cdn.org/jquery.js',
'http://media.cdn.org/jquery.js',
],
'//media.cdn.org/jquery.js': [
'//media.cdn.org/jquery.js',
'//media.cdn.org/jquery.js',
'//media.cdn.org/jquery.js',
],
'media.cdn.org/jquery.js': [
'media.cdn.org/jquery.js',
'../media.cdn.org/jquery.js',
'../../media.cdn.org/jquery.js',
],
'local/file/jquery.js': [
'local/file/jquery.js',
'../local/file/jquery.js',
'../../local/file/jquery.js',
],
'image.png': [
'image.png',
'../image.png',
'../../image.png',
],
'style.css?v=20180308c': [
'style.css?v=20180308c',
'../style.css?v=20180308c',
'../../style.css?v=20180308c',
],
'#some_id': [
'#some_id',
'#some_id',
'#some_id',
],
}
cfg = load_config(use_directory_urls=True)
pages = [
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,
),
Page(
'FooBar',
File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
cfg,
),
]
for i, page in enumerate(pages):
urls = utils.create_media_urls(expected_results.keys(), page)
self.assertEqual([v[i] for v in expected_results.values()], urls)
# TODO: This shouldn't pass on Linux
# @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_create_media_urls_windows(self):
expected_results = {
'local\\windows\\file\\jquery.js': [
'local/windows/file/jquery.js',
'local/windows/file/jquery.js',
'../local/windows/file/jquery.js',
],
}
cfg = load_config(use_directory_urls=False)
pages = [
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,
),
Page(
'FooBar',
File('foo/bar.md', cfg['docs_dir'], cfg['site_dir'], cfg['use_directory_urls']),
cfg,
),
]
with self.assertLogs('mkdocs', level='WARNING'):
for i, page in enumerate(pages):
urls = utils.create_media_urls(expected_results.keys(), page)
self.assertEqual([v[i] for v in expected_results.values()], urls)
def test_reduce_list(self):
self.assertEqual(
utils.reduce_list([1, 2, 3, 4, 5, 5, 2, 4, 6, 7, 8]),
[1, 2, 3, 4, 5, 6, 7, 8],
)
def test_insort(self):
a = [1, 2, 3]
utils.insort(a, 5)
self.assertEqual(a, [1, 2, 3, 5])
utils.insort(a, -1)
self.assertEqual(a, [-1, 1, 2, 3, 5])
utils.insort(a, 2)
self.assertEqual(a, [-1, 1, 2, 2, 3, 5])
utils.insort(a, 4)
self.assertEqual(a, [-1, 1, 2, 2, 3, 4, 5])
def test_insort_key(self):
a = [(1, 'a'), (1, 'b'), (2, 'c')]
utils.insort(a, (1, 'a'), key=lambda v: v[0])
self.assertEqual(a, [(1, 'a'), (1, 'b'), (1, 'a'), (2, 'c')])
def test_get_themes(self):
themes = utils.get_theme_names()
self.assertIn('mkdocs', themes)
self.assertIn('readthedocs', themes)
@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir(self, mock_iter):
path = 'some/path'
theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load().__file__ = os.path.join(path, '__init__.py')
mock_iter.return_value = [theme]
self.assertEqual(utils.get_theme_dir(theme.name), os.path.abspath(path))
def test_get_theme_dir_keyerror(self):
with self.assertRaises(KeyError):
utils.get_theme_dir('nonexistanttheme')
@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_theme_dir_importerror(self, mock_iter):
theme = mock.Mock()
theme.name = 'mkdocs2'
theme.dist.name = 'mkdocs2'
theme.load.side_effect = ImportError()
mock_iter.return_value = [theme]
with self.assertRaises(ImportError):
utils.get_theme_dir(theme.name)
@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_warning(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs2'
theme1.dist.name = 'mkdocs2'
theme1.load().__file__ = "some/path1"
theme2 = mock.Mock()
theme2.name = 'mkdocs2'
theme2.dist.name = 'mkdocs3'
theme2.load().__file__ = "some/path2"
mock_iter.return_value = [theme1, theme2]
with self.assertLogs('mkdocs') as cm:
theme_names = utils.get_theme_names()
self.assertEqual(
'\n'.join(cm.output),
"WARNING:mkdocs.utils:A theme named 'mkdocs2' is provided by the Python "
"packages 'mkdocs3' and 'mkdocs2'. The one in 'mkdocs3' will be used.",
)
self.assertCountEqual(theme_names, ['mkdocs2'])
@mock.patch('mkdocs.utils.entry_points', autospec=True)
def test_get_themes_error(self, mock_iter):
theme1 = mock.Mock()
theme1.name = 'mkdocs'
theme1.dist.name = 'mkdocs'
theme1.load().__file__ = "some/path1"
theme2 = mock.Mock()
theme2.name = 'mkdocs'
theme2.dist.name = 'mkdocs2'
theme2.load().__file__ = "some/path2"
mock_iter.return_value = [theme1, theme2]
with self.assertRaisesRegex(
exceptions.ConfigurationError,
"The theme 'mkdocs' is a builtin theme but the package 'mkdocs2' "
"attempts to provide a theme with the same name.",
):
utils.get_theme_names()
def test_nest_paths(self, j=posixpath.join):
result = utils.nest_paths(
[
'index.md',
j('user-guide', 'configuration.md'),
j('user-guide', 'styling-your-docs.md'),
j('user-guide', 'writing-your-docs.md'),
j('about', 'contributing.md'),
j('about', 'license.md'),
j('about', 'release-notes.md'),
]
)
self.assertEqual(
result,
[
'index.md',
{
'User guide': [
j('user-guide', 'configuration.md'),
j('user-guide', 'styling-your-docs.md'),
j('user-guide', 'writing-your-docs.md'),
]
},
{
'About': [
j('about', 'contributing.md'),
j('about', 'license.md'),
j('about', 'release-notes.md'),
]
},
],
)
def test_nest_paths_native(self):
self.test_nest_paths(os.path.join)
def test_unicode_yaml(self):
yaml_src = dedent(
'''
key: value
key2:
- value
'''
).encode('utf-8')
config = utils.yaml_load(yaml_src)
self.assertTrue(isinstance(config['key'], str))
self.assertTrue(isinstance(config['key2'][0], str))
@mock.patch.dict(os.environ, {'VARNAME': 'Hello, World!', 'BOOLVAR': 'false'})
def test_env_var_in_yaml(self):
yaml_src = dedent(
'''
key1: !ENV VARNAME
key2: !ENV UNDEFINED
key3: !ENV [UNDEFINED, default]
key4: !ENV [UNDEFINED, VARNAME, default]
key5: !ENV BOOLVAR
'''
)
config = utils.yaml_load(yaml_src)
self.assertEqual(config['key1'], 'Hello, World!')
self.assertIsNone(config['key2'])
self.assertEqual(config['key3'], 'default')
self.assertEqual(config['key4'], 'Hello, World!')
self.assertIs(config['key5'], False)
@tempdir(files={'base.yml': BASEYML, 'parent.yml': PARENTYML})
def test_yaml_inheritance(self, tdir):
expected = {
'foo': 'bar',
'baz': {
'sub1': 'replaced',
'sub2': 2,
'sub3': 'new',
},
'deep1': {
'deep2-1': {
'deep3-1': 'replaced',
'deep3-2': 'bar',
},
'deep2-2': 'baz',
},
}
with open(os.path.join(tdir, 'base.yml')) as fd:
result = utils.yaml_load(fd)
self.assertEqual(result, expected)
@tempdir(files={'base.yml': BASEYML})
def test_yaml_inheritance_missing_parent(self, tdir):
with open(os.path.join(tdir, 'base.yml')) as fd:
with self.assertRaises(exceptions.ConfigurationError):
utils.yaml_load(fd)
@tempdir()
@tempdir()
def test_copy_files(self, src_dir, dst_dir):
cases = [
dict(
src_path='foo.txt',
dst_path='foo.txt',
expected='foo.txt',
),
dict(
src_path='bar.txt',
dst_path='foo/', # ensure src filename is appended
expected='foo/bar.txt',
),
dict(
src_path='baz.txt',
dst_path='foo/bar/baz.txt', # ensure missing dirs are created
expected='foo/bar/baz.txt',
),
]
for case in cases:
src, dst, expected = case['src_path'], case['dst_path'], case['expected']
with self.subTest(src):
src = os.path.join(src_dir, src)
with open(src, 'w') as f:
f.write('content')
dst = os.path.join(dst_dir, dst)
utils.copy_file(src, dst)
self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected)))
@tempdir()
@tempdir()
def test_copy_files_without_permissions(self, src_dir, dst_dir):
cases = [
dict(src_path='foo.txt', expected='foo.txt'),
dict(src_path='bar.txt', expected='bar.txt'),
dict(src_path='baz.txt', expected='baz.txt'),
]
try:
for case in cases:
src, expected = case['src_path'], case['expected']
with self.subTest(src):
src = os.path.join(src_dir, src)
with open(src, 'w') as f:
f.write('content')
# Set src file to read-only
os.chmod(src, stat.S_IRUSR)
utils.copy_file(src, dst_dir)
self.assertTrue(os.path.isfile(os.path.join(dst_dir, expected)))
self.assertNotEqual(
os.stat(src).st_mode, os.stat(os.path.join(dst_dir, expected)).st_mode
)
# While src was read-only, dst must remain writable
self.assertTrue(os.access(os.path.join(dst_dir, expected), os.W_OK))
finally:
for case in cases:
# Undo read-only so we can delete temp files
src = os.path.join(src_dir, case['src_path'])
if os.path.exists(src):
os.chmod(src, stat.S_IRUSR | stat.S_IWUSR)
def test_mm_meta_data(self):
doc = dedent(
"""
Title: Foo Bar
Date: 2018-07-10
Summary: Line one
Line two
Tags: foo
Tags: bar
Doc body
"""
)
self.assertEqual(
meta.get_data(doc),
(
"Doc body",
{
'title': 'Foo Bar',
'date': '2018-07-10',
'summary': 'Line one Line two',
'tags': 'foo bar',
},
),
)
def test_mm_meta_data_blank_first_line(self):
doc = '\nfoo: bar\nDoc body'
self.assertEqual(meta.get_data(doc), (doc.lstrip(), {}))
def test_yaml_meta_data(self):
doc = dedent(
"""
---
Title: Foo Bar
Date: 2018-07-10
Summary: Line one
Line two
Tags:
- foo
- bar
---
Doc body
"""
)
self.assertEqual(
meta.get_data(doc),
(
"Doc body",
{
'Title': 'Foo Bar',
'Date': datetime.date(2018, 7, 10),
'Summary': 'Line one Line two',
'Tags': ['foo', 'bar'],
},
),
)
def test_yaml_meta_data_not_dict(self):
doc = dedent(
"""
---
- List item
---
Doc body
"""
)
self.assertEqual(meta.get_data(doc), (doc, {}))
def test_yaml_meta_data_invalid(self):
doc = dedent(
"""
---
foo: bar: baz
---
Doc body
"""
)
self.assertEqual(meta.get_data(doc), (doc, {}))
def test_no_meta_data(self):
doc = dedent(
"""
Doc body
"""
)
self.assertEqual(meta.get_data(doc), (doc, {}))
class LogCounterTests(unittest.TestCase):
def setUp(self):
self.log = logging.getLogger('dummy')
self.log.propagate = False
self.log.setLevel(1)
self.counter = utils.CountHandler()
self.log.addHandler(self.counter)
def tearDown(self):
self.log.removeHandler(self.counter)
def test_default_values(self):
self.assertEqual(self.counter.get_counts(), [])
def test_count_critical(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.critical('msg')
self.assertEqual(self.counter.get_counts(), [('CRITICAL', 1)])
def test_count_error(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.error('msg')
self.assertEqual(self.counter.get_counts(), [('ERROR', 1)])
def test_count_warning(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.warning('msg')
self.assertEqual(self.counter.get_counts(), [('WARNING', 1)])
def test_count_info(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.info('msg')
self.assertEqual(self.counter.get_counts(), [('INFO', 1)])
def test_count_debug(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.debug('msg')
self.assertEqual(self.counter.get_counts(), [('DEBUG', 1)])
def test_count_multiple(self):
self.assertEqual(self.counter.get_counts(), [])
self.log.warning('msg 1')
self.assertEqual(self.counter.get_counts(), [('WARNING', 1)])
self.log.warning('msg 2')
self.assertEqual(self.counter.get_counts(), [('WARNING', 2)])
self.log.debug('msg 3')
self.assertEqual(self.counter.get_counts(), [('WARNING', 2), ('DEBUG', 1)])
self.log.error('mdg 4')
self.assertEqual(self.counter.get_counts(), [('ERROR', 1), ('WARNING', 2), ('DEBUG', 1)])
def test_log_level(self):
self.assertEqual(self.counter.get_counts(), [])
self.counter.setLevel(logging.ERROR)
self.log.error('counted')
self.log.warning('not counted')
self.log.info('not counted')
self.assertEqual(self.counter.get_counts(), [('ERROR', 1)])
self.counter.setLevel(logging.WARNING)
self.log.error('counted')
self.log.warning('counted')
self.log.info('not counted')
self.assertEqual(self.counter.get_counts(), [('ERROR', 2), ('WARNING', 1)])