mirror of
https://github.com/mkdocs/mkdocs.git
synced 2026-03-27 09:58:31 +07:00
Expand plugin testing
This commit is contained in:
@@ -6,7 +6,6 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
@@ -14,6 +13,7 @@ from typing import (
|
||||
Dict,
|
||||
Generic,
|
||||
List,
|
||||
MutableMapping,
|
||||
Optional,
|
||||
Tuple,
|
||||
Type,
|
||||
@@ -461,7 +461,7 @@ def event_priority(priority: float) -> Callable[[T], T]:
|
||||
return decorator
|
||||
|
||||
|
||||
class PluginCollection(OrderedDict):
|
||||
class PluginCollection(dict, MutableMapping[str, BasePlugin]):
|
||||
"""
|
||||
A collection of plugins.
|
||||
|
||||
@@ -480,8 +480,11 @@ class PluginCollection(OrderedDict):
|
||||
self.events[event_name], method, key=lambda m: -getattr(m, 'mkdocs_priority', 0)
|
||||
)
|
||||
|
||||
def __setitem__(self, key: str, value: BasePlugin, **kwargs) -> None:
|
||||
super().__setitem__(key, value, **kwargs)
|
||||
def __getitem__(self, key: str) -> BasePlugin:
|
||||
return super().__getitem__(key)
|
||||
|
||||
def __setitem__(self, key: str, value: BasePlugin) -> None:
|
||||
super().__setitem__(key, value)
|
||||
# Register all of the event methods defined for this Plugin.
|
||||
for event_name in (x for x in dir(value) if x.startswith('on_')):
|
||||
method = getattr(value, event_name, None)
|
||||
|
||||
@@ -20,6 +20,7 @@ else:
|
||||
import mkdocs
|
||||
from mkdocs.config import config_options as c
|
||||
from mkdocs.config.base import Config
|
||||
from mkdocs.plugins import BasePlugin, PluginCollection
|
||||
from mkdocs.tests.base import tempdir
|
||||
from mkdocs.theme import Theme
|
||||
from mkdocs.utils import write_file, yaml_load
|
||||
@@ -1619,6 +1620,205 @@ class MarkdownExtensionsTest(TestCase):
|
||||
self.assertIsNone(conf.mdx_configs.get('toc'))
|
||||
|
||||
|
||||
class _FakePluginConfig(Config):
|
||||
foo = c.Type(str, default='default foo')
|
||||
bar = c.Type(int, default=0)
|
||||
dir = c.Optional(c.Dir(exists=False))
|
||||
|
||||
|
||||
class FakePlugin(BasePlugin[_FakePluginConfig]):
|
||||
pass
|
||||
|
||||
|
||||
class FakeEntryPoint:
|
||||
@property
|
||||
def name(self):
|
||||
return 'sample'
|
||||
|
||||
def load(self):
|
||||
return FakePlugin
|
||||
|
||||
|
||||
@patch('mkdocs.plugins.entry_points', return_value=[FakeEntryPoint()])
|
||||
class PluginsTest(TestCase):
|
||||
def test_plugin_config_without_options(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': ['sample'],
|
||||
}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
assert_type(conf.plugins, PluginCollection)
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertIn('sample', conf.plugins)
|
||||
|
||||
plugin = conf.plugins['sample']
|
||||
assert_type(plugin, BasePlugin)
|
||||
self.assertIsInstance(plugin, FakePlugin)
|
||||
self.assertIsInstance(plugin.config, _FakePluginConfig)
|
||||
expected = {
|
||||
'foo': 'default foo',
|
||||
'bar': 0,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(plugin.config, expected)
|
||||
|
||||
def test_plugin_config_with_options(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': [
|
||||
{
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertIn('sample', conf.plugins)
|
||||
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
|
||||
expected = {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(conf.plugins['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_as_dict(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': {
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
},
|
||||
}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertIn('sample', conf.plugins)
|
||||
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
|
||||
expected = {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(conf.plugins['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_empty_list_with_empty_default(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins(default=[])
|
||||
|
||||
cfg: Dict[str, Any] = {'plugins': []}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertEqual(len(conf.plugins), 0)
|
||||
|
||||
def test_plugin_config_empty_list_with_default(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins(default=['sample'])
|
||||
|
||||
# Default is ignored
|
||||
cfg: Dict[str, Any] = {'plugins': []}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertEqual(len(conf.plugins), 0)
|
||||
|
||||
def test_plugin_config_none_with_empty_default(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins(default=[])
|
||||
|
||||
cfg = {'plugins': None}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertEqual(len(conf.plugins), 0)
|
||||
|
||||
def test_plugin_config_none_with_default(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins(default=['sample'])
|
||||
|
||||
# Default is used.
|
||||
cfg = {'plugins': None}
|
||||
conf = self.get_config(Schema, cfg)
|
||||
|
||||
self.assertIsInstance(conf.plugins, PluginCollection)
|
||||
self.assertIn('sample', conf.plugins)
|
||||
self.assertIsInstance(conf.plugins['sample'], BasePlugin)
|
||||
expected = {
|
||||
'foo': 'default foo',
|
||||
'bar': 0,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(conf.plugins['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_uninstalled(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {'plugins': ['uninstalled']}
|
||||
with self.expect_error(plugins='The "uninstalled" plugin is not installed'):
|
||||
self.get_config(Schema, cfg)
|
||||
|
||||
def test_plugin_config_not_list(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {'plugins': 'sample'}
|
||||
with self.expect_error(plugins="Invalid Plugins configuration. Expected a list or dict."):
|
||||
self.get_config(Schema, cfg)
|
||||
|
||||
def test_plugin_config_multivalue_dict(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': [
|
||||
{
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
'extra_key': 'baz',
|
||||
}
|
||||
],
|
||||
}
|
||||
with self.expect_error(plugins="Invalid Plugins configuration"):
|
||||
self.get_config(Schema, cfg)
|
||||
|
||||
def test_plugin_config_not_string_or_dict(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': [('not a string or dict',)],
|
||||
}
|
||||
with self.expect_error(plugins="'('not a string or dict',)' is not a valid plugin name."):
|
||||
self.get_config(Schema, cfg)
|
||||
|
||||
def test_plugin_config_options_not_dict(self, mock_class) -> None:
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins()
|
||||
|
||||
cfg = {
|
||||
'plugins': [{'sample': 'not a dict'}],
|
||||
}
|
||||
with self.expect_error(plugins="Invalid config options for the 'sample' plugin."):
|
||||
self.get_config(Schema, cfg)
|
||||
|
||||
|
||||
class TestHooks(TestCase):
|
||||
class Schema(Config):
|
||||
plugins = c.Plugins(default=[])
|
||||
@@ -1646,7 +1846,7 @@ class TestHooks(TestCase):
|
||||
{**conf.plugins.events, 'page_markdown': [hook.on_page_markdown]},
|
||||
conf.plugins.events,
|
||||
)
|
||||
self.assertEqual(hook.on_page_markdown('foo foo'), 'zoo zoo')
|
||||
self.assertEqual(hook.on_page_markdown('foo foo'), 'zoo zoo') # type: ignore[call-arg]
|
||||
self.assertFalse(hasattr(hook, 'on_nav'))
|
||||
|
||||
|
||||
|
||||
@@ -3,9 +3,17 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from mkdocs import config, plugins
|
||||
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
|
||||
@@ -39,7 +47,7 @@ class DummyPlugin(plugins.BasePlugin[_DummyPluginConfig]):
|
||||
|
||||
|
||||
class TestPluginClass(unittest.TestCase):
|
||||
def test_valid_plugin_options(self):
|
||||
def test_valid_plugin_options(self) -> None:
|
||||
test_dir = 'test'
|
||||
|
||||
options = {
|
||||
@@ -51,20 +59,24 @@ class TestPluginClass(unittest.TestCase):
|
||||
cfg_fname = os.path.abspath(cfg_fname)
|
||||
|
||||
cfg_dirname = os.path.dirname(cfg_fname)
|
||||
expected = os.path.join(cfg_dirname, test_dir)
|
||||
|
||||
expected = {
|
||||
'foo': 'some value',
|
||||
'bar': 0,
|
||||
'dir': expected,
|
||||
'dir': os.path.join(cfg_dirname, test_dir),
|
||||
}
|
||||
|
||||
plugin = DummyPlugin()
|
||||
errors, warnings = plugin.load_config(options, config_file_path=cfg_fname)
|
||||
self.assertEqual(plugin.config, expected)
|
||||
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})
|
||||
@@ -285,160 +297,3 @@ class TestPluginCollection(unittest.TestCase):
|
||||
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')
|
||||
|
||||
|
||||
MockEntryPoint = mock.Mock()
|
||||
MockEntryPoint.configure_mock(**{'name': 'sample', 'load.return_value': DummyPlugin})
|
||||
|
||||
|
||||
@mock.patch('mkdocs.plugins.entry_points', return_value=[MockEntryPoint])
|
||||
class TestPluginConfig(unittest.TestCase):
|
||||
def test_plugin_config_without_options(self, mock_class):
|
||||
cfg = {'plugins': ['sample']}
|
||||
option = c.Plugins()
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertIn('sample', cfg['plugins'])
|
||||
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
|
||||
expected = {
|
||||
'foo': 'default foo',
|
||||
'bar': 0,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(cfg['plugins']['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_with_options(self, mock_class):
|
||||
cfg = {
|
||||
'plugins': [
|
||||
{
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
option = c.Plugins()
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertIn('sample', cfg['plugins'])
|
||||
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
|
||||
expected = {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(cfg['plugins']['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_as_dict(self, mock_class):
|
||||
cfg = {
|
||||
'plugins': {
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
},
|
||||
}
|
||||
option = c.Plugins()
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertIn('sample', cfg['plugins'])
|
||||
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
|
||||
expected = {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(cfg['plugins']['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_empty_list_with_empty_default(self, mock_class):
|
||||
cfg = {'plugins': []}
|
||||
option = c.Plugins(default=[])
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertEqual(len(cfg['plugins']), 0)
|
||||
|
||||
def test_plugin_config_empty_list_with_default(self, mock_class):
|
||||
# Default is ignored
|
||||
cfg = {'plugins': []}
|
||||
option = c.Plugins(default=['sample'])
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertEqual(len(cfg['plugins']), 0)
|
||||
|
||||
def test_plugin_config_none_with_empty_default(self, mock_class):
|
||||
cfg = {'plugins': None}
|
||||
option = c.Plugins(default=[])
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertEqual(len(cfg['plugins']), 0)
|
||||
|
||||
def test_plugin_config_none_with_default(self, mock_class):
|
||||
# Default is used.
|
||||
cfg = {'plugins': None}
|
||||
option = c.Plugins(default=['sample'])
|
||||
cfg['plugins'] = option.validate(cfg['plugins'])
|
||||
|
||||
self.assertIsInstance(cfg['plugins'], plugins.PluginCollection)
|
||||
self.assertIn('sample', cfg['plugins'])
|
||||
self.assertIsInstance(cfg['plugins']['sample'], plugins.BasePlugin)
|
||||
expected = {
|
||||
'foo': 'default foo',
|
||||
'bar': 0,
|
||||
'dir': None,
|
||||
}
|
||||
self.assertEqual(cfg['plugins']['sample'].config, expected)
|
||||
|
||||
def test_plugin_config_uninstalled(self, mock_class):
|
||||
cfg = {'plugins': ['uninstalled']}
|
||||
option = c.Plugins()
|
||||
with self.assertRaises(config.base.ValidationError):
|
||||
option.validate(cfg['plugins'])
|
||||
|
||||
def test_plugin_config_not_list(self, mock_class):
|
||||
cfg = {'plugins': 'sample'} # should be a list
|
||||
option = c.Plugins()
|
||||
with self.assertRaises(config.base.ValidationError):
|
||||
option.validate(cfg['plugins'])
|
||||
|
||||
def test_plugin_config_multivalue_dict(self, mock_class):
|
||||
cfg = {
|
||||
'plugins': [
|
||||
{
|
||||
'sample': {
|
||||
'foo': 'foo value',
|
||||
'bar': 42,
|
||||
},
|
||||
'extra_key': 'baz',
|
||||
}
|
||||
],
|
||||
}
|
||||
option = c.Plugins()
|
||||
with self.assertRaises(config.base.ValidationError):
|
||||
option.validate(cfg['plugins'])
|
||||
|
||||
def test_plugin_config_not_string_or_dict(self, mock_class):
|
||||
cfg = {
|
||||
'plugins': [('not a string or dict',)],
|
||||
}
|
||||
option = c.Plugins()
|
||||
with self.assertRaises(config.base.ValidationError):
|
||||
option.validate(cfg['plugins'])
|
||||
|
||||
def test_plugin_config_options_not_dict(self, mock_class):
|
||||
cfg = {
|
||||
'plugins': [
|
||||
{
|
||||
'sample': 'not a dict',
|
||||
}
|
||||
],
|
||||
}
|
||||
option = c.Plugins()
|
||||
with self.assertRaises(config.base.ValidationError):
|
||||
option.validate(cfg['plugins'])
|
||||
|
||||
Reference in New Issue
Block a user