added podman, json and yaml
This commit is contained in:
13
venv/lib/python3.11/site-packages/mkdocs/tests/__init__.py
Executable file
13
venv/lib/python3.11/site-packages/mkdocs/tests/__init__.py
Executable 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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
140
venv/lib/python3.11/site-packages/mkdocs/tests/base.py
Normal file
140
venv/lib/python3.11/site-packages/mkdocs/tests/base.py
Normal 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)
|
||||
571
venv/lib/python3.11/site-packages/mkdocs/tests/build_tests.py
Normal file
571
venv/lib/python3.11/site-packages/mkdocs/tests/build_tests.py
Normal 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))
|
||||
626
venv/lib/python3.11/site-packages/mkdocs/tests/cli_tests.py
Normal file
626
venv/lib/python3.11/site-packages/mkdocs/tests/cli_tests.py
Normal 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',
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
File diff suppressed because it is too large
Load Diff
@ -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, [])
|
||||
@ -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.',
|
||||
)
|
||||
@ -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()
|
||||
@ -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))
|
||||
@ -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)
|
||||
24
venv/lib/python3.11/site-packages/mkdocs/tests/new_tests.py
Normal file
24
venv/lib/python3.11/site-packages/mkdocs/tests/new_tests.py
Normal 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))
|
||||
299
venv/lib/python3.11/site-packages/mkdocs/tests/plugin_tests.py
Normal file
299
venv/lib/python3.11/site-packages/mkdocs/tests/plugin_tests.py
Normal 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')
|
||||
645
venv/lib/python3.11/site-packages/mkdocs/tests/search_tests.py
Normal file
645
venv/lib/python3.11/site-packages/mkdocs/tests/search_tests.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)
|
||||
@ -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)
|
||||
@ -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=''))
|
||||
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=''))
|
||||
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=''))
|
||||
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="mailto:mail@e'
|
||||
'xample.com">mail@'
|
||||
'example.com</a></p>',
|
||||
)
|
||||
@ -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 & 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_charref(self):
|
||||
md = '# @Header'
|
||||
expected = '@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))
|
||||
108
venv/lib/python3.11/site-packages/mkdocs/tests/theme_tests.py
Normal file
108
venv/lib/python3.11/site-packages/mkdocs/tests/theme_tests.py
Normal 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'})
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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', '_')
|
||||
@ -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)])
|
||||
Reference in New Issue
Block a user