"""
Test utilities for :py:mod:`betty.plugin`.
"""
from collections.abc import Sequence
from typing import Generic, TypeVar, Any
from typing_extensions import override
from betty.locale.localizable import Localizable, plain
from betty.locale.localizer import DEFAULT_LOCALIZER
from betty.machine_name import assert_machine_name, MachineName
from betty.plugin import Plugin
from betty.string import camel_case_to_kebab_case
_PluginT = TypeVar("_PluginT", bound=Plugin)
[docs]
def assert_plugin_identifier(value: Any, plugin_type: type[_PluginT]) -> None:
"""
Assert that something is a plugin identifier.
"""
if isinstance(value, str):
assert_machine_name()(value)
else:
assert issubclass(value, plugin_type)
[docs]
class PluginTestBase(Generic[_PluginT]):
"""
A base class for testing :py:class:`betty.plugin.Plugin` implementations.
"""
[docs]
def get_sut_class(self) -> type[_PluginT]:
"""
Produce the class of the plugin under test.
"""
raise NotImplementedError
[docs]
async def test_class_is_public(self) -> None:
"""
Tests that the plugin class is public.
"""
assert not self.get_sut_class().__name__.startswith("_"), (
f"Failed asserting that plugin class {self.get_sut_class()} is public (its name must not start with an underscore)"
)
[docs]
async def test_plugin_description(self) -> None:
"""
Tests :py:meth:`betty.plugin.Plugin.plugin_description` implementations.
"""
description = self.get_sut_class().plugin_description()
if description is not None:
assert description.localize(DEFAULT_LOCALIZER)
[docs]
class PluginInstanceTestBase(Generic[_PluginT], PluginTestBase[_PluginT]):
"""
A base class for testing :py:class:`betty.plugin.Plugin` implementation instances.
"""
[docs]
def get_sut_instances(self) -> Sequence[_PluginT]:
"""
Produce instances of the plugin under test.
"""
raise NotImplementedError
[docs]
class DummyPlugin(Plugin):
"""
A dummy plugin implementation.
"""