diff --git a/.coveragerc b/.coveragerc
index bbd146fee..22b0c1382 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -3,7 +3,6 @@ source = betty
omit =
betty/_package/*
betty/tests/*
- betty/pytests/*
[report]
exclude_lines =
diff --git a/betty/_package/pyinstaller/__init__.py b/betty/_package/pyinstaller/__init__.py
index e49f4d222..018a6d877 100644
--- a/betty/_package/pyinstaller/__init__.py
+++ b/betty/_package/pyinstaller/__init__.py
@@ -26,8 +26,6 @@ def _collect_submodules() -> List[str]:
def _filter_submodule(submodule: str) -> bool:
if submodule.startswith('betty.tests'):
return False
- if submodule.startswith('betty.pytests'):
- return False
if submodule.startswith('betty._package'):
return False
return True
diff --git a/betty/anonymizer/__init__.py b/betty/anonymizer/__init__.py
index c2f42ac5d..0dbc4f40f 100644
--- a/betty/anonymizer/__init__.py
+++ b/betty/anonymizer/__init__.py
@@ -1,6 +1,5 @@
from typing import Set, Type, TYPE_CHECKING
-
if TYPE_CHECKING:
from betty.builtins import _
@@ -16,7 +15,21 @@ class AnonymousSource(Source):
_ID = 'betty-anonymous-source'
def __init__(self):
- super().__init__(self._ID, _('Private'))
+ super().__init__(self._ID)
+
+ @property # type: ignore
+ def name(self) -> str: # type: ignore
+ return _('Private')
+
+ @name.setter
+ def name(self, _) -> None:
+ # This is a no-op as the name is 'hardcoded'.
+ pass
+
+ @name.deleter
+ def name(self) -> None:
+ # This is a no-op as the name is 'hardcoded'.
+ pass
def replace(self, other: Source, ancestry: Ancestry) -> None:
if isinstance(other, AnonymousSource):
@@ -36,7 +49,20 @@ class AnonymousCitation(Citation):
def __init__(self, source: Source):
super().__init__(self._ID, source)
- self.location = _("A citation is available, but has not been published in order to protect people's privacy")
+
+ @property # type: ignore
+ def location(self) -> str: # type: ignore
+ return _("A citation is available, but has not been published in order to protect people's privacy")
+
+ @location.setter
+ def location(self, _) -> None:
+ # This is a no-op as the location is 'hardcoded'.
+ pass
+
+ @location.deleter
+ def location(self) -> None:
+ # This is a no-op as the location is 'hardcoded'.
+ pass
def replace(self, other: Citation, ancestry: Ancestry) -> None:
if isinstance(other, AnonymousCitation):
diff --git a/betty/app/extension.py b/betty/app/extension.py
index 5e0aeaf15..b597bd701 100644
--- a/betty/app/extension.py
+++ b/betty/app/extension.py
@@ -2,22 +2,18 @@
import asyncio
from collections import defaultdict
+from importlib.metadata import entry_points
from pathlib import Path
from typing import Type, Set, Optional, Any, List, Dict, Sequence, TypeVar, Union, Iterable, TYPE_CHECKING, Generic
-from betty.config import ConfigurationT, Configurable
-
from reactives.factory.type import ReactiveInstance
from betty import fs
+from betty.config import ConfigurationT, Configurable
from betty.requirement import Requirer, AllRequirements
if TYPE_CHECKING:
from betty.app import App
-try:
- from importlib.metadata import entry_points
-except ImportError:
- from importlib_metadata import entry_points
from betty.dispatch import Dispatcher, TargetedDispatcher
from betty.importlib import import_any
@@ -52,6 +48,7 @@ class Extension(Requirer):
"""
def __init__(self, app: App, *args, **kwargs):
+ assert type(self) != Extension
super().__init__(*args, **kwargs)
self._app = app
@@ -93,6 +90,7 @@ def cache_directory_path(self) -> Path:
class ConfigurableExtension(Extension, Generic[ConfigurationT], Configurable[ConfigurationT]):
def __init__(self, *args, **kwargs):
+ assert type(self) != ConfigurableExtension
if 'configuration' not in kwargs or kwargs['configuration'] is None:
kwargs['configuration'] = self.default_configuration()
super().__init__(*args, **kwargs)
@@ -176,7 +174,7 @@ async def _dispatch(*args, **kwargs) -> List[Any]:
return _dispatch
-def build_extension_type_graph(extension_types: Set[Type[Extension]]) -> Dict:
+def build_extension_type_graph(extension_types: Iterable[Type[Extension]]) -> Dict:
extension_types_graph = defaultdict(set)
# Add dependencies to the extension graph.
for extension_type in extension_types:
diff --git a/betty/cache.py b/betty/cache.py
index 1eb0f426a..683ee24a2 100644
--- a/betty/cache.py
+++ b/betty/cache.py
@@ -5,7 +5,6 @@
from betty import fs
-
if TYPE_CHECKING:
from betty.builtins import _
diff --git a/betty/cli.py b/betty/cli.py
index d98655664..ddb93bb58 100644
--- a/betty/cli.py
+++ b/betty/cli.py
@@ -11,14 +11,14 @@
from click import get_current_context, Context, Option
from betty import about, cache, demo, generate, load, serve
+from betty.app import App
+from betty.asyncio import sync
from betty.config import ConfigurationError
from betty.error import UserFacingError
-from betty.asyncio import sync
from betty.gui import BettyApplication
from betty.gui.app import WelcomeWindow
from betty.gui.project import ProjectWindow
from betty.logging import CliHandler
-from betty.app import App
class CommandProvider:
diff --git a/betty/deriver/__init__.py b/betty/deriver/__init__.py
index f752a5d6f..da00fdd88 100644
--- a/betty/deriver/__init__.py
+++ b/betty/deriver/__init__.py
@@ -1,16 +1,16 @@
from __future__ import annotations
+
import logging
from typing import List, Tuple, Set, Type, Iterable, Optional, TYPE_CHECKING, cast
from betty.app.extension import Extension
-from betty.model.ancestry import Person, Presence, Event, Subject, EventType, Ancestry
from betty.gui import GuiBuilder
-from betty.locale import DateRange, Date, Datey
from betty.load import PostLoader
+from betty.locale import DateRange, Date, Datey
+from betty.model.ancestry import Person, Presence, Event, Subject, EventType, Ancestry
from betty.model.event_type import DerivableEventType, CreatableDerivableEventType
from betty.privatizer import Privatizer
-
if TYPE_CHECKING:
from betty.builtins import _
diff --git a/betty/fs.py b/betty/fs.py
index 847a60b81..48f853434 100644
--- a/betty/fs.py
+++ b/betty/fs.py
@@ -7,14 +7,13 @@
from os.path import getmtime
from pathlib import Path
from shutil import copy2
-from typing import AsyncIterable, Optional, Tuple, AsyncContextManager, Sequence
+from typing import AsyncIterable, Optional, Tuple, AsyncContextManager, Sequence, IO
import aiofiles
from betty import _ROOT_DIRECTORY_PATH
from betty.os import PathLike
-
ROOT_DIRECTORY_PATH = _ROOT_DIRECTORY_PATH
@@ -72,7 +71,7 @@ def prepend(self, path: PathLike, fs_encoding: Optional[str] = None) -> None:
def clear(self) -> None:
self._paths.clear()
- def open(self, *file_paths: PathLike) -> AsyncContextManager[object]:
+ def open(self, *file_paths: PathLike) -> AsyncContextManager[IO]:
return self._Open(self, file_paths)
async def copy2(self, source_path: PathLike, destination_path: PathLike) -> Path:
diff --git a/betty/generate.py b/betty/generate.py
index 84a27e468..3b68753c1 100644
--- a/betty/generate.py
+++ b/betty/generate.py
@@ -13,12 +13,12 @@
from babel import Locale
from jinja2 import TemplateNotFound
+from betty.app import App
from betty.jinja2 import Environment
from betty.json import JSONEncoder
from betty.locale import bcp_47_to_rfc_1766
from betty.model.ancestry import File, Person, Place, Event, Citation, Source, Note
from betty.openapi import build_specification
-from betty.app import App
try:
from resource import getrlimit, RLIMIT_NOFILE
diff --git a/betty/gramps/__init__.py b/betty/gramps/__init__.py
index ec6309a56..8bf80f53c 100644
--- a/betty/gramps/__init__.py
+++ b/betty/gramps/__init__.py
@@ -1,14 +1,14 @@
-from typing import Optional, TYPE_CHECKING
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
from betty.app.extension import ConfigurableExtension
if TYPE_CHECKING:
from betty.builtins import _
-
-from PyQt6.QtWidgets import QWidget
+ from betty.gramps.gui import _GrampsGuiWidget
from betty.gramps.config import GrampsConfiguration
-from betty.gramps.gui import _GrampsGuiWidget
from betty.gramps.loader import load_file
from betty.gui import GuiBuilder
from betty.load import Loader
@@ -31,5 +31,7 @@ def label(cls) -> str:
def gui_description(cls) -> str:
return _('Load Gramps family trees.')
- def gui_build(self) -> Optional[QWidget]:
- return _GrampsGuiWidget(self._app, self.configuration)
+ def gui_build(self) -> _GrampsGuiWidget:
+ from betty.gramps.gui import _GrampsGuiWidget
+
+ return _GrampsGuiWidget(self._app)
diff --git a/betty/gramps/gui.py b/betty/gramps/gui.py
index fdda1167f..8b4c12145 100644
--- a/betty/gramps/gui.py
+++ b/betty/gramps/gui.py
@@ -1,4 +1,5 @@
from __future__ import annotations
+
from typing import List
from PyQt6.QtCore import Qt
@@ -8,25 +9,28 @@
from betty.app import App
from betty.config import Path, ConfigurationError
-from betty.gramps.config import FamilyTreeConfiguration, GrampsConfiguration
+from betty.gramps import Gramps
+from betty.gramps.config import FamilyTreeConfiguration
from betty.gui import BettyWindow, mark_valid, mark_invalid
from betty.gui.error import catch_exceptions
from betty.gui.locale import LocalizedWidget
from betty.gui.text import Text
-@reactive
-class _GrampsGuiWidget(LocalizedWidget):
- def __init__(self, app: App, configuration: GrampsConfiguration, *args, **kwargs):
+class _FamilyTrees(LocalizedWidget):
+ def __init__(self, app: App, *args, **kwargs):
super().__init__(*args, **kwargs)
self._app = app
- self._configuration = configuration
+
self._layout = QVBoxLayout()
self.setLayout(self._layout)
- self._family_trees_widget = None
+ self._family_trees_widget: QWidget = None # type: ignore
+ self._family_trees_layout: QGridLayout = None # type: ignore
+ self._family_trees_remove_buttons: List[QPushButton] = None # type: ignore
self._build_family_trees()
+
self._add_family_tree_button = QPushButton()
self._add_family_tree_button.released.connect(self._add_family_tree)
self._layout.addWidget(self._add_family_tree_button, 1)
@@ -37,36 +41,52 @@ def _build_family_trees(self) -> None:
self._layout.removeWidget(self._family_trees_widget)
self._family_trees_widget.setParent(None)
del self._family_trees_widget
+ del self._family_trees_layout
+ del self._family_trees_remove_buttons
+
self._family_trees_widget = QWidget()
- family_trees_layout = QGridLayout()
- self._family_trees_widget.setLayout(family_trees_layout)
- self._family_trees_widget._remove_buttons = []
- for i, family_tree in enumerate(self._configuration.family_trees):
+ self._family_trees_layout = QGridLayout()
+ self._family_trees_remove_buttons = []
+ self._family_trees_widget.setLayout(self._family_trees_layout)
+ self._layout.addWidget(self._family_trees_widget)
+
+ for i, family_tree in enumerate(self._app.extensions[Gramps].configuration.family_trees):
def _remove_family_tree() -> None:
- del self._configuration.family_trees[i]
- family_trees_layout.addWidget(Text(str(family_tree.file_path)), i, 0)
- self._family_trees_widget._remove_buttons.insert(i, QPushButton())
- self._family_trees_widget._remove_buttons[i].released.connect(_remove_family_tree)
- family_trees_layout.addWidget(self._family_trees_widget._remove_buttons[i], i, 1)
+ del self._app.extensions[Gramps].configuration.family_trees[i]
+ self._family_trees_layout.addWidget(Text(str(family_tree.file_path)), i, 0)
+ self._family_trees_remove_buttons.insert(i, QPushButton())
+ self._family_trees_remove_buttons[i].released.connect(_remove_family_tree)
+ self._family_trees_layout.addWidget(self._family_trees_remove_buttons[i], i, 1)
self._layout.insertWidget(0, self._family_trees_widget, alignment=Qt.AlignmentFlag.AlignTop)
def _do_set_translatables(self) -> None:
self._add_family_tree_button.setText(_('Add a family tree'))
- for button in self._family_trees_widget._remove_buttons:
+ for button in self._family_trees_remove_buttons:
button.setText(_('Remove'))
def _add_family_tree(self):
- window = _AddFamilyTreeWindow(self._app, self._configuration.family_trees, self)
+ window = _AddFamilyTreeWindow(self._app, self)
window.show()
+@reactive
+class _GrampsGuiWidget(LocalizedWidget):
+ def __init__(self, app: App, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._app = app
+ self._layout = QVBoxLayout()
+ self.setLayout(self._layout)
+
+ self._family_trees = _FamilyTrees(self._app)
+ self._layout.addWidget(self._family_trees)
+
+
class _AddFamilyTreeWindow(BettyWindow):
width = 500
height = 100
- def __init__(self, app: App, family_trees: List[FamilyTreeConfiguration], *args, **kwargs):
+ def __init__(self, app: App, *args, **kwargs):
super().__init__(app, *args, **kwargs)
- self._family_trees = family_trees
self._family_tree = None
self._layout = QFormLayout()
@@ -115,7 +135,7 @@ def find_family_tree_file_path() -> None:
@catch_exceptions
def save_and_close_family_tree() -> None:
- self._family_trees.append(self._family_tree)
+ self._app.extensions[Gramps].configuration.family_trees.append(self._family_tree)
self.close()
self._widget._save_and_close = QPushButton()
self._widget._save_and_close.setDisabled(True)
diff --git a/betty/gui/app.py b/betty/gui/app.py
index 591fe6fa3..6ee820e0e 100644
--- a/betty/gui/app.py
+++ b/betty/gui/app.py
@@ -12,9 +12,9 @@
from betty.asyncio import sync
from betty.gui import BettyWindow, get_configuration_file_filter
from betty.gui.error import catch_exceptions
+from betty.gui.locale import TranslationsLocaleCollector
from betty.gui.serve import ServeDemoWindow
from betty.gui.text import Text
-from betty.gui.locale import TranslationsLocaleCollector
from betty.importlib import import_any
from betty.project import ProjectConfiguration
diff --git a/betty/gui/project.py b/betty/gui/project.py
index aa156c77c..1cf1a41f1 100644
--- a/betty/gui/project.py
+++ b/betty/gui/project.py
@@ -20,14 +20,14 @@
from betty.asyncio import sync
from betty.config import ConfigurationError
from betty.gui import get_configuration_file_filter, BettyWindow, GuiBuilder, mark_invalid, mark_valid
+from betty.gui.app import BettyMainWindow
from betty.gui.error import catch_exceptions
from betty.gui.locale import LocalizedWidget
-from betty.gui.text import Text, Caption
-from betty.gui.app import BettyMainWindow
from betty.gui.locale import TranslationsLocaleCollector
from betty.gui.logging import LogRecordViewerHandler, LogRecordViewer
from betty.gui.model import EntityReferenceCollector, EntityReferencesCollector
from betty.gui.serve import ServeAppWindow
+from betty.gui.text import Text, Caption
from betty.importlib import import_any
from betty.locale import rfc_1766_to_bcp_47, bcp_47_to_rfc_1766
from betty.project import ProjectExtensionConfiguration, LocaleConfiguration, LocalesConfiguration
diff --git a/betty/gui/serve.py b/betty/gui/serve.py
index 3803b6a67..39c721a1a 100644
--- a/betty/gui/serve.py
+++ b/betty/gui/serve.py
@@ -104,7 +104,6 @@ def _start(self) -> None:
self._thread.server_started.connect(self._server_started)
self._thread.start()
- @sync
def close(self) -> bool:
self._stop()
return super().close()
diff --git a/betty/jinja2.py b/betty/jinja2.py
index 1903e2d75..9893bc0cd 100644
--- a/betty/jinja2.py
+++ b/betty/jinja2.py
@@ -50,7 +50,7 @@ class _Citer:
def __init__(self):
self._citations = []
- def __iter__(self) -> Iterable[Citation]:
+ def __iter__(self) -> Iterator[Citation]:
return enumerate(self._citations, 1)
def __len__(self) -> int:
diff --git a/betty/json.py b/betty/json.py
index e4236aa11..2de3b91c6 100644
--- a/betty/json.py
+++ b/betty/json.py
@@ -7,13 +7,13 @@
from geopy import Point
from jsonschema import RefResolver
+from betty.app import App
from betty.asyncio import sync
+from betty.locale import Date, DateRange, Localized
+from betty.media_type import MediaType
from betty.model import Entity, get_entity_type_name, GeneratedEntityId
from betty.model.ancestry import Place, Person, PlaceName, Event, Described, HasLinks, HasCitations, Link, Dated, File, \
Note, PersonName, HasMediaType, PresenceRole, EventType, Citation, Source
-from betty.locale import Date, DateRange, Localized
-from betty.media_type import MediaType
-from betty.app import App
from betty.string import upper_camel_case_to_lower_camel_case
diff --git a/betty/locale.py b/betty/locale.py
index f86ad1709..d74c4b66a 100644
--- a/betty/locale.py
+++ b/betty/locale.py
@@ -10,12 +10,12 @@
import shutil
import threading
from collections import defaultdict
-from gettext import NullTranslations, GNUTranslations
-from io import StringIO
from contextlib import suppress
from functools import total_ordering
+from gettext import NullTranslations, GNUTranslations
+from io import StringIO
from pathlib import Path
-from typing import Optional, Tuple, Union, List, Dict, Callable, Any, Iterator, Set
+from typing import Optional, Tuple, Union, List, Dict, Callable, Any, Iterator, Set, Sequence
import babel
from babel import dates, Locale
@@ -408,7 +408,7 @@ def negotiate_locale(preferred_locale: str, available_locales: Set[str]) -> Opti
return available_locale
-def negotiate_localizeds(preferred_locale: str, localizeds: List[Localized]) -> Optional[Localized]:
+def negotiate_localizeds(preferred_locale: str, localizeds: Sequence[Localized]) -> Optional[Localized]:
negotiated_locale = negotiate_locale(preferred_locale, {localized.locale for localized in localizeds if localized.locale is not None})
if negotiated_locale is not None:
for localized in localizeds:
diff --git a/betty/maps/__init__.py b/betty/maps/__init__.py
index 83cd63091..bbf60ccb3 100644
--- a/betty/maps/__init__.py
+++ b/betty/maps/__init__.py
@@ -5,10 +5,10 @@
from typing import Optional, Iterable, Set, Type
from betty.app.extension import Extension
-from betty.npm import _Npm, NpmBuilder, npm
from betty.generate import Generator
from betty.gui import GuiBuilder
from betty.html import CssProvider, JsProvider
+from betty.npm import _Npm, NpmBuilder, npm
class Maps(Extension, CssProvider, JsProvider, Generator, GuiBuilder, NpmBuilder):
diff --git a/betty/model/__init__.py b/betty/model/__init__.py
index 9c5289bc6..8887de0b1 100644
--- a/betty/model/__init__.py
+++ b/betty/model/__init__.py
@@ -9,7 +9,10 @@
from functools import reduce
from typing import TypeVar, Generic, Callable, List, Optional, Iterable, Any, Type, Union, Set, overload, cast, Iterator
-from typing_extensions import Self
+try:
+ from typing import Self
+except ImportError:
+ from typing_extensions import Self
from betty.functools import slice_to_range
from betty.importlib import import_any
@@ -36,9 +39,9 @@ def __new__(cls, entity_id: Optional[str] = None):
class Entity:
- def __init__(self, entity_id: Optional[str] = None):
+ def __init__(self, entity_id: Optional[str] = None, *args, **kwargs):
self._id = GeneratedEntityId() if entity_id is None else entity_id
- super().__init__()
+ super().__init__(*args, **kwargs)
@classmethod
def entity_type(cls) -> Type[Entity]:
diff --git a/betty/model/ancestry.py b/betty/model/ancestry.py
index e7705f85d..afb4a5d9e 100644
--- a/betty/model/ancestry.py
+++ b/betty/model/ancestry.py
@@ -14,7 +14,6 @@
from betty.model.event_type import EventType, StartOfLifeEventType, EndOfLifeEventType
from betty.os import PathLike
-
if TYPE_CHECKING:
from betty.builtins import _
@@ -23,6 +22,7 @@ class HasPrivacy:
private: Optional[bool]
def __init__(self, *args, **kwargs):
+ assert type(self) != HasPrivacy
super().__init__(*args, **kwargs)
self.private = None
@@ -31,6 +31,7 @@ class Dated:
date: Optional[Datey]
def __init__(self, *args, **kwargs):
+ assert type(self) != Dated
super().__init__(*args, **kwargs)
self.date = None
@@ -58,16 +59,28 @@ def label(self) -> Optional[str]:
@one_to_many('notes', 'entity')
class HasNotes(Entity):
- notes: EntityCollection[Note]
-
def __init__(self, *args, **kwargs):
+ assert type(self) != HasNotes
super().__init__(*args, **kwargs)
+ @property
+ def notes(self) -> EntityCollection[Note]:
+ pass
+
+ @notes.setter
+ def notes(self, notes: Iterable[Note]) -> None:
+ pass
+
+ @notes.deleter
+ def notes(self) -> None:
+ pass
+
class Described:
description: Optional[str]
def __init__(self, *args, **kwargs):
+ assert type(self) != Described
super().__init__(*args, **kwargs)
self.description = None
@@ -76,6 +89,7 @@ class HasMediaType:
media_type: Optional[MediaType]
def __init__(self, *args, **kwargs):
+ assert type(self) != HasMediaType
super().__init__(*args, **kwargs)
self.media_type = None
@@ -94,6 +108,7 @@ def __init__(self, url: str, *args, **kwargs):
class HasLinks:
def __init__(self, *args, **kwargs):
+ assert type(self) != HasLinks
super().__init__(*args, **kwargs)
self._links = set()
@@ -104,21 +119,42 @@ def links(self) -> Set[Link]:
@many_to_many('citations', 'facts')
class HasCitations(Entity):
- citations: EntityCollection[Citation]
-
def __init__(self, *args, **kwargs):
+ assert type(self) != HasCitations
super().__init__(*args, **kwargs)
+ @property
+ def citations(self) -> EntityCollection[Citation]:
+ pass
+
+ @citations.setter
+ def citations(self, citations: Iterable[Citation]) -> None:
+ pass
+
+ @citations.deleter
+ def citations(self) -> None:
+ pass
+
@many_to_many('entities', 'files')
class File(Described, HasPrivacy, HasMediaType, HasNotes, HasCitations, Entity):
- entities: EntityCollection[HasFiles]
-
- def __init__(self, file_id: Optional[str], path: PathLike, media_type: Optional[MediaType] = None):
- super().__init__(file_id)
+ def __init__(self, file_id: Optional[str], path: PathLike, media_type: Optional[MediaType] = None, *args, **kwargs):
+ super().__init__(file_id, *args, **kwargs)
self._path = Path(path)
self.media_type = media_type
+ @property
+ def entities(self) -> EntityCollection[Entity]:
+ pass
+
+ @entities.setter
+ def entities(self, entities: Iterable[Entity]) -> None:
+ pass
+
+ @entities.deleter
+ def entities(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('File')
@@ -134,11 +170,22 @@ def label(self) -> Optional[str]:
@many_to_many('files', 'entities')
class HasFiles(Entity):
- files: EntityCollection[File]
-
def __init__(self, *args, **kwargs):
+ assert type(self) != HasFiles
super().__init__(*args, **kwargs)
+ @property
+ def files(self) -> EntityCollection[File]:
+ pass
+
+ @files.setter
+ def files(self, files: Iterable[File]) -> None:
+ pass
+
+ @files.deleter
+ def files(self) -> None:
+ pass
+
@property
def associated_files(self) -> Iterable[File]:
return self.files
@@ -150,8 +197,6 @@ def associated_files(self) -> Iterable[File]:
class Source(Dated, HasFiles, HasLinks, HasPrivacy, Entity):
name: Optional[str]
contained_by: Source
- contains: EntityCollection[Source]
- citations: EntityCollection[Citation]
author: Optional[str]
publisher: Optional[str]
@@ -161,6 +206,30 @@ def __init__(self, source_id: Optional[str], name: Optional[str] = None):
self.author = None
self.publisher = None
+ @property
+ def contains(self) -> EntityCollection[Source]:
+ pass
+
+ @contains.setter
+ def contains(self, contains: Iterable[Source]) -> None:
+ pass
+
+ @contains.deleter
+ def contains(self) -> None:
+ pass
+
+ @property
+ def citations(self) -> EntityCollection[Citation]:
+ pass
+
+ @citations.setter
+ def citations(self, citations: Iterable[Citation]) -> None:
+ pass
+
+ @citations.deleter
+ def citations(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('Source')
@@ -173,7 +242,6 @@ def label(self) -> Optional[str]:
@many_to_many('facts', 'citations')
@many_to_one('source', 'citations')
class Citation(Dated, HasFiles, HasPrivacy, Entity):
- facts: EntityCollection[HasCitations]
source: Source
location: Optional[str]
@@ -182,6 +250,18 @@ def __init__(self, citation_id: Optional[str], source: Source):
self.location = None
self.source = source
+ @property
+ def facts(self) -> EntityCollection[HasCitations]:
+ pass
+
+ @facts.setter
+ def facts(self, facts: Iterable[HasCitations]) -> None:
+ pass
+
+ @facts.deleter
+ def facts(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('Citation')
@@ -229,15 +309,47 @@ def entity_type_label(cls) -> str:
@one_to_many('enclosed_by', 'encloses')
@one_to_many('encloses', 'enclosed_by')
class Place(HasLinks, Entity):
- enclosed_by: EntityCollection[Enclosure]
- encloses: EntityCollection[Enclosure]
- events: EntityCollection[Event]
-
def __init__(self, place_id: Optional[str], names: List[PlaceName], *args, **kwargs):
super().__init__(place_id, *args, **kwargs)
self._names = names
self._coordinates = None
+ @property
+ def enclosed_by(self) -> EntityCollection[Enclosure]:
+ pass
+
+ @enclosed_by.setter
+ def enclosed_by(self, enclosed_by: Iterable[Enclosure]) -> None:
+ pass
+
+ @enclosed_by.deleter
+ def enclosed_by(self) -> None:
+ pass
+
+ @property
+ def encloses(self) -> EntityCollection[Enclosure]:
+ pass
+
+ @encloses.setter
+ def encloses(self, encloses: Iterable[Enclosure]) -> None:
+ pass
+
+ @encloses.deleter
+ def encloses(self) -> None:
+ pass
+
+ @property
+ def events(self) -> EntityCollection[Event]:
+ pass
+
+ @events.setter
+ def events(self, events: Iterable[Event]) -> None:
+ pass
+
+ @events.deleter
+ def events(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('Place')
@@ -332,14 +444,25 @@ def entity_type_label(cls) -> str:
@many_to_one('place', 'events')
@one_to_many('presences', 'event')
class Event(Dated, HasFiles, HasCitations, Described, HasPrivacy, Entity):
- place: Place
- presences: EntityCollection[Presence]
+ place: Optional[Place]
def __init__(self, event_id: Optional[str], event_type: EventType, date: Optional[Datey] = None, *args, **kwargs):
super().__init__(event_id, *args, **kwargs)
self.date = date
self._type = event_type
+ @property
+ def presences(self) -> EntityCollection[Presence]:
+ pass
+
+ @presences.setter
+ def presences(self, presences: Iterable[Presence]) -> None:
+ pass
+
+ @presences.deleter
+ def presences(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('Event')
@@ -383,6 +506,9 @@ def __init__(self, person: Person, individual: Optional[str] = None, affiliation
def entity_type_label(cls) -> str:
return _('Person name')
+ def __repr__(self) -> str:
+ return '<%s.%s(%s, %s, %s)>' % (self.__class__.__module__, self.__class__.__name__, self.individual, self.affiliation, repr(self.person))
+
def __eq__(self, other: Any) -> bool:
if other is None:
return False
@@ -416,14 +542,58 @@ def label(self) -> Optional[str]:
@one_to_many('presences', 'person')
@one_to_many('names', 'person')
class Person(HasFiles, HasCitations, HasLinks, HasPrivacy, Entity):
- parents: EntityCollection[Person]
- children: EntityCollection[Person]
- presences: EntityCollection[Presence]
- names: EntityCollection[PersonName]
-
def __init__(self, person_id: Optional[str], *args, **kwargs):
+
super().__init__(person_id, *args, **kwargs)
+ @property
+ def parents(self) -> EntityCollection[Person]:
+ pass
+
+ @parents.setter
+ def parents(self, parents: Iterable[Person]) -> None:
+ pass
+
+ @parents.deleter
+ def parents(self) -> None:
+ pass
+
+ @property
+ def children(self) -> EntityCollection[Person]:
+ pass
+
+ @children.setter
+ def children(self, children: Iterable[Person]) -> None:
+ pass
+
+ @children.deleter
+ def children(self) -> None:
+ pass
+
+ @property
+ def presences(self) -> EntityCollection[Presence]:
+ pass
+
+ @presences.setter
+ def presences(self, presences: Iterable[Presence]) -> None:
+ pass
+
+ @presences.deleter
+ def presences(self) -> None:
+ pass
+
+ @property
+ def names(self) -> EntityCollection[PersonName]:
+ pass
+
+ @names.setter
+ def names(self, names: Iterable[PersonName]) -> None:
+ pass
+
+ @names.deleter
+ def names(self) -> None:
+ pass
+
@classmethod
def entity_type_label(cls) -> str:
return _('Person')
diff --git a/betty/model/event_type.py b/betty/model/event_type.py
index ad8ac53c9..f767dd4f0 100644
--- a/betty/model/event_type.py
+++ b/betty/model/event_type.py
@@ -2,7 +2,6 @@
from typing import Set, Type, TYPE_CHECKING
-
if TYPE_CHECKING:
from betty.builtins import _
diff --git a/betty/privatizer/__init__.py b/betty/privatizer/__init__.py
index 19bb4a12c..a7471c081 100644
--- a/betty/privatizer/__init__.py
+++ b/betty/privatizer/__init__.py
@@ -3,11 +3,12 @@
from typing import Optional, List
from betty.app.extension import Extension
-from betty.model.ancestry import Ancestry, Person, Event, Citation, Source, HasPrivacy, Subject, File, HasFiles, HasCitations
from betty.functools import walk
from betty.gui import GuiBuilder
-from betty.locale import DateRange, Date
from betty.load import PostLoader
+from betty.locale import DateRange, Date
+from betty.model.ancestry import Ancestry, Person, Event, Citation, Source, HasPrivacy, Subject, File, HasFiles, \
+ HasCitations
class Privatizer(Extension, PostLoader, GuiBuilder):
diff --git a/betty/pytests/gui/__init__.py b/betty/pytests/gui/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/betty/requirement.py b/betty/requirement.py
index 97d999990..8de0337d0 100644
--- a/betty/requirement.py
+++ b/betty/requirement.py
@@ -1,3 +1,4 @@
+from abc import ABC, abstractmethod
from textwrap import indent
from typing import Optional, Iterable, TYPE_CHECKING
@@ -102,10 +103,11 @@ def requirement(self) -> Requirement:
return self._requirement
-class Requirer:
+class Requirer(ABC):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@classmethod
+ @abstractmethod
def requires(cls) -> Requirement:
raise NotImplementedError
diff --git a/betty/search.py b/betty/search.py
index f0102125c..a56584e87 100644
--- a/betty/search.py
+++ b/betty/search.py
@@ -1,8 +1,8 @@
from typing import Dict, Iterable, Optional
+from betty.app import App
from betty.model import get_entity_type_name
from betty.model.ancestry import Person, Place, File, Entity
-from betty.app import App
from betty.string import camel_case_to_snake_case
diff --git a/betty/serve.py b/betty/serve.py
index e9d4343d1..51fef7b09 100644
--- a/betty/serve.py
+++ b/betty/serve.py
@@ -1,4 +1,5 @@
from __future__ import annotations
+
import contextlib
import copy
import logging
@@ -8,9 +9,9 @@
from io import StringIO
from typing import Iterable, Optional
+from betty.app import App
from betty.error import UserFacingError
from betty.os import ChDir
-from betty.app import App
DEFAULT_PORT = 8000
diff --git a/betty/subprocess.py b/betty/subprocess.py
index e70879d61..2ef0c8258 100644
--- a/betty/subprocess.py
+++ b/betty/subprocess.py
@@ -1,5 +1,5 @@
-from asyncio import subprocess
import subprocess as stdsubprocess
+from asyncio import subprocess
from typing import Sequence
diff --git a/betty/tests/__init__.py b/betty/tests/__init__.py
index ee1ed2b14..4e057f619 100644
--- a/betty/tests/__init__.py
+++ b/betty/tests/__init__.py
@@ -1,33 +1,30 @@
import functools
-import logging
-import unittest
+import inspect
from contextlib import contextmanager, ExitStack
from pathlib import Path
from tempfile import TemporaryDirectory
-from typing import Optional, Dict, Callable, Tuple, Iterator, ContextManager, Union, TypeVar, List
+from typing import Optional, Dict, Callable, ContextManager, Iterator, Tuple, TypeVar
-try:
- from typing import TypeGuard
-except ImportError:
- from typing_extensions import TypeGuard
-
-from jinja2 import Environment, Template
+from jinja2.environment import Template
from betty import fs
-from betty.app import App, AppConfiguration
-from betty.typing import Void
+from betty.app import App
+from betty.jinja2 import Environment
T = TypeVar('T')
def patch_cache(f):
@functools.wraps(f)
- def _patch_cache(*args, **kwargs):
+ async def _patch_cache(*args, **kwargs) -> None:
original_cache_directory_path = fs.CACHE_DIRECTORY_PATH
cache_directory = TemporaryDirectory()
fs.CACHE_DIRECTORY_PATH = Path(cache_directory.name)
try:
- f(*args, **kwargs)
+ result = f(*args, **kwargs)
+ if inspect.iscoroutinefunction(f):
+ await result
+
finally:
fs.CACHE_DIRECTORY_PATH = original_cache_directory_path
cache_directory.cleanup()
@@ -35,53 +32,31 @@ def _patch_cache(*args, **kwargs):
return _patch_cache
-class TestCase(unittest.TestCase):
- @classmethod
- def setUpClass(cls) -> None:
- logging.disable(logging.CRITICAL)
- # Prevent App from loading its application configuration from the current user session, as it would pollute the
- # tests.
- AppConfiguration._read = AppConfiguration.read
- AppConfiguration.read = lambda _: None
-
- @classmethod
- def tearDownClass(cls) -> None:
- AppConfiguration.read = AppConfiguration._read
- del AppConfiguration._read
- logging.disable(logging.NOTSET)
-
- def assertIsNotVoid(self, value: Union[T, Void]) -> TypeGuard[T]:
- if value is Void:
- raise AssertionError('Failed asserting that a value is not Void.')
- return value
-
-
-class TemplateTestCase(TestCase):
+class TemplateTestCase:
template_string: Optional[str] = None
template_file: Optional[str] = None
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
+ @contextmanager
+ def _render(self, data: Optional[Dict] = None, template_file: Optional[str] = None, template_string: Optional[str] = None, set_up: Optional[Callable[[App], ContextManager]] = None) -> Iterator[Tuple[str, App]]:
if self.template_string is not None and self.template_file is not None:
class_name = self.__class__.__name__
raise RuntimeError(f'{class_name} must define either `{class_name}.template_string` or `{class_name}.template_file`, but not both.')
- @contextmanager
- def _render(self, data: Optional[Dict] = None, template_file: Optional[Template] = None, template_string: Optional[str] = None, set_up: Optional[Callable[[App], List[ContextManager]]] = None) -> Iterator[Tuple[str, App]]:
if template_string is not None and template_file is not None:
raise RuntimeError('You must define either `template_string` or `template_file`, but not both.')
+ template_factory: Callable[..., Template]
if template_string is not None:
template = template_string
- template_factory = Environment.from_string
+ template_factory = Environment.from_string # type: ignore
elif template_file is not None:
template = template_file
- template_factory = Environment.get_template
+ template_factory = Environment.get_template # type: ignore
elif self.template_string is not None:
template = self.template_string
- template_factory = Environment.from_string
+ template_factory = Environment.from_string # type: ignore
elif self.template_file is not None:
template = self.template_file
- template_factory = Environment.get_template
+ template_factory = Environment.get_template # type: ignore
else:
class_name = self.__class__.__name__
raise RuntimeError(f'You must define one of `template_string`, `template_file`, `{class_name}.template_string`, or `{class_name}.template_file`.')
@@ -93,8 +68,7 @@ def _render(self, data: Optional[Dict] = None, template_file: Optional[Template]
with app:
try:
if set_up is not None:
- for context in set_up(app):
- contexts.enter_context(context)
+ contexts.enter_context(set_up(app))
rendered = template_factory(app.jinja2_environment, template).render(**data)
app.wait()
yield rendered, app
diff --git a/betty/tests/_package/test_license_compatibility.py b/betty/tests/_package/test_license_compatibility.py
index d991bd40d..19e9add15 100644
--- a/betty/tests/_package/test_license_compatibility.py
+++ b/betty/tests/_package/test_license_compatibility.py
@@ -5,10 +5,8 @@
import piplicenses
from pkg_resources import get_distribution
-from betty.tests import TestCase
-
-class PackageLicensesTest(TestCase):
+class TestPackageLicenses:
_GPL_V3_COMPATIBLE_DISTRIBUTIONS = (
'PyQt6-sip',
'graphlib-backport', # Released under the Python Software Foundation License.
@@ -31,10 +29,10 @@ def assert_is_compatible(self, package_license: dict) -> None:
for compatible_license in self._GPL_V3_COMPATIBLE_LICENSES:
if compatible_license in package_license['License']:
return
- self.fail("%s is released under the %s, which is not known to be compatible with Betty's own license" % (
+ assert False, "%s is released under the %s, which is not known to be compatible with Betty's own license" % (
package_license['Name'],
package_license['License'],
- ))
+ )
def test_runtime_dependency_license_compatibility(self) -> None:
"""
@@ -61,7 +59,7 @@ def _get_dependency_distribution_names(name: str):
sys.stdout = piplicenses_stdout
piplicenses.main()
package_licenses = json.loads(piplicenses_stdout.getvalue())
- self.assertGreater(len(package_licenses), 1)
+ assert len(package_licenses) > 1
for package_license in package_licenses:
self.assert_is_compatible(package_license)
finally:
diff --git a/betty/tests/anonymizer/test__init__.py b/betty/tests/anonymizer/test___init__.py
similarity index 74%
rename from betty/tests/anonymizer/test__init__.py
rename to betty/tests/anonymizer/test___init__.py
index 71b9c5d48..a7cbaa3df 100644
--- a/betty/tests/anonymizer/test__init__.py
+++ b/betty/tests/anonymizer/test___init__.py
@@ -1,60 +1,45 @@
-from gettext import NullTranslations
from unittest.mock import patch, ANY, Mock
-from betty.app import App
-from betty.asyncio import sync
from betty.anonymizer import anonymize, anonymize_person, anonymize_event, anonymize_file, anonymize_citation, \
anonymize_source, AnonymousSource, AnonymousCitation, Anonymizer
+from betty.app import App
from betty.load import load
-from betty.locale import Translations
from betty.model import Entity
from betty.model.ancestry import Ancestry, Person, File, Source, Citation, PersonName, Presence, Event, Subject, \
HasCitations
from betty.model.event_type import Birth
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
-
-class AnonymousSourceTest(TestCase):
- def setUp(self) -> None:
- self._translations = Translations(NullTranslations())
- self._translations.install()
-
- def tearDown(self) -> None:
- self._translations.uninstall()
+class TestAnonymousSource:
def test_name(self):
- self.assertIsInstance(AnonymousSource().name, str)
+ with App():
+ assert isinstance(AnonymousSource().name, str)
def test_replace(self):
- ancestry = Ancestry()
- citations = [Citation(None, Source(None))]
- contains = [Source(None)]
- files = [Mock(File)]
- sut = AnonymousSource()
- other = Source(None)
- ancestry.entities.append(other)
- other.citations = citations
- other.contains = contains
- other.files = files
- sut.replace(other, ancestry)
- self.assertEqual(citations, list(sut.citations))
- self.assertEqual(contains, list(sut.contains))
- self.assertEqual(files, list(sut.files))
- self.assertNotIn(other, ancestry.entities)
-
-
-class AnonymousCitationTest(TestCase):
- def setUp(self) -> None:
- self._translations = Translations(NullTranslations())
- self._translations.install()
-
- def tearDown(self) -> None:
- self._translations.uninstall()
-
+ with App():
+ ancestry = Ancestry()
+ citations = [Citation(None, Source(None))]
+ contains = [Source(None)]
+ files = [Mock(File)]
+ sut = AnonymousSource()
+ other = Source(None)
+ ancestry.entities.append(other)
+ other.citations = citations # type: ignore
+ other.contains = contains # type: ignore
+ other.files = files # type: ignore
+ sut.replace(other, ancestry)
+ assert citations == list(sut.citations)
+ assert contains == list(sut.contains)
+ assert files == list(sut.files)
+ assert other not in ancestry.entities
+
+
+class TestAnonymousCitation:
def test_location(self):
source = Mock(Source)
- self.assertIsInstance(AnonymousCitation(source).location, str)
+ with App():
+ assert isinstance(AnonymousCitation(source).location, str)
def test_replace(self):
class _HasCitations(HasCitations, Entity):
@@ -66,22 +51,15 @@ class _HasCitations(HasCitations, Entity):
sut = AnonymousCitation(source)
other = Citation(None, source)
ancestry.entities.append(other)
- other.facts = facts
- other.files = files
+ other.facts = facts # type: ignore
+ other.files = files # type: ignore
sut.replace(other, ancestry)
- self.assertEqual(facts, list(sut.facts))
- self.assertEqual(files, list(sut.files))
- self.assertNotIn(other, ancestry.entities)
-
-
-class AnonymizeTest(TestCase):
- def setUp(self) -> None:
- self._translations = Translations(NullTranslations())
- self._translations.install()
+ assert facts == list(sut.facts)
+ assert files == list(sut.files)
+ assert other not in ancestry.entities
- def tearDown(self) -> None:
- self._translations.uninstall()
+class TestAnonymize:
@patch('betty.anonymizer.anonymize_person')
def test_with_public_person_should_not_anonymize(self, m_anonymize_person) -> None:
person = Person('P0')
@@ -175,20 +153,20 @@ def test_with_private_citation_should_anonymize(self, m_anonymize_citation) -> N
m_anonymize_citation.assert_called_once_with(citation, ancestry, ANY)
-class AnonymizePersonTest(TestCase):
+class TestAnonymizePerson:
def test_should_remove_citations(self) -> None:
person = Person('P0')
source = Source('The Source')
citation = Citation(None, source)
person.citations.append(citation)
anonymize_person(person)
- self.assertEqual(0, len(person.citations))
+ assert 0 == len(person.citations)
def test_should_remove_files(self) -> None:
person = Person('P0')
person.files.append(File('F0', __file__))
anonymize_person(person)
- self.assertEqual(0, len(person.files))
+ assert 0 == len(person.files)
def test_should_remove_names(self) -> None:
person = Person('P0')
@@ -197,16 +175,16 @@ def test_should_remove_names(self) -> None:
citation = Citation(None, source)
name.citations.append(citation)
anonymize_person(person)
- self.assertEqual(0, len(person.names))
- self.assertEqual(0, len(citation.facts))
+ assert 0 == len(person.names)
+ assert 0 == len(citation.facts)
def test_should_remove_presences(self) -> None:
person = Person('P0')
event = Event(None, Birth())
Presence(person, Subject(), event)
anonymize_person(person)
- self.assertEqual(0, len(person.presences))
- self.assertEqual(0, len(event.presences))
+ assert 0 == len(person.presences)
+ assert 0 == len(event.presences)
def test_should_remove_parents_without_public_descendants(self) -> None:
person = Person('P0')
@@ -219,7 +197,7 @@ def test_should_remove_parents_without_public_descendants(self) -> None:
person.parents.append(parent)
anonymize_person(person)
- self.assertCountEqual([], person.parents)
+ assert [] == list(person.parents)
def test_should_not_remove_parents_with_public_descendants(self) -> None:
person = Person('P0')
@@ -232,48 +210,41 @@ def test_should_not_remove_parents_with_public_descendants(self) -> None:
person.parents.append(parent)
anonymize_person(person)
- self.assertCountEqual([parent], person.parents)
+ assert [parent] == list(person.parents)
-class AnonymizeEventTest(TestCase):
+class TestAnonymizeEvent:
def test_should_remove_citations(self) -> None:
event = Event(None, Birth())
source = Source(None, 'The Source')
citation = Citation(None, source)
event.citations.append(citation)
anonymize_event(event)
- self.assertEqual(0, len(event.citations))
+ assert 0 == len(event.citations)
def test_should_remove_files(self) -> None:
event = Event(None, Birth())
event.files.append(File('F0', __file__))
anonymize_event(event)
- self.assertEqual(0, len(event.files))
+ assert 0 == len(event.files)
def test_should_remove_presences(self) -> None:
event = Event(None, Birth())
person = Person('P1')
Presence(person, Subject(), event)
anonymize_event(event)
- self.assertEqual(0, len(event.presences))
+ assert 0 == len(event.presences)
-class AnonymizeFileTest(TestCase):
+class TestAnonymizeFile:
def test_should_remove_entity(self) -> None:
file = File('F0', __file__)
file.entities.append(Person('P0'))
anonymize_file(file)
- self.assertEqual(0, len(file.entities))
+ assert 0 == len(file.entities)
-class AnonymizeSourceTest(TestCase):
- def setUp(self) -> None:
- self._translations = Translations(NullTranslations())
- self._translations.install()
-
- def tearDown(self) -> None:
- self._translations.uninstall()
-
+class TestAnonymizeSource:
def test_should_remove_citations(self) -> None:
ancestry = Ancestry()
source = Source('S0', 'The Source')
@@ -282,8 +253,8 @@ def test_should_remove_citations(self) -> None:
source.citations.append(citation)
anonymous_source = AnonymousSource()
anonymize_source(source, ancestry, anonymous_source)
- self.assertEqual(0, len(source.citations))
- self.assertIn(citation, anonymous_source.citations)
+ assert 0 == len(source.citations)
+ assert citation in anonymous_source.citations
def test_should_remove_contained_by(self) -> None:
ancestry = Ancestry()
@@ -293,7 +264,7 @@ def test_should_remove_contained_by(self) -> None:
source.contained_by = contained_by
anonymous_source = AnonymousSource()
anonymize_source(source, ancestry, anonymous_source)
- self.assertIsNone(source.contained_by)
+ assert source.contained_by is None
def test_should_remove_contains(self) -> None:
ancestry = Ancestry()
@@ -303,8 +274,8 @@ def test_should_remove_contains(self) -> None:
source.contains.append(contains)
anonymous_source = AnonymousSource()
anonymize_source(source, ancestry, anonymous_source)
- self.assertEqual(0, len(source.contains))
- self.assertIn(contains, anonymous_source.contains)
+ assert 0 == len(source.contains)
+ assert contains in anonymous_source.contains
def test_should_remove_files(self) -> None:
ancestry = Ancestry()
@@ -314,18 +285,11 @@ def test_should_remove_files(self) -> None:
source.files.append(file)
anonymous_source = AnonymousSource()
anonymize_source(source, ancestry, anonymous_source)
- self.assertEqual(0, len(source.files))
- self.assertIn(file, anonymous_source.files)
-
-
-class AnonymizeCitationTest(TestCase):
- def setUp(self) -> None:
- self._translations = Translations(NullTranslations())
- self._translations.install()
+ assert 0 == len(source.files)
+ assert file in anonymous_source.files
- def tearDown(self) -> None:
- self._translations.uninstall()
+class TestAnonymizeCitation:
def test_should_remove_facts(self) -> None:
ancestry = Ancestry()
source = Source('The Source')
@@ -336,8 +300,8 @@ def test_should_remove_facts(self) -> None:
anonymous_source = AnonymousSource()
anonymous_citation = AnonymousCitation(anonymous_source)
anonymize_citation(citation, ancestry, anonymous_citation)
- self.assertEqual(0, len(citation.facts))
- self.assertIn(fact, anonymous_citation.facts)
+ assert 0 == len(citation.facts)
+ assert fact in anonymous_citation.facts
def test_should_remove_files(self) -> None:
ancestry = Ancestry()
@@ -349,8 +313,8 @@ def test_should_remove_files(self) -> None:
anonymous_source = AnonymousSource()
anonymous_citation = AnonymousCitation(anonymous_source)
anonymize_citation(citation, ancestry, anonymous_citation)
- self.assertEqual(0, len(citation.files))
- self.assertIn(file, anonymous_citation.files)
+ assert 0 == len(citation.files)
+ assert file in anonymous_citation.files
def test_should_remove_source(self) -> None:
ancestry = Ancestry()
@@ -360,11 +324,10 @@ def test_should_remove_source(self) -> None:
anonymous_source = AnonymousSource()
anonymous_citation = AnonymousCitation(anonymous_source)
anonymize_citation(citation, ancestry, anonymous_citation)
- self.assertIsNone(citation.source)
+ assert citation.source is None
-class AnonymizerTest(TestCase):
- @sync
+class TestAnonymizer:
async def test_post_parse(self) -> None:
person = Person('P0')
person.private = True
@@ -373,4 +336,4 @@ async def test_post_parse(self) -> None:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Anonymizer))
app.project.ancestry.entities.append(person)
await load(app)
- self.assertEqual(0, len(person.names))
+ assert 0 == len(person.names)
diff --git a/betty/tests/app/test___init__.py b/betty/tests/app/test___init__.py
index 842e8e181..66e333f54 100644
--- a/betty/tests/app/test___init__.py
+++ b/betty/tests/app/test___init__.py
@@ -1,11 +1,11 @@
from typing import Type, List, Set
+import pytest
+
from betty.app import Extension, App, CyclicDependencyError
-from betty.app.extension import ConfigurableExtension
-from betty.asyncio import sync
+from betty.app.extension import ConfigurableExtension as GenericConfigurableExtension
from betty.config import Configuration, ConfigurationError, DumpedConfiguration
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
class Tracker:
@@ -82,11 +82,17 @@ def comes_after(cls) -> Set[Type[Extension]]:
return {NonConfigurableExtension}
-class AppTest(TestCase):
+class ConfigurableExtension(GenericConfigurableExtension[ConfigurableExtensionConfiguration]):
+ @classmethod
+ def default_configuration(cls) -> ConfigurableExtensionConfiguration:
+ return ConfigurableExtensionConfiguration(False)
+
+
+class TestApp:
def test_extensions_with_one_extension(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(NonConfigurableExtension))
- self.assertIsInstance(sut.extensions[NonConfigurableExtension], NonConfigurableExtension)
+ assert isinstance(sut.extensions[NonConfigurableExtension], NonConfigurableExtension)
def test_extensions_with_one_configurable_extension(self) -> None:
check = 1337
@@ -94,88 +100,78 @@ def test_extensions_with_one_configurable_extension(self) -> None:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(ConfigurableExtension, True, ConfigurableExtensionConfiguration(
check=check,
)))
- self.assertIsInstance(sut.extensions[ConfigurableExtension], ConfigurableExtension)
- self.assertEqual(check, sut.extensions[ConfigurableExtension]._configuration.check)
+ assert isinstance(sut.extensions[ConfigurableExtension], ConfigurableExtension)
+ assert check == sut.extensions[ConfigurableExtension].configuration.check
- @sync
async def test_extensions_with_one_extension_with_single_chained_dependency(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(DependsOnNonConfigurableExtensionExtensionExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(3, len(carrier))
- self.assertEqual(NonConfigurableExtension, type(carrier[0]))
- self.assertEqual(DependsOnNonConfigurableExtensionExtension, type(carrier[1]))
- self.assertEqual(DependsOnNonConfigurableExtensionExtensionExtension, type(carrier[2]))
+ assert 3 == len(carrier)
+ assert isinstance(carrier[0], NonConfigurableExtension)
+ assert isinstance(carrier[1], DependsOnNonConfigurableExtensionExtension)
+ assert isinstance(carrier[2], DependsOnNonConfigurableExtensionExtensionExtension)
- @sync
async def test_extensions_with_multiple_extensions_with_duplicate_dependencies(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(DependsOnNonConfigurableExtensionExtension))
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(AlsoDependsOnNonConfigurableExtensionExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(3, len(carrier))
- self.assertEqual(NonConfigurableExtension, type(carrier[0]))
- self.assertIn(DependsOnNonConfigurableExtensionExtension, [
- type(extension) for extension in carrier])
- self.assertIn(AlsoDependsOnNonConfigurableExtensionExtension, [
- type(extension) for extension in carrier])
+ assert 3 == len(carrier)
+ assert isinstance(carrier[0], NonConfigurableExtension)
+ assert DependsOnNonConfigurableExtensionExtension in [type(extension) for extension in carrier]
+ assert AlsoDependsOnNonConfigurableExtensionExtension in [type(extension) for extension in carrier]
def test_extensions_with_multiple_extensions_with_cyclic_dependencies(self) -> None:
- with self.assertRaises(CyclicDependencyError):
+ with pytest.raises(CyclicDependencyError):
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(CyclicDependencyOneExtension))
sut.extensions
- @sync
async def test_extensions_with_comes_before_with_other_extension(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(NonConfigurableExtension))
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(ComesBeforeNonConfigurableExtensionExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(2, len(carrier))
- self.assertEqual(
- ComesBeforeNonConfigurableExtensionExtension, type(carrier[0]))
- self.assertEqual(NonConfigurableExtension, type(carrier[1]))
+ assert 2 == len(carrier)
+ assert isinstance(carrier[0], ComesBeforeNonConfigurableExtensionExtension)
+ assert isinstance(carrier[1], NonConfigurableExtension)
- @sync
async def test_extensions_with_comes_before_without_other_extension(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(ComesBeforeNonConfigurableExtensionExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(1, len(carrier))
- self.assertEqual(
- ComesBeforeNonConfigurableExtensionExtension, type(carrier[0]))
+ assert 1 == len(carrier)
+ assert isinstance(carrier[0], ComesBeforeNonConfigurableExtensionExtension)
- @sync
async def test_extensions_with_comes_after_with_other_extension(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(ComesAfterNonConfigurableExtensionExtension))
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(NonConfigurableExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(2, len(carrier))
- self.assertEqual(NonConfigurableExtension, type(carrier[0]))
- self.assertEqual(ComesAfterNonConfigurableExtensionExtension, type(carrier[1]))
+ assert 2 == len(carrier)
+ assert isinstance(carrier[0], NonConfigurableExtension)
+ assert isinstance(carrier[1], ComesAfterNonConfigurableExtensionExtension)
- @sync
async def test_extensions_with_comes_after_without_other_extension(self) -> None:
with App() as sut:
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(ComesAfterNonConfigurableExtensionExtension))
- carrier = []
+ carrier: List[TrackableExtension] = []
await sut.dispatcher.dispatch(Tracker)(carrier)
- self.assertEqual(1, len(carrier))
- self.assertEqual(ComesAfterNonConfigurableExtensionExtension, type(carrier[0]))
+ assert 1 == len(carrier)
+ assert isinstance(carrier[0], ComesAfterNonConfigurableExtensionExtension)
def test_extensions_addition_to_configuration(self) -> None:
with App() as sut:
# Get the extensions before making configuration changes to warm the cache.
sut.extensions
sut.project.configuration.extensions.add(ProjectExtensionConfiguration(NonConfigurableExtension))
- self.assertIsInstance(sut.extensions[NonConfigurableExtension], NonConfigurableExtension)
+ assert isinstance(sut.extensions[NonConfigurableExtension], NonConfigurableExtension)
def test_extensions_removal_from_configuration(self) -> None:
with App() as sut:
@@ -183,4 +179,4 @@ def test_extensions_removal_from_configuration(self) -> None:
# Get the extensions before making configuration changes to warm the cache.
sut.extensions
del sut.project.configuration.extensions[NonConfigurableExtension]
- self.assertNotIn(NonConfigurableExtension, sut.extensions)
+ assert NonConfigurableExtension not in sut.extensions
diff --git a/betty/tests/app/test_extension.py b/betty/tests/app/test_extension.py
index 2e235fe37..55fe9da3f 100644
--- a/betty/tests/app/test_extension.py
+++ b/betty/tests/app/test_extension.py
@@ -1,24 +1,22 @@
-from typing import Set, Type, Any
+from typing import Set, Type, Any, Dict
from betty.app import App
from betty.app.extension import Extension, ListExtensions, ExtensionDispatcher, build_extension_type_graph, \
discover_extension_types
-from betty.asyncio import sync
-from betty.tests import TestCase
-class ExtensionTest(TestCase):
+class TestExtension:
def test_depends_on(self):
- self.assertEqual(set(), Extension.depends_on())
+ assert set() == Extension.depends_on()
def test_comes_after(self):
- self.assertEqual(set(), Extension.comes_after())
+ assert set() == Extension.comes_after()
def test_comes_before(self):
- self.assertEqual(set(), Extension.comes_before())
+ assert set() == Extension.comes_before()
-class ExtensionDispatcherTest(TestCase):
+class TestExtensionDispatcher:
class _Multiplier:
async def multiply(self, term: int) -> Any:
raise NotImplementedError
@@ -31,7 +29,6 @@ def __init__(self, app: App, multiplier: int):
async def multiply(self, term: int) -> Any:
return self._multiplier * term
- @sync
async def test(self) -> None:
with App() as app:
extensions = ListExtensions([
@@ -41,12 +38,12 @@ async def test(self) -> None:
sut = ExtensionDispatcher(extensions)
actual_returned_somethings = await sut.dispatch(self._Multiplier)(3)
expected_returned_somethings = [3, 9, 6, 12]
- self.assertEqual(expected_returned_somethings, actual_returned_somethings)
+ assert expected_returned_somethings == actual_returned_somethings
-class BuildExtensionTypeGraphTest(TestCase):
+class TestBuildExtensionTypeGraph:
def test_without_extension_types(self) -> None:
- self.assertEqual({}, build_extension_type_graph(set()))
+ assert {} == build_extension_type_graph(set())
def test_with_isolated_extension_types(self) -> None:
class IsolatedExtensionOne(Extension):
@@ -58,11 +55,11 @@ class IsolatedExtensionTwo(Extension):
IsolatedExtensionOne,
IsolatedExtensionTwo,
}
- expected = {
+ expected: Dict[Type[Extension], Set[Type[Extension]]] = {
IsolatedExtensionOne: set(),
IsolatedExtensionTwo: set(),
}
- self.assertEqual(expected, build_extension_type_graph(extension_types))
+ assert expected == build_extension_type_graph(extension_types)
def test_with_unknown_dependencies(self) -> None:
class IsDependencyExtension(Extension):
@@ -79,7 +76,7 @@ def depends_on(cls) -> Set[Type[Extension]]:
HasDependencyExtension: {IsDependencyExtension},
IsDependencyExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_known_dependencies(self) -> None:
class IsDependencyExtension(Extension):
@@ -97,7 +94,7 @@ def depends_on(cls) -> Set[Type[Extension]]:
HasDependencyExtension: {IsDependencyExtension},
IsDependencyExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_nested_dependencies(self) -> None:
class IsDependencyExtension(Extension):
@@ -120,7 +117,7 @@ def depends_on(cls) -> Set[Type[Extension]]:
HasDependencyExtension: {IsAndHasDependencyExtension},
IsDependencyExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_unknown_comes_after(self) -> None:
class ComesBeforeExtension(Extension):
@@ -133,10 +130,10 @@ def comes_after(cls) -> Set[Type[Extension]]:
extension_types = {
ComesAfterExtension,
}
- expected = {
+ expected: Dict[Type[Extension], Set[Type[Extension]]] = {
ComesAfterExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_known_comes_after(self) -> None:
class ComesBeforeExtension(Extension):
@@ -154,7 +151,7 @@ def comes_after(cls) -> Set[Type[Extension]]:
ComesAfterExtension: {ComesBeforeExtension},
ComesBeforeExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_unknown_comes_before(self) -> None:
class ComesAfterExtension(Extension):
@@ -167,10 +164,10 @@ def comes_before(cls) -> Set[Type[Extension]]:
extension_types = {
ComesBeforeExtension,
}
- expected = {
+ expected: Dict[Type[Extension], Set[Type[Extension]]] = {
ComesBeforeExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
def test_with_known_comes_before(self) -> None:
class ComesAfterExtension(Extension):
@@ -188,12 +185,12 @@ def comes_before(cls) -> Set[Type[Extension]]:
ComesAfterExtension: {ComesBeforeExtension},
ComesBeforeExtension: set(),
}
- self.assertDictEqual(expected, dict(build_extension_type_graph(extension_types)))
+ assert expected == dict(build_extension_type_graph(extension_types))
-class DiscoverExtensionTypesTest(TestCase):
+class TestDiscoverExtensionTypes:
def test(self):
extension_types = discover_extension_types()
- self.assertLessEqual(1, len(extension_types))
+ assert 1 <= len(extension_types)
for extension_type in extension_types:
- self.assertTrue(issubclass(extension_type, Extension))
+ assert issubclass(extension_type, Extension)
diff --git a/betty/tests/assets/templates/macro/test_person_html_j2.py b/betty/tests/assets/templates/macro/test_person_html_j2.py
index fcd8ce0ce..452d3946c 100644
--- a/betty/tests/assets/templates/macro/test_person_html_j2.py
+++ b/betty/tests/assets/templates/macro/test_person_html_j2.py
@@ -12,7 +12,7 @@ def test_with_full_name(self):
with self._render(data={
'name': name,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_individual_name(self):
person = Person(None)
@@ -21,7 +21,7 @@ def test_with_individual_name(self):
with self._render(data={
'name': name,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_affiliation_name(self):
person = Person(None)
@@ -30,7 +30,7 @@ def test_with_affiliation_name(self):
with self._render(data={
'name': name,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_embedded(self):
person = Person(None)
@@ -43,7 +43,7 @@ def test_embedded(self):
'name': name,
'embedded': True,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_citation(self):
person = Person(None)
@@ -55,4 +55,4 @@ def test_with_citation(self):
with self._render(data={
'name': name,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/page/test_person_html_j2.py b/betty/tests/assets/templates/page/test_person_html_j2.py
index 30198459b..60c59971a 100644
--- a/betty/tests/assets/templates/page/test_person_html_j2.py
+++ b/betty/tests/assets/templates/page/test_person_html_j2.py
@@ -22,5 +22,5 @@ def test_without_enclosing_places(self):
'entity_type_name': 'person',
'entity': person,
}) as (actual, _):
- self.assertIn('Descendant names include FamilyOneAssociationName.', actual)
- self.assertIn('Descendant names include FamilyTwoAssociationName.', actual)
+ assert 'Descendant names include FamilyOneAssociationName.' in actual
+ assert 'Descendant names include FamilyTwoAssociationName.' in actual
diff --git a/betty/tests/assets/templates/test_event_dimensions_html_j2.py b/betty/tests/assets/templates/test_event_dimensions_html_j2.py
index 7b01ad617..660f9954e 100644
--- a/betty/tests/assets/templates/test_event_dimensions_html_j2.py
+++ b/betty/tests/assets/templates/test_event_dimensions_html_j2.py
@@ -13,7 +13,7 @@ def test_without_meta(self):
with self._render(data={
'event': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_date(self):
event = Event(None, Birth())
@@ -22,7 +22,7 @@ def test_with_date(self):
with self._render(data={
'event': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_place(self):
event = Event(None, Birth())
@@ -31,7 +31,7 @@ def test_with_place(self):
with self._render(data={
'event': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_place_is_place_context(self):
event = Event(None, Birth())
@@ -42,7 +42,7 @@ def test_with_place_is_place_context(self):
'event': event,
'place_context': place,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_date_and_place(self):
event = Event(None, Birth())
@@ -52,7 +52,7 @@ def test_with_date_and_place(self):
with self._render(data={
'event': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_citation(self):
event = Event(None, Birth())
@@ -61,7 +61,7 @@ def test_with_citation(self):
with self._render(data={
'event': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_embedded(self):
event = Event(None, Birth())
@@ -73,4 +73,4 @@ def test_embedded(self):
'event': event,
'embedded': True,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/test_label_event_html_j2.py b/betty/tests/assets/templates/test_label_event_html_j2.py
index 3a958250c..e05dedd14 100644
--- a/betty/tests/assets/templates/test_label_event_html_j2.py
+++ b/betty/tests/assets/templates/test_label_event_html_j2.py
@@ -12,7 +12,7 @@ def test_minimal(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_identifiable(self):
event = Event('E0', Birth())
@@ -20,7 +20,7 @@ def test_with_identifiable(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_embedded_with_identifiable(self):
event = Event('E0', Birth())
@@ -30,7 +30,7 @@ def test_embedded_with_identifiable(self):
'entity': event,
'embedded': True,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_description(self):
event = Event(None, Birth())
@@ -39,7 +39,7 @@ def test_with_description(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_witnesses(self):
event = Event(None, Birth())
@@ -48,7 +48,7 @@ def test_with_witnesses(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_person_context_as_witness(self):
event = Event(None, Birth())
@@ -59,7 +59,7 @@ def test_with_person_context_as_witness(self):
'entity': event,
'person_context': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_person_context_as_subject(self):
event = Event(None, Birth())
@@ -70,7 +70,7 @@ def test_with_person_context_as_subject(self):
'entity': event,
'person_context': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_person_context_and_other_as_subject(self):
event = Event(None, Marriage())
@@ -83,7 +83,7 @@ def test_with_person_context_and_other_as_subject(self):
'entity': event,
'person_context': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_subjects(self):
event = Event(None, Birth())
@@ -93,7 +93,7 @@ def test_with_subjects(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_without_subjects(self):
event = Event(None, Birth())
@@ -101,7 +101,7 @@ def test_without_subjects(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_entity(self):
event = Event(None, Birth())
@@ -109,4 +109,4 @@ def test_with_entity(self):
with self._render(data={
'entity': event,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/test_label_person_html_j2.py b/betty/tests/assets/templates/test_label_person_html_j2.py
index 1b9e39f95..1b6451f21 100644
--- a/betty/tests/assets/templates/test_label_person_html_j2.py
+++ b/betty/tests/assets/templates/test_label_person_html_j2.py
@@ -12,7 +12,7 @@ def test_with_name(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_without_name(self):
person = Person('P0')
@@ -20,7 +20,7 @@ def test_without_name(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_embedded(self):
person = Person('P0')
@@ -29,7 +29,7 @@ def test_embedded(self):
'entity': person,
'embedded': True,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_person_is_context(self):
person = Person('P0')
@@ -38,7 +38,7 @@ def test_person_is_context(self):
'entity': person,
'person_context': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_private(self):
person = Person('P0')
@@ -47,7 +47,7 @@ def test_private(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_entity(self):
person = Person('P0')
@@ -56,4 +56,4 @@ def test_with_entity(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/test_label_place_html_j2.py b/betty/tests/assets/templates/test_label_place_html_j2.py
index bea074835..4cbcb23ff 100644
--- a/betty/tests/assets/templates/test_label_place_html_j2.py
+++ b/betty/tests/assets/templates/test_label_place_html_j2.py
@@ -1,6 +1,6 @@
-from typing import Optional, ContextManager, List
+from typing import Optional, ContextManager
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.locale import DateRange, Date
@@ -11,18 +11,20 @@
class Test(TemplateTestCase):
template_file = 'entity/label--place.html.j2'
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, data, locale', [
(
'
The Place',
{
'entity': Place('P0', [PlaceName('The Place')]),
},
+ None,
),
(
'The Place',
{
'entity': Place('P0', [PlaceName('The Place', 'en')]),
},
+ None,
),
(
'De Plaats',
@@ -37,6 +39,7 @@ class Test(TemplateTestCase):
'entity': Place('P0', [PlaceName('The Place')]),
'embedded': True,
},
+ None,
),
(
'De Nieuwe Plaats',
@@ -50,9 +53,9 @@ class Test(TemplateTestCase):
'nl',
),
])
- def test(self, expected: str, data, locale: Optional[str] = None) -> None:
- def _set_up(app: App) -> List[ContextManager]:
- return [app.acquire_locale(locale)] # type: ignore
+ def test(self, expected: str, data, locale: Optional[str]) -> None:
+ def _set_up(app: App) -> ContextManager[None]:
+ return app.acquire_locale(locale) # type: ignore
with self._render(data=data, set_up=_set_up) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/test_meta_person_html_j2.py b/betty/tests/assets/templates/test_meta_person_html_j2.py
index ab4d15c89..279dbce01 100644
--- a/betty/tests/assets/templates/test_meta_person_html_j2.py
+++ b/betty/tests/assets/templates/test_meta_person_html_j2.py
@@ -13,7 +13,7 @@ def test_without_meta(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_private(self):
person = Person('P0')
@@ -22,7 +22,7 @@ def test_private(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_one_alternative_name(self):
person = Person('P0')
@@ -33,7 +33,7 @@ def test_with_one_alternative_name(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_multiple_alternative_names(self):
person = Person('P0')
@@ -44,7 +44,7 @@ def test_with_multiple_alternative_names(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_start(self):
person = Person('P0')
@@ -53,7 +53,7 @@ def test_with_start(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_end(self):
person = Person('P0')
@@ -62,7 +62,7 @@ def test_with_end(self):
with self._render(data={
'entity': person,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_embedded(self):
person = Person('P0')
@@ -75,4 +75,4 @@ def test_embedded(self):
'entity': person,
'embedded': True,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/assets/templates/test_meta_place_html_j2.py b/betty/tests/assets/templates/test_meta_place_html_j2.py
index 3dd775850..4f21a9049 100644
--- a/betty/tests/assets/templates/test_meta_place_html_j2.py
+++ b/betty/tests/assets/templates/test_meta_place_html_j2.py
@@ -11,7 +11,7 @@ def test_without_enclosing_places(self):
with self._render(data={
'entity': place,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_enclosing_place_without_place_context(self):
place = Place('P0', [PlaceName('The Place')])
@@ -23,7 +23,7 @@ def test_with_enclosing_place_without_place_context(self):
with self._render(data={
'entity': place,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_enclosing_place_with_matching_place_context(self):
place = Place('P0', [PlaceName('The Place')])
@@ -36,7 +36,7 @@ def test_with_enclosing_place_with_matching_place_context(self):
'entity': place,
'place_context': all_enclosing_place,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_with_enclosing_place_with_non_matching_place_context(self):
place = Place('P0', [PlaceName('The Place')])
@@ -50,4 +50,4 @@ def test_with_enclosing_place_with_non_matching_place_context(self):
'entity': place,
'place_context': unrelated_place,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/cleaner/test__init__.py b/betty/tests/cleaner/test___init__.py
similarity index 69%
rename from betty/tests/cleaner/test__init__.py
rename to betty/tests/cleaner/test___init__.py
index 9169ef59a..efad3e4d2 100644
--- a/betty/tests/cleaner/test__init__.py
+++ b/betty/tests/cleaner/test___init__.py
@@ -1,26 +1,23 @@
from betty.app import App
-from betty.asyncio import sync
from betty.cleaner import clean, Cleaner
from betty.load import load
from betty.model.ancestry import Ancestry, Person, Place, Presence, PlaceName, File, PersonName, Subject, \
Enclosure, Source, Citation, Event
from betty.model.event_type import Birth
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
-class CleanerTest(TestCase):
- @sync
+class TestCleaner:
async def test_post_parse(self) -> None:
event = Event('E0', Birth())
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Cleaner))
app.project.ancestry.entities.append(event)
await load(app)
- self.assertEqual([], list(app.project.ancestry.entities[Event]))
+ assert [] == list(app.project.ancestry.entities[Event])
-class CleanTest(TestCase):
+class TestClean:
def test_clean(self) -> None:
ancestry = Ancestry()
@@ -46,13 +43,12 @@ def test_clean(self) -> None:
clean(ancestry)
- self.assertEqual([onymous_event], list(ancestry.entities[Event]))
- self.assertEqual([onymous_place, onmyous_place_because_encloses_onmyous_places], list(ancestry.entities[Place]))
+ assert [onymous_event] == list(ancestry.entities[Event])
+ assert [onymous_place == onmyous_place_because_encloses_onmyous_places], list(ancestry.entities[Place])
- self.assertNotIn(
- anonymous_place, onmyous_place_because_encloses_onmyous_places.encloses)
+ assert anonymous_place not in onmyous_place_because_encloses_onmyous_places.encloses
- def test_clean_should_not_clean_person_if_public(self):
+ def test_clean_should_not_clean_person_if_public(self) -> None:
ancestry = Ancestry()
person = Person('P0')
@@ -61,7 +57,7 @@ def test_clean_should_not_clean_person_if_public(self):
clean(ancestry)
- self.assertEqual(person, ancestry.entities[Person][person.id])
+ assert person == ancestry.entities[Person][person.id]
def test_clean_should_clean_person_with_private_children(self) -> None:
ancestry = Ancestry()
@@ -81,9 +77,9 @@ def test_clean_should_clean_person_with_private_children(self) -> None:
clean(ancestry)
- self.assertNotIn(person.id, ancestry.entities[Person])
+ assert person.id not in ancestry.entities[Person]
- def test_clean_should_not_clean_person_with_public_children(self):
+ def test_clean_should_not_clean_person_with_public_children(self) -> None:
ancestry = Ancestry()
person = Person('P0')
@@ -101,7 +97,7 @@ def test_clean_should_not_clean_person_with_public_children(self):
clean(ancestry)
- self.assertEqual(person, ancestry.entities[Person][person.id])
+ assert person == ancestry.entities[Person][person.id]
def test_clean_should_clean_event(self) -> None:
ancestry = Ancestry()
@@ -126,14 +122,14 @@ def test_clean_should_clean_event(self) -> None:
clean(ancestry)
- self.assertNotIn(event.id, ancestry.entities[Event])
- self.assertIsNone(event.place)
- self.assertNotIn(event, place.events)
- self.assertNotIn(place.id, ancestry.entities[Place])
- self.assertNotIn(event, citation.facts)
- self.assertNotIn(citation.id, ancestry.entities[Citation])
- self.assertNotIn(event, file.entities)
- self.assertNotIn(file.id, ancestry.entities[File])
+ assert event.id not in ancestry.entities[Event]
+ assert event.place is None
+ assert event not in place.events
+ assert place.id not in ancestry.entities[Place]
+ assert event not in citation.facts
+ assert citation.id not in ancestry.entities[Citation]
+ assert event not in file.entities
+ assert file.id not in ancestry.entities[File]
def test_clean_should_not_clean_event_with_presences_with_people(self) -> None:
ancestry = Ancestry()
@@ -162,13 +158,13 @@ def test_clean_should_not_clean_event_with_presences_with_people(self) -> None:
clean(ancestry)
- self.assertEqual(event, ancestry.entities[Event][event.id])
- self.assertIn(event, place.events)
- self.assertEqual(place, ancestry.entities[Place][place.id])
- self.assertIn(event, citation.facts)
- self.assertEqual(citation, ancestry.entities[Citation][citation.id])
- self.assertIn(event, file.entities)
- self.assertEqual(file, ancestry.entities[File][file.id])
+ assert event == ancestry.entities[Event][event.id]
+ assert event in place.events
+ assert place == ancestry.entities[Place][place.id]
+ assert event in citation.facts
+ assert citation == ancestry.entities[Citation][citation.id]
+ assert event in file.entities
+ assert file == ancestry.entities[File][file.id]
def test_clean_should_clean_file(self) -> None:
ancestry = Ancestry()
@@ -178,7 +174,7 @@ def test_clean_should_clean_file(self) -> None:
clean(ancestry)
- self.assertNotIn(file.id, ancestry.entities[File])
+ assert file.id not in ancestry.entities[File]
def test_clean_should_not_clean_file_with_entities(self) -> None:
ancestry = Ancestry()
@@ -192,9 +188,9 @@ def test_clean_should_not_clean_file_with_entities(self) -> None:
clean(ancestry)
- self.assertEqual(file, ancestry.entities[File][file.id])
- self.assertIn(person, file.entities)
- self.assertEqual(person, ancestry.entities[Person][person.id])
+ assert file == ancestry.entities[File][file.id]
+ assert person in file.entities
+ assert person == ancestry.entities[Person][person.id]
def test_clean_should_not_clean_file_with_citations(self) -> None:
ancestry = Ancestry()
@@ -210,9 +206,9 @@ def test_clean_should_not_clean_file_with_citations(self) -> None:
clean(ancestry)
- self.assertEqual(file, ancestry.entities[File][file.id])
- self.assertIn(citation, file.citations)
- self.assertEqual(citation, ancestry.entities[Citation][citation.id])
+ assert file == ancestry.entities[File][file.id]
+ assert citation in file.citations
+ assert citation == ancestry.entities[Citation][citation.id]
def test_clean_should_clean_source(self) -> None:
ancestry = Ancestry()
@@ -222,7 +218,7 @@ def test_clean_should_clean_source(self) -> None:
clean(ancestry)
- self.assertNotIn(source.id, ancestry.entities[Source])
+ assert source.id not in ancestry.entities[Source]
def test_clean_should_not_clean_source_with_citations(self) -> None:
ancestry = Ancestry()
@@ -236,9 +232,9 @@ def test_clean_should_not_clean_source_with_citations(self) -> None:
clean(ancestry)
- self.assertEqual(source, ancestry.entities[Source][source.id])
- self.assertEqual(source, citation.source)
- self.assertEqual(citation, ancestry.entities[Citation][citation.id])
+ assert source == ancestry.entities[Source][source.id]
+ assert source == citation.source
+ assert citation == ancestry.entities[Citation][citation.id]
def test_clean_should_not_clean_source_with_contained_by(self) -> None:
ancestry = Ancestry()
@@ -252,9 +248,9 @@ def test_clean_should_not_clean_source_with_contained_by(self) -> None:
clean(ancestry)
- self.assertEqual(source, ancestry.entities[Source][source.id])
- self.assertIn(source, contained_by.contains)
- self.assertEqual(contained_by, ancestry.entities[Source][contained_by.id])
+ assert source == ancestry.entities[Source][source.id]
+ assert source in contained_by.contains
+ assert contained_by == ancestry.entities[Source][contained_by.id]
def test_clean_should_not_clean_source_with_contains(self) -> None:
ancestry = Ancestry()
@@ -268,9 +264,9 @@ def test_clean_should_not_clean_source_with_contains(self) -> None:
clean(ancestry)
- self.assertEqual(source, ancestry.entities[Source][source.id])
- self.assertEqual(source, contains.contained_by)
- self.assertEqual(contains, ancestry.entities[Source][contains.id])
+ assert source == ancestry.entities[Source][source.id]
+ assert source == contains.contained_by
+ assert contains == ancestry.entities[Source][contains.id]
def test_clean_should_not_clean_source_with_files(self) -> None:
ancestry = Ancestry()
@@ -284,9 +280,9 @@ def test_clean_should_not_clean_source_with_files(self) -> None:
clean(ancestry)
- self.assertEqual(source, ancestry.entities[Source][source.id])
- self.assertIn(source, file.entities)
- self.assertEqual(file, ancestry.entities[File][file.id])
+ assert source == ancestry.entities[Source][source.id]
+ assert source in file.entities
+ assert file == ancestry.entities[File][file.id]
def test_clean_should_clean_citation(self) -> None:
ancestry = Ancestry()
@@ -299,8 +295,8 @@ def test_clean_should_clean_citation(self) -> None:
clean(ancestry)
- self.assertNotIn(citation.id, ancestry.entities[Citation])
- self.assertNotIn(citation, source.citations)
+ assert citation.id not in ancestry.entities[Citation]
+ assert citation not in source.citations
def test_clean_should_not_clean_citation_with_facts(self) -> None:
ancestry = Ancestry()
@@ -318,9 +314,9 @@ def test_clean_should_not_clean_citation_with_facts(self) -> None:
clean(ancestry)
- self.assertEqual(citation, ancestry.entities[Citation][citation.id])
- self.assertIn(citation, fact.citations)
- self.assertEqual(fact, ancestry.entities[Person][fact.id])
+ assert citation == ancestry.entities[Citation][citation.id]
+ assert citation in fact.citations
+ assert fact == ancestry.entities[Person][fact.id]
def test_clean_should_not_clean_citation_with_files(self) -> None:
ancestry = Ancestry()
@@ -337,7 +333,7 @@ def test_clean_should_not_clean_citation_with_files(self) -> None:
clean(ancestry)
- self.assertEqual(citation, ancestry.entities[Citation][citation.id])
- self.assertEqual(file, ancestry.entities[File][file.id])
- self.assertIn(citation, source.citations)
- self.assertEqual(source, ancestry.entities[Source][source.id])
+ assert citation == ancestry.entities[Citation][citation.id]
+ assert file == ancestry.entities[File][file.id]
+ assert citation in source.citations
+ assert source == ancestry.entities[Source][source.id]
diff --git a/betty/pytests/conftest.py b/betty/tests/conftest.py
similarity index 59%
rename from betty/pytests/conftest.py
rename to betty/tests/conftest.py
index 43ca8e97e..ec3e22804 100644
--- a/betty/pytests/conftest.py
+++ b/betty/tests/conftest.py
@@ -1,17 +1,41 @@
import gc
-from typing import Union, List, Type
+import logging
+from typing import Union, List, Type, Callable, Iterator, Optional, TypeVar
import pytest
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QMainWindow, QMenu, QWidget
+from pytestqt.qtbot import QtBot
+from betty.app import AppConfiguration
from betty.gui import BettyApplication
from betty.gui.error import Error
+_qapp_instance: Optional[BettyApplication] = None
-@pytest.fixture(scope="function")
-def qapp(qapp_args):
+
+@pytest.fixture(scope='session', autouse=True)
+def mock_app_configuration() -> Iterator[None]:
+ """
+ Prevent App from loading its application configuration from the current user session, as it would pollute the tests.
+ """
+
+ AppConfiguration._read = AppConfiguration.read # type: ignore
+ AppConfiguration.read = lambda _: None # type: ignore
+ yield
+ AppConfiguration.read = AppConfiguration._read # type: ignore
+ del AppConfiguration._read # type: ignore
+
+
+@pytest.fixture(scope='function', autouse=True)
+def set_logging(caplog) -> Iterator[None]:
+ with caplog.at_level(logging.CRITICAL):
+ yield
+
+
+@pytest.fixture(scope='function')
+def qapp(qapp_args) -> Iterator[BettyApplication]:
"""
Fixture that instantiates the BettyApplication instance that will be used by
the tests.
@@ -21,18 +45,21 @@ def qapp(qapp_args):
This overrides pytest-qt's built-in qapp fixture and adds forced garbage collection after each function.
"""
- app = BettyApplication.instance()
- if app is None:
+ qapp_instance = BettyApplication.instance()
+ if qapp_instance is None:
global _qapp_instance
_qapp_instance = BettyApplication(qapp_args)
yield _qapp_instance
else:
- yield app # pragma: no cover
+ yield qapp_instance # type: ignore
gc.collect()
+Navigate = Callable[[Union[QMainWindow, QMenu], List[str]], None]
+
+
@pytest.fixture
-def navigate(qtbot):
+def navigate(qtbot: QtBot) -> Navigate:
def _navigate(item: Union[QMainWindow, QMenu], attributes: List[str]) -> None:
if attributes:
attribute = attributes.pop(0)
@@ -48,30 +75,14 @@ def _navigate(item: Union[QMainWindow, QMenu], attributes: List[str]) -> None:
return _navigate
-@pytest.fixture
-def assert_window(assert_top_level_widget):
- def _assert_window(window_type: Type[QMainWindow]) -> QMainWindow:
- return assert_top_level_widget(window_type)
- return _assert_window
+QWidgetT = TypeVar('QWidgetT', bound=QWidget)
-@pytest.fixture
-def assert_error(qapp, qtbot):
- def _wait_assert_error(error_type: Type[Error]) -> Error:
- widget = None
-
- def _assert_error_modal():
- nonlocal widget
- widget = qapp.activeModalWidget()
- assert isinstance(widget, error_type)
- qtbot.waitUntil(_assert_error_modal)
- qtbot.addWidget(widget)
- return widget
- return _wait_assert_error
+AssertTopLevelWidget = Callable[[Type[QWidgetT]], QWidgetT]
@pytest.fixture
-def assert_top_level_widget(qapp, qtbot):
+def assert_top_level_widget(qapp: BettyApplication, qtbot: QtBot) -> AssertTopLevelWidget:
def _wait_assert_top_level_widget(widget_type: Type[QWidget]) -> QWidget:
widgets = []
@@ -86,30 +97,67 @@ def __assert_top_level_widget():
return _wait_assert_top_level_widget
-@pytest.fixture
-def assert_not_window(assert_not_top_level_widget):
- def _assert_window(window_type: Type[QMainWindow]) -> None:
- return assert_not_top_level_widget(window_type)
- return _assert_window
+AssertNotTopLevelWidget = Callable[[Type[QWidget]], None]
@pytest.fixture
-def assert_not_top_level_widget(qapp, qtbot):
+def assert_not_top_level_widget(qapp: BettyApplication, qtbot: QtBot) -> AssertNotTopLevelWidget:
def _assert_not_top_level_widget(widget_type: Type[QWidget]) -> None:
widgets = [widget for widget in qapp.topLevelWidgets() if isinstance(widget, widget_type) and widget.isVisible()]
assert len(widgets) == 0
return _assert_not_top_level_widget
+QMainWindowT = TypeVar('QMainWindowT', bound=QMainWindow)
+
+
+AssertWindow = Callable[[Type[QMainWindowT]], QMainWindowT]
+
+
+@pytest.fixture
+def assert_window(assert_top_level_widget: AssertTopLevelWidget) -> AssertWindow:
+ def _assert_window(window_type: Type[QMainWindowT]) -> QMainWindowT:
+ return assert_top_level_widget(window_type)
+ return _assert_window
+
+
+@pytest.fixture
+def assert_not_window(assert_not_top_level_widget: AssertNotTopLevelWidget):
+ def _assert_window(window_type: Type[QMainWindow]) -> None:
+ return assert_not_top_level_widget(window_type)
+ return _assert_window
+
+
+@pytest.fixture
+def assert_error(qapp: BettyApplication, qtbot: QtBot):
+ def _wait_assert_error(error_type: Type[Error]) -> Error:
+ widget = None
+
+ def _assert_error_modal():
+ nonlocal widget
+ widget = qapp.activeModalWidget()
+ assert isinstance(widget, error_type)
+ qtbot.waitUntil(_assert_error_modal)
+ qtbot.addWidget(widget)
+ return widget # type: ignore
+ return _wait_assert_error
+
+
+AssertValid = Callable[[QWidget], None]
+
+
@pytest.fixture
-def assert_valid():
+def assert_valid() -> AssertValid:
def _assert_valid(widget: QWidget) -> None:
assert widget.property('invalid') in {'false', None}
return _assert_valid
+AssertInvalid = Callable[[QWidget], None]
+
+
@pytest.fixture
-def assert_invalid():
+def assert_invalid() -> AssertInvalid:
def _assert_invalid(widget: QWidget) -> None:
assert 'true' == widget.property('invalid')
return _assert_invalid
diff --git a/betty/tests/demo/test___init__.py b/betty/tests/demo/test___init__.py
new file mode 100644
index 000000000..c99f9a1e4
--- /dev/null
+++ b/betty/tests/demo/test___init__.py
@@ -0,0 +1,17 @@
+from betty.app import App
+from betty.demo import Demo
+from betty.load import load
+from betty.model.ancestry import Person, Place, Event, Source, Citation
+from betty.project import ProjectExtensionConfiguration
+
+
+class TestDemo:
+ async def test_load(self):
+ with App() as app:
+ app.project.configuration.extensions.add(ProjectExtensionConfiguration(Demo))
+ await load(app)
+ assert 0 != len(app.project.ancestry.entities[Person])
+ assert 0 != len(app.project.ancestry.entities[Place])
+ assert 0 != len(app.project.ancestry.entities[Event])
+ assert 0 != len(app.project.ancestry.entities[Source])
+ assert 0 != len(app.project.ancestry.entities[Citation])
diff --git a/betty/tests/demo/test__init__.py b/betty/tests/demo/test__init__.py
deleted file mode 100644
index 179920048..000000000
--- a/betty/tests/demo/test__init__.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from betty.app import App
-from betty.asyncio import sync
-from betty.demo import Demo
-from betty.load import load
-from betty.model.ancestry import Person, Place, Event, Source, Citation
-from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
-
-
-class DemoTest(TestCase):
- @sync
- async def test_load(self):
- with App() as app:
- app.project.configuration.extensions.add(ProjectExtensionConfiguration(Demo))
- await load(app)
- self.assertNotEqual(0, len(app.project.ancestry.entities[Person]))
- self.assertNotEqual(0, len(app.project.ancestry.entities[Place]))
- self.assertNotEqual(0, len(app.project.ancestry.entities[Event]))
- self.assertNotEqual(0, len(app.project.ancestry.entities[Source]))
- self.assertNotEqual(0, len(app.project.ancestry.entities[Citation]))
diff --git a/betty/tests/deriver/test__init__.py b/betty/tests/deriver/test___init__.py
similarity index 77%
rename from betty/tests/deriver/test__init__.py
rename to betty/tests/deriver/test___init__.py
index c1000fe6f..e264e71ab 100644
--- a/betty/tests/deriver/test__init__.py
+++ b/betty/tests/deriver/test___init__.py
@@ -1,16 +1,14 @@
-from typing import Optional, Set, Type
+from typing import Optional, Set, Type, Iterator
-from parameterized import parameterized
+import pytest
from betty.app import App
-from betty.asyncio import sync
from betty.deriver import Deriver
from betty.load import load
from betty.locale import DateRange, Date, Datey
from betty.model.ancestry import Person, Presence, Subject, EventType, Event
from betty.model.event_type import DerivableEventType, CreatableDerivableEventType, Residence
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
class Ignored(EventType):
@@ -59,8 +57,7 @@ class ComesBeforeAndAfterCreatableDerivable(CreatableDerivableEventType, Derivab
pass
-class DeriverTest(TestCase):
- @sync
+class TestDeriver:
async def test_post_parse(self):
person = Person('P0')
reference_presence = Presence(person, Subject(), Event(None, Residence()))
@@ -71,79 +68,83 @@ async def test_post_parse(self):
app.project.ancestry.entities.append(person)
await load(app)
- self.assertEqual(3, len(person.presences))
- self.assertEqual(DateRange(None, Date(1970, 1, 1), end_is_boundary=True), person.start.date)
- self.assertEqual(DateRange(Date(1970, 1, 1), start_is_boundary=True), person.end.date)
-
-
-class DeriveTest(TestCase):
- def setUp(self) -> None:
- self._app = App()
- self._app.acquire()
- self._app.project.configuration.extensions.add(ProjectExtensionConfiguration(Deriver))
-
- def tearDown(self) -> None:
- self._app.release()
-
- @parameterized.expand([
- (ComesBeforeDerivable,),
- (ComesBeforeCreatableDerivable,),
- (ComesAfterDerivable,),
- (ComesAfterCreatableDerivable,),
- (ComesBeforeAndAfterDerivable,),
- (ComesBeforeAndAfterCreatableDerivable,),
+ assert 3 == len(person.presences)
+ start = person.start
+ assert isinstance(start, Event)
+ assert DateRange(None, Date(1970, 1, 1), end_is_boundary=True) == start.date
+ end = person.end
+ assert isinstance(end, Event)
+ assert DateRange(Date(1970, 1, 1), start_is_boundary=True) == end.date
+
+
+@pytest.fixture(scope='function')
+def test_derive_app() -> Iterator[App]:
+ app = App()
+ with app:
+ app.project.configuration.extensions.add(ProjectExtensionConfiguration(Deriver))
+ yield app
+
+
+class TestDerive:
+ @pytest.mark.parametrize('event_type_type', [
+ ComesBeforeDerivable,
+ ComesBeforeCreatableDerivable,
+ ComesAfterDerivable,
+ ComesAfterCreatableDerivable,
+ ComesBeforeAndAfterDerivable,
+ ComesBeforeAndAfterCreatableDerivable,
])
- def test_derive_without_events(self, event_type_type: Type[DerivableEventType]):
+ def test_derive_without_events(self, event_type_type: Type[DerivableEventType], test_derive_app: App):
person = Person('P0')
- created, updated = self._app.extensions[Deriver].derive_person(person, event_type_type)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, event_type_type)
- self.assertEqual(0, created)
- self.assertEqual(0, updated)
- self.assertEqual(0, len(person.presences))
+ assert 0 == created
+ assert 0 == updated
+ assert 0 == len(person.presences)
- @parameterized.expand([
- (ComesBeforeDerivable,),
- (ComesBeforeCreatableDerivable,),
- (ComesAfterDerivable,),
- (ComesAfterCreatableDerivable,),
- (ComesBeforeAndAfterDerivable,),
- (ComesBeforeAndAfterCreatableDerivable,),
+ @pytest.mark.parametrize('event_type_type', [
+ ComesBeforeDerivable,
+ ComesBeforeCreatableDerivable,
+ ComesAfterDerivable,
+ ComesAfterCreatableDerivable,
+ ComesBeforeAndAfterDerivable,
+ ComesBeforeAndAfterCreatableDerivable,
])
- def test_derive_create_derivable_events_without_reference_events(self, event_type_type: Type[DerivableEventType]):
+ def test_derive_create_derivable_events_without_reference_events(self, event_type_type: Type[DerivableEventType], test_derive_app: App):
person = Person('P0')
derivable_event = Event(None, Ignored())
Presence(person, Subject(), derivable_event)
- created, updated = self._app.extensions[Deriver].derive_person(person, event_type_type)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, event_type_type)
- self.assertEqual(0, created)
- self.assertEqual(0, updated)
- self.assertEqual(1, len(person.presences))
- self.assertIsNone(derivable_event.date)
+ assert 0 == created
+ assert 0 == updated
+ assert 1 == len(person.presences)
+ assert derivable_event.date is None
- @parameterized.expand([
- (ComesBeforeDerivable,),
- (ComesBeforeCreatableDerivable,),
- (ComesAfterDerivable,),
- (ComesAfterCreatableDerivable,),
- (ComesBeforeAndAfterDerivable,),
- (ComesBeforeAndAfterCreatableDerivable,),
+ @pytest.mark.parametrize('event_type_type', [
+ ComesBeforeDerivable,
+ ComesBeforeCreatableDerivable,
+ ComesAfterDerivable,
+ ComesAfterCreatableDerivable,
+ ComesBeforeAndAfterDerivable,
+ ComesBeforeAndAfterCreatableDerivable,
])
- def test_derive_update_derivable_event_without_reference_events(self, event_type_type: Type[DerivableEventType]):
+ def test_derive_update_derivable_event_without_reference_events(self, event_type_type: Type[DerivableEventType], test_derive_app: App):
person = Person('P0')
Presence(person, Subject(), Event(None, Ignored()))
derivable_event = Event(None, event_type_type())
Presence(person, Subject(), derivable_event)
- created, updated = self._app.extensions[Deriver].derive_person(person, event_type_type)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, event_type_type)
- self.assertEqual(0, created)
- self.assertEqual(0, updated)
- self.assertEqual(2, len(person.presences))
- self.assertIsNone(derivable_event.date)
+ assert 0 == created
+ assert 0 == updated
+ assert 2 == len(person.presences)
+ assert derivable_event.date is None
- @parameterized.expand([
+ @pytest.mark.parametrize('expected_datey, before_datey, derivable_datey', [
(None, None, None),
(Date(2000, 1, 1), Date(1970, 1, 1), Date(2000, 1, 1)),
(Date(1969, 1, 1), Date(1970, 1, 1), Date(1969, 1, 1)),
@@ -190,7 +191,7 @@ def test_derive_update_derivable_event_without_reference_events(self, event_type
(DateRange(Date(1969, 1, 1), Date(1969, 12, 31)), DateRange(None, Date(1970, 1, 1)), DateRange(Date(1969, 1, 1), Date(1969, 12, 31))),
(DateRange(None, Date(1970, 1, 1), end_is_boundary=True), DateRange(Date(1970, 1, 1), Date(1999, 12, 31)), None),
])
- def test_derive_update_comes_before_derivable_event(self, expected_datey: Optional[Datey], before_datey: Optional[Datey], derivable_datey: Optional[Datey]):
+ def test_derive_update_comes_before_derivable_event(self, expected_datey: Optional[Datey], before_datey: Optional[Datey], derivable_datey: Optional[Datey], test_derive_app: App):
expected_updates = 0 if expected_datey == derivable_datey else 1
person = Person('P0')
Presence(person, Subject(), Event(None, Ignored(), Date(0, 0, 0)))
@@ -198,14 +199,14 @@ def test_derive_update_comes_before_derivable_event(self, expected_datey: Option
derivable_event = Event(None, ComesBeforeDerivable(), derivable_datey)
Presence(person, Subject(), derivable_event)
- created, updated = self._app.extensions[Deriver].derive_person(person, ComesBeforeDerivable)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, ComesBeforeDerivable)
- self.assertEqual(0, created)
- self.assertEqual(expected_updates, updated)
- self.assertEqual(3, len(person.presences))
- self.assertEqual(expected_datey, derivable_event.date)
+ assert 0 == created
+ assert expected_updates == updated
+ assert 3 == len(person.presences)
+ assert expected_datey == derivable_event.date
- @parameterized.expand([
+ @pytest.mark.parametrize('expected_datey, before_datey', [
(None, None,),
(DateRange(None, Date(1970, 1, 1), end_is_boundary=True), Date(1970, 1, 1)),
(None, DateRange(None, None)),
@@ -213,25 +214,25 @@ def test_derive_update_comes_before_derivable_event(self, expected_datey: Option
(DateRange(None, Date(1970, 1, 1), end_is_boundary=True), DateRange(None, Date(1970, 1, 1))),
(DateRange(None, Date(1970, 1, 1), end_is_boundary=True), DateRange(Date(1970, 1, 1), Date(1971, 1, 1))),
])
- def test_derive_create_comes_before_derivable_event(self, expected_datey: Optional[Datey], before_datey: Optional[Datey]):
+ def test_derive_create_comes_before_derivable_event(self, expected_datey: Optional[Datey], before_datey: Optional[Datey], test_derive_app: App):
expected_creations = 0 if expected_datey is None else 1
person = Person('P0')
Presence(person, Subject(), Event(None, Ignored(), Date(0, 0, 0)))
Presence(person, Subject(), Event(None, ComesBeforeReference(), before_datey))
- created, updated = self._app.extensions[Deriver].derive_person(person, ComesBeforeCreatableDerivable)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, ComesBeforeCreatableDerivable)
derived_presences = [presence for presence in person.presences if isinstance(presence.event.type, ComesBeforeCreatableDerivable)]
- self.assertEqual(expected_creations, len(derived_presences))
+ assert expected_creations == len(derived_presences)
if expected_creations:
derived_presence = derived_presences[0]
- self.assertIsInstance(derived_presence.role, Subject)
- self.assertEqual(expected_datey, derived_presence.event.date)
- self.assertEqual(expected_creations, created)
- self.assertEqual(0, updated)
- self.assertEqual(2 + expected_creations, len(person.presences))
+ assert isinstance(derived_presence.role, Subject)
+ assert expected_datey == derived_presence.event.date
+ assert expected_creations == created
+ assert 0 == updated
+ assert 2 + expected_creations == len(person.presences)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected_datey, after_datey, derivable_datey', [
(None, None, None),
(Date(2000, 1, 1), Date(1970, 1, 1), Date(2000, 1, 1)),
(Date(1969, 1, 1), Date(1970, 1, 1), Date(1969, 1, 1)),
@@ -278,7 +279,7 @@ def test_derive_create_comes_before_derivable_event(self, expected_datey: Option
(DateRange(Date(1969, 1, 1), Date(1969, 12, 31)), DateRange(None, Date(1970, 1, 1)), DateRange(Date(1969, 1, 1), Date(1969, 12, 31))),
(DateRange(Date(1999, 12, 31), start_is_boundary=True), DateRange(Date(1970, 1, 1), Date(1999, 12, 31)), None),
])
- def test_derive_update_comes_after_derivable_event(self, expected_datey: Optional[Datey], after_datey: Optional[Datey], derivable_datey: Optional[Datey]):
+ def test_derive_update_comes_after_derivable_event(self, expected_datey: Optional[Datey], after_datey: Optional[Datey], derivable_datey: Optional[Datey], test_derive_app: App):
expected_updates = 0 if expected_datey == derivable_datey else 1
person = Person('P0')
Presence(person, Subject(), Event(None, Ignored(), Date(0, 0, 0)))
@@ -286,14 +287,14 @@ def test_derive_update_comes_after_derivable_event(self, expected_datey: Optiona
derivable_event = Event(None, ComesAfterDerivable(), derivable_datey)
Presence(person, Subject(), derivable_event)
- created, updated = self._app.extensions[Deriver].derive_person(person, ComesAfterDerivable)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, ComesAfterDerivable)
- self.assertEqual(expected_datey, derivable_event.date)
- self.assertEqual(0, created)
- self.assertEqual(expected_updates, updated)
- self.assertEqual(3, len(person.presences))
+ assert expected_datey == derivable_event.date
+ assert 0 == created
+ assert expected_updates == updated
+ assert 3 == len(person.presences)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected_datey, after_datey', [
(None, None),
(None, Date()),
(DateRange(Date(1970, 1, 1), start_is_boundary=True), Date(1970, 1, 1)),
@@ -302,20 +303,20 @@ def test_derive_update_comes_after_derivable_event(self, expected_datey: Optiona
(DateRange(Date(1999, 12, 31), start_is_boundary=True), DateRange(Date(1970, 1, 1), Date(1999, 12, 31))),
(DateRange(Date(1970, 1, 1), start_is_boundary=True), DateRange(Date(1970, 1, 1), Date(1999, 12, 31), end_is_boundary=True)),
])
- def test_derive_create_comes_after_derivable_event(self, expected_datey: Optional[Datey], after_datey: Optional[Datey]):
+ def test_derive_create_comes_after_derivable_event(self, expected_datey: Optional[Datey], after_datey: Optional[Datey], test_derive_app: App):
expected_creations = 0 if expected_datey is None else 1
person = Person('P0')
Presence(person, Subject(), Event(None, Ignored(), Date(0, 0, 0)))
Presence(person, Subject(), Event(None, ComesAfterReference(), after_datey))
- created, updated = self._app.extensions[Deriver].derive_person(person, ComesAfterCreatableDerivable)
+ created, updated = test_derive_app.extensions[Deriver].derive_person(person, ComesAfterCreatableDerivable)
derived_presences = [presence for presence in person.presences if isinstance(presence.event.type, ComesAfterCreatableDerivable)]
- self.assertEqual(expected_creations, len(derived_presences))
+ assert expected_creations == len(derived_presences)
if expected_creations:
derived_presence = derived_presences[0]
- self.assertIsInstance(derived_presence.role, Subject)
- self.assertEqual(expected_datey, derived_presence.event.date)
- self.assertEqual(expected_creations, created)
- self.assertEqual(0, updated)
- self.assertEqual(2 + expected_creations, len(person.presences))
+ assert isinstance(derived_presence.role, Subject)
+ assert expected_datey == derived_presence.event.date
+ assert expected_creations == created
+ assert 0 == updated
+ assert 2 + expected_creations == len(person.presences)
diff --git a/betty/pytests/__init__.py b/betty/tests/extension/__init__.py
similarity index 100%
rename from betty/pytests/__init__.py
rename to betty/tests/extension/__init__.py
diff --git a/betty/pytests/extension/__init__.py b/betty/tests/extension/gramps/__init__.py
similarity index 100%
rename from betty/pytests/extension/__init__.py
rename to betty/tests/extension/gramps/__init__.py
diff --git a/betty/pytests/extension/gramps/test_gui.py b/betty/tests/extension/gramps/test_gui.py
similarity index 74%
rename from betty/pytests/extension/gramps/test_gui.py
rename to betty/tests/extension/gramps/test_gui.py
index 8ab2bad29..857cbf7df 100644
--- a/betty/pytests/extension/gramps/test_gui.py
+++ b/betty/tests/extension/gramps/test_gui.py
@@ -9,9 +9,10 @@
from betty.gramps.config import FamilyTreeConfiguration
from betty.gramps.gui import _AddFamilyTreeWindow
from betty.project import ProjectExtensionConfiguration
+from betty.tests.conftest import AssertWindow
-async def test_add_family_tree_set_path(assert_not_window, assert_window, tmpdir, qtbot) -> None:
+async def test_add_family_tree_set_path(assert_not_window, assert_window: AssertWindow, qtbot) -> None:
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Gramps))
sut = app.extensions[Gramps]
@@ -19,7 +20,7 @@ async def test_add_family_tree_set_path(assert_not_window, assert_window, tmpdir
qtbot.addWidget(widget)
widget.show()
- qtbot.mouseClick(widget._add_family_tree_button, Qt.MouseButton.LeftButton)
+ qtbot.mouseClick(widget._family_trees._add_family_tree_button, Qt.MouseButton.LeftButton)
add_family_tree_window = assert_window(_AddFamilyTreeWindow)
file_path = '/tmp/family-tree.gpkg'
@@ -28,12 +29,12 @@ async def test_add_family_tree_set_path(assert_not_window, assert_window, tmpdir
qtbot.mouseClick(add_family_tree_window._widget._save_and_close, Qt.MouseButton.LeftButton)
assert_not_window(_AddFamilyTreeWindow)
- assert len(sut._configuration.family_trees) == 1
- family_tree = sut._configuration.family_trees[0]
+ assert len(sut.configuration.family_trees) == 1
+ family_tree = sut.configuration.family_trees[0]
assert family_tree.file_path == Path(file_path)
-async def test_add_family_tree_find_path(assert_window, mocker, tmpdir, qtbot) -> None:
+async def test_add_family_tree_find_path(assert_window, mocker, qtbot) -> None:
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Gramps))
sut = app.extensions[Gramps]
@@ -41,7 +42,7 @@ async def test_add_family_tree_find_path(assert_window, mocker, tmpdir, qtbot) -
qtbot.addWidget(widget)
widget.show()
- qtbot.mouseClick(widget._add_family_tree_button, Qt.MouseButton.LeftButton)
+ qtbot.mouseClick(widget._family_trees._add_family_tree_button, Qt.MouseButton.LeftButton)
add_family_tree_window = assert_window(_AddFamilyTreeWindow)
file_path = '/tmp/family-tree.gpkg'
@@ -49,12 +50,12 @@ async def test_add_family_tree_find_path(assert_window, mocker, tmpdir, qtbot) -
qtbot.mouseClick(add_family_tree_window._widget._file_path_find, Qt.MouseButton.LeftButton)
qtbot.mouseClick(add_family_tree_window._widget._save_and_close, Qt.MouseButton.LeftButton)
- assert len(sut._configuration.family_trees) == 1
- family_tree = sut._configuration.family_trees[0]
+ assert len(sut.configuration.family_trees) == 1
+ family_tree = sut.configuration.family_trees[0]
assert family_tree.file_path == Path(file_path)
-async def test_remove_family_tree(tmpdir, qtbot) -> None:
+async def test_remove_family_tree(qtbot) -> None:
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(
Gramps,
@@ -69,7 +70,7 @@ async def test_remove_family_tree(tmpdir, qtbot) -> None:
qtbot.addWidget(widget)
widget.show()
- qtbot.mouseClick(widget._family_trees_widget._remove_buttons[0], Qt.MouseButton.LeftButton)
+ qtbot.mouseClick(widget._family_trees._family_trees_remove_buttons[0], Qt.MouseButton.LeftButton)
- assert len(sut._configuration.family_trees) == 0
- assert [] == widget._family_trees_widget._remove_buttons
+ assert len(sut.configuration.family_trees) == 0
+ assert [] == widget._family_trees._family_trees_remove_buttons
diff --git a/betty/tests/gramps/test___init__.py b/betty/tests/gramps/test___init__.py
index bd7a2180d..0d0eacbc1 100644
--- a/betty/tests/gramps/test___init__.py
+++ b/betty/tests/gramps/test___init__.py
@@ -4,18 +4,15 @@
from reactives import ReactiveList
from betty.app import App
-from betty.asyncio import sync
from betty.gramps import Gramps, GrampsConfiguration
from betty.gramps.config import FamilyTreeConfiguration
from betty.load import load
from betty.model.ancestry import Citation, Note, Source, File, \
Event, Person, Place
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
-class GrampsTest(TestCase):
- @sync
+class TestGramps:
async def test_load_multiple_family_trees(self):
family_tree_one_xml = """
@@ -137,19 +134,19 @@ async def test_load_multiple_family_trees(self):
])
)))
await load(app)
- self.assertIn('O0001', app.project.ancestry.entities[File])
- self.assertIn('O0002', app.project.ancestry.entities[File])
- self.assertIn('I0001', app.project.ancestry.entities[Person])
- self.assertIn('I0002', app.project.ancestry.entities[Person])
- self.assertIn('P0001', app.project.ancestry.entities[Place])
- self.assertIn('P0002', app.project.ancestry.entities[Place])
- self.assertIn('E0001', app.project.ancestry.entities[Event])
- self.assertIn('E0002', app.project.ancestry.entities[Event])
- self.assertIn('S0001', app.project.ancestry.entities[Source])
- self.assertIn('S0002', app.project.ancestry.entities[Source])
- self.assertIn('R0001', app.project.ancestry.entities[Source])
- self.assertIn('R0002', app.project.ancestry.entities[Source])
- self.assertIn('C0001', app.project.ancestry.entities[Citation])
- self.assertIn('C0002', app.project.ancestry.entities[Citation])
- self.assertIn('N0001', app.project.ancestry.entities[Note])
- self.assertIn('N0002', app.project.ancestry.entities[Note])
+ assert 'O0001' in app.project.ancestry.entities[File]
+ assert 'O0002' in app.project.ancestry.entities[File]
+ assert 'I0001' in app.project.ancestry.entities[Person]
+ assert 'I0002' in app.project.ancestry.entities[Person]
+ assert 'P0001' in app.project.ancestry.entities[Place]
+ assert 'P0002' in app.project.ancestry.entities[Place]
+ assert 'E0001' in app.project.ancestry.entities[Event]
+ assert 'E0002' in app.project.ancestry.entities[Event]
+ assert 'S0001' in app.project.ancestry.entities[Source]
+ assert 'S0002' in app.project.ancestry.entities[Source]
+ assert 'R0001' in app.project.ancestry.entities[Source]
+ assert 'R0002' in app.project.ancestry.entities[Source]
+ assert 'C0001' in app.project.ancestry.entities[Citation]
+ assert 'C0002' in app.project.ancestry.entities[Citation]
+ assert 'N0001' in app.project.ancestry.entities[Note]
+ assert 'N0002' in app.project.ancestry.entities[Note]
diff --git a/betty/tests/gramps/test_loader.py b/betty/tests/gramps/test_loader.py
index 08a39ac5f..0e7f4a552 100644
--- a/betty/tests/gramps/test_loader.py
+++ b/betty/tests/gramps/test_loader.py
@@ -2,42 +2,41 @@
from tempfile import TemporaryDirectory
from typing import Optional
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.gramps.loader import load_xml, load_gpkg, load_gramps
-from betty.locale import Date
+from betty.locale import Date, DateRange
from betty.model.ancestry import Ancestry, PersonName, Citation, Note, Source, File, Event, Person, Place
from betty.model.event_type import Birth, Death, UnknownEventType
from betty.path import rootname
-from betty.tests import TestCase
-class LoadGrampsTest(TestCase):
+class TestLoadGramps:
def test_load_gramps(self):
with App() as app:
gramps_file_path = Path(__file__).parent / 'assets' / 'minimal.gramps'
load_gramps(app.project.ancestry, gramps_file_path)
-class LoadGpkgTest(TestCase):
+class TestLoadGpkg:
def test_load_gpkg(self):
with App() as app:
gramps_file_path = Path(__file__).parent / 'assets' / 'minimal.gpkg'
load_gpkg(app.project.ancestry, gramps_file_path)
-class LoadXmlTest(TestCase):
- @classmethod
- def setUpClass(cls) -> None:
- TestCase.setUpClass()
- # @todo Convert each test method to use self._load(), so we can remove this shared XML file.
- with App() as app:
- cls.ancestry = app.project.ancestry
- xml_file_path = Path(__file__).parent / 'assets' / 'data.xml'
- with open(xml_file_path) as f:
- load_xml(app.project.ancestry, f.read(), rootname(xml_file_path))
+@pytest.fixture(scope='class')
+def test_load_xml_ancestry() -> Ancestry:
+ # @todo Convert each test method to use self._load(), so we can remove this shared XML file.
+ with App() as app:
+ xml_file_path = Path(__file__).parent / 'assets' / 'data.xml'
+ with open(xml_file_path) as f:
+ load_xml(app.project.ancestry, f.read(), rootname(xml_file_path))
+ return app.project.ancestry
+
+class TestLoadXml:
def load(self, xml: str) -> Ancestry:
with App() as app:
with TemporaryDirectory() as tree_directory_path:
@@ -70,83 +69,94 @@ def test_load_xml_with_file_path(self):
gramps_file_path = Path(__file__).parent / 'assets' / 'minimal.xml'
load_xml(app.project.ancestry, gramps_file_path, rootname(gramps_file_path))
- def test_place_should_include_name(self):
- place = self.ancestry.entities[Place]['P0000']
+ def test_place_should_include_name(self, test_load_xml_ancestry):
+ place = test_load_xml_ancestry.entities[Place]['P0000']
names = place.names
- self.assertEqual(1, len(names))
+ assert 1 == len(names)
name = names[0]
- self.assertEqual('Amsterdam', name.name)
+ assert 'Amsterdam' == name.name
- def test_place_should_include_coordinates(self):
- place = self.ancestry.entities[Place]['P0000']
- self.assertAlmostEqual(52.366667, place.coordinates.latitude)
- self.assertAlmostEqual(4.9, place.coordinates.longitude)
+ def test_place_should_include_coordinates(self, test_load_xml_ancestry):
+ place = test_load_xml_ancestry.entities[Place]['P0000']
+ assert pytest.approx(52.366667) == place.coordinates.latitude
+ assert pytest.approx(4.9) == place.coordinates.longitude
- def test_place_should_include_events(self):
- place = self.ancestry.entities[Place]['P0000']
- event = self.ancestry.entities[Event]['E0000']
- self.assertEqual(place, event.place)
- self.assertIn(event, place.events)
+ def test_place_should_include_events(self, test_load_xml_ancestry):
+ place = test_load_xml_ancestry.entities[Place]['P0000']
+ event = test_load_xml_ancestry.entities[Event]['E0000']
+ assert place == event.place
+ assert event in place.events
- def test_person_should_include_name(self):
- person = self.ancestry.entities[Person]['I0000']
+ def test_person_should_include_name(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0000']
expected = PersonName(person, 'Jane', 'Doe')
- self.assertEqual(expected, person.name)
-
- def test_person_should_include_alternative_names(self):
- person = self.ancestry.entities[Person]['I0000']
- self.assertEqual(2, len(person.alternative_names))
- self.assertIs(person, person.alternative_names[0].person)
- self.assertEqual('Jane', person.alternative_names[0].individual)
- self.assertEqual('Doh', person.alternative_names[0].affiliation)
- self.assertIs(person, person.alternative_names[1].person)
- self.assertEqual('Jen', person.alternative_names[1].individual)
- self.assertEqual('Van Doughie', person.alternative_names[1].affiliation)
-
- def test_person_should_include_birth(self):
- person = self.ancestry.entities[Person]['I0000']
- self.assertEqual('E0000', person.start.id)
-
- def test_person_should_include_death(self):
- person = self.ancestry.entities[Person]['I0003']
- self.assertEqual('E0002', person.end.id)
-
- def test_person_should_be_private(self):
- person = self.ancestry.entities[Person]['I0003']
- self.assertTrue(person.private)
-
- def test_person_should_not_be_private(self):
- person = self.ancestry.entities[Person]['I0002']
- self.assertFalse(person.private)
-
- def test_person_should_include_citation(self):
- person = self.ancestry.entities[Person]['I0000']
- citation = self.ancestry.entities[Citation]['C0000']
- self.assertIn(citation, person.citations)
-
- def test_family_should_set_parents(self):
- expected_parents = [self.ancestry.entities[Person]['I0002'],
- self.ancestry.entities[Person]['I0003']]
- children = [self.ancestry.entities[Person]['I0000'],
- self.ancestry.entities[Person]['I0001']]
+ assert expected == person.name
+
+ def test_person_should_include_alternative_names(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0000']
+ assert 3 == len(person.alternative_names)
+ assert person is person.alternative_names[0].person
+ assert 'Jane' == person.alternative_names[0].individual
+ assert 'Doh' == person.alternative_names[0].affiliation
+ assert person is person.alternative_names[1].person
+ assert 'Jen' == person.alternative_names[1].individual
+ assert 'Van Doughie' == person.alternative_names[1].affiliation
+ assert person is person.alternative_names[2].person
+ assert 'Jane' == person.alternative_names[2].individual
+ assert 'Doe' == person.alternative_names[2].affiliation
+
+ def test_person_should_include_birth(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0000']
+ assert 'E0000' == person.start.id
+
+ def test_person_should_include_death(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0003']
+ assert 'E0002' == person.end.id
+
+ def test_person_should_be_private(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0003']
+ assert person.private
+
+ def test_person_should_not_be_private(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0002']
+ assert not person.private
+
+ def test_person_should_include_citation(self, test_load_xml_ancestry):
+ person = test_load_xml_ancestry.entities[Person]['I0000']
+ citation = test_load_xml_ancestry.entities[Citation]['C0000']
+ assert citation in person.citations
+
+ def test_family_should_set_parents(self, test_load_xml_ancestry):
+ expected_parents = [
+ test_load_xml_ancestry.entities[Person]['I0002'],
+ test_load_xml_ancestry.entities[Person]['I0003'],
+ ]
+ children = [
+ test_load_xml_ancestry.entities[Person]['I0000'],
+ test_load_xml_ancestry.entities[Person]['I0001'],
+ ]
for child in children:
- self.assertCountEqual(expected_parents, child.parents)
-
- def test_family_should_set_children(self):
- parents = [self.ancestry.entities[Person]['I0002'],
- self.ancestry.entities[Person]['I0003']]
- expected_children = [self.ancestry.entities[Person]['I0000'],
- self.ancestry.entities[Person]['I0001']]
+ assert sorted(expected_parents) == sorted(child.parents)
+
+ def test_family_should_set_children(self, test_load_xml_ancestry):
+ parents = [
+ test_load_xml_ancestry.entities[Person]['I0002'],
+ test_load_xml_ancestry.entities[Person]['I0003'],
+ ]
+ expected_children = [
+ test_load_xml_ancestry.entities[Person]['I0000'],
+ test_load_xml_ancestry.entities[Person]['I0001'],
+ ]
for parent in parents:
- self.assertCountEqual(expected_children, parent.children)
+ assert sorted(expected_children) == sorted(parent.children)
- def test_event_should_be_birth(self):
- self.assertIsInstance(self.ancestry.entities[Event]['E0000'].type, Birth)
+ def test_event_should_be_birth(self, test_load_xml_ancestry):
+ assert isinstance(test_load_xml_ancestry.entities[Event]['E0000'].type, Birth)
- def test_event_should_be_death(self):
- self.assertIsInstance(self.ancestry.entities[Event]['E0002'].type, Death)
+ def test_event_should_be_death(self, test_load_xml_ancestry):
+ assert isinstance(test_load_xml_ancestry.entities[Event]['E0002'].type, Death)
- def test_event_should_load_unknown(self):
+ def test_event_should_load_unknown(self, test_load_xml_ancestry):
ancestry = self._load_partial("""
@@ -155,30 +165,29 @@ def test_event_should_load_unknown(self):
""")
- self.assertIsInstance(ancestry.entities[Event]['E0000'].type, UnknownEventType)
-
- def test_event_should_include_place(self):
- event = self.ancestry.entities[Event]['E0000']
- place = self.ancestry.entities[Place]['P0000']
- self.assertEqual(place, event.place)
-
- def test_event_should_include_date(self):
- event = self.ancestry.entities[Event]['E0000']
- self.assertEqual(1970, event.date.year)
- self.assertEqual(1, event.date.month)
- self.assertEqual(1, event.date.day)
-
- def test_event_should_include_people(self):
- event = self.ancestry.entities[Event]['E0000']
- expected_people = [self.ancestry.entities[Person]['I0000']]
- self.assertCountEqual(
- expected_people, [presence.person for presence in event.presences])
-
- def test_event_should_include_description(self):
- event = self.ancestry.entities[Event]['E0008']
- self.assertEqual('Something happened!', event.description)
-
- @parameterized.expand([
+ assert isinstance(ancestry.entities[Event]['E0000'].type, UnknownEventType)
+
+ def test_event_should_include_place(self, test_load_xml_ancestry):
+ event = test_load_xml_ancestry.entities[Event]['E0000']
+ place = test_load_xml_ancestry.entities[Place]['P0000']
+ assert place == event.place
+
+ def test_event_should_include_date(self, test_load_xml_ancestry):
+ event = test_load_xml_ancestry.entities[Event]['E0000']
+ assert 1970 == event.date.year
+ assert 1 == event.date.month
+ assert 1 == event.date.day
+
+ def test_event_should_include_people(self, test_load_xml_ancestry):
+ event = test_load_xml_ancestry.entities[Event]['E0000']
+ expected_people = [test_load_xml_ancestry.entities[Person]['I0000']]
+ assert expected_people == [presence.person for presence in event.presences]
+
+ def test_event_should_include_description(self, test_load_xml_ancestry):
+ event = test_load_xml_ancestry.entities[Event]['E0008']
+ assert 'Something happened!' == event.description
+
+ @pytest.mark.parametrize('expected, dateval_val', [
(Date(), '0000-00-00'),
(Date(None, None, 1), '0000-00-01'),
(Date(None, 1), '0000-01-00'),
@@ -197,28 +206,28 @@ def test_date_should_load_parts(self, expected: Date, dateval_val: str):
""" % dateval_val)
- self.assertEqual(expected, ancestry.entities[Event]['E0000'].date)
-
- def test_date_should_ignore_calendar_format(self):
- self.assertIsNone(self.ancestry.entities[Event]['E0005'].date)
-
- def test_date_should_load_before(self):
- date = self.ancestry.entities[Event]['E0003'].date
- self.assertIsNone(date.start)
- self.assertEqual(1970, date.end.year)
- self.assertEqual(1, date.end.month)
- self.assertEqual(1, date.end.day)
- self.assertTrue(date.end_is_boundary)
- self.assertFalse(date.end.fuzzy)
-
- def test_date_should_load_after(self):
- date = self.ancestry.entities[Event]['E0004'].date
- self.assertIsNone(date.end)
- self.assertEqual(1970, date.start.year)
- self.assertEqual(1, date.start.month)
- self.assertEqual(1, date.start.day)
- self.assertTrue(date.start_is_boundary)
- self.assertFalse(date.start.fuzzy)
+ assert expected == ancestry.entities[Event]['E0000'].date
+
+ def test_date_should_ignore_calendar_format(self, test_load_xml_ancestry):
+ assert test_load_xml_ancestry.entities[Event]['E0005'].date is None
+
+ def test_date_should_load_before(self, test_load_xml_ancestry):
+ date = test_load_xml_ancestry.entities[Event]['E0003'].date
+ assert date.start is None
+ assert 1970 == date.end.year
+ assert 1 == date.end.month
+ assert 1 == date.end.day
+ assert date.end_is_boundary
+ assert not date.end.fuzzy
+
+ def test_date_should_load_after(self, test_load_xml_ancestry):
+ date = test_load_xml_ancestry.entities[Event]['E0004'].date
+ assert date.end is None
+ assert 1970 == date.start.year
+ assert 1 == date.start.month
+ assert 1 == date.start.day
+ assert date.start_is_boundary
+ assert not date.start.fuzzy
def test_date_should_load_calculated(self):
ancestry = self._load_partial("""
@@ -230,10 +239,11 @@ def test_date_should_load_calculated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertEqual(1970, date.year)
- self.assertEqual(1, date.month)
- self.assertEqual(1, date.day)
- self.assertFalse(date.fuzzy)
+ assert isinstance(date, Date)
+ assert 1970 == date.year
+ assert 1 == date.month
+ assert 1 == date.day
+ assert not date.fuzzy
def test_date_should_load_estimated(self):
ancestry = self._load_partial("""
@@ -245,17 +255,18 @@ def test_date_should_load_estimated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertEqual(1970, date.year)
- self.assertEqual(1, date.month)
- self.assertEqual(1, date.day)
- self.assertTrue(date.fuzzy)
-
- def test_date_should_load_about(self):
- date = self.ancestry.entities[Event]['E0007'].date
- self.assertEqual(1970, date.year)
- self.assertEqual(1, date.month)
- self.assertEqual(1, date.day)
- self.assertTrue(date.fuzzy)
+ assert isinstance(date, Date)
+ assert 1970 == date.year
+ assert 1 == date.month
+ assert 1 == date.day
+ assert date.fuzzy
+
+ def test_date_should_load_about(self, test_load_xml_ancestry):
+ date = test_load_xml_ancestry.entities[Event]['E0007'].date
+ assert 1970 == date.year
+ assert 1 == date.month
+ assert 1 == date.day
+ assert date.fuzzy
def test_daterange_should_load(self):
ancestry = self._load_partial("""
@@ -267,16 +278,21 @@ def test_daterange_should_load(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertEqual(1970, date.start.year)
- self.assertEqual(1, date.start.month)
- self.assertEqual(1, date.start.day)
- self.assertFalse(date.start.fuzzy)
- self.assertTrue(date.start_is_boundary)
- self.assertEqual(1999, date.end.year)
- self.assertEqual(12, date.end.month)
- self.assertEqual(31, date.end.day)
- self.assertTrue(date.end_is_boundary)
- self.assertFalse(date.end.fuzzy)
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ end = date.end
+ assert isinstance(end, Date)
+ assert 1970 == start.year
+ assert 1 == start.month
+ assert 1 == start.day
+ assert not start.fuzzy
+ assert date.start_is_boundary
+ assert 1999 == end.year
+ assert 12 == end.month
+ assert 31 == end.day
+ assert date.end_is_boundary
+ assert not end.fuzzy
def test_daterange_should_load_calculated(self):
ancestry = self._load_partial("""
@@ -288,8 +304,13 @@ def test_daterange_should_load_calculated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertFalse(date.start.fuzzy)
- self.assertFalse(date.end.fuzzy)
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ assert not start.fuzzy
+ end = date.end
+ assert isinstance(end, Date)
+ assert not end.fuzzy
def test_daterange_should_load_estimated(self):
ancestry = self._load_partial("""
@@ -301,8 +322,13 @@ def test_daterange_should_load_estimated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertTrue(date.start.fuzzy)
- self.assertTrue(date.end.fuzzy)
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ assert start.fuzzy
+ end = date.end
+ assert isinstance(end, Date)
+ assert end.fuzzy
def test_datespan_should_load(self):
ancestry = self._load_partial("""
@@ -314,14 +340,19 @@ def test_datespan_should_load(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertEqual(1970, date.start.year)
- self.assertEqual(1, date.start.month)
- self.assertEqual(1, date.start.day)
- self.assertFalse(date.start.fuzzy)
- self.assertEqual(1999, date.end.year)
- self.assertEqual(12, date.end.month)
- self.assertEqual(31, date.end.day)
- self.assertFalse(date.end.fuzzy)
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ end = date.end
+ assert isinstance(end, Date)
+ assert 1970 == start.year
+ assert 1 == start.month
+ assert 1 == start.day
+ assert not start.fuzzy
+ assert 1999 == end.year
+ assert 12 == end.month
+ assert 31 == end.day
+ assert not end.fuzzy
def test_datespan_should_load_calculated(self):
ancestry = self._load_partial("""
@@ -333,8 +364,13 @@ def test_datespan_should_load_calculated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertFalse(date.start.fuzzy)
- self.assertFalse(date.end.fuzzy)
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ assert not start.fuzzy
+ end = date.end
+ assert isinstance(end, Date)
+ assert not end.fuzzy
def test_datespan_should_load_estimated(self):
ancestry = self._load_partial("""
@@ -346,38 +382,43 @@ def test_datespan_should_load_estimated(self):
""")
date = ancestry.entities[Event]['E0000'].date
- self.assertTrue(date.start.fuzzy)
- self.assertTrue(date.end.fuzzy)
-
- def test_source_from_repository_should_include_name(self):
- source = self.ancestry.entities[Source]['R0000']
- self.assertEqual('Library of Alexandria', source.name)
-
- def test_source_from_repository_should_include_link(self):
- links = self.ancestry.entities[Source]['R0000'].links
- self.assertEqual(1, len(links))
+ assert isinstance(date, DateRange)
+ start = date.start
+ assert isinstance(start, Date)
+ assert start.fuzzy
+ end = date.end
+ assert isinstance(end, Date)
+ assert end.fuzzy
+
+ def test_source_from_repository_should_include_name(self, test_load_xml_ancestry):
+ source = test_load_xml_ancestry.entities[Source]['R0000']
+ assert 'Library of Alexandria' == source.name
+
+ def test_source_from_repository_should_include_link(self, test_load_xml_ancestry):
+ links = test_load_xml_ancestry.entities[Source]['R0000'].links
+ assert 1 == len(links)
link = list(links)[0]
- self.assertEqual('https://alexandria.example.com', link.url)
- self.assertEqual('Library of Alexandria Catalogue', link.label)
+ assert 'https://alexandria.example.com' == link.url
+ assert 'Library of Alexandria Catalogue' == link.label
- def test_source_from_source_should_include_title(self):
- source = self.ancestry.entities[Source]['S0000']
- self.assertEqual('A Whisper', source.name)
+ def test_source_from_source_should_include_title(self, test_load_xml_ancestry):
+ source = test_load_xml_ancestry.entities[Source]['S0000']
+ assert 'A Whisper' == source.name
- def test_source_from_source_should_include_author(self):
- source = self.ancestry.entities[Source]['S0000']
- self.assertEqual('A Little Birdie', source.author)
+ def test_source_from_source_should_include_author(self, test_load_xml_ancestry):
+ source = test_load_xml_ancestry.entities[Source]['S0000']
+ assert 'A Little Birdie' == source.author
- def test_source_from_source_should_include_publisher(self):
- source = self.ancestry.entities[Source]['S0000']
- self.assertEqual('Somewhere over the rainbow', source.publisher)
+ def test_source_from_source_should_include_publisher(self, test_load_xml_ancestry):
+ source = test_load_xml_ancestry.entities[Source]['S0000']
+ assert 'Somewhere over the rainbow' == source.publisher
- def test_source_from_source_should_include_repository(self):
- source = self.ancestry.entities[Source]['S0000']
- containing_source = self.ancestry.entities[Source]['R0000']
- self.assertEqual(containing_source, source.contained_by)
+ def test_source_from_source_should_include_repository(self, test_load_xml_ancestry):
+ source = test_load_xml_ancestry.entities[Source]['S0000']
+ containing_source = test_load_xml_ancestry.entities[Source]['R0000']
+ assert containing_source == source.contained_by
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, attribute_value', [
(True, 'private'),
(False, 'public'),
(None, 'publi'),
@@ -393,9 +434,9 @@ def test_person_should_include_privacy_from_attribute(self, expected: Optional[b
""" % attribute_value)
person = ancestry.entities[Person]['I0000']
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, attribute_value', [
(True, 'private'),
(False, 'public'),
(None, 'publi'),
@@ -411,9 +452,9 @@ def test_event_should_include_privacy_from_attribute(self, expected: Optional[bo
""" % attribute_value)
event = ancestry.entities[Event]['E0000']
- self.assertEqual(expected, event.private)
+ assert expected == event.private
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, attribute_value', [
(True, 'private'),
(False, 'public'),
(None, 'publi'),
@@ -429,9 +470,9 @@ def test_file_should_include_privacy_from_attribute(self, expected: Optional[boo
""" % attribute_value)
file = ancestry.entities[File]['O0000']
- self.assertEqual(expected, file.private)
+ assert expected == file.private
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, attribute_value', [
(True, 'private'),
(False, 'public'),
(None, 'publi'),
@@ -447,9 +488,9 @@ def test_source_from_source_should_include_privacy_from_attribute(self, expected
""" % attribute_value)
source = ancestry.entities[Source]['S0000']
- self.assertEqual(expected, source.private)
+ assert expected == source.private
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, attribute_value', [
(True, 'private'),
(False, 'public'),
(None, 'publi'),
@@ -471,7 +512,7 @@ def test_citation_should_include_privacy_from_attribute(self, expected: Optional
""" % attribute_value)
citation = ancestry.entities[Citation]['C0000']
- self.assertEqual(expected, citation.private)
+ assert expected == citation.private
def test_note_should_include_text(self) -> None:
ancestry = self._load_partial("""
@@ -482,4 +523,4 @@ def test_note_should_include_text(self) -> None:
""")
note = ancestry.entities[Note]['N0000']
- self.assertEqual('I left this for you.', note.text)
+ assert 'I left this for you.' == note.text
diff --git a/betty/pytests/extension/gramps/__init__.py b/betty/tests/gui/__init__.py
similarity index 100%
rename from betty/pytests/extension/gramps/__init__.py
rename to betty/tests/gui/__init__.py
diff --git a/betty/pytests/gui/test_app.py b/betty/tests/gui/test_app.py
similarity index 100%
rename from betty/pytests/gui/test_app.py
rename to betty/tests/gui/test_app.py
diff --git a/betty/pytests/gui/test_project.py b/betty/tests/gui/test_project.py
similarity index 100%
rename from betty/pytests/gui/test_project.py
rename to betty/tests/gui/test_project.py
diff --git a/betty/tests/http_api_doc/test__init__.py b/betty/tests/http_api_doc/test___init__.py
similarity index 51%
rename from betty/tests/http_api_doc/test__init__.py
rename to betty/tests/http_api_doc/test___init__.py
index d136ae7b3..ffcfff425 100644
--- a/betty/tests/http_api_doc/test__init__.py
+++ b/betty/tests/http_api_doc/test___init__.py
@@ -1,17 +1,15 @@
-from betty.asyncio import sync
-from betty.http_api_doc import HttpApiDoc
-from betty.generate import generate
from betty.app import App
+from betty.generate import generate
+from betty.http_api_doc import HttpApiDoc
from betty.project import ProjectExtensionConfiguration
-from betty.tests import patch_cache, TestCase
+from betty.tests import patch_cache
-class HttpApiDocTest(TestCase):
+class TestHttpApiDoc:
@patch_cache
- @sync
async def test(self):
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(HttpApiDoc))
await generate(app)
- self.assertTrue((app.project.configuration.www_directory_path / 'api' / 'index.html').is_file())
- self.assertTrue((app.project.configuration.www_directory_path / 'http-api-doc.js').is_file())
+ assert (app.project.configuration.www_directory_path / 'api' / 'index.html').is_file()
+ assert (app.project.configuration.www_directory_path / 'http-api-doc.js').is_file()
diff --git a/betty/tests/maps/test__init__.py b/betty/tests/maps/test___init__.py
similarity index 77%
rename from betty/tests/maps/test__init__.py
rename to betty/tests/maps/test___init__.py
index 429e1fdee..82b9f7faa 100644
--- a/betty/tests/maps/test__init__.py
+++ b/betty/tests/maps/test___init__.py
@@ -1,14 +1,12 @@
-from betty.asyncio import sync
+from betty.app import App
from betty.generate import generate
from betty.maps import Maps
-from betty.app import App
from betty.project import ProjectExtensionConfiguration
-from betty.tests import patch_cache, TestCase
+from betty.tests import patch_cache
-class MapsTest(TestCase):
+class TestMaps:
@patch_cache
- @sync
async def test_post_generate_event(self):
with App() as app:
app.project.configuration.debug = True
@@ -16,7 +14,7 @@ async def test_post_generate_event(self):
await generate(app)
with open(app.project.configuration.www_directory_path / 'maps.js', encoding='utf-8') as f:
betty_js = f.read()
- self.assertIn('maps.js', betty_js)
+ assert 'maps.js' in betty_js
with open(app.project.configuration.www_directory_path / 'maps.css', encoding='utf-8') as f:
betty_css = f.read()
- self.assertIn('.map', betty_css)
+ assert '.map' in betty_css
diff --git a/betty/tests/model/test___init__.py b/betty/tests/model/test___init__.py
index 46cf7dd28..d01d198cd 100644
--- a/betty/tests/model/test___init__.py
+++ b/betty/tests/model/test___init__.py
@@ -2,125 +2,121 @@
import copy
import pickle
-from typing import Optional, Any
+from typing import Optional, Any, Iterator, Tuple, List
-from parameterized import parameterized
+import pytest
from betty.model import GeneratedEntityId, get_entity_type_name, Entity, get_entity_type, _EntityTypeAssociation, \
- _EntityTypeAssociationRegistry, SingleTypeEntityCollection, _AssociateCollection, EntityT, \
- MultipleTypesEntityCollection, one_to_many, many_to_one_to_many, FlattenedEntityCollection, many_to_many, \
+ _EntityTypeAssociationRegistry, SingleTypeEntityCollection, _AssociateCollection, MultipleTypesEntityCollection, \
+ one_to_many, many_to_one_to_many, FlattenedEntityCollection, many_to_many, \
EntityCollection, to_many, many_to_one, to_one, one_to_one
from betty.model.ancestry import Person
-from betty.tests import TestCase
class _OtherEntity(Entity):
pass
-class GeneratedEntityidTest(TestCase):
+class TestGeneratedEntityid:
def test_pickle(self) -> None:
sut = GeneratedEntityId()
unpickled_sut = pickle.loads(pickle.dumps(sut))
- self.assertEqual(sut, unpickled_sut)
+ assert sut == unpickled_sut
def test_copy(self) -> None:
sut = GeneratedEntityId()
copied_sut = copy.copy(sut)
- self.assertEqual(sut, copied_sut)
+ assert sut == copied_sut
def test_deepcopy(self) -> None:
sut = GeneratedEntityId()
copied_sut = copy.deepcopy(sut)
- self.assertEqual(sut, copied_sut)
+ assert sut == copied_sut
-class EntityTest(TestCase):
+class TestEntity:
def test_id(self) -> None:
entity_id = '000000001'
sut = Entity(entity_id)
- self.assertEqual(entity_id, sut.id)
+ assert entity_id == sut.id
def test_entity_type_with_class(self) -> None:
- self.assertEqual(Entity, Entity.entity_type())
+ assert Entity == Entity.entity_type()
def test_entity_type_with_instance(self) -> None:
- self.assertEqual(Entity, Entity().entity_type())
+ assert Entity == Entity.entity_type()
-class GetEntityTypeNameTest(TestCase):
+class TestGetEntityTypeName:
def test_with_betty_entity(self) -> None:
- self.assertEqual('Person', get_entity_type_name(Person))
+ assert 'Person' == get_entity_type_name(Person)
def test_with_other_entity(self) -> None:
- self.assertEqual('betty.tests.model.test___init__._OtherEntity', get_entity_type_name(_OtherEntity))
+ assert 'betty.tests.model.test___init__._OtherEntity' == get_entity_type_name(_OtherEntity)
-class GetEntityTypeTest(TestCase):
+class TestGetEntityType:
def test_with_betty_entity(self) -> None:
- self.assertEqual(Person, get_entity_type('Person'))
+ assert Person == get_entity_type('Person')
def test_with_other_entity(self) -> None:
- self.assertEqual(_OtherEntity, get_entity_type('betty.tests.model.test___init__._OtherEntity'))
+ assert _OtherEntity == get_entity_type('betty.tests.model.test___init__._OtherEntity')
def test_with_unknown_entity(self) -> None:
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
get_entity_type('betty_non_existent.UnknownEntity')
-class _EntityTypeAssociationRegistryTest(TestCase):
+class Test_EntityTypeAssociationRegistry:
class _ParentEntity(Entity):
pass
class _ChildEntity(_ParentEntity):
pass
- _parent_registration = None
- _child_registration = None
+ @pytest.fixture(scope='class', autouse=True)
+ def registrations(self) -> Iterator[Tuple[_EntityTypeAssociation, _EntityTypeAssociation]]:
+ parent_registration = _EntityTypeAssociation(self._ParentEntity, 'parent_associate', _EntityTypeAssociation.Cardinality.ONE)
+ _EntityTypeAssociationRegistry.register(parent_registration)
+ child_registration = _EntityTypeAssociation(self._ChildEntity, 'child_associate', _EntityTypeAssociation.Cardinality.MANY)
+ _EntityTypeAssociationRegistry.register(child_registration)
+ yield parent_registration, child_registration
+ _EntityTypeAssociationRegistry._registrations.remove(parent_registration)
+ _EntityTypeAssociationRegistry._registrations.remove(child_registration)
- @classmethod
- def setUpClass(cls) -> None:
- cls._parent_registration = _EntityTypeAssociation(cls._ParentEntity, 'parent_associate', _EntityTypeAssociation.Cardinality.ONE)
- _EntityTypeAssociationRegistry.register(cls._parent_registration)
- cls._child_registration = _EntityTypeAssociation(cls._ChildEntity, 'child_associate', _EntityTypeAssociation.Cardinality.MANY)
- _EntityTypeAssociationRegistry.register(cls._child_registration)
+ def test_get_associations_with_parent_class_should_return_parent_associations(self, registrations) -> None:
+ parent_registration, _ = registrations
+ assert {parent_registration} == _EntityTypeAssociationRegistry.get_associations(self._ParentEntity)
- @classmethod
- def tearDownClass(cls) -> None:
- _EntityTypeAssociationRegistry._registrations.remove(cls._parent_registration)
- _EntityTypeAssociationRegistry._registrations.remove(cls._child_registration)
+ def test_get_associations_with_child_class_should_return_child_associations(self, registrations) -> None:
+ parent_registration, child_registration = registrations
+ assert {parent_registration, child_registration} == _EntityTypeAssociationRegistry.get_associations(self._ChildEntity)
- def test_get_associations_with_parent_class_should_return_parent_associations(self) -> None:
- self.assertSetEqual({self._parent_registration}, _EntityTypeAssociationRegistry.get_associations(self._ParentEntity))
- def test_get_associations_with_child_class_should_return_child_associations(self) -> None:
- self.assertSetEqual({self._parent_registration, self._child_registration}, _EntityTypeAssociationRegistry.get_associations(self._ChildEntity))
-
-
-class SingleTypeEntityCollectionTest(TestCase):
+class TestSingleTypeEntityCollection:
def test_pickle(self) -> None:
entity = Entity()
sut = SingleTypeEntityCollection(Entity)
sut.append(entity)
unpickled_sut = pickle.loads(pickle.dumps(sut))
- self.assertEqual(1, len(unpickled_sut))
- self.assertEqual(entity.id, unpickled_sut[0].id)
+ assert 1 == len(unpickled_sut)
+ assert entity.id == unpickled_sut[0].id
def test_copy(self) -> None:
entity = Entity()
sut = SingleTypeEntityCollection(Entity)
sut.append(entity)
copied_sut = copy.copy(sut)
- self.assertEqual(1, len(copied_sut))
- self.assertEqual(entity.id, copied_sut[0].id)
+ assert 1 == len(copied_sut)
+ assert entity.id == copied_sut[0].id
def test_deepcopy(self) -> None:
entity = Entity()
sut = SingleTypeEntityCollection(Entity)
sut.append(entity)
copied_sut = copy.deepcopy(sut)
- self.assertEqual(1, len(copied_sut))
- self.assertEqual(entity.id, copied_sut[0].id)
+ assert 1 == len(copied_sut)
+ assert entity.id == copied_sut[0].id
def test_prepend(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -132,9 +128,9 @@ def test_prepend(self) -> None:
sut.prepend(entity1)
# Prepend an already prepended value again, and assert that it was ignored.
sut.prepend(entity1)
- self.assertIs(entity1, sut['1'])
- self.assertIs(entity2, sut['2'])
- self.assertIs(entity3, sut['3'])
+ assert entity1 is sut['1']
+ assert entity2 is sut['2']
+ assert entity3 is sut['3']
def test_append(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -146,7 +142,7 @@ def test_append(self) -> None:
sut.append(entity1)
# Append an already appended value again, and assert that it was ignored.
sut.append(entity1)
- self.assertSequenceEqual([entity3, entity2, entity1], sut)
+ assert [entity3, entity2, entity1] == list(sut)
def test_remove(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -156,7 +152,7 @@ def test_remove(self) -> None:
entity4 = Entity()
sut.append(entity1, entity2, entity3, entity4)
sut.remove(entity4, entity2)
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_replace(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -168,7 +164,7 @@ def test_replace(self) -> None:
entity6 = Entity()
sut.append(entity1, entity2, entity3)
sut.replace(entity4, entity5, entity6)
- self.assertSequenceEqual([entity4, entity5, entity6], sut)
+ assert [entity4, entity5, entity6] == list(sut)
def test_clear(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -177,7 +173,7 @@ def test_clear(self) -> None:
entity3 = Entity()
sut.append(entity1, entity2, entity3)
sut.clear()
- self.assertSequenceEqual([], sut)
+ assert [] == list(sut)
def test_list(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -185,9 +181,9 @@ def test_list(self) -> None:
entity2 = Entity()
entity3 = Entity()
sut.append(entity1, entity2, entity3)
- self.assertIs(entity1, sut[0])
- self.assertIs(entity2, sut[1])
- self.assertIs(entity3, sut[2])
+ assert entity1 is sut[0]
+ assert entity2 is sut[1]
+ assert entity3 is sut[2]
def test_len(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -195,7 +191,7 @@ def test_len(self) -> None:
entity2 = Entity()
entity3 = Entity()
sut.append(entity1, entity2, entity3)
- self.assertEqual(3, len(sut))
+ assert 3 == len(sut)
def test_iter(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -203,7 +199,7 @@ def test_iter(self) -> None:
entity2 = Entity()
entity3 = Entity()
sut.append(entity1, entity2, entity3)
- self.assertSequenceEqual([entity1, entity2, entity3], list(sut))
+ assert [entity1, entity2, entity3] == list(list(sut))
def test_getitem_by_index(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -211,10 +207,10 @@ def test_getitem_by_index(self) -> None:
entity2 = Entity()
entity3 = Entity()
sut.append(entity1, entity2, entity3)
- self.assertIs(entity1, sut[0])
- self.assertIs(entity2, sut[1])
- self.assertIs(entity3, sut[2])
- with self.assertRaises(IndexError):
+ assert entity1 is sut[0]
+ assert entity2 is sut[1]
+ assert entity3 is sut[2]
+ with pytest.raises(IndexError):
sut[3]
def test_getitem_by_indices(self) -> None:
@@ -223,7 +219,7 @@ def test_getitem_by_indices(self) -> None:
entity2 = Entity()
entity3 = Entity()
sut.append(entity1, entity2, entity3)
- self.assertSequenceEqual([entity1, entity3], sut[0::2])
+ assert [entity1, entity3] == list(sut[0::2])
def test_getitem_by_entity_id(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -231,10 +227,10 @@ def test_getitem_by_entity_id(self) -> None:
entity2 = Entity('2')
entity3 = Entity('3')
sut.append(entity1, entity2, entity3)
- self.assertIs(entity1, sut['1'])
- self.assertIs(entity2, sut['2'])
- self.assertIs(entity3, sut['3'])
- with self.assertRaises(KeyError):
+ assert entity1 is sut['1']
+ assert entity2 is sut['2']
+ assert entity3 is sut['3']
+ with pytest.raises(KeyError):
sut['4']
def test_delitem_by_index(self) -> None:
@@ -246,7 +242,7 @@ def test_delitem_by_index(self) -> None:
del sut[1]
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_delitem_by_indices(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -257,7 +253,7 @@ def test_delitem_by_indices(self) -> None:
del sut[0::2]
- self.assertSequenceEqual([entity2], sut)
+ assert [entity2] == list(sut)
def test_delitem_by_entity(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -268,7 +264,7 @@ def test_delitem_by_entity(self) -> None:
del sut[entity2]
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_delitem_by_entity_id(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -279,7 +275,7 @@ def test_delitem_by_entity_id(self) -> None:
del sut['2']
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_contains_by_entity(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -287,8 +283,8 @@ def test_contains_by_entity(self) -> None:
entity2 = Entity()
sut.append(entity1)
- self.assertIn(entity1, sut)
- self.assertNotIn(entity2, sut)
+ assert entity1 in sut
+ assert entity2 not in sut
def test_contains_by_entity_id(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -296,20 +292,20 @@ def test_contains_by_entity_id(self) -> None:
entity2 = Entity()
sut.append(entity1)
- self.assertIn(entity1.id, sut)
- self.assertNotIn(entity2.id, sut)
+ assert entity1.id in sut
+ assert entity2.id not in sut
- @parameterized.expand([
- (True,),
- (False,),
- ([],),
+ @pytest.mark.parametrize('value', [
+ True,
+ False,
+ [],
])
def test_contains_by_unsupported_typed(self, value: Any) -> None:
sut = SingleTypeEntityCollection(Entity)
entity = Entity()
sut.append(entity)
- self.assertNotIn(value, sut)
+ assert value not in sut
def test_set_like_functionality(self) -> None:
sut = SingleTypeEntityCollection(Entity)
@@ -326,7 +322,7 @@ def test_set_like_functionality(self) -> None:
sut.append(entity1, entity2, entity3, entity1, entity2, entity3, entity1, entity2, entity3)
# Ensure skipped duplicates do not affect further new values.
sut.append(entity1, entity2, entity3, entity4, entity5, entity6, entity7, entity8, entity9)
- self.assertSequenceEqual([entity1, entity2, entity3, entity4, entity5, entity6, entity7, entity8, entity9], sut)
+ assert [entity1, entity2, entity3, entity4, entity5, entity6, entity7, entity8, entity9] == list(sut)
def test_add(self) -> None:
sut1 = SingleTypeEntityCollection(Entity)
@@ -336,11 +332,11 @@ def test_add(self) -> None:
sut1.append(entity1)
sut2.append(entity2)
sut_added = sut1 + sut2
- self.assertIsInstance(sut_added, SingleTypeEntityCollection)
- self.assertSequenceEqual([entity1, entity2], sut_added)
+ assert isinstance(sut_added, SingleTypeEntityCollection)
+ assert [entity1, entity2] == list(sut_added)
-class AssociateCollectionTest(TestCase):
+class TestAssociateCollection:
class _PickleableCallable:
def __call__(self, *args, **kwargs):
pass
@@ -360,28 +356,28 @@ def __call__(self, other_self):
class _SelfReferentialEntity(Entity):
def __init__(self, entity_id: Optional[str] = None):
super().__init__(entity_id)
- self.other_selfs = AssociateCollectionTest._TrackingAssociateCollection(self)
+ self.other_selfs = TestAssociateCollection._TrackingAssociateCollection(self)
- class _TrackingAssociateCollection(_AssociateCollection):
- def __init__(self, owner: AssociateCollectionTest._SelfReferentialEntity):
- super().__init__(owner, AssociateCollectionTest._SelfReferentialEntity)
- self.added = []
- self.removed = []
+ class _TrackingAssociateCollection(_AssociateCollection[Entity, Entity]):
+ def __init__(self, owner: TestAssociateCollection._SelfReferentialEntity):
+ super().__init__(owner, TestAssociateCollection._SelfReferentialEntity)
+ self.added: List[Entity] = []
+ self.removed: List[Entity] = []
- def _on_add(self, associate: EntityT) -> None:
+ def _on_add(self, associate: Entity) -> None:
self.added.append(associate)
- def _on_remove(self, associate: EntityT) -> None:
+ def _on_remove(self, associate: Entity) -> None:
self.removed.append(associate)
- class _NoOpAssociateCollection(_AssociateCollection):
- def __init__(self, owner: AssociateCollectionTest._SelfReferentialEntity):
- super().__init__(owner, AssociateCollectionTest._SelfReferentialEntity)
+ class _NoOpAssociateCollection(_AssociateCollection[Entity, Entity]):
+ def __init__(self, owner: TestAssociateCollection._SelfReferentialEntity):
+ super().__init__(owner, TestAssociateCollection._SelfReferentialEntity)
- def _on_add(self, associate: EntityT) -> None:
+ def _on_add(self, associate: Entity) -> None:
pass
- def _on_remove(self, associate: EntityT) -> None:
+ def _on_remove(self, associate: Entity) -> None:
pass
def test_pickle(self) -> None:
@@ -390,8 +386,8 @@ def test_pickle(self) -> None:
sut = self._NoOpAssociateCollection(owner)
sut.append(associate)
unpickled_sut = pickle.loads(pickle.dumps(sut))
- self.assertEqual(1, len(unpickled_sut))
- self.assertEqual(associate.id, unpickled_sut[0].id)
+ assert 1 == len(unpickled_sut)
+ assert associate.id == unpickled_sut[0].id
def test_copy(self) -> None:
owner = self._SelfReferentialEntity()
@@ -399,8 +395,8 @@ def test_copy(self) -> None:
sut = self._NoOpAssociateCollection(owner)
sut.append(associate)
copied_sut = copy.copy(sut)
- self.assertEqual(1, len(copied_sut))
- self.assertEqual(associate.id, copied_sut[0].id)
+ assert 1 == len(copied_sut)
+ assert associate.id == copied_sut[0].id
def test_deepcopy(self) -> None:
owner = self._SelfReferentialEntity()
@@ -408,8 +404,8 @@ def test_deepcopy(self) -> None:
sut = self._NoOpAssociateCollection(owner)
sut.append(associate)
copied_sut = copy.deepcopy(sut)
- self.assertEqual(1, len(copied_sut))
- self.assertEqual(associate.id, copied_sut[0].id)
+ assert 1 == len(copied_sut)
+ assert associate.id == copied_sut[0].id
def test_pickle_with_recursion(self) -> None:
owner = self._SelfReferentialEntity()
@@ -429,13 +425,13 @@ def test_prepend(self) -> None:
sut.prepend(associate1)
# Prepend an already prepended value again, and assert that it was ignored.
sut.prepend(associate1)
- self.assertIs(associate1, sut['1'])
- self.assertIs(associate2, sut['2'])
- self.assertIs(associate3, sut['3'])
- self.assertIs(associate3, sut.added[0])
- self.assertIs(associate2, sut.added[1])
- self.assertIs(associate1, sut.added[2])
- self.assertSequenceEqual([], sut.removed)
+ assert associate1 is sut['1']
+ assert associate2 is sut['2']
+ assert associate3 is sut['3']
+ assert associate3 is sut.added[0]
+ assert associate2 is sut.added[1]
+ assert associate1 is sut.added[2]
+ assert [] == list(sut.removed)
def test_append(self) -> None:
owner = self._SelfReferentialEntity()
@@ -448,9 +444,9 @@ def test_append(self) -> None:
sut.append(associate1)
# Append an already appended value again, and assert that it was ignored.
sut.append(associate1)
- self.assertSequenceEqual([associate3, associate2, associate1], sut)
- self.assertSequenceEqual([associate3, associate2, associate1], sut.added)
- self.assertSequenceEqual([], sut.removed)
+ assert [associate3, associate2, associate1] == list(sut)
+ assert [associate3, associate2, associate1] == list(sut.added)
+ assert [] == list(sut.removed)
def test_remove(self) -> None:
owner = self._SelfReferentialEntity()
@@ -461,9 +457,9 @@ def test_remove(self) -> None:
associate4 = self._SelfReferentialEntity()
sut.append(associate1, associate2, associate3, associate4)
sut.remove(associate4, associate2)
- self.assertSequenceEqual([associate1, associate3], sut)
- self.assertSequenceEqual([associate1, associate2, associate3, associate4], sut.added)
- self.assertSequenceEqual([associate4, associate2], sut.removed)
+ assert [associate1, associate3] == list(sut)
+ assert [associate1, associate2, associate3, associate4] == list(sut.added)
+ assert [associate4, associate2] == list(sut.removed)
def test_replace(self) -> None:
owner = self._SelfReferentialEntity()
@@ -476,9 +472,9 @@ def test_replace(self) -> None:
associate6 = self._SelfReferentialEntity()
sut.append(associate1, associate2, associate3)
sut.replace(associate4, associate5, associate6)
- self.assertSequenceEqual([associate4, associate5, associate6], sut)
- self.assertSequenceEqual([associate1, associate2, associate3, associate4, associate5, associate6], sut.added)
- self.assertSequenceEqual([associate1, associate2, associate3], sut.removed)
+ assert [associate4, associate5, associate6] == list(sut)
+ assert [associate1, associate2, associate3, associate4, associate5, associate6] == list(sut.added)
+ assert [associate1, associate2, associate3] == list(sut.removed)
def test_clear(self) -> None:
owner = self._SelfReferentialEntity()
@@ -488,13 +484,13 @@ def test_clear(self) -> None:
associate3 = self._SelfReferentialEntity()
sut.append(associate1, associate2, associate3)
sut.clear()
- self.assertSequenceEqual([], sut)
- self.assertIs(associate1, sut.added[0])
- self.assertIs(associate2, sut.added[1])
- self.assertIs(associate3, sut.added[2])
- self.assertIs(associate1, sut.removed[0])
- self.assertIs(associate2, sut.removed[1])
- self.assertIs(associate3, sut.removed[2])
+ assert [] == list(sut)
+ assert associate1 is sut.added[0]
+ assert associate2 is sut.added[1]
+ assert associate3 is sut.added[2]
+ assert associate1 is sut.removed[0]
+ assert associate2 is sut.removed[1]
+ assert associate3 is sut.removed[2]
def test_list(self) -> None:
owner = self._SelfReferentialEntity()
@@ -503,9 +499,9 @@ def test_list(self) -> None:
associate2 = self._SelfReferentialEntity()
associate3 = self._SelfReferentialEntity()
sut.append(associate1, associate2, associate3)
- self.assertIs(associate1, sut[0])
- self.assertIs(associate2, sut[1])
- self.assertIs(associate3, sut[2])
+ assert associate1 is sut[0]
+ assert associate2 is sut[1]
+ assert associate3 is sut[2]
def test_delitem_by_index(self) -> None:
owner = self._SelfReferentialEntity()
@@ -517,8 +513,8 @@ def test_delitem_by_index(self) -> None:
del sut[1]
- self.assertSequenceEqual([associate1, associate3], sut)
- self.assertSequenceEqual([associate2], sut.removed)
+ assert [associate1, associate3] == list(sut)
+ assert [associate2] == list(sut.removed)
def test_delitem_by_indices(self) -> None:
owner = self._SelfReferentialEntity()
@@ -530,8 +526,8 @@ def test_delitem_by_indices(self) -> None:
del sut[0::2]
- self.assertSequenceEqual([associate2], sut)
- self.assertSequenceEqual([associate1, associate3], sut.removed)
+ assert [associate2] == list(sut)
+ assert [associate1, associate3] == list(sut.removed)
def test_delitem_by_entity(self) -> None:
owner = self._SelfReferentialEntity()
@@ -543,8 +539,8 @@ def test_delitem_by_entity(self) -> None:
del sut[associate2]
- self.assertSequenceEqual([associate1, associate3], sut)
- self.assertSequenceEqual([associate2], sut.removed)
+ assert [associate1, associate3] == list(sut)
+ assert [associate2] == list(sut.removed)
def test_delitem_by_entity_id(self) -> None:
owner = self._SelfReferentialEntity()
@@ -556,11 +552,11 @@ def test_delitem_by_entity_id(self) -> None:
del sut['2']
- self.assertSequenceEqual([associate1, associate3], sut)
- self.assertSequenceEqual([associate2], sut.removed)
+ assert [associate1, associate3] == list(sut)
+ assert [associate2] == list(sut.removed)
-class MultipleTypesEntityCollectionTest(TestCase):
+class TestMultipleTypesEntityCollection:
class _One(Entity):
pass
@@ -573,11 +569,11 @@ def test_pickle(self) -> None:
sut = MultipleTypesEntityCollection()
sut.append(entity_one, entity_other)
unpickled_sut = pickle.loads(pickle.dumps(sut))
- self.assertEqual(2, len(unpickled_sut))
- self.assertEqual(1, len(unpickled_sut[self._One]))
- self.assertEqual(1, len(unpickled_sut[self._Other]))
- self.assertEqual(entity_one.id, unpickled_sut[self._One][0].id)
- self.assertEqual(entity_other.id, unpickled_sut[self._Other][0].id)
+ assert 2 == len(unpickled_sut)
+ assert 1 == len(unpickled_sut[self._One])
+ assert 1 == len(unpickled_sut[self._Other])
+ assert entity_one.id == unpickled_sut[self._One][0].id
+ assert entity_other.id == unpickled_sut[self._Other][0].id
def test_copy(self) -> None:
entity_one = self._One()
@@ -585,11 +581,11 @@ def test_copy(self) -> None:
sut = MultipleTypesEntityCollection()
sut.append(entity_one, entity_other)
copied_sut = copy.copy(sut)
- self.assertEqual(2, len(copied_sut))
- self.assertEqual(1, len(copied_sut[self._One]))
- self.assertEqual(1, len(copied_sut[self._Other]))
- self.assertEqual(entity_one.id, copied_sut[self._One][0].id)
- self.assertEqual(entity_other.id, copied_sut[self._Other][0].id)
+ assert 2 == len(copied_sut)
+ assert 1 == len(copied_sut[self._One])
+ assert 1 == len(copied_sut[self._Other])
+ assert entity_one.id == copied_sut[self._One][0].id
+ assert entity_other.id == copied_sut[self._Other][0].id
def test_deepcopy(self) -> None:
entity_one = self._One()
@@ -597,11 +593,11 @@ def test_deepcopy(self) -> None:
sut = MultipleTypesEntityCollection()
sut.append(entity_one, entity_other)
copied_sut = copy.deepcopy(sut)
- self.assertEqual(2, len(copied_sut))
- self.assertEqual(1, len(copied_sut[self._One]))
- self.assertEqual(1, len(copied_sut[self._Other]))
- self.assertEqual(entity_one.id, copied_sut[self._One][0].id)
- self.assertEqual(entity_other.id, copied_sut[self._Other][0].id)
+ assert 2 == len(copied_sut)
+ assert 1 == len(copied_sut[self._One])
+ assert 1 == len(copied_sut[self._Other])
+ assert entity_one.id == copied_sut[self._One][0].id
+ assert entity_other.id == copied_sut[self._Other][0].id
def test_prepend(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -610,7 +606,7 @@ def test_prepend(self) -> None:
entity_other2 = self._Other()
entity_other3 = self._Other()
sut.prepend(entity_one, entity_other1, entity_other2, entity_other3)
- self.assertSequenceEqual([entity_other3, entity_other2, entity_other1], sut[self._Other])
+ assert [entity_other3, entity_other2, entity_other1] == list(sut[self._Other])
def test_append(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -619,7 +615,7 @@ def test_append(self) -> None:
entity_other2 = self._Other()
entity_other3 = self._Other()
sut.append(entity_one, entity_other1, entity_other2, entity_other3)
- self.assertSequenceEqual([entity_other1, entity_other2, entity_other3], sut[self._Other])
+ assert [entity_other1, entity_other2, entity_other3] == list(sut[self._Other])
def test_remove(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -628,18 +624,18 @@ def test_remove(self) -> None:
sut[self._One].append(entity_one)
sut[self._Other].append(entity_other)
sut.remove(entity_one)
- self.assertSequenceEqual([entity_other], list(sut))
+ assert [entity_other] == list(list(sut))
sut.remove(entity_other)
- self.assertSequenceEqual([], list(sut))
+ assert [] == list(list(sut))
def test_getitem_by_index(self) -> None:
sut = MultipleTypesEntityCollection()
entity_one = self._One()
entity_other = self._Other()
sut.append(entity_one, entity_other)
- self.assertIs(entity_one, sut[0])
- self.assertIs(entity_other, sut[1])
- with self.assertRaises(IndexError):
+ assert entity_one is sut[0]
+ assert entity_other is sut[1]
+ with pytest.raises(IndexError):
sut[2]
def test_getitem_by_indices(self) -> None:
@@ -647,18 +643,18 @@ def test_getitem_by_indices(self) -> None:
entity_one = self._One()
entity_other = self._Other()
sut.append(entity_one, entity_other)
- self.assertSequenceEqual([entity_one], sut[0:1:1])
- self.assertSequenceEqual([entity_other], sut[1::1])
+ assert [entity_one] == list(sut[0:1:1])
+ assert [entity_other] == list(sut[1::1])
def test_getitem_by_entity_type(self) -> None:
sut = MultipleTypesEntityCollection()
entity_one = self._One()
entity_other = self._Other()
sut.append(entity_one, entity_other)
- self.assertSequenceEqual([entity_one], sut[self._One])
- self.assertSequenceEqual([entity_other], sut[self._Other])
+ assert [entity_one] == list(sut[self._One])
+ assert [entity_other] == list(sut[self._Other])
# Ensure that getting previously unseen entity types automatically creates and returns a new collection.
- self.assertSequenceEqual([], sut[Entity])
+ assert [] == list(sut[Entity])
def test_getitem_by_entity_type_name(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -666,9 +662,9 @@ def test_getitem_by_entity_type_name(self) -> None:
# entity types in a single module namespace.
entity = Person(None)
sut.append(entity)
- self.assertSequenceEqual([entity], sut['Person'])
+ assert [entity] == list(sut['Person'])
# Ensure that getting previously unseen entity types automatically creates and returns a new collection.
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
sut['NonExistentEntityType']
def test_delitem_by_index(self) -> None:
@@ -680,7 +676,7 @@ def test_delitem_by_index(self) -> None:
del sut[1]
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_delitem_by_indices(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -691,7 +687,7 @@ def test_delitem_by_indices(self) -> None:
del sut[0::2]
- self.assertSequenceEqual([entity2], sut)
+ assert [entity2] == list(sut)
def test_delitem_by_entity(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -702,7 +698,7 @@ def test_delitem_by_entity(self) -> None:
del sut[entity2]
- self.assertSequenceEqual([entity1, entity3], sut)
+ assert [entity1, entity3] == list(sut)
def test_delitem_by_entity_type(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -712,7 +708,7 @@ def test_delitem_by_entity_type(self) -> None:
del sut[Entity.entity_type()]
- self.assertSequenceEqual([entity_other], sut)
+ assert [entity_other] == list(sut)
def test_delitem_by_entity_type_name(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -722,7 +718,7 @@ def test_delitem_by_entity_type_name(self) -> None:
del sut[get_entity_type_name(Entity.entity_type())]
- self.assertSequenceEqual([entity_other], sut)
+ assert [entity_other] == list(sut)
def test_iter(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -730,7 +726,7 @@ def test_iter(self) -> None:
entity_other = self._Other()
sut[self._One].append(entity_one)
sut[self._Other].append(entity_other)
- self.assertSequenceEqual([entity_one, entity_other], list(sut))
+ assert [entity_one, entity_other] == list(list(sut))
def test_len(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -738,7 +734,7 @@ def test_len(self) -> None:
entity_other = self._Other()
sut[self._One].append(entity_one)
sut[self._Other].append(entity_other)
- self.assertEqual(2, len(sut))
+ assert 2 == len(sut)
def test_contain_by_entity(self) -> None:
sut = MultipleTypesEntityCollection()
@@ -747,21 +743,21 @@ def test_contain_by_entity(self) -> None:
entity_other2 = self._Other()
sut[self._One].append(entity_one)
sut[self._Other].append(entity_other1)
- self.assertIn(entity_one, sut)
- self.assertIn(entity_other1, sut)
- self.assertNotIn(entity_other2, sut)
-
- @parameterized.expand([
- (True,),
- (False,),
- ([],),
+ assert entity_one in sut
+ assert entity_other1 in sut
+ assert entity_other2 not in sut
+
+ @pytest.mark.parametrize('value', [
+ True,
+ False,
+ [],
])
def test_contains_by_unsupported_typed(self, value: Any) -> None:
sut = MultipleTypesEntityCollection()
entity = Entity()
sut.append(entity)
- self.assertNotIn(value, sut)
+ assert value not in sut
def test_add(self) -> None:
sut1 = MultipleTypesEntityCollection()
@@ -773,31 +769,31 @@ def test_add(self) -> None:
sut1.append(entity1_one, entity1_other)
sut2.append(entity2_one, entity2_other)
sut_added = sut1 + sut2
- self.assertIsInstance(sut_added, MultipleTypesEntityCollection)
- self.assertSequenceEqual([entity1_one, entity1_other, entity2_one, entity2_other], sut_added)
+ assert isinstance(sut_added, MultipleTypesEntityCollection)
+ assert [entity1_one, entity1_other, entity2_one, entity2_other] == list(sut_added)
-class FlattenedEntityCollectionTest(TestCase):
+class TestFlattenedEntityCollection:
@many_to_many('other_many', 'many')
class _ManyToMany_Many(Entity):
- other_many: EntityCollection[FlattenedEntityCollectionTest._ManyToMany_OtherMany]
+ other_many: EntityCollection[TestFlattenedEntityCollection._ManyToMany_OtherMany]
@many_to_many('many', 'other_many')
class _ManyToMany_OtherMany(Entity):
- many: EntityCollection[FlattenedEntityCollectionTest._ManyToMany_Many]
+ many: EntityCollection[TestFlattenedEntityCollection._ManyToMany_Many]
@one_to_many('other_many', 'many')
class _ManyToOneToMany_Many(Entity):
- other_many: FlattenedEntityCollectionTest._ManyToOneToMany_OtherMany
+ other_many: EntityCollection[TestFlattenedEntityCollection._ManyToOneToMany_OtherMany]
@many_to_one_to_many('other_many', 'many', 'other_many', 'many')
class _ManyToOneToMany_One(Entity):
- many: FlattenedEntityCollectionTest._ManyToOneToMany_Many
- other_many: FlattenedEntityCollectionTest._ManyToOneToMany_OtherMany
+ many: TestFlattenedEntityCollection._ManyToOneToMany_Many
+ other_many: TestFlattenedEntityCollection._ManyToOneToMany_OtherMany
@one_to_many('many', 'other_many')
class _ManyToOneToMany_OtherMany(Entity):
- many: FlattenedEntityCollectionTest._ManyToOneToMany_Many
+ many: EntityCollection[TestFlattenedEntityCollection._ManyToOneToMany_Many]
def test_add_to_many_association_then_unflatten(self) -> None:
entity_many = self._ManyToMany_Many()
@@ -812,14 +808,14 @@ def test_add_to_many_association_then_unflatten(self) -> None:
unflattened_entities = flattened_entities.unflatten()
- unflattened_entity_many = unflattened_entities[self._ManyToMany_Many][0]
- unflattened_entity_other_many = unflattened_entities[self._ManyToMany_OtherMany][0]
- self.assertIsNot(entity_many, unflattened_entity_many)
- self.assertIsNot(entity_other_many, unflattened_entity_other_many)
- self.assertEqual(1, len(unflattened_entity_other_many.many))
- self.assertIn(unflattened_entity_many, unflattened_entity_other_many.many)
- self.assertEqual(1, len(unflattened_entity_many.other_many))
- self.assertIn(unflattened_entity_other_many, unflattened_entity_many.other_many)
+ unflattened_entity_many: TestFlattenedEntityCollection._ManyToMany_Many = unflattened_entities[self._ManyToMany_Many][0]
+ unflattened_entity_other_many: TestFlattenedEntityCollection._ManyToMany_OtherMany = unflattened_entities[self._ManyToMany_OtherMany][0]
+ assert entity_many is not unflattened_entity_many
+ assert entity_other_many is not unflattened_entity_other_many
+ assert 1 == len(unflattened_entity_other_many.many)
+ assert unflattened_entity_many in unflattened_entity_other_many.many
+ assert 1 == len(unflattened_entity_many.other_many)
+ assert unflattened_entity_other_many in unflattened_entity_many.other_many
def test_add_entity_with_to_many_association_then_unflatten(self) -> None:
entity_many = self._ManyToMany_Many()
@@ -830,8 +826,8 @@ def test_add_entity_with_to_many_association_then_unflatten(self) -> None:
flattened_entities.add_entity(entity_many, entity_other_many)
# Assert the original entities remain unchanged.
- self.assertIn(entity_many, entity_other_many.many)
- self.assertIn(entity_other_many, entity_many.other_many)
+ assert entity_many in entity_other_many.many
+ assert entity_other_many in entity_many.other_many
# Assert the result is pickleable.
pickle.dumps(flattened_entities)
@@ -839,10 +835,10 @@ def test_add_entity_with_to_many_association_then_unflatten(self) -> None:
unflattened_entity_many = unflattened_entities[self._ManyToMany_Many][0]
unflattened_entity_other_many = unflattened_entities[self._ManyToMany_OtherMany][0]
- self.assertIsNot(entity_many, unflattened_entity_many)
- self.assertIsNot(entity_other_many, unflattened_entity_other_many)
- self.assertIn(unflattened_entity_many, unflattened_entity_other_many.many)
- self.assertIn(unflattened_entity_other_many, unflattened_entity_many.other_many)
+ assert entity_many is not unflattened_entity_many
+ assert entity_other_many is not unflattened_entity_other_many
+ assert unflattened_entity_many in unflattened_entity_other_many.many
+ assert unflattened_entity_other_many in unflattened_entity_many.other_many
def test_add_entity_with_many_to_one_to_many_association_then_unflatten(self) -> None:
entity_many = self._ManyToOneToMany_Many()
@@ -855,82 +851,79 @@ def test_add_entity_with_many_to_one_to_many_association_then_unflatten(self) ->
flattened_entities.add_entity(entity_many, entity_one, entity_other_many)
# Assert the original entities remain unchanged.
- self.assertIs(entity_many, entity_one.many)
- self.assertIs(entity_other_many, entity_one.other_many)
+ assert entity_many is entity_one.many
+ assert entity_other_many is entity_one.other_many
# Assert the result is pickleable.
pickle.dumps(flattened_entities)
- unflattened_entity_many, unflattened_entity_one, unflattened_entity_other_many = flattened_entities.unflatten()
+ unflattened_entity_many: TestFlattenedEntityCollection._ManyToOneToMany_Many
+ unflattened_entity_one: TestFlattenedEntityCollection._ManyToOneToMany_One
+ unflattened_entity_other_many: TestFlattenedEntityCollection._ManyToOneToMany_OtherMany
+ unflattened_entity_many, unflattened_entity_one, unflattened_entity_other_many = flattened_entities.unflatten() # type: ignore
- self.assertIsNot(entity_many, unflattened_entity_many)
- self.assertIsNot(entity_other_many, unflattened_entity_other_many)
- self.assertIn(unflattened_entity_one, unflattened_entity_other_many.many)
- self.assertIn(unflattened_entity_one, unflattened_entity_many.other_many)
- self.assertIs(unflattened_entity_many, unflattened_entity_one.many)
- self.assertIs(unflattened_entity_other_many, unflattened_entity_one.other_many)
+ assert entity_many is not unflattened_entity_many
+ assert entity_other_many is not unflattened_entity_other_many
+ assert unflattened_entity_one in unflattened_entity_other_many.many
+ assert unflattened_entity_one in unflattened_entity_many.other_many
+ assert unflattened_entity_many is unflattened_entity_one.many
+ assert unflattened_entity_other_many is unflattened_entity_one.other_many
-class ToOneTest(TestCase):
+class TestToOne:
@to_one('one')
class _Some(Entity):
- one: Optional[ManyToOneTest._One]
+ one: Optional[TestToOne._One]
class _One(Entity):
pass
def test(self) -> None:
- self.assertSetEqual(
- {'one'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._Some)
- },
- )
+ assert {'one'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._Some)
+ }
entity_some = self._Some()
entity_one = self._One()
entity_some.one = entity_one
- self.assertIs(entity_one, entity_some.one)
+ assert entity_one is entity_some.one
del entity_some.one
- self.assertIsNone(entity_some.one)
+ assert entity_some.one is None
def test_pickle(self) -> None:
entity = self._Some()
pickle.dumps(entity)
-class OneToOneTest(TestCase):
+class TestOneToOne:
@one_to_one('other_one', 'one')
class _One(Entity):
- other_one: Optional[OneToOneTest._OtherOne]
+ other_one: Optional[TestOneToOne._OtherOne]
@one_to_one('one', 'other_one')
class _OtherOne(Entity):
- one: Optional[OneToOneTest._One]
+ one: Optional[TestOneToOne._One]
def test(self) -> None:
- self.assertSetEqual(
- {'one'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._OtherOne)
- },
- )
+ assert{'one'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._OtherOne)
+ }
entity_one = self._One()
entity_other_one = self._OtherOne()
entity_other_one.one = entity_one
- self.assertIs(entity_one, entity_other_one.one)
- self.assertEqual(entity_other_one, entity_one.other_one)
+ assert entity_one is entity_other_one.one
+ assert entity_other_one == entity_one.other_one
del entity_other_one.one
- self.assertIsNone(entity_other_one.one)
- self.assertIsNone(entity_one.other_one)
+ assert entity_other_one.one is None
+ assert entity_one.other_one is None
def test_pickle(self) -> None:
entity_one = self._One()
@@ -939,39 +932,36 @@ def test_pickle(self) -> None:
entity_one.other_one = entity_other_one
unpickled_entity_one, unpickled_entity_other_one = pickle.loads(pickle.dumps((entity_one, entity_other_one)))
- self.assertEqual(entity_other_one.id, unpickled_entity_one.other_one.id)
- self.assertEqual(entity_one.id, unpickled_entity_other_one.one.id)
+ assert entity_other_one.id == unpickled_entity_one.other_one.id
+ assert entity_one.id == unpickled_entity_other_one.one.id
-class ManyToOneTest(TestCase):
+class TestManyToOne:
@many_to_one('one', 'many')
class _Many(Entity):
- one: Optional[ManyToOneTest._One]
+ one: Optional[TestManyToOne._One]
@one_to_many('many', 'one')
class _One(Entity):
- many: EntityCollection[ManyToOneTest._Many]
+ many: EntityCollection[TestManyToOne._Many]
def test(self) -> None:
- self.assertSetEqual(
- {'one'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._Many)
- },
- )
+ assert {'one'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._Many)
+ }
entity_many = self._Many()
entity_one = self._One()
entity_many.one = entity_one
- self.assertIs(entity_one, entity_many.one)
- self.assertSequenceEqual([entity_many], entity_one.many)
+ assert entity_one is entity_many.one
+ assert [entity_many] == list(entity_one.many)
del entity_many.one
- self.assertIsNone(entity_many.one)
- self.assertSequenceEqual([], entity_one.many)
+ assert entity_many.one is None
+ assert [] == list(entity_one.many)
def test_pickle(self) -> None:
entity_many = self._Many()
@@ -979,74 +969,68 @@ def test_pickle(self) -> None:
entity_many.one = entity_one
unpickled_entity_many, unpickled_entity_one = pickle.loads(pickle.dumps((entity_many, entity_one)))
- self.assertEqual(unpickled_entity_many.id, unpickled_entity_one.many[0].id)
- self.assertEqual(unpickled_entity_one.id, unpickled_entity_many.one.id)
+ assert unpickled_entity_many.id == unpickled_entity_one.many[0].id
+ assert unpickled_entity_one.id == unpickled_entity_many.one.id
-class ToManyTest(TestCase):
+class TestToMany:
@to_many('many')
class _One(Entity):
- many: EntityCollection[OneToManyTest._Many]
+ many: EntityCollection[TestToMany._Many]
class _Many(Entity):
pass
def test(self) -> None:
- self.assertSetEqual(
- {'many'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._One)
- },
- )
+ assert {'many'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._One)
+ }
entity_one = self._One()
entity_many = self._Many()
entity_one.many.append(entity_many)
- self.assertSequenceEqual([entity_many], entity_one.many)
+ assert [entity_many] == list(entity_one.many)
entity_one.many.remove(entity_many)
- self.assertSequenceEqual([], entity_one.many)
+ assert [] == list(entity_one.many)
def test_pickle(self) -> None:
entity_one = self._One()
entity_other = self._Many()
entity_one.many.append(entity_other)
unpickled_entity_one = pickle.loads(pickle.dumps(entity_one))
- self.assertEqual(entity_other.id, unpickled_entity_one.many[0].id)
+ assert entity_other.id == unpickled_entity_one.many[0].id
-class OneToManyTest(TestCase):
+class TestOneToMany:
@one_to_many('many', 'one')
class _One(Entity):
- many: SingleTypeEntityCollection[OneToManyTest._Many]
+ many: SingleTypeEntityCollection[TestOneToMany._Many]
@many_to_one('one', 'many')
class _Many(Entity):
- one: Optional[OneToManyTest._One]
+ one: Optional[TestOneToMany._One]
def test(self) -> None:
- self.assertSetEqual(
- {'many'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._One)
- },
- )
+ assert {'many'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._One)
+ }
entity_one = self._One()
entity_many = self._Many()
entity_one.many.append(entity_many)
- self.assertSequenceEqual([entity_many], entity_one.many)
- self.assertIs(entity_one, entity_many.one)
+ assert [entity_many] == list(entity_one.many)
+ assert entity_one is entity_many.one
entity_one.many.remove(entity_many)
- self.assertSequenceEqual([], entity_one.many)
- self.assertIsNone(entity_many.one)
+ assert [] == list(entity_one.many)
+ assert entity_many.one is None
def test_pickle(self) -> None:
entity_one = self._One()
@@ -1055,39 +1039,36 @@ def test_pickle(self) -> None:
entity_one.many.append(entity_many)
unpickled_entity_one, unpickled_entity_many = pickle.loads(pickle.dumps((entity_one, entity_many)))
- self.assertEqual(entity_many.id, unpickled_entity_one.many[0].id)
- self.assertEqual(entity_one.id, unpickled_entity_many.one.id)
+ assert entity_many.id == unpickled_entity_one.many[0].id
+ assert entity_one.id == unpickled_entity_many.one.id
-class ManyToManyTest(TestCase):
+class TestManyToMany:
@many_to_many('other_many', 'many')
class _Many(Entity):
- other_many: EntityCollection[ManyToManyTest._OtherMany]
+ other_many: EntityCollection[TestManyToMany._OtherMany]
@many_to_many('many', 'other_many')
class _OtherMany(Entity):
- many: EntityCollection[ManyToManyTest._Many]
+ many: EntityCollection[TestManyToMany._Many]
def test(self) -> None:
- self.assertSetEqual(
- {'other_many'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._Many)
- },
- )
+ assert {'other_many'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._Many)
+ }
entity_many = self._Many()
entity_other_many = self._OtherMany()
entity_many.other_many.append(entity_other_many)
- self.assertSequenceEqual([entity_other_many], entity_many.other_many)
- self.assertSequenceEqual([entity_many], entity_other_many.many)
+ assert [entity_other_many] == list(entity_many.other_many)
+ assert [entity_many] == list(entity_other_many.many)
entity_many.other_many.remove(entity_other_many)
- self.assertSequenceEqual([], entity_many.other_many)
- self.assertSequenceEqual([], entity_other_many.many)
+ assert [] == list(entity_many.other_many)
+ assert [] == list(entity_other_many.many)
def test_pickle(self) -> None:
entity_many = self._Many()
@@ -1096,47 +1077,44 @@ def test_pickle(self) -> None:
entity_many.other_many.append(entity_other_many)
unpickled_entity_many, unpickled_entity_other_many = pickle.loads(pickle.dumps((entity_many, entity_other_many)))
- self.assertEqual(entity_many.id, unpickled_entity_other_many.many[0].id)
- self.assertEqual(entity_other_many.id, unpickled_entity_many.other_many[0].id)
+ assert entity_many.id == unpickled_entity_other_many.many[0].id
+ assert entity_other_many.id == unpickled_entity_many.other_many[0].id
-class ManyToOneToManyTest(TestCase):
+class TestManyToOneToMany:
@many_to_one_to_many('one', 'left_many', 'right_many', 'one')
class _One(Entity):
- left_many: Optional[ManyToOneToManyTest._Many]
- right_many: Optional[ManyToOneToManyTest._Many]
+ left_many: Optional[TestManyToOneToMany._Many]
+ right_many: Optional[TestManyToOneToMany._Many]
@one_to_many('one', 'many')
class _Many(Entity):
- one: EntityCollection[ManyToOneToManyTest._One]
+ one: EntityCollection[TestManyToOneToMany._One]
def test(self) -> None:
- self.assertSetEqual(
- {'left_many', 'right_many'},
- {
- association.attr_name
- for association
- in _EntityTypeAssociationRegistry.get_associations(self._One)
- },
- )
+ assert {'left_many', 'right_many'} == {
+ association.attr_name
+ for association
+ in _EntityTypeAssociationRegistry.get_associations(self._One)
+ }
entity_one = self._One()
entity_left_many = self._Many()
entity_right_many = self._Many()
entity_one.left_many = entity_left_many
- self.assertIs(entity_left_many, entity_one.left_many)
- self.assertSequenceEqual([entity_one], entity_left_many.one)
+ assert entity_left_many is entity_one.left_many
+ assert [entity_one] == list(entity_left_many.one)
entity_one.right_many = entity_right_many
- self.assertIs(entity_right_many, entity_one.right_many)
- self.assertSequenceEqual([entity_one], entity_right_many.one)
+ assert entity_right_many is entity_one.right_many
+ assert [entity_one] == list(entity_right_many.one)
del entity_one.left_many
- self.assertIsNone(entity_one.left_many)
- self.assertSequenceEqual([], entity_left_many.one)
- self.assertIsNone(entity_one.right_many)
- self.assertSequenceEqual([], entity_right_many.one)
+ assert entity_one.left_many is None
+ assert [] == list(entity_left_many.one)
+ assert entity_one.right_many is None
+ assert [] == list(entity_right_many.one)
def test_pickle(self) -> None:
entity_one = self._One()
@@ -1147,5 +1125,5 @@ def test_pickle(self) -> None:
entity_one.right_many = entity_right_many
unpickled_entity_one = pickle.loads(pickle.dumps(entity_one))
- self.assertEqual(entity_left_many.id, unpickled_entity_one.left_many.id)
- self.assertEqual(entity_right_many.id, unpickled_entity_one.right_many.id)
+ assert entity_left_many.id == unpickled_entity_one.left_many.id
+ assert entity_right_many.id == unpickled_entity_one.right_many.id
diff --git a/betty/tests/model/test_ancestry.py b/betty/tests/model/test_ancestry.py
index 4651cdbb9..e19562a81 100644
--- a/betty/tests/model/test_ancestry.py
+++ b/betty/tests/model/test_ancestry.py
@@ -4,295 +4,315 @@
from typing import Any
from unittest.mock import Mock
+import pytest
from geopy import Point
-from parameterized import parameterized
from betty.locale import Date, Translations
from betty.media_type import MediaType
+from betty.model import Entity
from betty.model.ancestry import Person, Event, Place, File, Note, Presence, PlaceName, PersonName, Subject, \
Enclosure, Described, Dated, HasPrivacy, HasMediaType, Link, HasLinks, HasNotes, HasFiles, Source, Citation, \
HasCitations, PresenceRole, Attendee, Beneficiary, Witness, EventType
from betty.model.event_type import Burial, Birth
-from betty.tests import TestCase
-class HasPrivacyTest(TestCase):
+class TestHasPrivacy:
def test_date(self) -> None:
- sut = HasPrivacy()
- self.assertIsNone(sut.private)
+ class _HasPrivacy(HasPrivacy):
+ pass
+ sut = _HasPrivacy()
+ assert sut.private is None
-class DatedTest(TestCase):
+class TestDated:
def test_date(self) -> None:
- sut = Dated()
- self.assertIsNone(sut.date)
+ class _Dated(Dated):
+ pass
+ sut = _Dated()
+ assert sut.date is None
-class NoteTest(TestCase):
+class TestNote:
def test_id(self) -> None:
note_id = 'N1'
sut = Note(note_id, 'Betty wrote this.')
- self.assertEqual(note_id, sut.id)
+ assert note_id == sut.id
def test_text(self) -> None:
text = 'Betty wrote this.'
sut = Note('N1', text)
- self.assertEqual(text, sut.text)
+ assert text == sut.text
-class HasNotesTest(TestCase):
+class TestHasNotes:
def test_notes(self) -> None:
- sut = HasNotes()
- self.assertSequenceEqual([], sut.notes)
+ class _HasNotes(HasNotes):
+ pass
+ sut = _HasNotes()
+ assert [] == list(sut.notes)
-class DescribedTest(TestCase):
+class TestDescribed:
def test_description(self) -> None:
- sut = Described()
- self.assertIsNone(sut.description)
+ class _Described(Described):
+ pass
+ sut = _Described()
+ assert sut.description is None
-class HasMediaTypeTest(TestCase):
+class TestHasMediaType:
def test_media_type(self) -> None:
- sut = HasMediaType()
- self.assertIsNone(sut.media_type)
+ class _HasMediaType(HasMediaType):
+ pass
+ sut = _HasMediaType()
+ assert sut.media_type is None
-class LinkTest(TestCase):
+class TestLink:
def test_url(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertEqual(url, sut.url)
+ assert url == sut.url
def test_media_type(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertIsNone(sut.media_type)
+ assert sut.media_type is None
def test_locale(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertIsNone(sut.locale)
+ assert sut.locale is None
def test_description(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertIsNone(sut.description)
+ assert sut.description is None
def test_relationship(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertIsNone(sut.relationship)
+ assert sut.relationship is None
def test_label(self) -> None:
url = 'https://example.com'
sut = Link(url)
- self.assertIsNone(sut.label)
+ assert sut.label is None
-class HasLinksTest(TestCase):
+class TestHasLinks:
def test_links(self) -> None:
- sut = HasLinks()
- self.assertEqual(set(), sut.links)
+ class _HasLinks(HasLinks):
+ pass
+ sut = _HasLinks()
+ assert set() == sut.links
-class FileTest(TestCase):
+class _HasFiles(HasFiles, Entity):
+ pass
+
+
+class TestFile:
def test_id(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertEqual(file_id, sut.id)
+ assert file_id == sut.id
def test_private(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertIsNone(sut.private)
+ assert sut.private is None
private = True
sut.private = private
- self.assertEqual(private, sut.private)
+ assert private == sut.private
def test_media_type(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertIsNone(sut.media_type)
+ assert sut.media_type is None
media_type = MediaType('text/plain')
sut.media_type = media_type
- self.assertEqual(media_type, sut.media_type)
+ assert media_type == sut.media_type
def test_path_with_path(self) -> None:
with NamedTemporaryFile() as f:
file_id = 'BETTY01'
file_path = Path(f.name)
sut = File(file_id, file_path)
- self.assertEqual(file_path, sut.path)
+ assert file_path == sut.path
def test_path_with_str(self) -> None:
with NamedTemporaryFile() as f:
file_id = 'BETTY01'
sut = File(file_id, f.name)
- self.assertEqual(Path(f.name), sut.path)
+ assert Path(f.name) == sut.path
def test_description(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertIsNone(sut.description)
+ assert sut.description is None
description = 'Hi, my name is Betty!'
sut.description = description
- self.assertEqual(description, sut.description)
+ assert description == sut.description
def test_notes(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertCountEqual([], sut.notes)
+ assert [] == list(sut.notes)
notes = [Mock(Note), Mock(Note)]
- sut.notes = notes
- self.assertCountEqual(notes, sut.notes)
+ sut.notes = notes # type: ignore
+ assert notes == list(sut.notes)
def test_entities(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertCountEqual([], sut.entities)
+ assert [] == list(sut.entities)
- entities = [HasFiles(), HasFiles()]
- sut.entities = entities
- self.assertCountEqual(entities, sut.entities)
+ entities = [_HasFiles(), _HasFiles()]
+ sut.entities = entities # type: ignore
+ assert entities == list(sut.entities)
def test_citations(self) -> None:
file_id = 'BETTY01'
file_path = Path('~')
sut = File(file_id, file_path)
- self.assertCountEqual([], sut.citations)
+ assert [] == list(sut.citations)
-class HasFilesTest(TestCase):
+class TestHasFiles:
def test_files(self) -> None:
- sut = HasFiles()
- self.assertCountEqual([], sut.files)
+ sut = _HasFiles()
+ assert [] == list(sut.files)
files = [Mock(File), Mock(File)]
- sut.files = files
- self.assertCountEqual(files, sut.files)
+ sut.files = files # type: ignore
+ assert files == list(sut.files)
-class SourceTest(TestCase):
+class TestSource:
def test_id(self) -> None:
source_id = 'S1'
sut = Source(source_id)
- self.assertEqual(source_id, sut.id)
+ assert source_id == sut.id
def test_name(self) -> None:
name = 'The Source'
sut = Source(None, name)
- self.assertEqual(name, sut.name)
+ assert name == sut.name
def test_contained_by(self) -> None:
contained_by_source = Source(None)
sut = Source(None)
- self.assertIsNone(sut.contained_by)
+ assert sut.contained_by is None
sut.contained_by = contained_by_source
- self.assertEqual(contained_by_source, sut.contained_by)
+ assert contained_by_source == sut.contained_by
def test_contains(self) -> None:
contains_source = Source(None)
sut = Source(None)
- self.assertCountEqual([], sut.contains)
- sut.contains = [contains_source]
- self.assertCountEqual([contains_source], sut.contains)
+ assert [] == list(sut.contains)
+ sut.contains = [contains_source] # type: ignore
+ assert [contains_source] == list(sut.contains)
def test_citations(self) -> None:
sut = Source(None)
- self.assertCountEqual([], sut.citations)
+ assert [] == list(sut.citations)
def test_author(self) -> None:
sut = Source(None)
- self.assertIsNone(sut.author)
+ assert sut.author is None
author = 'Me'
sut.author = author
- self.assertEqual(author, sut.author)
+ assert author == sut.author
def test_publisher(self) -> None:
sut = Source(None)
- self.assertIsNone(sut.publisher)
+ assert sut.publisher is None
publisher = 'Me'
sut.publisher = publisher
- self.assertEqual(publisher, sut.publisher)
+ assert publisher == sut.publisher
def test_date(self) -> None:
sut = Source(None)
- self.assertIsNone(sut.date)
+ assert sut.date is None
def test_files(self) -> None:
sut = Source(None)
- self.assertCountEqual([], sut.files)
+ assert [] == list(sut.files)
def test_links(self) -> None:
sut = Source(None)
- self.assertCountEqual([], sut.links)
+ assert [] == list(sut.links)
def test_private(self) -> None:
sut = Source(None)
- self.assertIsNone(sut.private)
+ assert sut.private is None
private = True
sut.private = private
- self.assertEqual(private, sut.private)
+ assert private == sut.private
+
+class _HasCitations(HasCitations, Entity):
+ pass
-class CitationTest(TestCase):
+
+class TestCitation:
def test_id(self) -> None:
citation_id = 'C1'
sut = Citation(citation_id, Mock(Source))
- self.assertEqual(citation_id, sut.id)
+ assert citation_id == sut.id
def test_facts(self) -> None:
- fact = HasCitations()
+ fact = _HasCitations()
sut = Citation(None, Mock(Source))
- self.assertCountEqual([], sut.facts)
- sut.facts = [fact]
- self.assertCountEqual([fact], sut.facts)
+ assert [] == list(sut.facts)
+ sut.facts = [fact] # type: ignore
+ assert [fact] == list(sut.facts)
def test_source(self) -> None:
source = Mock(Source)
sut = Citation(None, source)
- self.assertEqual(source, sut.source)
+ assert source == sut.source
def test_location(self) -> None:
sut = Citation(None, Mock(Source))
- self.assertIsNone(sut.location)
+ assert sut.location is None
location = 'Somewhere'
sut.location = location
- self.assertEqual(location, sut.location)
+ assert location == sut.location
def test_date(self) -> None:
sut = Citation(None, Mock(Source))
- self.assertIsNone(sut.date)
+ assert sut.date is None
def test_files(self) -> None:
sut = Citation(None, Mock(Source))
- self.assertCountEqual([], sut.files)
+ assert [] == list(sut.files)
def test_private(self) -> None:
sut = Citation(None, Mock(Source))
- self.assertIsNone(sut.private)
+ assert sut.private is None
private = True
sut.private = private
- self.assertEqual(private, sut.private)
+ assert private == sut.private
-class HasCitationsTest(TestCase):
+class TestHasCitations:
def test_citations(self) -> None:
- sut = HasCitations()
- self.assertCountEqual([], sut.citations)
+ sut = _HasCitations()
+ assert [] == list(sut.citations)
citation = Mock(Citation)
- sut.citations = [citation]
- self.assertCountEqual([citation], sut.citations)
+ sut.citations = [citation] # type: ignore
+ assert [citation] == list(sut.citations)
-class PlaceNameTest(TestCase):
- @parameterized.expand([
+class TestPlaceName:
+ @pytest.mark.parametrize('expected, a, b', [
(True, PlaceName('Ikke'), PlaceName('Ikke')),
(True, PlaceName('Ikke', 'nl-NL'), PlaceName('Ikke', 'nl-NL')),
(False, PlaceName('Ikke', 'nl-NL'), PlaceName('Ikke', 'nl-BE')),
@@ -301,236 +321,236 @@ class PlaceNameTest(TestCase):
(False, PlaceName('Ikke'), None),
(False, PlaceName('Ikke'), 'not-a-place-name'),
])
- def test_eq(self, expected, a, b) -> None:
- self.assertEqual(expected, a == b)
+ def test_eq(self, expected: bool, a: PlaceName, b: Any) -> None:
+ assert expected == (a == b)
def test_str(self) -> None:
name = 'Ikke'
sut = PlaceName(name)
- self.assertEqual(name, str(sut))
+ assert name == str(sut)
def test_name(self) -> None:
name = 'Ikke'
sut = PlaceName(name)
- self.assertEqual(name, sut.name)
+ assert name == sut.name
def test_locale(self) -> None:
locale = 'nl-NL'
sut = PlaceName('Ikke', locale=locale)
- self.assertEqual(locale, sut.locale)
+ assert locale == sut.locale
def test_date(self) -> None:
date = Date()
sut = PlaceName('Ikke', date=date)
- self.assertEqual(date, sut.date)
+ assert date == sut.date
-class EnclosureTest(TestCase):
+class TestEnclosure:
def test_encloses(self) -> None:
encloses = Mock(Place)
enclosed_by = Mock(Place)
sut = Enclosure(encloses, enclosed_by)
- self.assertEqual(encloses, sut.encloses)
+ assert encloses == sut.encloses
def test_enclosed_by(self) -> None:
encloses = Mock(Place)
enclosed_by = Mock(Place)
sut = Enclosure(encloses, enclosed_by)
- self.assertEqual(enclosed_by, sut.enclosed_by)
+ assert enclosed_by == sut.enclosed_by
def test_date(self) -> None:
encloses = Mock(Place)
enclosed_by = Mock(Place)
sut = Enclosure(encloses, enclosed_by)
date = Date()
- self.assertIsNone(sut.date)
+ assert sut.date is None
sut.date = date
- self.assertEqual(date, sut.date)
+ assert date == sut.date
def test_citations(self) -> None:
encloses = Mock(Place)
enclosed_by = Mock(Place)
sut = Enclosure(encloses, enclosed_by)
citation = Mock(Citation)
- self.assertIsNone(sut.date)
- sut.citations = [citation]
- self.assertCountEqual([citation], sut.citations)
+ assert sut.date is None
+ sut.citations = [citation] # type: ignore
+ assert [citation] == list(sut.citations)
-class PlaceTest(TestCase):
+class TestPlace:
def test_events(self) -> None:
sut = Place('P1', [PlaceName('The Place')])
event = Event('1', Birth())
sut.events.append(event)
- self.assertIn(event, sut.events)
- self.assertEqual(sut, event.place)
+ assert event in sut.events
+ assert sut == event.place
sut.events.remove(event)
- self.assertCountEqual([], sut.events)
- self.assertEqual(None, event.place)
+ assert [] == list(sut.events)
+ assert event.place is None
def test_enclosed_by(self) -> None:
sut = Place('P1', [PlaceName('The Place')])
- self.assertCountEqual([], sut.enclosed_by)
+ assert [] == list(sut.enclosed_by)
enclosing_place = Place('P2', [PlaceName('The Other Place')])
enclosure = Enclosure(sut, enclosing_place)
- self.assertIn(enclosure, sut.enclosed_by)
- self.assertEqual(sut, enclosure.encloses)
+ assert enclosure in sut.enclosed_by
+ assert sut == enclosure.encloses
sut.enclosed_by.remove(enclosure)
- self.assertCountEqual([], sut.enclosed_by)
- self.assertIsNone(enclosure.encloses)
+ assert [] == list(sut.enclosed_by)
+ assert enclosure.encloses is None
def test_encloses(self) -> None:
sut = Place('P1', [PlaceName('The Place')])
- self.assertCountEqual([], sut.encloses)
+ assert [] == list(sut.encloses)
enclosed_place = Place('P2', [PlaceName('The Other Place')])
enclosure = Enclosure(enclosed_place, sut)
- self.assertIn(enclosure, sut.encloses)
- self.assertEqual(sut, enclosure.enclosed_by)
+ assert enclosure in sut.encloses
+ assert sut == enclosure.enclosed_by
sut.encloses.remove(enclosure)
- self.assertCountEqual([], sut.encloses)
- self.assertIsNone(enclosure.enclosed_by)
+ assert [] == list(sut.encloses)
+ assert enclosure.enclosed_by is None
def test_id(self) -> None:
place_id = 'C1'
sut = Place(place_id, [PlaceName('one')])
- self.assertEqual(place_id, sut.id)
+ assert place_id == sut.id
def test_links(self) -> None:
sut = Place('P1', [PlaceName('The Place')])
- self.assertCountEqual([], sut.links)
+ assert [] == list(sut.links)
def test_names(self) -> None:
name = PlaceName('The Place')
sut = Place('P1', [name])
- self.assertCountEqual([name], sut.names)
+ assert [name] == list(sut.names)
def test_coordinates(self) -> None:
name = PlaceName('The Place')
sut = Place('P1', [name])
coordinates = Point()
sut.coordinates = coordinates
- self.assertEqual(coordinates, sut.coordinates)
+ assert coordinates == sut.coordinates
-class SubjectTest(TestCase):
+class TestSubject:
def test_name(self) -> None:
- self.assertIsInstance(Subject.name(), str)
- self.assertNotEqual('', Subject.name)
+ assert isinstance(Subject.name(), str)
+ assert '' != Subject.name
def test_label(self) -> None:
sut = Subject()
with Translations(NullTranslations()):
- self.assertIsInstance(sut.label, str)
- self.assertNotEqual('', sut.label)
+ assert isinstance(sut.label, str)
+ assert '' != sut.label
-class WitnessTest(TestCase):
+class TestWitness:
def test_name(self) -> None:
- self.assertIsInstance(Witness.name(), str)
- self.assertNotEqual('', Witness.name)
+ assert isinstance(Witness.name(), str)
+ assert '' != Witness.name
def test_label(self) -> None:
sut = Witness()
with Translations(NullTranslations()):
- self.assertIsInstance(sut.label, str)
- self.assertNotEqual('', sut.label)
+ assert isinstance(sut.label, str)
+ assert '' != sut.label
-class BeneficiaryTest(TestCase):
+class TestBeneficiary:
def test_name(self) -> None:
- self.assertIsInstance(Beneficiary.name(), str)
- self.assertNotEqual('', Beneficiary.name)
+ assert isinstance(Beneficiary.name(), str)
+ assert '' != Beneficiary.name
def test_label(self) -> None:
sut = Beneficiary()
with Translations(NullTranslations()):
- self.assertIsInstance(sut.label, str)
- self.assertNotEqual('', sut.label)
+ assert isinstance(sut.label, str)
+ assert '' != sut.label
-class AttendeeTest(TestCase):
+class TestAttendee:
def test_name(self) -> None:
- self.assertIsInstance(Attendee.name(), str)
- self.assertNotEqual('', Attendee.name)
+ assert isinstance(Attendee.name(), str)
+ assert '' != Attendee.name
def test_label(self) -> None:
sut = Attendee()
with Translations(NullTranslations()):
- self.assertIsInstance(sut.label, str)
- self.assertNotEqual('', sut.label)
+ assert isinstance(sut.label, str)
+ assert '' != sut.label
-class PresenceTest(TestCase):
+class TestPresence:
def test_person(self) -> None:
person = Mock(Person)
sut = Presence(person, Mock(PresenceRole), Mock(Event))
- self.assertEqual(person, sut.person)
+ assert person == sut.person
def test_event(self) -> None:
role = Mock(PresenceRole)
sut = Presence(Mock(Person), role, Mock(Event))
- self.assertEqual(role, sut.role)
+ assert role == sut.role
def test_role(self) -> None:
event = Mock(Event)
sut = Presence(Mock(Person), Mock(PresenceRole), event)
- self.assertEqual(event, sut.event)
+ assert event == sut.event
-class EventTest(TestCase):
+class TestEvent:
def test_id(self) -> None:
event_id = 'E1'
sut = Event(event_id, Mock(EventType))
- self.assertEqual(event_id, sut.id)
+ assert event_id == sut.id
def test_place(self) -> None:
place = Place('1', [PlaceName('one')])
sut = Event(None, Mock(EventType))
sut.place = place
- self.assertEqual(place, sut.place)
- self.assertIn(sut, place.events)
+ assert place == sut.place
+ assert sut in place.events
sut.place = None
- self.assertEqual(None, sut.place)
- self.assertNotIn(sut, place.events)
+ assert sut.place is None
+ assert sut not in place.events
def test_presences(self) -> None:
person = Person('P1')
sut = Event(None, Mock(EventType))
presence = Presence(person, Subject(), sut)
sut.presences.append(presence)
- self.assertCountEqual([presence], sut.presences)
- self.assertEqual(sut, presence.event)
+ assert [presence] == list(sut.presences)
+ assert sut == presence.event
sut.presences.remove(presence)
- self.assertCountEqual([], sut.presences)
- self.assertIsNone(presence.event)
+ assert [] == list(sut.presences)
+ assert presence.event is None
def test_date(self) -> None:
sut = Event(None, Mock(EventType))
- self.assertIsNone(sut.date)
+ assert sut.date is None
date = Mock(Date)
sut.date = date
- self.assertEqual(date, sut.date)
+ assert date == sut.date
def test_files(self) -> None:
sut = Event(None, Mock(EventType))
- self.assertCountEqual([], sut.files)
+ assert [] == list(sut.files)
def test_citations(self) -> None:
sut = Event(None, Mock(EventType))
- self.assertCountEqual([], sut.citations)
+ assert [] == list(sut.citations)
def test_description(self) -> None:
sut = Event(None, Mock(EventType))
- self.assertIsNone(sut.description)
+ assert sut.description is None
def test_private(self) -> None:
sut = Event(None, Mock(EventType))
- self.assertIsNone(sut.private)
+ assert sut.private is None
def test_type(self) -> None:
event_type = Mock(EventType)
sut = Event(None, event_type)
- self.assertEqual(event_type, sut.type)
+ assert event_type == sut.type
def test_associated_files(self) -> None:
file1 = Mock(File)
@@ -538,46 +558,43 @@ def test_associated_files(self) -> None:
file3 = Mock(File)
file4 = Mock(File)
sut = Event(None, Mock(EventType))
- sut.files = [file1, file2, file1]
+ sut.files = [file1, file2, file1] # type: ignore
citation = Mock(Citation)
citation.associated_files = [file3, file4, file2]
- sut.citations = [citation]
- self.assertEqual([file1, file2, file3, file4], list(sut.associated_files))
+ sut.citations = [citation] # type: ignore
+ assert [file1 == file2, file3, file4], list(sut.associated_files)
-class PersonNameTest(TestCase):
+class TestPersonName:
def test_person(self) -> None:
person = Person('1')
sut = PersonName(person, 'Janet', 'Not a Girl')
- self.assertEqual(person, sut.person)
- self.assertCountEqual([sut], person.names)
- sut.person = None
- self.assertIsNone(sut.person)
- self.assertCountEqual([], person.names)
+ assert person == sut.person
+ assert [sut] == list(person.names)
def test_locale(self) -> None:
person = Person('1')
sut = PersonName(person, 'Janet', 'Not a Girl')
- self.assertIsNone(sut.locale)
+ assert sut.locale is None
def test_citations(self) -> None:
person = Person('1')
sut = PersonName(person, 'Janet', 'Not a Girl')
- self.assertCountEqual([], sut.citations)
+ assert [] == list(sut.citations)
def test_individual(self) -> None:
person = Person('1')
individual = 'Janet'
sut = PersonName(person, individual, 'Not a Girl')
- self.assertEqual(individual, sut.individual)
+ assert individual == sut.individual
def test_affiliation(self) -> None:
person = Person('1')
affiliation = 'Not a Girl'
sut = PersonName(person, 'Janet', affiliation)
- self.assertEqual(affiliation, sut.affiliation)
+ assert affiliation == sut.affiliation
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, left, right', [
(True, PersonName(Person('1'), 'Janet', 'Not a Girl'), PersonName(Person('1'), 'Janet', 'Not a Girl')),
(True, PersonName(Person('1'), 'Janet'), PersonName(Person('1'), 'Janet')),
(True, PersonName(Person('1'), None, 'Not a Girl'), PersonName(Person('1'), None, 'Not a Girl')),
@@ -588,122 +605,122 @@ def test_affiliation(self) -> None:
(False, PersonName(Person('1'), 'Janet', 'Not a Girl'), object()),
])
def test_eq(self, expected: bool, left: PersonName, right: Any) -> None:
- self.assertEqual(expected, left == right)
+ assert expected == (left == right)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, left, right', [
(False, PersonName(Person('1'), 'Janet', 'Not a Girl'), PersonName(Person('1'), 'Janet', 'Not a Girl')),
(True, PersonName(Person('1'), 'Janet', 'Not a Girl'), PersonName(Person('1'), 'Not a Girl', 'Janet')),
(True, PersonName(Person('1'), 'Janet', 'Not a Girl'), None),
])
def test_gt(self, expected: bool, left: PersonName, right: Any) -> None:
- self.assertEqual(expected, left > right)
+ assert expected == (left > right)
-class PersonTest(TestCase):
+class TestPerson:
def test_parents(self) -> None:
sut = Person('1')
parent = Person('2')
sut.parents.append(parent)
- self.assertCountEqual([parent], sut.parents)
- self.assertCountEqual([sut], parent.children)
+ assert [parent] == list(sut.parents)
+ assert [sut] == list(parent.children)
sut.parents.remove(parent)
- self.assertCountEqual([], sut.parents)
- self.assertCountEqual([], parent.children)
+ assert [] == list(sut.parents)
+ assert [] == list(parent.children)
def test_children(self) -> None:
sut = Person('1')
child = Person('2')
sut.children.append(child)
- self.assertCountEqual([child], sut.children)
- self.assertCountEqual([sut], child.parents)
+ assert [child] == list(sut.children)
+ assert [sut] == list(child.parents)
sut.children.remove(child)
- self.assertCountEqual([], sut.children)
- self.assertCountEqual([], child.parents)
+ assert [] == list(sut.children)
+ assert [] == list(child.parents)
def test_presences(self) -> None:
event = Event(None, Birth())
sut = Person('1')
presence = Presence(sut, Subject(), event)
sut.presences.append(presence)
- self.assertCountEqual([presence], sut.presences)
- self.assertEqual(sut, presence.person)
+ assert [presence] == list(sut.presences)
+ assert sut == presence.person
sut.presences.remove(presence)
- self.assertCountEqual([], sut.presences)
- self.assertIsNone(presence.person)
+ assert [] == list(sut.presences)
+ assert presence.person is None
def test_names(self) -> None:
sut = Person('1')
name = PersonName(sut, 'Janet', 'Not a Girl')
- self.assertCountEqual([name], sut.names)
- self.assertEqual(sut, name.person)
+ assert [name] == list(sut.names)
+ assert sut == name.person
sut.names.remove(name)
- self.assertCountEqual([], sut.names)
- self.assertIsNone(name.person)
+ assert [] == list(sut.names)
+ assert name.person is None
def test_id(self) -> None:
person_id = 'P1'
sut = Person(person_id)
- self.assertEqual(person_id, sut.id)
+ assert person_id == sut.id
def test_files(self) -> None:
- sut = Source(None)
- self.assertCountEqual([], sut.files)
+ sut = Person('1')
+ assert [] == list(sut.files)
def test_citations(self) -> None:
- sut = Source(None)
- self.assertCountEqual([], sut.citations)
+ sut = Person('1')
+ assert [] == list(sut.citations)
def test_links(self) -> None:
- sut = Source(None)
- self.assertCountEqual([], sut.links)
+ sut = Person('1')
+ assert [] == list(sut.links)
def test_private(self) -> None:
- sut = Event(None, Mock(EventType))
- self.assertIsNone(sut.private)
+ sut = Person('1')
+ assert sut.private is None
def test_name_with_names(self) -> None:
sut = Person('P1')
name = PersonName(sut)
- self.assertEqual(name, sut.name)
+ assert name == sut.name
def test_name_without_names(self) -> None:
- self.assertIsNone(Person('P1').name)
+ assert Person('P1').name is None
def test_alternative_names(self) -> None:
sut = Person('P1')
PersonName(sut, 'Janet', 'Not a Girl')
alternative_name = PersonName(sut, 'Janet', 'Still not a Girl')
- self.assertSequenceEqual([alternative_name], sut.alternative_names)
+ assert [alternative_name] == list(sut.alternative_names)
def test_start(self) -> None:
start = Event(None, Birth())
sut = Person('P1')
Presence(sut, Subject(), start)
- self.assertEqual(start, sut.start)
+ assert start == sut.start
def test_end(self) -> None:
end = Event(None, Burial())
sut = Person('P1')
Presence(sut, Subject(), end)
- self.assertEqual(end, sut.end)
+ assert end == sut.end
def test_siblings_without_parents(self) -> None:
sut = Person('person')
- self.assertCountEqual([], sut.siblings)
+ assert [] == list(sut.siblings)
def test_siblings_with_one_common_parent(self) -> None:
sut = Person('1')
sibling = Person('2')
parent = Person('3')
- parent.children = [sut, sibling]
- self.assertCountEqual([sibling], sut.siblings)
+ parent.children = [sut, sibling] # type: ignore
+ assert [sibling] == list(sut.siblings)
def test_siblings_with_multiple_common_parents(self) -> None:
sut = Person('1')
sibling = Person('2')
parent = Person('3')
- parent.children = [sut, sibling]
- self.assertCountEqual([sibling], sut.siblings)
+ parent.children = [sut, sibling] # type: ignore
+ assert [sibling] == list(sut.siblings)
def test_associated_files(self) -> None:
file1 = Mock(File)
@@ -713,12 +730,12 @@ def test_associated_files(self) -> None:
file5 = Mock(File)
file6 = Mock(File)
sut = Person('1')
- sut.files = [file1, file2, file1]
+ sut.files = [file1, file2, file1] # type: ignore
citation = Mock(Citation)
citation.associated_files = [file3, file4, file2]
name = PersonName(sut)
- name.citations = [citation]
+ name.citations = [citation] # type: ignore
event = Mock(Event)
event.associated_files = [file5, file6, file4]
Presence(sut, Subject(), event)
- self.assertEqual([file1, file2, file3, file4, file5, file6], list(sut.associated_files))
+ assert [file1 == file2, file3, file4, file5, file6], list(sut.associated_files)
diff --git a/betty/tests/npm/test___init__.py b/betty/tests/npm/test___init__.py
index c792daa04..a0862de17 100644
--- a/betty/tests/npm/test___init__.py
+++ b/betty/tests/npm/test___init__.py
@@ -1,26 +1,24 @@
from subprocess import CalledProcessError
-from unittest.mock import patch
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.npm import _NpmRequirement
-from betty.tests import TestCase
-class NpmRequirementTest(TestCase):
+class TestNpmRequirement:
def test_check_met(self) -> None:
with App():
sut = _NpmRequirement.check()
- self.assertTrue(sut.met)
+ assert sut.met
- @parameterized.expand([
- (CalledProcessError(1, ''),),
- (FileNotFoundError(),),
+ @pytest.mark.parametrize('e', [
+ CalledProcessError(1, ''),
+ FileNotFoundError(),
])
- @patch('betty.npm.npm')
- def test_check_unmet(self, e: Exception, m_npm) -> None:
+ def test_check_unmet(self, e: Exception, mocker) -> None:
+ m_npm = mocker.patch('betty.npm.npm')
m_npm.side_effect = e
with App():
sut = _NpmRequirement.check()
- self.assertFalse(sut.met)
+ assert not sut.met
diff --git a/betty/tests/privatizer/test__init__.py b/betty/tests/privatizer/test___init__.py
similarity index 82%
rename from betty/tests/privatizer/test__init__.py
rename to betty/tests/privatizer/test___init__.py
index 6baf7036f..22df3138a 100644
--- a/betty/tests/privatizer/test__init__.py
+++ b/betty/tests/privatizer/test___init__.py
@@ -1,17 +1,15 @@
from datetime import datetime
from typing import Optional
-from parameterized import parameterized
+import pytest
from betty.app import App
-from betty.asyncio import sync
from betty.load import load
from betty.locale import Date, DateRange
from betty.model.ancestry import Person, Presence, Event, Source, File, Subject, Attendee, Citation, Ancestry
from betty.model.event_type import Death, Birth, Marriage
from betty.privatizer import Privatizer, privatize
from betty.project import ProjectExtensionConfiguration
-from betty.tests import TestCase
def _expand_person(generation: int):
@@ -24,7 +22,7 @@ def _expand_person(generation: int):
date_over_lifetime_threshold = Date(lifetime_threshold_year - 1, 1, 1)
date_range_start_over_lifetime_threshold = DateRange(date_over_lifetime_threshold)
date_range_end_over_lifetime_threshold = DateRange(None, date_over_lifetime_threshold)
- return parameterized.expand([
+ return [
# If there are no events for a person, they are private.
(True, None, None),
(True, True, None),
@@ -75,11 +73,10 @@ def _expand_person(generation: int):
(False, None, Event(None, Birth(), date=date_range_end_over_lifetime_threshold)),
(True, True, Event(None, Birth(), date=date_range_end_over_lifetime_threshold)),
(False, False, Event(None, Birth(), date=date_range_end_over_lifetime_threshold)),
- ])
+ ]
-class PrivatizerTest(TestCase):
- @sync
+class TestPrivatizer:
async def test_post_load(self):
person = Person('P0')
Presence(person, Subject(), Event(None, Birth()))
@@ -102,9 +99,9 @@ async def test_post_load(self):
app.project.ancestry.entities.append(citation)
await load(app)
- self.assertTrue(person.private)
- self.assertTrue(source_file.private)
- self.assertTrue(citation_file.private)
+ assert person.private
+ assert source_file.private
+ assert citation_file.private
def test_privatize_person_should_not_privatize_if_public(self):
source_file = File('F0', __file__)
@@ -125,14 +122,14 @@ def test_privatize_person_should_not_privatize_if_public(self):
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(False, person.private)
- self.assertIsNone(citation.private)
- self.assertIsNone(source.private)
- self.assertIsNone(person_file.private)
- self.assertIsNone(citation_file.private)
- self.assertIsNone(source_file.private)
- self.assertIsNone(event_as_subject.private)
- self.assertIsNone(event_as_attendee.private)
+ assert not person.private
+ assert citation.private is None
+ assert source.private is None
+ assert person_file.private is None
+ assert citation_file.private is None
+ assert source_file.private is None
+ assert event_as_subject.private is None
+ assert event_as_attendee.private is None
def test_privatize_person_should_privatize_if_private(self):
source_file = File('F0', __file__)
@@ -153,16 +150,16 @@ def test_privatize_person_should_privatize_if_private(self):
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertTrue(person.private)
- self.assertTrue(citation.private)
- self.assertTrue(source.private)
- self.assertTrue(person_file.private)
- self.assertTrue(citation_file.private)
- self.assertTrue(source_file.private)
- self.assertTrue(event_as_subject.private)
- self.assertIsNone(event_as_attendee.private)
-
- @_expand_person(0)
+ assert person.private
+ assert citation.private
+ assert source.private
+ assert person_file.private
+ assert citation_file.private
+ assert source_file.private
+ assert event_as_subject.private
+ assert event_as_attendee.private is None
+
+ @pytest.mark.parametrize('expected, private, event', _expand_person(0))
def test_privatize_person_without_relatives(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -171,9 +168,9 @@ def test_privatize_person_without_relatives(self, expected, private, event: Opti
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(1)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(1))
def test_privatize_person_with_child(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -184,9 +181,9 @@ def test_privatize_person_with_child(self, expected, private, event: Optional[Ev
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(2)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(2))
def test_privatize_person_with_grandchild(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -199,9 +196,9 @@ def test_privatize_person_with_grandchild(self, expected, private, event: Option
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(3)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(3))
def test_privatize_person_with_great_grandchild(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -216,9 +213,9 @@ def test_privatize_person_with_great_grandchild(self, expected, private, event:
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(-1)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(-1))
def test_privatize_person_with_parent(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -229,9 +226,9 @@ def test_privatize_person_with_parent(self, expected, private, event: Optional[E
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(-2)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(-2))
def test_privatize_person_with_grandparent(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -244,9 +241,9 @@ def test_privatize_person_with_grandparent(self, expected, private, event: Optio
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
- @_expand_person(-3)
+ @pytest.mark.parametrize('expected, private, event', _expand_person(-3))
def test_privatize_person_with_great_grandparent(self, expected, private, event: Optional[Event]):
person = Person('P0')
person.private = private
@@ -261,7 +258,7 @@ def test_privatize_person_with_great_grandparent(self, expected, private, event:
ancestry = Ancestry()
ancestry.entities.append(person)
privatize(ancestry)
- self.assertEqual(expected, person.private)
+ assert expected == person.private
def test_privatize_event_should_not_privatize_if_public(self):
source_file = File('F0', __file__)
@@ -280,13 +277,13 @@ def test_privatize_event_should_not_privatize_if_public(self):
ancestry = Ancestry()
ancestry.entities.append(event)
privatize(ancestry)
- self.assertEqual(False, event.private)
- self.assertIsNone(event_file.private)
- self.assertIsNone(citation.private)
- self.assertIsNone(source.private)
- self.assertIsNone(citation_file.private)
- self.assertIsNone(source_file.private)
- self.assertIsNone(person.private)
+ assert not event.private
+ assert event_file.private is None
+ assert citation.private is None
+ assert source.private is None
+ assert citation_file.private is None
+ assert source_file.private is None
+ assert person.private is None
def test_privatize_event_should_privatize_if_private(self):
source_file = File('F0', __file__)
@@ -305,13 +302,13 @@ def test_privatize_event_should_privatize_if_private(self):
ancestry = Ancestry()
ancestry.entities.append(event)
privatize(ancestry)
- self.assertTrue(event.private)
- self.assertTrue(event_file.private)
- self.assertTrue(citation.private)
- self.assertTrue(source.private)
- self.assertTrue(citation_file.private)
- self.assertTrue(source_file.private)
- self.assertIsNone(person.private)
+ assert event.private
+ assert event_file.private
+ assert citation.private
+ assert source.private
+ assert citation_file.private
+ assert source_file.private
+ assert person.private is None
def test_privatize_source_should_not_privatize_if_public(self):
file = File('F0', __file__)
@@ -321,8 +318,8 @@ def test_privatize_source_should_not_privatize_if_public(self):
ancestry = Ancestry()
ancestry.entities.append(source)
privatize(ancestry)
- self.assertEqual(False, source.private)
- self.assertIsNone(file.private)
+ assert not source.private
+ assert file.private is None
def test_privatize_source_should_privatize_if_private(self):
file = File('F0', __file__)
@@ -332,8 +329,8 @@ def test_privatize_source_should_privatize_if_private(self):
ancestry = Ancestry()
ancestry.entities.append(source)
privatize(ancestry)
- self.assertTrue(source.private)
- self.assertTrue(file.private)
+ assert source.private
+ assert file.private
def test_privatize_citation_should_not_privatize_if_public(self):
source_file = File('F0', __file__)
@@ -346,10 +343,10 @@ def test_privatize_citation_should_not_privatize_if_public(self):
ancestry = Ancestry()
ancestry.entities.append(citation)
privatize(ancestry)
- self.assertEqual(False, citation.private)
- self.assertIsNone(source.private)
- self.assertIsNone(citation_file.private)
- self.assertIsNone(source_file.private)
+ assert not citation.private
+ assert source.private is None
+ assert citation_file.private is None
+ assert source_file.private is None
def test_privatize_citation_should_privatize_if_private(self):
source_file = File('F0', __file__)
@@ -362,10 +359,10 @@ def test_privatize_citation_should_privatize_if_private(self):
ancestry = Ancestry()
ancestry.entities.append(citation)
privatize(ancestry)
- self.assertTrue(citation.private)
- self.assertTrue(source.private)
- self.assertTrue(citation_file.private)
- self.assertTrue(source_file.private)
+ assert citation.private
+ assert source.private
+ assert citation_file.private
+ assert source_file.private
def test_privatize_file_should_not_privatize_if_public(self):
source = Source(None, 'The Source')
@@ -376,8 +373,8 @@ def test_privatize_file_should_not_privatize_if_public(self):
ancestry = Ancestry()
ancestry.entities.append(file)
privatize(ancestry)
- self.assertEqual(False, file.private)
- self.assertIsNone(citation.private)
+ assert not file.private
+ assert citation.private is None
def test_privatize_file_should_privatize_if_private(self):
source = Source(None, 'The Source')
@@ -388,5 +385,5 @@ def test_privatize_file_should_privatize_if_private(self):
ancestry = Ancestry()
ancestry.entities.append(file)
privatize(ancestry)
- self.assertTrue(True, file.private)
- self.assertTrue(citation.private)
+ assert True, file.private
+ assert citation.private
diff --git a/betty/tests/test_about.py b/betty/tests/test_about.py
index 4b941ac3e..40c8062a0 100644
--- a/betty/tests/test_about.py
+++ b/betty/tests/test_about.py
@@ -1,7 +1,6 @@
from betty import about
-from betty.tests import TestCase
-class VersionTest(TestCase):
+class TestVersion:
def test(self):
- self.assertIsInstance(about.version(), str)
+ assert isinstance(about.version(), str)
diff --git a/betty/tests/test_asyncio.py b/betty/tests/test_asyncio.py
index 8db06b7e9..f9cca501d 100644
--- a/betty/tests/test_asyncio.py
+++ b/betty/tests/test_asyncio.py
@@ -1,15 +1,16 @@
+import pytest
+
from betty.asyncio import sync
-from betty.tests import TestCase
-class SyncTest(TestCase):
+class TestSync:
def test_call_coroutinefunction_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
async def _async():
return expected
actual = sync(_async())
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_decorated_coroutinefunction_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -18,7 +19,7 @@ def test_call_decorated_coroutinefunction_should_return_result(self) -> None:
async def _async():
return expected
actual = _async()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_decorated_function_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -27,7 +28,7 @@ def test_call_decorated_function_should_return_result(self) -> None:
def _sync():
return expected
actual = _sync()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_decorated_callable_method_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -37,7 +38,7 @@ class _Sync:
def __call__(self, *args, **kwargs):
return expected
actual = _Sync()()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_decorated_callable_coroutinemethod_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -47,7 +48,7 @@ class _Sync:
async def __call__(self, *args, **kwargs):
return expected
actual = _Sync()()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_wrapped_callable_object_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -56,7 +57,7 @@ class _Sync:
def __call__(self, *args, **kwargs):
return expected
actual = sync(_Sync())()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_wrapped_coroutinecallable_object_should_return_result(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -65,7 +66,7 @@ class _Sync:
async def __call__(self, *args, **kwargs):
return expected
actual = sync(_Sync())()
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_call_nested_sync_and_async(self) -> None:
expected = 'Hello, oh asynchronous, world!'
@@ -81,8 +82,8 @@ def _sync():
async def _async_two():
return expected
- self.assertEqual(expected, _async_one())
+ assert expected == _async_one()
def test_unsychronizable(self) -> None:
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
sync(True)
diff --git a/betty/tests/test_cli.py b/betty/tests/test_cli.py
index 6ac89d667..ff9d4a57b 100644
--- a/betty/tests/test_cli.py
+++ b/betty/tests/test_cli.py
@@ -1,5 +1,4 @@
import os
-import unittest
from json import dump
from pathlib import Path
from tempfile import TemporaryDirectory
@@ -7,14 +6,16 @@
from unittest.mock import patch
import click
+import pytest
from click.testing import CliRunner
from betty import fs
+from betty.config import DumpedConfiguration
from betty.error import UserFacingError
from betty.os import ChDir
-from betty.project import ProjectConfiguration
+from betty.project import ProjectConfiguration, ProjectExtensionConfiguration
from betty.serve import Server
-from betty.tests import patch_cache, TestCase
+from betty.tests import patch_cache
try:
from unittest.mock import AsyncMock
@@ -25,11 +26,11 @@
from betty.app import App, Extension
-class TestCommandError(BaseException):
+class DummyCommandError(BaseException):
pass
-class TestExtension(Extension, CommandProvider):
+class DummyExtension(Extension, CommandProvider):
@property
def commands(self) -> Dict[str, Callable]:
return {
@@ -39,164 +40,139 @@ def commands(self) -> Dict[str, Callable]:
@click.command(name='test')
@global_command
async def _test_command(self):
- raise TestCommandError
+ raise DummyCommandError
@patch('sys.stderr')
@patch('sys.stdout')
-class MainTest(TestCase):
+class TestMain:
def test_without_arguments(self, _, __):
runner = CliRunner()
result = runner.invoke(main, catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ assert 0 == result.exit_code
def test_help_without_configuration(self, _, __):
runner = CliRunner()
result = runner.invoke(main, ('--help',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ assert 0 == result.exit_code
def test_configuration_without_help(self, _, __):
- with TemporaryDirectory() as working_directory_path:
- configuration_file_path = Path(working_directory_path) / 'betty.json'
- url = 'https://example.com'
- config_dict = {
- 'base_url': url,
- }
- with open(configuration_file_path, 'w') as f:
- dump(config_dict, f)
-
- runner = CliRunner()
- result = runner.invoke(main, ('-c', configuration_file_path), catch_exceptions=False)
- self.assertEqual(2, result.exit_code)
+ configuration = ProjectConfiguration()
+ configuration.write()
+ runner = CliRunner()
+ result = runner.invoke(main, ('-c', str(configuration.configuration_file_path)), catch_exceptions=False)
+ assert 2 == result.exit_code
def test_help_with_configuration(self, _, __):
- with TemporaryDirectory() as working_directory_path:
- configuration_file_path = Path(working_directory_path) / 'betty.json'
- url = 'https://example.com'
- config_dict = {
- 'base_url': url,
- 'extensions': {
- TestExtension.name(): {},
- },
- }
- with open(configuration_file_path, 'w') as f:
- dump(config_dict, f)
-
- runner = CliRunner()
- result = runner.invoke(main, ('-c', configuration_file_path, '--help',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ configuration = ProjectConfiguration()
+ configuration.extensions.add(ProjectExtensionConfiguration(DummyExtension))
+ configuration.write()
+ runner = CliRunner()
+ result = runner.invoke(main, ('-c', str(configuration.configuration_file_path), '--help',), catch_exceptions=False)
+ assert 0 == result.exit_code
def test_help_with_invalid_configuration_file_path(self, _, __):
with TemporaryDirectory() as working_directory_path:
configuration_file_path = Path(working_directory_path) / 'non-existent-betty.json'
runner = CliRunner()
- result = runner.invoke(main, ('-c', configuration_file_path, '--help',), catch_exceptions=False)
- self.assertEqual(1, result.exit_code)
+ result = runner.invoke(main, ('-c', str(configuration_file_path), '--help',), catch_exceptions=False)
+ assert 1 == result.exit_code
def test_help_with_invalid_configuration(self, _, __):
with TemporaryDirectory() as working_directory_path:
configuration_file_path = Path(working_directory_path) / 'betty.json'
- config_dict = {}
+ dumped_configuration: DumpedConfiguration = {}
with open(configuration_file_path, 'w') as f:
- dump(config_dict, f)
+ dump(dumped_configuration, f)
runner = CliRunner()
- result = runner.invoke(main, ('-c', configuration_file_path, '--help',), catch_exceptions=False)
- self.assertEqual(1, result.exit_code)
+ result = runner.invoke(main, ('-c', str(configuration_file_path), '--help',), catch_exceptions=False)
+ assert 1 == result.exit_code
def test_with_discovered_configuration(self, _, __):
with TemporaryDirectory() as working_directory_path:
with open(Path(working_directory_path) / 'betty.json', 'w') as config_file:
url = 'https://example.com'
- config_dict = {
+ dumped_configuration: DumpedConfiguration = {
'base_url': url,
'extensions': {
- TestExtension.name(): None,
+ DummyExtension.name(): None,
},
}
- dump(config_dict, config_file)
+ dump(dumped_configuration, config_file)
with ChDir(working_directory_path):
runner = CliRunner()
result = runner.invoke(main, ('test',), catch_exceptions=False)
- self.assertEqual(1, result.exit_code)
+ assert 1 == result.exit_code
-class CatchExceptionsTest(unittest.TestCase):
- def test_logging_user_facing_error(self) -> None:
+class TestCatchExceptions:
+ def test_logging_user_facing_error(self, caplog) -> None:
error_message = 'Something went wrong!'
- with self.assertLogs() as watcher:
- with self.assertRaises(SystemExit):
- with catch_exceptions():
- raise UserFacingError(error_message)
- self.assertEqual('ERROR:root:%s' % error_message, watcher.output[0])
+ with pytest.raises(SystemExit):
+ with catch_exceptions():
+ raise UserFacingError(error_message)
+ assert f'ERROR:root:{error_message}' == caplog.text
- def test_logging_uncaught_exception(self) -> None:
+ def test_logging_uncaught_exception(self, caplog) -> None:
error_message = 'Something went wrong!'
- with self.assertLogs() as watcher:
- with self.assertRaises(SystemExit):
- with catch_exceptions():
- raise Exception(error_message)
- self.assertTrue(watcher.output[0].startswith('ERROR:root:%s' % error_message))
- self.assertIn('Traceback', watcher.output[0])
+ with pytest.raises(SystemExit):
+ with catch_exceptions():
+ raise Exception(error_message)
+ assert caplog.text.startswith(f'ERROR:root:{error_message}')
+ assert 'Traceback' in caplog.text
-class VersionTest(TestCase):
+class TestVersion:
def test(self):
runner = CliRunner()
result = runner.invoke(main, ('--version'), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
- self.assertIn('Betty', result.stdout)
+ assert 0 == result.exit_code
+ assert 'Betty' in result.stdout
-class ClearCachesTest(TestCase):
+class TestClearCaches:
@patch_cache
def test(self):
cached_file_path = Path(fs.CACHE_DIRECTORY_PATH) / 'KeepMeAroundPlease'
open(cached_file_path, 'w').close()
runner = CliRunner()
result = runner.invoke(main, ('clear-caches',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
- with self.assertRaises(FileNotFoundError):
+ assert 0 == result.exit_code
+ with pytest.raises(FileNotFoundError):
open(cached_file_path)
-class DemoTest(TestCase):
+class TestDemo:
@patch('betty.serve.AppServer', new_callable=lambda: _KeyboardInterruptedServer)
def test(self, m_server):
runner = CliRunner()
result = runner.invoke(main, ('demo',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ assert 0 == result.exit_code
-class GenerateTest(TestCase):
+class TestGenerate:
@patch('betty.generate.generate', new_callable=AsyncMock)
@patch('betty.load.load', new_callable=AsyncMock)
- def test(self, m_parse, m_generate):
- with TemporaryDirectory() as working_directory_path:
- configuration_file_path = Path(working_directory_path) / 'betty.json'
- url = 'https://example.com'
- config_dict = {
- 'base_url': url,
- }
- with open(configuration_file_path, 'w') as f:
- dump(config_dict, f)
-
- runner = CliRunner()
- result = runner.invoke(main, ('-c', configuration_file_path, 'generate',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ def test(self, m_load, m_generate):
+ configuration = ProjectConfiguration()
+ configuration.write()
+ runner = CliRunner()
+ result = runner.invoke(main, ('-c', str(configuration.configuration_file_path), 'generate',), catch_exceptions=False)
+ assert 0 == result.exit_code
- m_parse.assert_called_once()
- parse_args, parse_kwargs = m_parse.await_args
- self.assertEqual(1, len(parse_args))
- self.assertIsInstance(parse_args[0], App)
- self.assertEqual({}, parse_kwargs)
+ m_load.assert_called_once()
+ parse_args, parse_kwargs = m_load.await_args
+ assert 1 == len(parse_args)
+ assert isinstance(parse_args[0], App)
+ assert {} == parse_kwargs
- m_generate.assert_called_once()
- render_args, render_kwargs = m_generate.call_args
- self.assertEqual(1, len(render_args))
- self.assertIsInstance(render_args[0], App)
- self.assertEqual({}, render_kwargs)
+ m_generate.assert_called_once()
+ render_args, render_kwargs = m_generate.call_args
+ assert 1 == len(render_args)
+ assert isinstance(render_args[0], App)
+ assert {} == render_kwargs
class _KeyboardInterruptedServer(Server):
@@ -207,7 +183,7 @@ async def start(self) -> None:
raise KeyboardInterrupt
-class ServeTest(TestCase):
+class Serve:
@patch('betty.serve.AppServer', new_callable=lambda: _KeyboardInterruptedServer)
def test(self, m_server):
configuration = ProjectConfiguration()
@@ -215,4 +191,4 @@ def test(self, m_server):
os.makedirs(configuration.www_directory_path)
runner = CliRunner()
result = runner.invoke(main, ('-c', configuration.configuration_file_path, 'serve',), catch_exceptions=False)
- self.assertEqual(0, result.exit_code)
+ assert 0 == result.exit_code
diff --git a/betty/tests/test_concurrent.py b/betty/tests/test_concurrent.py
index 9262e00dd..4f9cad516 100644
--- a/betty/tests/test_concurrent.py
+++ b/betty/tests/test_concurrent.py
@@ -1,11 +1,12 @@
from concurrent.futures.thread import ThreadPoolExecutor
from time import sleep
+import pytest
+
from betty.concurrent import ExceptionRaisingAwaitableExecutor
-from betty.tests import TestCase
-class ExceptionRaisingAwaitableExecutorTest(TestCase):
+class TestExceptionRaisingAwaitableExecutor:
def test_without_exception_should_not_raise(self) -> None:
def _task():
return
@@ -17,7 +18,7 @@ def test_with_exception_should_raise(self) -> None:
def _task():
raise RuntimeError()
- with self.assertRaises(RuntimeError):
+ with pytest.raises(RuntimeError):
with ExceptionRaisingAwaitableExecutor(ThreadPoolExecutor()) as sut:
sut.submit(_task)
@@ -32,12 +33,12 @@ def _task():
sut = ExceptionRaisingAwaitableExecutor(ThreadPoolExecutor())
future = sut.submit(_task)
sut.wait()
- self.assertTrue(future.result())
- self.assertEqual([True], tracker)
+ assert future.result() is True
+ assert [True] == tracker
future = sut.submit(_task)
sut.wait()
- self.assertTrue(future.result())
- self.assertEqual([True, True], tracker)
+ assert future.result() is True
+ assert [True, True] == tracker
def test_wait_with_mapped_tasks(self) -> None:
tracker = []
@@ -50,9 +51,9 @@ def _task(arg):
sut = ExceptionRaisingAwaitableExecutor(ThreadPoolExecutor())
future = sut.map(_task, [1])
sut.wait()
- self.assertEqual([1], list(future))
- self.assertEqual([True], tracker)
+ assert [1] == list(future)
+ assert [True] == tracker
future = sut.map(_task, [2])
sut.wait()
- self.assertEqual([2], list(future))
- self.assertEqual([1, 2], tracker)
+ assert [2] == list(future)
+ assert [1, 2] == tracker
diff --git a/betty/tests/test_config.py b/betty/tests/test_config.py
index 19703ebc8..10d9da391 100644
--- a/betty/tests/test_config.py
+++ b/betty/tests/test_config.py
@@ -1,27 +1,28 @@
from tempfile import NamedTemporaryFile
+import pytest
+
from betty.app import App
from betty.config import _from_json, _from_yaml, ConfigurationError, FileBasedConfiguration
-from betty.tests import TestCase
-class FromJsonTest(TestCase):
+class TestFromJson:
def test_should_error_if_invalid_json(self) -> None:
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
_from_json('')
-class FromYamlTest(TestCase):
+class TestFromYaml:
def test_should_error_if_invalid_yaml(self) -> None:
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
_from_yaml('"foo')
-class FileBasedConfigurationTest(TestCase):
+class TestFileBasedConfiguration:
def test_configuration_file_path_should_error_unknown_format(self) -> None:
configuration = FileBasedConfiguration()
with NamedTemporaryFile(mode='r+', suffix='.abc') as f:
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration.configuration_file_path = f.name
diff --git a/betty/tests/test_error.py b/betty/tests/test_error.py
index 052a6d15b..eb3351b0b 100644
--- a/betty/tests/test_error.py
+++ b/betty/tests/test_error.py
@@ -1,12 +1,11 @@
from betty.error import ContextError
-from betty.tests import TestCase
-class ContextErrorTest(TestCase):
+class TestContextError:
def test__str__(self):
message = 'Something went wrong!'
context = 'Somewhere, at some point...'
expected = 'Something went wrong!\n- Somewhere, at some point...'
sut = ContextError(message)
sut.add_context(context)
- self.assertEqual(expected, str(sut))
+ assert expected == str(sut)
diff --git a/betty/tests/test_fs.py b/betty/tests/test_fs.py
index dd2f90af6..fd904edb3 100644
--- a/betty/tests/test_fs.py
+++ b/betty/tests/test_fs.py
@@ -1,13 +1,12 @@
from pathlib import Path
from tempfile import TemporaryDirectory
+import pytest
+
from betty.fs import iterfiles, FileSystem, hashfile
-from betty.asyncio import sync
-from betty.tests import TestCase
-class IterfilesTest(TestCase):
- @sync
+class TestIterfiles:
async def test_iterfiles(self):
with TemporaryDirectory() as working_directory_path:
working_subdirectory_path = Path(working_directory_path) / 'subdir'
@@ -16,27 +15,26 @@ async def test_iterfiles(self):
open(Path(working_directory_path) / '.hiddenrootfile', 'a').close()
open(Path(working_subdirectory_path) / 'subdirfile', 'a').close()
actual = [str(actualpath)[len(working_directory_path) + 1:] async for actualpath in iterfiles(working_directory_path)]
- expected = [
+ expected = {
'.hiddenrootfile',
'rootfile',
str(Path('subdir') / 'subdirfile'),
- ]
- self.assertCountEqual(expected, actual)
+ }
+ assert expected == set(actual)
-class HashfileTest(TestCase):
+class TestHashfile:
def test_hashfile_with_identical_file(self):
file_path = Path(__file__).parents[1] / 'assets' / 'public' / 'static' / 'betty-16x16.png'
- self.assertEqual(hashfile(file_path), hashfile(file_path))
+ assert hashfile(file_path) == hashfile(file_path)
def test_hashfile_with_different_files(self):
file_path_1 = Path(__file__).parents[1] / 'assets' / 'public' / 'static' / 'betty-16x16.png'
file_path_2 = Path(__file__).parents[1] / 'assets' / 'public' / 'static' / 'betty-512x512.png'
- self.assertNotEqual(hashfile(file_path_1), hashfile(file_path_2))
+ assert hashfile(file_path_1) != hashfile(file_path_2)
-class FileSystemTest(TestCase):
- @sync
+class TestFileSystem:
async def test_open(self):
with TemporaryDirectory() as source_path_1:
with TemporaryDirectory() as source_path_2:
@@ -52,17 +50,16 @@ async def test_open(self):
sut = FileSystem((source_path_1, None), (source_path_2, None))
async with sut.open('apples') as f:
- self.assertEqual('apples', await f.read())
+ assert 'apples' == await f.read()
async with sut.open('oranges') as f:
- self.assertEqual('oranges', await f.read())
+ assert 'oranges' == await f.read()
async with sut.open('bananas') as f:
- self.assertEqual('bananas', await f.read())
+ assert 'bananas' == await f.read()
- with self.assertRaises(FileNotFoundError):
+ with pytest.raises(FileNotFoundError):
async with sut.open('mangos'):
pass
- @sync
async def test_open_with_first_file_path_alternative_first_source_path(self):
with TemporaryDirectory() as source_path_1:
with TemporaryDirectory() as source_path_2:
@@ -78,9 +75,8 @@ async def test_open_with_first_file_path_alternative_first_source_path(self):
sut = FileSystem((source_path_1, None), (source_path_2, None))
async with sut.open('pinkladies', 'apples') as f:
- self.assertEqual('pinkladies', await f.read())
+ assert 'pinkladies' == await f.read()
- @sync
async def test_open_with_first_file_path_alternative_second_source_path(self):
with TemporaryDirectory() as source_path_1:
with TemporaryDirectory() as source_path_2:
@@ -94,9 +90,8 @@ async def test_open_with_first_file_path_alternative_second_source_path(self):
sut = FileSystem((source_path_1, None), (source_path_2, None))
async with sut.open('pinkladies', 'apples') as f:
- self.assertEqual('pinkladies', await f.read())
+ assert 'pinkladies' == await f.read()
- @sync
async def test_open_with_second_file_path_alternative_first_source_path(self):
with TemporaryDirectory() as source_path_1:
with TemporaryDirectory() as source_path_2:
@@ -108,9 +103,8 @@ async def test_open_with_second_file_path_alternative_first_source_path(self):
sut = FileSystem((source_path_1, None), (source_path_2, None))
async with sut.open('pinkladies', 'apples') as f:
- self.assertEqual('apples', await f.read())
+ assert 'apples' == await f.read()
- @sync
async def test_copy2(self):
with TemporaryDirectory() as source_path_1:
with TemporaryDirectory() as source_path_2:
@@ -131,16 +125,15 @@ async def test_copy2(self):
await sut.copy2('bananas', destination_path)
async with sut.open(Path(destination_path) / 'apples') as f:
- self.assertEqual('apples', await f.read())
+ assert 'apples' == await f.read()
async with sut.open(Path(destination_path) / 'oranges') as f:
- self.assertEqual('oranges', await f.read())
+ assert 'oranges' == await f.read()
async with sut.open(Path(destination_path) / 'bananas') as f:
- self.assertEqual('bananas', await f.read())
+ assert 'bananas' == await f.read()
- with self.assertRaises(FileNotFoundError):
+ with pytest.raises(FileNotFoundError):
await sut.copy2('mangos', destination_path)
- @sync
async def test_copytree(self):
with TemporaryDirectory() as source_path_1:
(Path(source_path_1) / 'basket').mkdir()
@@ -161,8 +154,8 @@ async def test_copytree(self):
await sut.copytree('', destination_path)
async with sut.open(Path(destination_path) / 'basket' / 'apples') as f:
- self.assertEqual('apples', await f.read())
+ assert 'apples' == await f.read()
async with sut.open(Path(destination_path) / 'basket' / 'oranges') as f:
- self.assertEqual('oranges', await f.read())
+ assert 'oranges' == await f.read()
async with sut.open(Path(destination_path) / 'basket' / 'bananas') as f:
- self.assertEqual('bananas', await f.read())
+ assert 'bananas' == await f.read()
diff --git a/betty/tests/test_functools.py b/betty/tests/test_functools.py
index 0c4e7e485..262f0dbda 100644
--- a/betty/tests/test_functools.py
+++ b/betty/tests/test_functools.py
@@ -1,31 +1,30 @@
from typing import Any, List
-from parameterized import parameterized
+import pytest
from betty.functools import walk, slice_to_range
-from betty.tests import TestCase
-class WalkTest(TestCase):
+class TestWalk:
class _Item:
def __init__(self, child):
self.child = child
- @parameterized.expand([
- (None,),
- (True,),
- (object(),),
- ([],),
+ @pytest.mark.parametrize('item', [
+ None,
+ True,
+ object(),
+ [],
])
def test_with_invalid_attribute(self, item: Any) -> None:
- with self.assertRaises(AttributeError):
+ with pytest.raises(AttributeError):
list(walk(item, 'invalid_attribute_name'))
def test_one_to_one_without_descendants(self) -> None:
item = self._Item(None)
actual = walk(item, 'child')
- expected = []
- self.assertEqual(expected, list(actual))
+ expected: List[None] = []
+ assert expected == list(actual)
def test_one_to_one_with_descendants(self) -> None:
grandchild = self._Item(None)
@@ -33,13 +32,13 @@ def test_one_to_one_with_descendants(self) -> None:
item = self._Item(child)
actual = walk(item, 'child')
expected = [child, grandchild]
- self.assertEqual(expected, list(actual))
+ assert expected == list(actual)
def test_one_to_many_without_descendants(self) -> None:
item = self._Item([])
actual = walk(item, 'child')
- expected = []
- self.assertEqual(expected, list(actual))
+ expected: List[None] = []
+ assert expected == list(actual)
def test_with_one_to_many_descendants(self) -> None:
grandchild = self._Item([])
@@ -47,7 +46,7 @@ def test_with_one_to_many_descendants(self) -> None:
item = self._Item([child])
actual = walk(item, 'child')
expected = [child, grandchild]
- self.assertEqual(expected, list(actual))
+ assert expected == list(actual)
def test_with_mixed_descendants(self) -> None:
grandchild = self._Item([])
@@ -55,11 +54,11 @@ def test_with_mixed_descendants(self) -> None:
item = self._Item([child])
actual = walk(item, 'child')
expected = [child, grandchild]
- self.assertEqual(expected, list(actual))
+ assert expected == list(actual)
-class SliceToRangeTest(TestCase):
- @parameterized.expand([
+class TestSliceToRange:
+ @pytest.mark.parametrize('expected_range_items, ranged_slice', [
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], slice(0, 16, 1)),
([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], slice(0, None, 1)),
([0], slice(None, 1, 1)),
@@ -71,4 +70,4 @@ class SliceToRangeTest(TestCase):
])
def test(self, expected_range_items: List[int], ranged_slice: slice) -> None:
iterable = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
- self.assertEqual(expected_range_items, list(slice_to_range(ranged_slice, iterable)))
+ assert expected_range_items == list(slice_to_range(ranged_slice, iterable))
diff --git a/betty/tests/test_generate.py b/betty/tests/test_generate.py
index 187c3138d..21c87dc2f 100644
--- a/betty/tests/test_generate.py
+++ b/betty/tests/test_generate.py
@@ -9,39 +9,35 @@
from betty import json
from betty.app import App
-from betty.asyncio import sync
from betty.generate import generate
from betty.model.ancestry import Person, Place, Source, PlaceName, File, Event, Citation
from betty.model.event_type import Birth
from betty.project import LocaleConfiguration
-from betty.tests import TestCase
-class GenerateTestCase(TestCase):
- def assert_betty_html(self, app: App, url_path: str) -> Path:
- file_path = app.project.configuration.www_directory_path / Path(url_path.lstrip('/'))
- self.assertTrue(file_path.exists(), '%s does not exist' % file_path)
- with open(file_path) as f:
- parser = html5lib.HTMLParser(strict=True)
- parser.parse(f)
- return file_path
+def assert_betty_html(app: App, url_path: str) -> Path:
+ file_path = app.project.configuration.www_directory_path / Path(url_path.lstrip('/'))
+ assert file_path.exists(), f'{file_path} does not exist'
+ with open(file_path) as f:
+ parser = html5lib.HTMLParser(strict=True)
+ parser.parse(f)
+ return file_path
- def assert_betty_json(self, app: App, url_path: str, schema_definition: str) -> Path:
- file_path = app.project.configuration.www_directory_path / Path(url_path.lstrip('/'))
- self.assertTrue(file_path.exists(), '%s does not exist' % file_path)
- with open(file_path) as f:
- json.validate(stdjson.load(f), schema_definition, app)
- return file_path
+def assert_betty_json(app: App, url_path: str, schema_definition: str) -> Path:
+ file_path = app.project.configuration.www_directory_path / Path(url_path.lstrip('/'))
+ assert file_path.exists(), f'{file_path} does not exist'
+ with open(file_path) as f:
+ json.validate(stdjson.load(f), schema_definition, app)
+ return file_path
-class GenerateTest(GenerateTestCase):
- @sync
+
+class TestGenerate:
async def test_front_page(self):
with App() as app:
await generate(app)
- self.assert_betty_html(app, '/index.html')
+ assert_betty_html(app, '/index.html')
- @sync
async def test_translations(self):
app = App()
app.project.configuration.locales.replace([
@@ -50,105 +46,92 @@ async def test_translations(self):
])
with app:
await generate(app)
- with open(self.assert_betty_html(app, '/nl/index.html')) as f:
+ with open(assert_betty_html(app, '/nl/index.html')) as f:
html = f.read()
- self.assertIn(' None:
sut = Jinja2Provider()
- self.assertIsInstance(sut.globals, dict)
+ assert isinstance(sut.globals, dict)
def test_filters(self) -> None:
sut = Jinja2Provider()
- self.assertIsInstance(sut.filters, dict)
+ assert isinstance(sut.filters, dict)
-class Jinja2RendererTest(TestCase):
- @sync
+class TestJinja2Renderer:
async def test_render_file(self) -> None:
with App() as app:
sut = Jinja2Renderer(app.jinja2_environment, app.project.configuration)
@@ -39,10 +37,9 @@ async def test_render_file(self) -> None:
f.write(template)
await sut.render_file(template_file_path)
with open(Path(working_directory_path) / 'betty.txt') as f:
- self.assertEqual(expected_output, f.read().strip())
- self.assertFalse(template_file_path.exists())
+ assert expected_output == f.read().strip()
+ assert not template_file_path.exists()
- @sync
async def test_render_tree(self) -> None:
with App() as app:
sut = Jinja2Renderer(app.jinja2_environment, app.project.configuration)
@@ -56,12 +53,12 @@ async def test_render_tree(self) -> None:
f.write(template)
await sut.render_tree(working_directory_path)
with open(Path(working_subdirectory_path) / 'betty.txt') as f:
- self.assertEqual(expected_output, f.read().strip())
- self.assertFalse(scss_file_path.exists())
+ assert expected_output == f.read().strip()
+ assert not scss_file_path.exists()
class FilterFlattenTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template', [
('', '{{ [] | flatten | join(", ") }}'),
('', '{{ [[], [], []] | flatten | join(", ") }}'),
('kiwi, apple, banana',
@@ -69,7 +66,7 @@ class FilterFlattenTest(TemplateTestCase):
])
def test(self, expected, template):
with self._render(template_string=template) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class FilterWalkTest(TemplateTestCase):
@@ -81,7 +78,7 @@ def __init__(self, label, children=None):
def __str__(self):
return self._label
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template, data', [
('', '{{ data | walk("children") | join }}', WalkData('parent')),
('child1, child1child1, child2', '{{ data | walk("children") | join(", ") }}',
WalkData('parent', [WalkData('child1', [WalkData('child1child1')]), WalkData('child2')])),
@@ -90,28 +87,28 @@ def test(self, expected, template, data):
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class FilterParagraphsTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template', [
('', '{{ "" | paragraphs }}'),
('Apples
\n and
\n oranges
',
'{{ "Apples \n and \n oranges" | paragraphs }}'),
])
def test(self, expected, template):
with self._render(template_string=template) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class FilterFormatDegreesTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template', [
('0° 0' 0"', '{{ 0 | format_degrees }}'),
('52° 22' 1"', '{{ 52.367 | format_degrees }}'),
])
def test(self, expected, template):
with self._render(template_string=template) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class FilterUniqueTest(TemplateTestCase):
@@ -125,7 +122,7 @@ def test(self):
with self._render(template_string='{{ data | unique | join(", ") }}', data={
'data': data,
}) as (actual, _):
- self.assertEqual('999, {}', actual)
+ assert '999 == {}', actual
class FilterMapTest(TemplateTestCase):
@@ -133,7 +130,7 @@ class MapData:
def __init__(self, label):
self.label = label
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template, data', [
('kiwi, apple, banana', '{{ data | map(attribute="label") | join(", ") }}',
[MapData('kiwi'), MapData('apple'), MapData('banana')]),
('kiwi, None, apple, None, banana',
@@ -144,11 +141,11 @@ def test(self, expected, template, data):
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class FilterFileTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template, file', [
(
'/file/F1/file/test_jinja2.py',
'{{ file | file }}',
@@ -164,15 +161,15 @@ def test(self, expected, template, file):
with self._render(template_string=template, data={
'file': file,
}) as (actual, app):
- self.assertEqual(expected, actual)
+ assert expected == actual
for file_path in actual.split(':'):
- self.assertTrue((app.project.configuration.www_directory_path / file_path[1:]).exists())
+ assert ((app.project.configuration.www_directory_path / file_path[1:]).exists())
class FilterImageTest(TemplateTestCase):
image_path = Path(__file__).parents[1] / 'assets' / 'public' / 'static' / 'betty-512x512.png'
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, template, file', [
('/file/F1-99x-.png',
'{{ file | image(width=99) }}', File('F1', image_path, media_type=MediaType('image/png'))),
('/file/F1--x99.png',
@@ -186,13 +183,13 @@ def test(self, expected, template, file):
with self._render(template_string=template, data={
'file': file,
}) as (actual, app):
- self.assertEqual(expected, actual)
+ assert expected == actual
for file_path in actual.split(':'):
- self.assertTrue((app.project.configuration.www_directory_path / file_path[1:]).exists())
+ assert ((app.project.configuration.www_directory_path / file_path[1:]).exists())
def test_without_width(self):
file = File('F1', self.image_path, media_type=MediaType('image/png'))
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
with self._render(template_string='{{ file | image }}', data={
'file': file,
}):
@@ -204,9 +201,9 @@ def test_cite(self):
citation1 = Mock(Citation)
citation2 = Mock(Citation)
sut = _Citer()
- self.assertEqual(1, sut.cite(citation1))
- self.assertEqual(2, sut.cite(citation2))
- self.assertEqual(1, sut.cite(citation1))
+ assert 1 == sut.cite(citation1)
+ assert 2 == sut.cite(citation2)
+ assert 1 == sut.cite(citation1)
def test_iter(self):
citation1 = Mock(Citation)
@@ -214,7 +211,7 @@ def test_iter(self):
sut = _Citer()
sut.cite(citation1)
sut.cite(citation2)
- self.assertEqual([(1, citation1), (2, citation2)], list(sut))
+ assert [(1, citation1), (2, citation2)] == list(sut)
def test_len(self):
citation1 = Mock(Citation)
@@ -222,7 +219,7 @@ def test_len(self):
sut = _Citer()
sut.cite(citation1)
sut.cite(citation2)
- self.assertEqual(2, len(sut))
+ assert 2 == len(sut)
class FormatDateTest(TemplateTestCase):
@@ -232,7 +229,7 @@ def test(self):
with self._render(template_string=template, data={
'date': date,
}) as (actual, _):
- self.assertEqual('January 1, 1970', actual)
+ assert 'January 1 == 1970', actual
class FilterSortLocalizedsTest(TemplateTestCase):
@@ -262,18 +259,18 @@ def test(self):
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual('[first, second, third]', actual)
+ assert '[first == second, third]', actual
def test_with_empty_iterable(self):
template = '{{ data | sort_localizeds(localized_attribute="names", sort_attribute="name") }}'
with self._render(template_string=template, data={
'data': [],
}) as (actual, _):
- self.assertEqual('[]', actual)
+ assert '[]' == actual
class FilterSelectLocalizedsTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale, data', [
('', 'en', []),
('Apple', 'en', [
PlaceName('Apple', 'en')
@@ -294,12 +291,12 @@ class FilterSelectLocalizedsTest(TemplateTestCase):
def test(self, expected: str, locale: str, data: Iterable[Localized]):
template = '{{ data | select_localizeds | map(attribute="name") | join(", ") }}'
- def _set_up(app: App) -> Iterator[ContextManager]:
- return (app.acquire_locale(locale),)
+ def _set_up(app: App) -> ContextManager[None]:
+ return app.acquire_locale(locale) # type: ignore
with self._render(template_string=template, data={
'data': data,
}, set_up=_set_up) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
def test_include_unspecified(self):
template = '{{ data | select_localizeds(include_unspecified=true) | map(attribute="name") | join(", ") }}'
@@ -311,12 +308,12 @@ def test_include_unspecified(self):
PlaceName('Apple', None),
]
- def _set_up(app: App) -> Iterator[ContextManager]:
- return (app.acquire_locale('en-US'),)
+ def _set_up(app: App) -> ContextManager[None]:
+ return app.acquire_locale('en-US') # type: ignore
with self._render(template_string=template, data={
'data': data,
}, set_up=_set_up) as (actual, _):
- self.assertEqual('Apple, Apple, Apple, Apple, Apple', actual)
+ assert 'Apple == Apple, Apple, Apple, Apple', actual
class FilterSelectDatedsTest(TemplateTestCase):
@@ -329,7 +326,7 @@ def __init__(self, value: str, date: Optional[Datey] = None):
def __str__(self):
return self._value
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, data', [
('Apple', {
'dateds': [
DatedDummy('Apple'),
@@ -379,11 +376,11 @@ def __str__(self):
def test(self, expected: str, data: Dict):
template = '{{ dateds | select_dateds(date=date) | join(", ") }}'
with self._render(template_string=template, data=data) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class TestSubjectRoleTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, data', [
('true', Subject()),
('false', Subject),
('false', Attendee()),
@@ -394,11 +391,11 @@ def test(self, expected, data) -> None:
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class TestWitnessRoleTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, data', [
('true', Witness()),
('false', Witness),
('false', Attendee()),
@@ -409,11 +406,11 @@ def test(self, expected, data) -> None:
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
class TestResourceTest(TemplateTestCase):
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, entity_type, data', [
('true', Person, Person('P1')),
('false', Person, Place('P1', [PlaceName('The Place')])),
('true', Place, Place('P1', [PlaceName('The Place')])),
@@ -426,4 +423,4 @@ def test(self, expected, entity_type: Type[Entity], data) -> None:
with self._render(template_string=template, data={
'data': data,
}) as (actual, _):
- self.assertEqual(expected, actual)
+ assert expected == actual
diff --git a/betty/tests/test_json.py b/betty/tests/test_json.py
index ff2821813..1ff8f3509 100644
--- a/betty/tests/test_json.py
+++ b/betty/tests/test_json.py
@@ -12,10 +12,9 @@
Subject, Enclosure, Citation, Event
from betty.model.event_type import Birth
from betty.project import LocaleConfiguration
-from betty.tests import TestCase
-class JSONEncoderTest(TestCase):
+class TestJSONEncoder:
def assert_encodes(self, expected, data, schema_definition: str):
app = App()
app.project.configuration.locales.replace([
@@ -25,7 +24,7 @@ def assert_encodes(self, expected, data, schema_definition: str):
with app:
encoded_data = stdjson.loads(stdjson.dumps(data, cls=JSONEncoder.get_factory(app)))
json.validate(encoded_data, schema_definition, app)
- self.assertEqual(expected, encoded_data)
+ assert expected == encoded_data
def test_coordinates_should_encode(self):
latitude = 12.345
@@ -587,7 +586,6 @@ def test_citation_should_encode_minimal(self):
def test_citation_should_encode_full(self):
citation = Citation('the_citation', Source('the_source', 'The Source'))
- citation.description = 'The Source Description'
citation.facts.append(Event('the_event', Birth()))
expected = {
'$schema': '/schema.json#/definitions/citation',
diff --git a/betty/tests/test_locale.py b/betty/tests/test_locale.py
index 78dd13d90..d0cd2c835 100644
--- a/betty/tests/test_locale.py
+++ b/betty/tests/test_locale.py
@@ -6,19 +6,18 @@
from gettext import NullTranslations
from pathlib import Path
from tempfile import TemporaryDirectory
-from typing import List, Optional, Iterator, Set
+from typing import List, Optional, Iterator, Set, Tuple
-from parameterized import parameterized
+import pytest
from betty.fs import FileSystem, ROOT_DIRECTORY_PATH
from betty.locale import Localized, negotiate_localizeds, Date, format_datey, DateRange, Translations, negotiate_locale, \
Datey, TranslationsInstallationError, TranslationsRepository
-from betty.tests import TestCase
-class PotFileTest(TestCase):
- def _readlines(self, root_directory_path) -> Iterator[str]:
- with open(Path(root_directory_path) / 'betty' / 'assets' / 'betty.pot') as f:
+class TestPotFile:
+ def _readlines(self, directory_path: Path) -> Iterator[str]:
+ with open(directory_path / 'betty' / 'assets' / 'betty.pot') as f:
return filter(
lambda line: not line.startswith((
'"POT-Creation-Date: ',
@@ -48,15 +47,15 @@ def test(self) -> None:
shell=sys.platform == 'win32',
)
actual_pot_contents = self._readlines(ROOT_DIRECTORY_PATH)
- expected_pot_contents = self._readlines(working_directory_path)
+ expected_pot_contents = self._readlines(Path(working_directory_path))
diff = difflib.unified_diff(
list(actual_pot_contents),
list(expected_pot_contents),
)
- self.assertEqual(0, len(list(diff)), f'The gettext *.po files are not up to date. Did you run {Path() / "bin" / "extract-translatables"}?')
+ assert 0 == len(list(diff)), f'The gettext *.po files are not up to date. Did you run {Path() / "bin" / "extract-translatables"}?'
-class TranslationsTest(TestCase):
+class TestTranslations:
_GETTEXT_BUILTINS_TO_TRANSLATIONS_METHODS = {
'_': 'gettext',
'gettext': 'gettext',
@@ -69,12 +68,12 @@ class TranslationsTest(TestCase):
def assert_gettext_builtins(self, translations: NullTranslations) -> None:
for builtin_name, translations_method_name in self._GETTEXT_BUILTINS_TO_TRANSLATIONS_METHODS.items():
- self.assertIn(builtin_name, builtins.__dict__)
- self.assertEqual(getattr(translations, translations_method_name), builtins.__dict__[builtin_name])
+ assert builtin_name in builtins.__dict__
+ assert getattr(translations, translations_method_name) == builtins.__dict__[builtin_name]
def assert_no_gettext_builtins(self) -> None:
for builtin_name in self._GETTEXT_BUILTINS_TO_TRANSLATIONS_METHODS:
- self.assertNotIn(builtin_name, builtins.__dict__)
+ assert builtin_name not in builtins.__dict__
def test_install_uninstall(self) -> None:
sut_one = Translations(NullTranslations())
@@ -95,7 +94,7 @@ def test_install_uninstall_out_of_order_should_fail(self) -> None:
self.assert_gettext_builtins(sut_one)
sut_two.install()
self.assert_gettext_builtins(sut_two)
- with self.assertRaises(TranslationsInstallationError):
+ with pytest.raises(TranslationsInstallationError):
sut_one.uninstall()
# Clean up the global environment.
@@ -105,7 +104,7 @@ def test_install_uninstall_out_of_order_should_fail(self) -> None:
def test_install_reentry_without_uninstall_should_fail(self) -> None:
sut = Translations(NullTranslations())
sut.install()
- with self.assertRaises(TranslationsInstallationError):
+ with pytest.raises(TranslationsInstallationError):
sut.install()
# Clean up the global environment.
@@ -135,7 +134,7 @@ def test_context_manager(self) -> None:
def test_context_manager_reentry_without_exit_should_fail(self) -> None:
sut = Translations(NullTranslations())
with sut:
- with self.assertRaises(TranslationsInstallationError):
+ with pytest.raises(TranslationsInstallationError):
with sut:
pass
@@ -149,29 +148,29 @@ def test_context_manager_reentry(self) -> None:
self.assert_no_gettext_builtins()
-class DateTest(TestCase):
+class TestDate:
def test_year(self):
year = 1970
sut = Date(year=year)
- self.assertEqual(year, sut.year)
+ assert year == sut.year
def test_month(self):
month = 1
sut = Date(month=month)
- self.assertEqual(month, sut.month)
+ assert month == sut.month
def test_day(self):
day = 1
sut = Date(day=day)
- self.assertEqual(day, sut.day)
+ assert day == sut.day
def test_fuzzy(self):
fuzzy = True
sut = Date()
sut.fuzzy = fuzzy
- self.assertEqual(fuzzy, sut.fuzzy)
+ assert fuzzy == sut.fuzzy
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, year, month, day', [
(True, 1970, 1, 1),
(False, None, 1, 1),
(True, 1970, None, 1),
@@ -182,9 +181,9 @@ def test_fuzzy(self):
])
def test_comparable(self, expected, year, month, day):
sut = Date(year, month, day)
- self.assertEqual(expected, sut.comparable)
+ assert expected == sut.comparable
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, year, month, day', [
(True, 1970, 1, 1),
(False, None, 1, 1),
(False, 1970, None, 1),
@@ -195,29 +194,29 @@ def test_comparable(self, expected, year, month, day):
])
def test_complete(self, expected, year, month, day):
sut = Date(year, month, day)
- self.assertEqual(expected, sut.complete)
+ assert expected == sut.complete
def test_to_range_when_incomparable_should_raise(self):
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
Date(None, 1, 1).to_range()
- @parameterized.expand([
+ @pytest.mark.parametrize('year, month, day', [
(1970, 1, 1),
(None, None, None),
])
def test_parts(self, year, month, day):
- self.assertEqual((year, month, day), Date(year, month, day).parts)
+ assert (year, month, day) == Date(year, month, day).parts
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(False, Date(1970, 2, 1)),
(True, Date(1970, 2, 2)),
(False, Date(1970, 2, 3)),
(False, DateRange()),
])
def test_in(self, expected, other):
- self.assertEqual(expected, other in Date(1970, 2, 2))
+ assert expected == (other in Date(1970, 2, 2))
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(False, Date(1970, 2, 1)),
(False, Date(1970, 2, 2)),
(True, Date(1970, 2, 3)),
@@ -227,9 +226,9 @@ def test_in(self, expected, other):
(True, Date(1970, 3)),
])
def test_lt(self, expected, other):
- self.assertEqual(expected, Date(1970, 2, 2) < other)
+ assert expected == (Date(1970, 2, 2) < other)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(True, Date(1970, 1, 1)),
(False, Date(1970, 1, None)),
(False, Date(1970, None, 1)),
@@ -240,20 +239,20 @@ def test_lt(self, expected, other):
(False, None),
])
def test_eq(self, expected, other):
- self.assertEqual(expected, Date(1970, 1, 1) == other)
- self.assertEqual(expected, other == Date(1970, 1, 1))
+ assert expected == (Date(1970, 1, 1) == other)
+ assert expected == (other == Date(1970, 1, 1))
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(True, Date(1970, 2, 1)),
(False, Date(1970, 2, 2)),
(False, Date(1970, 2, 3)),
])
def test_gt(self, expected, other):
- self.assertEqual(expected, Date(1970, 2, 2) > other)
+ assert expected == (Date(1970, 2, 2) > other)
-class DateRangeTest(TestCase):
- _TEST_IN_PARAMETERS = [
+class TestDateRange:
+ _TEST_IN_PARAMETERS: List[Tuple[bool, Datey, Datey]] = [
(False, Date(1970, 2, 2), DateRange()),
(False, Date(1970, 2), DateRange()),
(False, Date(1970), DateRange()),
@@ -290,11 +289,11 @@ class DateRangeTest(TestCase):
]
# Mirror the arguments because we want the containment check to work in either direction.
- @parameterized.expand(_TEST_IN_PARAMETERS + list(map(lambda x: (x[0], x[2], x[1]), _TEST_IN_PARAMETERS)))
- def test_in(self, expected: bool, other: Datey, sut: DateRange):
- self.assertEqual(expected, other in sut)
+ @pytest.mark.parametrize('expected, other, sut', _TEST_IN_PARAMETERS + list(map(lambda x: (x[0], x[2], x[1]), _TEST_IN_PARAMETERS)))
+ def test_in(self, expected: bool, other: Datey, sut: Datey):
+ assert expected == (other in sut)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(False, Date(1970, 2, 1)),
(False, Date(1970, 2, 2)),
(True, Date(1970, 2, 3)),
@@ -309,9 +308,9 @@ def test_in(self, expected: bool, other: Datey, sut: DateRange):
(False, DateRange(Date(1970, 2, 1), Date(1970, 2, 3))),
])
def test_lt_with_start_date(self, expected, other):
- self.assertEqual(expected, DateRange(Date(1970, 2, 2)) < other)
+ assert expected == (DateRange(Date(1970, 2, 2)) < other)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(False, Date(1970, 2, 1)),
(True, Date(1970, 2, 2)),
(True, Date(1970, 2, 3)),
@@ -326,9 +325,9 @@ def test_lt_with_start_date(self, expected, other):
(False, DateRange(Date(1970, 2, 1), Date(1970, 2, 3))),
])
def test_lt_with_end_date(self, expected, other):
- self.assertEqual(expected, DateRange(None, Date(1970, 2, 2)) < other)
+ assert expected == (DateRange(None, Date(1970, 2, 2)) < other)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(False, Date(1970, 2, 1)),
(True, Date(1970, 2, 2)),
(True, Date(1970, 2, 3)),
@@ -343,9 +342,9 @@ def test_lt_with_end_date(self, expected, other):
(False, DateRange(Date(1970, 2, 1), Date(1970, 2, 3))),
])
def test_lt_with_both_dates(self, expected, other):
- self.assertEqual(expected, DateRange(Date(1970, 2, 1), Date(1970, 2, 3)) < other)
+ assert expected == (DateRange(Date(1970, 2, 1), Date(1970, 2, 3)) < other)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(True, DateRange(Date(1970, 2, 2))),
(False, DateRange(Date(1970, 2, None))),
(False, DateRange(Date(1970, None, 2))),
@@ -356,9 +355,9 @@ def test_lt_with_both_dates(self, expected, other):
(False, None),
])
def test_eq(self, expected, other):
- self.assertEqual(expected, DateRange(Date(1970, 2, 2)) == other)
+ assert expected == (DateRange(Date(1970, 2, 2)) == other)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, other', [
(True, Date(1970, 2, 1)),
(True, Date(1970, 2, 2)),
(False, Date(1970, 2, 3)),
@@ -373,11 +372,11 @@ def test_eq(self, expected, other):
(True, DateRange(Date(1970, 2, 1), Date(1970, 2, 3))),
])
def test_gt(self, expected, other):
- self.assertEqual(expected, DateRange(Date(1970, 2, 2)) > other)
+ assert expected == (DateRange(Date(1970, 2, 2)) > other)
-class NegotiateLocaleTest(TestCase):
- @parameterized.expand([
+class TestNegotiateLocale:
+ @pytest.mark.parametrize('expected, preferred_locale, available_locales', [
('nl', 'nl', {'nl'}),
('nl-NL', 'nl', {'nl-NL'}),
('nl', 'nl-NL', {'nl'}),
@@ -387,10 +386,10 @@ class NegotiateLocaleTest(TestCase):
('nl-NL', 'nl-BE', {'nl-NL'})
])
def test(self, expected: Optional[str], preferred_locale: str, available_locales: Set[str]):
- self.assertEqual(expected, negotiate_locale(preferred_locale, available_locales))
+ assert expected == negotiate_locale(preferred_locale, available_locales)
-class NegotiateLocalizedsTest(TestCase):
+class TestNegotiateLocalizeds:
class DummyLocalized(Localized):
def __eq__(self, other):
return self.locale == other.locale
@@ -398,7 +397,7 @@ def __eq__(self, other):
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self.locale)
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, preferred_locale, localizeds', [
(DummyLocalized('nl'), 'nl', [DummyLocalized('nl')]),
(DummyLocalized('nl-NL'), 'nl', [DummyLocalized('nl-NL')]),
(DummyLocalized('nl'), 'nl-NL', [DummyLocalized('nl')]),
@@ -409,18 +408,15 @@ def __repr__(self):
(None, 'nl', []),
])
def test_with_match_should_return_match(self, expected: Localized, preferred_locale: str, localizeds: List[Localized]):
- self.assertEqual(expected, negotiate_localizeds(
- preferred_locale, localizeds))
+ assert expected == negotiate_localizeds(preferred_locale, localizeds)
def test_without_match_should_return_default(self):
preferred_locale = 'de'
- localizeds = [self.DummyLocalized('nl'), self.DummyLocalized(
- 'en'), self.DummyLocalized('uk')]
- self.assertEqual(self.DummyLocalized('nl'), negotiate_localizeds(
- preferred_locale, localizeds))
+ localizeds = [self.DummyLocalized('nl'), self.DummyLocalized('en'), self.DummyLocalized('uk')]
+ assert self.DummyLocalized('nl') == negotiate_localizeds(preferred_locale, localizeds)
-_FORMAT_DATE_TEST_PARAMETERS = [
+_FORMAT_DATE_TEST_PARAMETERS: List[Tuple[str, Datey]] = [
# Dates that cannot be formatted.
('unknown date', Date()),
('unknown date', Date(None, None, 1)),
@@ -438,15 +434,15 @@ def test_without_match_should_return_default(self):
]
-class FormatDateTest(TestCase):
- @parameterized.expand(_FORMAT_DATE_TEST_PARAMETERS)
+class TestFormatDate:
+ @pytest.mark.parametrize('expected, datey', _FORMAT_DATE_TEST_PARAMETERS)
def test(self, expected: str, datey: Datey):
locale = 'en'
with Translations(NullTranslations()):
- self.assertEqual(expected, format_datey(datey, locale))
+ assert expected == format_datey(datey, locale)
-_FORMAT_DATE_RANGE_TEST_PARAMETERS = [
+_FORMAT_DATE_RANGE_TEST_PARAMETERS: List[Tuple[str, Datey]] = [
('from January 1, 1970 until December 31, 1999', DateRange(Date(1970, 1, 1), Date(1999, 12, 31))),
('from January 1, 1970 until sometime before December 31, 1999', DateRange(Date(1970, 1, 1), Date(1999, 12, 31), end_is_boundary=True)),
('from January 1, 1970 until around December 31, 1999', DateRange(Date(1970, 1, 1), Date(1999, 12, 31, fuzzy=True))),
@@ -474,23 +470,23 @@ def test(self, expected: str, datey: Datey):
]
-class FormatDateRangeTest(TestCase):
- @parameterized.expand(_FORMAT_DATE_RANGE_TEST_PARAMETERS)
+class TestFormatDateRange:
+ @pytest.mark.parametrize('expected, datey', _FORMAT_DATE_RANGE_TEST_PARAMETERS)
def test(self, expected: str, datey: Datey):
locale = 'en'
with Translations(NullTranslations()):
- self.assertEqual(expected, format_datey(datey, locale))
+ assert expected == format_datey(datey, locale)
-class FormatDateyTest(TestCase):
- @parameterized.expand(_FORMAT_DATE_TEST_PARAMETERS + _FORMAT_DATE_RANGE_TEST_PARAMETERS)
+class TestFormatDatey:
+ @pytest.mark.parametrize('expected, datey', _FORMAT_DATE_TEST_PARAMETERS + _FORMAT_DATE_RANGE_TEST_PARAMETERS)
def test(self, expected: str, datey: Datey):
locale = 'en'
with Translations(NullTranslations()):
- self.assertEqual(expected, format_datey(datey, locale))
+ assert expected == format_datey(datey, locale)
-class TranslationsRepositoryTest(TestCase):
+class TestTranslationsRepository:
def test_getitem(self) -> None:
locale = 'nl-NL'
locale_path_name = 'nl_NL'
@@ -527,4 +523,4 @@ def test_getitem(self) -> None:
"""
with open(lc_messages_directory_path / 'betty.po', 'w') as f:
f.write(po)
- self.assertIsInstance(sut[locale], NullTranslations)
+ assert isinstance(sut[locale], NullTranslations)
diff --git a/betty/tests/test_lock.py b/betty/tests/test_lock.py
index 15db853d8..ca75b4411 100644
--- a/betty/tests/test_lock.py
+++ b/betty/tests/test_lock.py
@@ -1,13 +1,14 @@
+import pytest
+
from betty.lock import Locks, AcquiredError
-from betty.tests import TestCase
-class LocksTest(TestCase):
+class TestLocks:
def test_acquire(self):
resource = 999
sut = Locks()
sut.acquire(resource)
- with self.assertRaises(AcquiredError):
+ with pytest.raises(AcquiredError):
sut.acquire(resource)
def test_release(self):
diff --git a/betty/tests/test_logging.py b/betty/tests/test_logging.py
index dfca4fbfc..d6ca50093 100644
--- a/betty/tests/test_logging.py
+++ b/betty/tests/test_logging.py
@@ -1,15 +1,14 @@
import io
from logging import Logger, CRITICAL, ERROR, WARNING, INFO, DEBUG
-from unittest import TestCase
-from unittest.mock import patch
-from parameterized import parameterized
+import pytest
+from pytest_mock import MockerFixture
from betty.logging import CliHandler
-class CliHandlerTest(TestCase):
- @parameterized.expand([
+class TestCliHandler:
+ @pytest.mark.parametrize('expected, message, level', [
('\033[91mSomething went wrong!\033[0m\n',
'Something went wrong!', CRITICAL),
('\033[91mSomething went wrong!\033[0m\n',
@@ -21,9 +20,9 @@ class CliHandlerTest(TestCase):
('\033[97mSomething went wrong!\033[0m\n',
'Something went wrong!', DEBUG),
])
- @patch('sys.stderr', new_callable=io.StringIO)
- def test_log(self, expected: str, message: str, level: int, stderr: io.StringIO):
+ def test_log(self, expected: str, message: str, level: int, mocker: MockerFixture):
+ m_stderr = mocker.patch('sys.stderr', new_callable=io.StringIO)
logger = Logger(__name__)
logger.addHandler(CliHandler())
logger.log(level, message)
- self.assertEqual(expected, stderr.getvalue())
+ assert expected == m_stderr.getvalue()
diff --git a/betty/tests/test_media_type.py b/betty/tests/test_media_type.py
index 2ed395e65..274804a65 100644
--- a/betty/tests/test_media_type.py
+++ b/betty/tests/test_media_type.py
@@ -1,42 +1,38 @@
from typing import Optional, List, Dict
-from parameterized import parameterized
+import pytest
from betty.media_type import MediaType, InvalidMediaType
-from betty.tests import TestCase
-class MediaTypeTest(TestCase):
- @parameterized.expand([
+class TestMediaType:
+ @pytest.mark.parametrize('expected_type, expected_subtype, expected_subtypes, expected_suffix, expected_parameters, media_type', [
# The simplest possible media type.
('text', 'plain', ['plain'], None, {}, 'text/plain'),
# A media type with a hyphenated subtype.
('multipart', 'form-data', ['form-data'], None, {}, 'multipart/form-data'),
# A media type with a tree subtype.
- ('application', 'vnd.oasis.opendocument.text', ['vnd', 'oasis', 'opendocument', 'text'], None, {},
- 'application/vnd.oasis.opendocument.text'),
+ ('application', 'vnd.oasis.opendocument.text', ['vnd', 'oasis', 'opendocument', 'text'], None, {}, 'application/vnd.oasis.opendocument.text'),
# A media type with a subtype suffix.
('application', 'ld+json', ['ld'], 'json', {}, 'application/ld+json'),
# A media type with a parameter.
- ('text', 'html', ['html'], None, {
- 'charset': 'UTF-8',
- }, 'text/html; charset=UTF-8'),
+ ('text', 'html', ['html'], None, {'charset': 'UTF-8'}, 'text/html; charset=UTF-8'),
])
def test(self, expected_type: str, expected_subtype: str, expected_subtypes: List[str], expected_suffix: Optional[str], expected_parameters: Dict[str, str], media_type: str):
sut = MediaType(media_type)
- self.assertEqual(expected_type, sut.type)
- self.assertEqual(expected_subtype, sut.subtype)
- self.assertEqual(expected_subtypes, sut.subtypes)
- self.assertEqual(expected_suffix, sut.suffix)
- self.assertEqual(expected_parameters, sut.parameters)
- self.assertEqual(media_type, str(sut))
+ assert expected_type == sut.type
+ assert expected_subtype == sut.subtype
+ assert expected_subtypes == sut.subtypes
+ assert expected_suffix == sut.suffix
+ assert expected_parameters == sut.parameters
+ assert media_type == str(sut)
- @parameterized.expand([
- ('',),
- ('/',),
- ('text',),
- ('text/',),
+ @pytest.mark.parametrize('media_type', [
+ '',
+ '/',
+ 'text',
+ 'text/',
])
def test_invalid_type_should_raise_value_error(self, media_type: str):
- with self.assertRaises(InvalidMediaType):
+ with pytest.raises(InvalidMediaType):
MediaType(media_type)
diff --git a/betty/tests/test_openapi.py b/betty/tests/test_openapi.py
index 5775f8207..50c2f915b 100644
--- a/betty/tests/test_openapi.py
+++ b/betty/tests/test_openapi.py
@@ -2,17 +2,16 @@
from pathlib import Path
import jsonschema
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.openapi import build_specification
-from betty.tests import TestCase
-class BuildSpecificationTest(TestCase):
- @parameterized.expand([
- (True,),
- (False,),
+class TestBuildSpecification:
+ @pytest.mark.parametrize('content_negotiation', [
+ True,
+ False,
])
def test(self, content_negotiation: str):
with open(Path(__file__).parent / 'test_openapi_assets' / 'schema.json') as f:
diff --git a/betty/tests/test_os.py b/betty/tests/test_os.py
index 5061a95f2..24695272f 100644
--- a/betty/tests/test_os.py
+++ b/betty/tests/test_os.py
@@ -3,10 +3,9 @@
from unittest.mock import patch
from betty.os import link_or_copy
-from betty.tests import TestCase
-class LinkOrCopyTest(TestCase):
+class TestLinkOrCopy:
def test(self):
with TemporaryDirectory() as working_directory_path_str:
working_directory_path = Path(working_directory_path_str)
@@ -17,7 +16,7 @@ def test(self):
f.write(content)
link_or_copy(source_path, destination_path)
with open(destination_path) as f:
- self.assertEqual(content, f.read())
+ assert content == f.read()
@patch('os.link')
def test_with_os_error(self, m_link):
@@ -31,4 +30,4 @@ def test_with_os_error(self, m_link):
f.write(content)
link_or_copy(source_path, destination_path)
with open(destination_path) as f:
- self.assertEqual(content, f.read())
+ assert content == f.read()
diff --git a/betty/tests/test_project.py b/betty/tests/test_project.py
index f07c53c86..bd6f4a80d 100644
--- a/betty/tests/test_project.py
+++ b/betty/tests/test_project.py
@@ -1,6 +1,8 @@
+from __future__ import annotations
+
from typing import Type, Dict, Any
-from parameterized import parameterized
+import pytest
from reactives.tests import assert_reactor_called, assert_in_scope, assert_scope_empty
from betty.app import Extension, App, ConfigurableExtension
@@ -8,31 +10,30 @@
from betty.model import Entity, get_entity_type_name
from betty.project import ProjectExtensionConfiguration, ProjectExtensionsConfiguration, ProjectConfiguration, \
LocaleConfiguration, LocalesConfiguration, ThemeConfiguration, EntityReference, EntityReferences
-from betty.tests import TestCase
from betty.typing import Void
-class EntityReferenceTest(TestCase):
+class TestEntityReference:
def test_entity_type_with_constraint(self) -> None:
entity_type = Entity
sut = EntityReference(entity_type_constraint=entity_type)
- self.assertEqual(entity_type, sut.entity_type)
- with self.assertRaises(AttributeError):
+ assert entity_type == sut.entity_type
+ with pytest.raises(AttributeError):
sut.entity_type = entity_type
def test_entity_type_without_constraint(self) -> None:
entity_type = Entity
sut = EntityReference()
- self.assertIsNone(sut.entity_type)
+ assert sut.entity_type is None
sut.entity_type = entity_type
- self.assertEqual(entity_type, sut.entity_type)
+ assert entity_type == sut.entity_type
def test_entity_id(self) -> None:
entity_id = '123'
sut = EntityReference()
- self.assertIsNone(sut.entity_id)
+ assert sut.entity_id is None
sut.entity_id = entity_id
- self.assertEqual(entity_id, sut.entity_id)
+ assert entity_id == sut.entity_id
def test_load_with_constraint(self) -> None:
entity_type_constraint = Entity
@@ -40,7 +41,7 @@ def test_load_with_constraint(self) -> None:
entity_id = '123'
dumped_configuration = entity_id
sut.load(dumped_configuration)
- self.assertEqual(entity_id, sut.entity_id)
+ assert entity_id == sut.entity_id
def test_load_with_constraint_without_string_should_error(self) -> None:
entity_type_constraint = Entity
@@ -48,7 +49,7 @@ def test_load_with_constraint_without_string_should_error(self) -> None:
entity_id = None
dumped_configuration = entity_id
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_load_without_constraint(self) -> None:
@@ -60,8 +61,8 @@ def test_load_without_constraint(self) -> None:
'entity_id': entity_id,
}
sut.load(dumped_configuration)
- self.assertEqual(entity_type, sut.entity_type)
- self.assertEqual(entity_id, sut.entity_id)
+ assert entity_type == sut.entity_type
+ assert entity_id == sut.entity_id
def test_load_without_constraint_without_entity_type_should_error(self) -> None:
sut = EntityReference()
@@ -70,7 +71,7 @@ def test_load_without_constraint_without_entity_type_should_error(self) -> None:
'entity_id': entity_id,
}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_load_without_constraint_without_string_entity_type_should_error(self) -> None:
@@ -80,7 +81,7 @@ def test_load_without_constraint_without_string_entity_type_should_error(self) -
'entity_type': None,
'entity_id': entity_id,
}
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_load_without_constraint_without_importable_entity_type_should_error(self) -> None:
@@ -90,7 +91,7 @@ def test_load_without_constraint_without_importable_entity_type_should_error(sel
'entity_type': 'betty.non_existent.Entity',
'entity_id': entity_id,
}
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_load_without_constraint_without_entity_id_should_error(self) -> None:
@@ -100,7 +101,7 @@ def test_load_without_constraint_without_entity_id_should_error(self) -> None:
'entity_type': get_entity_type_name(entity_type),
}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_load_without_constraint_without_string_entity_id_should_error(self) -> None:
@@ -111,7 +112,7 @@ def test_load_without_constraint_without_string_entity_id_should_error(self) ->
'entity_id': None,
}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
def test_dump_with_constraint(self) -> None:
@@ -119,7 +120,7 @@ def test_dump_with_constraint(self) -> None:
entity_id = '123'
sut.entity_id = entity_id
expected = entity_id
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_without_constraint(self) -> None:
sut = EntityReference()
@@ -131,28 +132,28 @@ def test_dump_without_constraint(self) -> None:
'entity_type': get_entity_type_name(entity_type),
'entity_id': entity_id,
}
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
-class EntityReferencesTest(TestCase):
- @parameterized.expand([
- (EntityReferences(),),
- (EntityReferences(entity_type_constraint=Entity),),
+class TestEntityReferences:
+ @pytest.mark.parametrize('sut', [
+ EntityReferences(),
+ EntityReferences(entity_type_constraint=Entity),
])
def test_load_without_list_should_error(self, sut: EntityReferences) -> None:
dumped_configuration = None
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.load(dumped_configuration)
- @parameterized.expand([
- (EntityReferences(),),
- (EntityReferences(entity_type_constraint=Entity),),
+ @pytest.mark.parametrize('sut', [
+ EntityReferences(),
+ EntityReferences(entity_type_constraint=Entity),
])
def test_load_without_entity_references(self, sut: EntityReferences) -> None:
dumped_configuration: DumpedConfiguration = []
sut.load(dumped_configuration)
- self.assertCountEqual([], sut)
+ assert [] == list(sut)
def test_load_with_constraint_with_entity_references(self) -> None:
entity_type = Entity
@@ -162,7 +163,7 @@ def test_load_with_constraint_with_entity_references(self) -> None:
entity_id,
]
sut.load(dumped_configuration)
- self.assertCountEqual([EntityReference(entity_type, entity_id)], sut)
+ assert [EntityReference(entity_type, entity_id)] == list(sut)
def test_load_without_constraint_with_entity_references(self) -> None:
sut = EntityReferences()
@@ -175,7 +176,7 @@ def test_load_without_constraint_with_entity_references(self) -> None:
},
]
sut.load(dumped_configuration)
- self.assertCountEqual([EntityReference(entity_type, entity_id)], sut)
+ assert [EntityReference(entity_type, entity_id)] == list(sut)
def test_dump_with_constraint_with_entity_references(self) -> None:
entity_type = Entity
@@ -185,12 +186,12 @@ def test_dump_with_constraint_with_entity_references(self) -> None:
expected = [
entity_id,
]
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_with_constraint_without_entity_references(self) -> None:
sut = EntityReferences(entity_type_constraint=Entity)
expected = Void
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_without_constraint_with_entity_references(self) -> None:
entity_type = Entity
@@ -203,48 +204,48 @@ def test_dump_without_constraint_with_entity_references(self) -> None:
'entity_id': entity_id,
},
]
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_without_constraint_without_entity_references(self) -> None:
sut = EntityReferences()
expected = Void
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
-class LocaleConfigurationTest(TestCase):
+class TestLocaleConfiguration:
def test_locale(self):
locale = 'nl-NL'
sut = LocaleConfiguration(locale)
- self.assertEqual(locale, sut.locale)
+ assert locale == sut.locale
def test_alias_implicit(self):
locale = 'nl-NL'
sut = LocaleConfiguration(locale)
- self.assertEqual(locale, sut.alias)
+ assert locale == sut.alias
def test_alias_explicit(self):
locale = 'nl-NL'
alias = 'nl'
sut = LocaleConfiguration(locale, alias)
- self.assertEqual(alias, sut.alias)
+ assert alias == sut.alias
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, sut, other', [
(False, LocaleConfiguration('nl', 'NL'), 'not a locale configuration'),
(False, LocaleConfiguration('nl', 'NL'), 999),
(False, LocaleConfiguration('nl', 'NL'), object()),
])
def test_eq(self, expected, sut, other):
- self.assertEqual(expected, sut == other)
+ assert expected == (sut == other)
-class LocalesConfigurationTest(TestCase):
+class TestLocalesConfiguration:
def test_getitem(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
sut = LocalesConfiguration([
locale_configuration_a,
])
with assert_in_scope(sut):
- self.assertEqual(locale_configuration_a, sut['nl-NL'])
+ assert locale_configuration_a == sut['nl-NL']
def test_delitem(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -256,7 +257,7 @@ def test_delitem(self) -> None:
with assert_scope_empty():
with assert_reactor_called(sut):
del sut['nl-NL']
- self.assertCountEqual([locale_configuration_b], sut)
+ assert [locale_configuration_b] == list(sut)
def test_delitem_with_one_remaining_locale_configuration(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -264,7 +265,7 @@ def test_delitem_with_one_remaining_locale_configuration(self) -> None:
locale_configuration_a,
])
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
del sut['nl-NL']
def test_iter(self) -> None:
@@ -275,7 +276,7 @@ def test_iter(self) -> None:
locale_configuration_b,
])
with assert_in_scope(sut):
- self.assertCountEqual([locale_configuration_a, locale_configuration_b], iter(sut))
+ assert [locale_configuration_a, locale_configuration_b] == list(iter(sut))
def test_len(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -285,7 +286,7 @@ def test_len(self) -> None:
locale_configuration_b,
])
with assert_in_scope(sut):
- self.assertEqual(2, len(sut))
+ assert 2 == len(sut)
def test_eq(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -299,7 +300,7 @@ def test_eq(self) -> None:
locale_configuration_b,
])
with assert_in_scope(sut):
- self.assertEqual(other, sut)
+ assert other == sut
def test_contains(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -307,8 +308,8 @@ def test_contains(self) -> None:
locale_configuration_a,
])
with assert_in_scope(sut):
- self.assertIn('nl-NL', sut)
- self.assertNotIn('en-US', sut)
+ assert 'nl-NL' in sut
+ assert 'en-US' not in sut
def test_repr(self) -> None:
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -316,7 +317,7 @@ def test_repr(self) -> None:
locale_configuration_a,
])
with assert_in_scope(sut):
- self.assertIsInstance(repr(sut), str)
+ assert isinstance(repr(sut), str)
def test_add(self) -> None:
sut = LocalesConfiguration()
@@ -326,7 +327,7 @@ def test_add(self) -> None:
def test_default_without_explicit_locale_configurations(self):
sut = LocalesConfiguration()
- self.assertEqual(LocaleConfiguration('en-US'), sut.default)
+ assert LocaleConfiguration('en-US') == sut.default
def test_default_without_explicit_default(self):
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -335,7 +336,7 @@ def test_default_without_explicit_default(self):
locale_configuration_a,
locale_configuration_b,
])
- self.assertEqual(locale_configuration_a, sut.default)
+ assert locale_configuration_a == sut.default
def test_default_with_explicit_default(self):
locale_configuration_a = LocaleConfiguration('nl-NL')
@@ -344,7 +345,7 @@ def test_default_with_explicit_default(self):
locale_configuration_a,
])
sut.default = locale_configuration_b
- self.assertEqual(locale_configuration_b, sut.default)
+ assert locale_configuration_b == sut.default
class _DummyExtension(Extension):
@@ -367,27 +368,27 @@ def configuration_type(cls) -> Type[_DummyConfiguration]:
return _DummyConfiguration
-class ProjectExtensionConfigurationTest(TestCase):
+class TestProjectExtensionConfiguration:
def test_extension_type(self) -> None:
extension_type = _DummyExtension
sut = ProjectExtensionConfiguration(extension_type)
- self.assertEqual(extension_type, sut.extension_type)
+ assert extension_type == sut.extension_type
def test_enabled(self) -> None:
enabled = True
sut = ProjectExtensionConfiguration(_DummyExtension, enabled)
- self.assertEqual(enabled, sut.enabled)
+ assert enabled == sut.enabled
with assert_reactor_called(sut):
sut.enabled = False
def test_configuration(self) -> None:
extension_type_configuration = Configuration()
sut = ProjectExtensionConfiguration(Extension, True, extension_type_configuration)
- self.assertEqual(extension_type_configuration, sut.extension_configuration)
+ assert extension_type_configuration == sut.extension_configuration
with assert_reactor_called(sut):
extension_type_configuration.react.trigger()
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, one, other', [
(True, ProjectExtensionConfiguration(_DummyExtension, True), ProjectExtensionConfiguration(_DummyExtension, True)),
(True, ProjectExtensionConfiguration(_DummyExtension, True, None), ProjectExtensionConfiguration(_DummyExtension, True, None)),
(False, ProjectExtensionConfiguration(_DummyExtension, True, Configuration()), ProjectExtensionConfiguration(_DummyExtension, True, Configuration())),
@@ -395,17 +396,17 @@ def test_configuration(self) -> None:
(False, ProjectExtensionConfiguration(_DummyExtension, True), ProjectExtensionConfiguration(_DummyConfigurableExtension, True)),
])
def test_eq(self, expected: bool, one: ProjectExtensionConfiguration, other: ProjectExtensionConfiguration) -> None:
- self.assertEqual(expected, one == other)
+ assert expected == (one == other)
-class ProjectExtensionsConfigurationTest(TestCase):
+class TestProjectExtensionsConfiguration:
def test_getitem(self) -> None:
app_extension_configuration_a = ProjectExtensionConfiguration(DummyConfigurableExtension)
sut = ProjectExtensionsConfiguration([
app_extension_configuration_a,
])
with assert_in_scope(sut):
- self.assertEqual(app_extension_configuration_a, sut[DummyConfigurableExtension])
+ assert app_extension_configuration_a == sut[DummyConfigurableExtension]
def test_delitem(self) -> None:
app_extension_configuration = ProjectExtensionConfiguration(DummyConfigurableExtension)
@@ -415,8 +416,8 @@ def test_delitem(self) -> None:
with assert_scope_empty():
with assert_reactor_called(sut):
del sut[DummyConfigurableExtension]
- self.assertCountEqual([], sut)
- self.assertCountEqual([], app_extension_configuration.react._reactors)
+ assert [] == list(sut)
+ assert [] == list(app_extension_configuration.react._reactors)
def test_iter(self) -> None:
app_extension_configuration_a = ProjectExtensionConfiguration(DummyConfigurableExtension)
@@ -426,7 +427,7 @@ def test_iter(self) -> None:
app_extension_configuration_b,
])
with assert_in_scope(sut):
- self.assertCountEqual([app_extension_configuration_a, app_extension_configuration_b], iter(sut))
+ assert [app_extension_configuration_a, app_extension_configuration_b] == list(iter(sut))
def test_len(self) -> None:
app_extension_configuration_a = ProjectExtensionConfiguration(DummyConfigurableExtension)
@@ -436,7 +437,7 @@ def test_len(self) -> None:
app_extension_configuration_b,
])
with assert_in_scope(sut):
- self.assertEqual(2, len(sut))
+ assert 2 == len(sut)
def test_eq(self) -> None:
app_extension_configuration_a = ProjectExtensionConfiguration(DummyConfigurableExtension)
@@ -450,7 +451,7 @@ def test_eq(self) -> None:
app_extension_configuration_b,
])
with assert_in_scope(sut):
- self.assertEqual(other, sut)
+ assert other == sut
def test_add(self) -> None:
sut = ProjectExtensionsConfiguration()
@@ -458,12 +459,12 @@ def test_add(self) -> None:
with assert_scope_empty():
with assert_reactor_called(sut):
sut.add(app_extension_configuration)
- self.assertEqual(app_extension_configuration, sut[DummyConfigurableExtension])
+ assert app_extension_configuration == sut[DummyConfigurableExtension]
with assert_reactor_called(sut):
app_extension_configuration.react.trigger()
-class ThemeConfigurationTest(TestCase):
+class TestThemeConfiguration:
def test_load_with_minimal_configuration(self) -> None:
dumped_configuration: Dict = {}
with App():
@@ -472,13 +473,13 @@ def test_load_with_minimal_configuration(self) -> None:
def test_load_without_dict_should_error(self) -> None:
dumped_configuration = None
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
ThemeConfiguration().load(dumped_configuration)
def test_dump_with_minimal_configuration(self) -> None:
sut = ThemeConfiguration()
expected = Void
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_with_background_image_id(self) -> None:
sut = ThemeConfiguration()
@@ -487,7 +488,7 @@ def test_dump_with_background_image_id(self) -> None:
expected = {
'background_image_id': background_image_id,
}
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
def test_dump_with_featured_entities(self) -> None:
sut = ThemeConfiguration()
@@ -502,26 +503,26 @@ def test_dump_with_featured_entities(self) -> None:
},
],
}
- self.assertEqual(expected, sut.dump())
+ assert expected == sut.dump()
-class ConfigurationTest(TestCase):
+class TestConfiguration:
def test_base_url(self):
sut = ProjectConfiguration()
base_url = 'https://example.com'
sut.base_url = base_url
- self.assertEqual(base_url, sut.base_url)
+ assert base_url == sut.base_url
def test_base_url_without_scheme_should_error(self):
sut = ProjectConfiguration()
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.base_url = '/'
def test_base_url_without_path_should_error(self):
sut = ProjectConfiguration()
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
sut.base_url = 'file://'
def test_root_path(self):
@@ -529,73 +530,77 @@ def test_root_path(self):
configured_root_path = '/betty/'
expected_root_path = 'betty'
sut.root_path = configured_root_path
- self.assertEqual(expected_root_path, sut.root_path)
+ assert expected_root_path == sut.root_path
def test_clean_urls(self):
sut = ProjectConfiguration()
clean_urls = True
sut.clean_urls = clean_urls
- self.assertEqual(clean_urls, sut.clean_urls)
+ assert clean_urls == sut.clean_urls
def test_content_negotiation(self):
sut = ProjectConfiguration()
content_negotiation = True
sut.content_negotiation = content_negotiation
- self.assertEqual(content_negotiation, sut.content_negotiation)
+ assert content_negotiation == sut.content_negotiation
def test_clean_urls_implied_by_content_negotiation(self):
sut = ProjectConfiguration()
sut.content_negotiation = True
- self.assertTrue(sut.clean_urls)
+ assert sut.clean_urls
def test_author_without_author(self):
sut = ProjectConfiguration()
- self.assertIsNone(sut.author)
+ assert sut.author is None
def test_author_with_author(self):
sut = ProjectConfiguration()
author = 'Bart'
sut.author = author
- self.assertEqual(author, sut.author)
+ assert author == sut.author
def test_load_should_load_minimal(self) -> None:
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(dumped_configuration['base_url'], configuration.base_url)
- self.assertEqual('Betty', configuration.title)
- self.assertIsNone(configuration.author)
- self.assertFalse(configuration.debug)
- self.assertEqual('', configuration.root_path)
- self.assertFalse(configuration.clean_urls)
- self.assertFalse(configuration.content_negotiation)
+ assert dumped_configuration['base_url'] == configuration.base_url
+ assert 'Betty' == configuration.title
+ assert configuration.author is None
+ assert not configuration.debug
+ assert '' == configuration.root_path
+ assert not configuration.clean_urls
+ assert not configuration.content_negotiation
def test_load_should_load_title(self) -> None:
title = 'My first Betty site'
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['title'] = title
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(title, configuration.title)
+ assert title == configuration.title
def test_load_should_load_author(self) -> None:
author = 'Bart'
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['author'] = author
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(author, configuration.author)
+ assert author == configuration.author
def test_load_should_load_locale_locale(self) -> None:
locale = 'nl-NL'
locale_config = {
'locale': locale,
}
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['locales'] = [locale_config]
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(LocalesConfiguration([LocaleConfiguration(locale)]), configuration.locales)
+ assert LocalesConfiguration([LocaleConfiguration(locale)]) == configuration.locales
def test_load_should_load_locale_alias(self) -> None:
locale = 'nl-NL'
@@ -604,50 +609,56 @@ def test_load_should_load_locale_alias(self) -> None:
'locale': locale,
'alias': alias,
}
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['locales'] = [locale_config]
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(LocalesConfiguration([LocaleConfiguration(locale, alias)]), configuration.locales)
+ assert LocalesConfiguration([LocaleConfiguration(locale, alias)]) == configuration.locales
def test_load_should_root_path(self) -> None:
configured_root_path = '/betty/'
expected_root_path = 'betty'
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['root_path'] = configured_root_path
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(expected_root_path, configuration.root_path)
+ assert expected_root_path == configuration.root_path
def test_load_should_clean_urls(self) -> None:
clean_urls = True
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['clean_urls'] = clean_urls
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(clean_urls, configuration.clean_urls)
+ assert clean_urls == configuration.clean_urls
def test_load_should_content_negotiation(self) -> None:
content_negotiation = True
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['content_negotiation'] = content_negotiation
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(content_negotiation, configuration.content_negotiation)
+ assert content_negotiation == configuration.content_negotiation
- @parameterized.expand([
- (True,),
- (False,),
+ @pytest.mark.parametrize('debug', [
+ True,
+ False,
])
def test_load_should_load_debug(self, debug: bool) -> None:
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['debug'] = debug
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(debug, configuration.debug)
+ assert debug == configuration.debug
def test_load_should_load_one_extension_with_configuration(self) -> None:
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
extension_configuration = {
'check': 1337,
}
@@ -663,10 +674,11 @@ def test_load_should_load_one_extension_with_configuration(self) -> None:
check=1337,
)),
])
- self.assertEqual(expected, configuration.extensions)
+ assert expected == configuration.extensions
def test_load_should_load_one_extension_without_configuration(self) -> None:
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['extensions'] = {
DummyNonConfigurableExtension.name(): {},
}
@@ -675,90 +687,98 @@ def test_load_should_load_one_extension_without_configuration(self) -> None:
expected = ProjectExtensionsConfiguration([
ProjectExtensionConfiguration(DummyNonConfigurableExtension, True),
])
- self.assertEqual(expected, configuration.extensions)
+ assert expected == configuration.extensions
def test_load_extension_with_invalid_configuration_should_raise_error(self):
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['extensions'] = {
DummyConfigurableExtension.name(): 1337,
}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
def test_load_unknown_extension_type_name_should_error(self):
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['extensions'] = {
'non.existent.type': None,
}
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
def test_load_not_an_extension_type_name_should_error(self):
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['extensions'] = {
'%s.%s' % (self.__class__.__module__, self.__class__.__name__): None,
}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
def test_load_should_load_theme_background_image_id(self) -> None:
background_image_id = 'my-favorite-picture'
- dumped_configuration: Any = self.assertIsNotVoid(ProjectConfiguration().dump())
+ dumped_configuration: Any = ProjectConfiguration().dump()
+ assert not isinstance(dumped_configuration, Void)
dumped_configuration['theme'] = {
'background_image_id': background_image_id
}
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
- self.assertEqual(background_image_id, configuration.theme.background_image.entity_id)
+ assert background_image_id == configuration.theme.background_image.entity_id
def test_load_should_error_if_invalid_config(self) -> None:
dumped_configuration: Dict = {}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
def test_dump_should_dump_minimal(self) -> None:
configuration = ProjectConfiguration()
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(dumped_configuration['base_url'], configuration.base_url)
- self.assertEqual('Betty', configuration.title)
- self.assertIsNone(configuration.author)
- self.assertEqual(False, configuration.debug)
- self.assertEqual('', configuration.root_path)
- self.assertFalse(configuration.clean_urls)
- self.assertFalse(configuration.content_negotiation)
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert dumped_configuration['base_url'] == configuration.base_url
+ assert 'Betty' == configuration.title
+ assert configuration.author is None
+ assert not configuration.debug
+ assert '' == configuration.root_path
+ assert not configuration.clean_urls
+ assert not configuration.content_negotiation
def test_dump_should_dump_title(self) -> None:
title = 'My first Betty site'
configuration = ProjectConfiguration()
configuration.title = title
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(title, dumped_configuration['title'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert title == dumped_configuration['title']
def test_dump_should_dump_author(self) -> None:
author = 'Bart'
configuration = ProjectConfiguration()
configuration.author = author
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(author, dumped_configuration['author'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert author == dumped_configuration['author']
def test_dump_should_dump_locale_locale(self) -> None:
locale = 'nl-NL'
locale_configuration = LocaleConfiguration(locale)
configuration = ProjectConfiguration()
configuration.locales.replace([locale_configuration])
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertListEqual([
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert [
{
'locale': locale,
},
- ], dumped_configuration['locales'])
+ ] == dumped_configuration['locales']
def test_dump_should_dump_locale_alias(self) -> None:
locale = 'nl-NL'
@@ -766,51 +786,57 @@ def test_dump_should_dump_locale_alias(self) -> None:
locale_configuration = LocaleConfiguration(locale, alias)
configuration = ProjectConfiguration()
configuration.locales.replace([locale_configuration])
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertListEqual([
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert [
{
'locale': locale,
'alias': alias,
},
- ], dumped_configuration['locales'])
+ ] == dumped_configuration['locales']
def test_dump_should_dump_root_path(self) -> None:
root_path = 'betty'
configuration = ProjectConfiguration()
configuration.root_path = root_path
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(root_path, dumped_configuration['root_path'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert root_path == dumped_configuration['root_path']
def test_dump_should_dump_clean_urls(self) -> None:
clean_urls = True
configuration = ProjectConfiguration()
configuration.clean_urls = clean_urls
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(clean_urls, dumped_configuration['clean_urls'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert clean_urls == dumped_configuration['clean_urls']
def test_dump_should_dump_content_negotiation(self) -> None:
content_negotiation = True
configuration = ProjectConfiguration()
configuration.content_negotiation = content_negotiation
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(content_negotiation, dumped_configuration['content_negotiation'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert content_negotiation == dumped_configuration['content_negotiation']
- @parameterized.expand([
- (True,),
- (False,),
+ @pytest.mark.parametrize('debug', [
+ True,
+ False,
])
def test_dump_should_dump_debug(self, debug: bool) -> None:
configuration = ProjectConfiguration()
configuration.debug = debug
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(debug, dumped_configuration['debug'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert debug == dumped_configuration['debug']
def test_dump_should_dump_one_extension_with_configuration(self) -> None:
configuration = ProjectConfiguration()
configuration.extensions.add(ProjectExtensionConfiguration(DummyConfigurableExtension, True, DummyConfigurableExtensionConfiguration(
check=1337,
)))
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
expected = {
DummyConfigurableExtension.name(): {
'enabled': True,
@@ -819,23 +845,24 @@ def test_dump_should_dump_one_extension_with_configuration(self) -> None:
},
},
}
- self.assertEqual(expected, dumped_configuration['extensions'])
+ assert expected == dumped_configuration['extensions']
def test_dump_should_dump_one_extension_without_configuration(self) -> None:
configuration = ProjectConfiguration()
configuration.extensions.add(ProjectExtensionConfiguration(DummyNonConfigurableExtension))
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
expected = {
DummyNonConfigurableExtension.name(): {
'enabled': True,
},
}
- self.assertEqual(expected, dumped_configuration['extensions'])
+ assert expected == dumped_configuration['extensions']
def test_dump_should_error_if_invalid_config(self) -> None:
dumped_configuration: Dict = {}
with App():
- with self.assertRaises(ConfigurationError):
+ with pytest.raises(ConfigurationError):
configuration = ProjectConfiguration()
configuration.load(dumped_configuration)
@@ -843,8 +870,9 @@ def test_dump_should_dump_theme_background_image(self) -> None:
background_image_id = 'my-favorite-picture'
configuration = ProjectConfiguration()
configuration.theme.background_image.entity_id = background_image_id
- dumped_configuration: Any = self.assertIsNotVoid(configuration.dump())
- self.assertEqual(background_image_id, dumped_configuration['theme']['background_image_id'])
+ dumped_configuration: Any = configuration.dump()
+ assert not isinstance(dumped_configuration, Void)
+ assert background_image_id == dumped_configuration['theme']['background_image_id']
class DummyNonConfigurableExtension(Extension):
diff --git a/betty/tests/test_readme.py b/betty/tests/test_readme.py
index 50aabd343..49dfc8d5f 100644
--- a/betty/tests/test_readme.py
+++ b/betty/tests/test_readme.py
@@ -1,16 +1,14 @@
import json
+import subprocess as stdsubprocess
import sys
+from asyncio import StreamReader
from pathlib import Path
-import subprocess as stdsubprocess
from tempfile import TemporaryDirectory
from betty import os, subprocess
-from betty.asyncio import sync
-from betty.tests import TestCase
-class ReadmeTest(TestCase):
- @sync
+class TestReadme:
async def test_readme_should_contain_cli_help(self):
with TemporaryDirectory() as working_directory_path_str:
working_directory_path = Path(working_directory_path_str)
@@ -21,9 +19,11 @@ async def test_readme_should_contain_cli_help(self):
json.dump(configuration, f)
with os.ChDir(working_directory_path):
process = await subprocess.run_exec(['betty', '--help'], stdout=stdsubprocess.PIPE)
- expected = (await process.stdout.read()).decode()
+ stdout = process.stdout
+ assert isinstance(stdout, StreamReader)
+ expected = (await stdout.read()).decode()
if sys.platform.startswith('win32'):
expected = expected.replace('\r\n', '\n')
with open('README.md') as f:
actual = f.read()
- self.assertIn(expected, actual)
+ assert expected in actual
diff --git a/betty/tests/test_search.py b/betty/tests/test_search.py
index a9bcc945a..2a1594557 100644
--- a/betty/tests/test_search.py
+++ b/betty/tests/test_search.py
@@ -1,13 +1,12 @@
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.model.ancestry import Person, Place, PlaceName, PersonName, File
from betty.project import LocaleConfiguration
from betty.search import Index
-from betty.tests import TestCase
-class IndexTest(TestCase):
+class TestIndex:
def test_empty(self):
app = App()
app.project.configuration.locales.replace([
@@ -17,7 +16,7 @@ def test_empty(self):
with app:
indexed = [item for item in Index(app).build()]
- self.assertEqual([], indexed)
+ assert [] == indexed
def test_person_without_names(self):
person_id = 'P1'
@@ -32,7 +31,7 @@ def test_person_without_names(self):
app.project.ancestry.entities.append(person)
indexed = [item for item in Index(app).build()]
- self.assertEqual([], indexed)
+ assert [] == indexed
def test_private_person(self):
person_id = 'P1'
@@ -50,9 +49,9 @@ def test_private_person(self):
app.project.ancestry.entities.append(person)
indexed = [item for item in Index(app).build()]
- self.assertEqual([], indexed)
+ assert [] == indexed
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale', [
('/nl/person/P1/index.html', 'nl-NL'),
('/en/person/P1/index.html', 'en-US'),
])
@@ -72,10 +71,10 @@ def test_person_with_individual_name(self, expected: str, locale: str):
app.project.ancestry.entities.append(person)
indexed = [item for item in Index(app).build()]
- self.assertEqual('jane', indexed[0]['text'])
- self.assertIn(expected, indexed[0]['result'])
+ assert 'jane' == indexed[0]['text']
+ assert expected in indexed[0]['result']
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale', [
('/nl/person/P1/index.html', 'nl-NL'),
('/en/person/P1/index.html', 'en-US'),
])
@@ -95,10 +94,10 @@ def test_person_with_affiliation_name(self, expected: str, locale: str):
app.project.ancestry.entities.append(person)
indexed = [item for item in Index(app).build()]
- self.assertEqual('doughnut', indexed[0]['text'])
- self.assertIn(expected, indexed[0]['result'])
+ assert 'doughnut' == indexed[0]['text']
+ assert expected in indexed[0]['result']
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale', [
('/nl/person/P1/index.html', 'nl-NL'),
('/en/person/P1/index.html', 'en-US'),
])
@@ -119,10 +118,10 @@ def test_person_with_individual_and_affiliation_names(self, expected: str, local
app.project.ancestry.entities.append(person)
indexed = [item for item in Index(app).build()]
- self.assertEqual('jane doughnut', indexed[0]['text'])
- self.assertIn(expected, indexed[0]['result'])
+ assert 'jane doughnut' == indexed[0]['text']
+ assert expected in indexed[0]['result']
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale', [
('/nl/place/P1/index.html', 'nl-NL'),
('/en/place/P1/index.html', 'en-US'),
])
@@ -140,8 +139,8 @@ def test_place(self, expected: str, locale: str):
app.project.ancestry.entities.append(place)
indexed = [item for item in Index(app).build()]
- self.assertEqual('netherlands nederland', indexed[0]['text'])
- self.assertIn(expected, indexed[0]['result'])
+ assert 'netherlands nederland' == indexed[0]['text']
+ assert expected in indexed[0]['result']
def test_file_without_description(self):
file_id = 'F1'
@@ -156,9 +155,9 @@ def test_file_without_description(self):
app.project.ancestry.entities.append(file)
indexed = [item for item in Index(app).build()]
- self.assertEqual([], indexed)
+ assert [] == indexed
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, locale', [
('/nl/file/F1/index.html', 'nl-NL'),
('/en/file/F1/index.html', 'en-US'),
])
@@ -177,5 +176,5 @@ def test_file(self, expected: str, locale: str):
app.project.ancestry.entities.append(file)
indexed = [item for item in Index(app).build()]
- self.assertEqual('"file" is dutch for "traffic jam"', indexed[0]['text'])
- self.assertIn(expected, indexed[0]['result'])
+ assert '"file" is dutch for "traffic jam"' == indexed[0]['text']
+ assert expected in indexed[0]['result']
diff --git a/betty/tests/test_serve.py b/betty/tests/test_serve.py
index f7704e53b..a373796a9 100644
--- a/betty/tests/test_serve.py
+++ b/betty/tests/test_serve.py
@@ -4,13 +4,10 @@
import requests
from betty.app import App
-from betty.asyncio import sync
from betty.serve import BuiltinServer
-from betty.tests import TestCase
-class BuiltinServerTest(TestCase):
- @sync
+class TestBuiltinServer:
async def test(self):
content = 'Hello, and welcome to my site!'
with App() as app:
@@ -21,6 +18,6 @@ async def test(self):
# Wait for the server to start.
sleep(1)
response = requests.get(server.public_url)
- self.assertEqual(200, response.status_code)
- self.assertEqual(content, response.content.decode('utf-8'))
- self.assertEqual('no-cache', response.headers['Cache-Control'])
+ assert 200 == response.status_code
+ assert content == response.content.decode('utf-8')
+ assert 'no-cache' == response.headers['Cache-Control']
diff --git a/betty/tests/test_string.py b/betty/tests/test_string.py
index 145c42dfa..a7201ce7f 100644
--- a/betty/tests/test_string.py
+++ b/betty/tests/test_string.py
@@ -1,34 +1,33 @@
-from parameterized import parameterized
+import pytest
from betty.string import camel_case_to_snake_case, camel_case_to_kebab_case, upper_camel_case_to_lower_camel_case
-from betty.tests import TestCase
-class CamelCaseToSnakeCaseTest(TestCase):
- @parameterized.expand([
+class TestCamelCaseToSnakeCase:
+ @pytest.mark.parametrize('expected, string', [
('snake_case', 'snakeCase'),
('snake_case', 'SnakeCase'),
('snake__case', 'Snake_Case'),
])
def test(self, expected: str, string: str) -> None:
- self.assertEqual(expected, camel_case_to_snake_case(string))
+ assert expected == camel_case_to_snake_case(string)
-class CamelCaseToKebabCaseTest(TestCase):
- @parameterized.expand([
+class TestCamelCaseToKebabCase:
+ @pytest.mark.parametrize('expected, string', [
('snake-case', 'snakeCase'),
('snake-case', 'SnakeCase'),
('snake--case', 'Snake-Case'),
])
def test(self, expected: str, string: str) -> None:
- self.assertEqual(expected, camel_case_to_kebab_case(string))
+ assert expected == camel_case_to_kebab_case(string)
-class UpperCamelCaseToLowerCamelCase(TestCase):
- @parameterized.expand([
+class TestUpperCamelCaseToLowerCamelCase:
+ @pytest.mark.parametrize('expected, string', [
('snakeCase', 'snakeCase'),
('snakeCase', 'SnakeCase'),
('123SnakeCase', '123SnakeCase'),
])
def test(self, expected: str, string: str) -> None:
- self.assertEqual(expected, upper_camel_case_to_lower_camel_case(string))
+ assert expected == upper_camel_case_to_lower_camel_case(string)
diff --git a/betty/tests/test_subprocess.py b/betty/tests/test_subprocess.py
index 103eb886d..b1cb72ad8 100644
--- a/betty/tests/test_subprocess.py
+++ b/betty/tests/test_subprocess.py
@@ -1,30 +1,26 @@
from asyncio.subprocess import Process
from subprocess import CalledProcessError
+import pytest
+
from betty import subprocess
-from betty.asyncio import sync
-from betty.tests import TestCase
-class RunExecTest(TestCase):
- @sync
+class TestRunExec:
async def test_without_errors(self):
process = await subprocess.run_exec(['true'])
- self.assertIsInstance(process, Process)
+ assert isinstance(process, Process)
- @sync
async def test_with_errors(self):
- with self.assertRaises(CalledProcessError):
+ with pytest.raises(CalledProcessError):
await subprocess.run_exec(['false'])
-class RunShellTest(TestCase):
- @sync
+class TestRunShell:
async def test_without_errors(self):
process = await subprocess.run_shell(['true'])
- self.assertIsInstance(process, Process)
+ assert isinstance(process, Process)
- @sync
async def test_with_errors(self):
- with self.assertRaises(CalledProcessError):
+ with pytest.raises(CalledProcessError):
await subprocess.run_shell(['false'])
diff --git a/betty/tests/test_url.py b/betty/tests/test_url.py
index 90e7cce9b..9e7b841d5 100644
--- a/betty/tests/test_url.py
+++ b/betty/tests/test_url.py
@@ -1,18 +1,17 @@
from typing import Any
-from parameterized import parameterized
+import pytest
from betty.app import App
from betty.model import Entity
from betty.model.ancestry import Person, Place, File, Source, PlaceName, Event, Citation
from betty.model.event_type import Death
from betty.project import LocaleConfiguration
-from betty.tests import TestCase
from betty.url import ContentNegotiationPathUrlGenerator, _EntityUrlGenerator, AppUrlGenerator
-class LocalizedPathUrlGeneratorTest(TestCase):
- @parameterized.expand([
+class TestLocalizedPathUrlGenerator:
+ @pytest.mark.parametrize('expected, resource', [
('', '/'),
('/index.html', '/index.html'),
('/example', 'example'),
@@ -25,9 +24,9 @@ class LocalizedPathUrlGeneratorTest(TestCase):
def test_generate(self, expected: str, resource: str):
with App() as app:
sut = ContentNegotiationPathUrlGenerator(app)
- self.assertEqual(expected, sut.generate(resource, 'text/html'))
+ assert expected == sut.generate(resource, 'text/html')
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, resource', [
('', 'index.html'),
('', '/index.html'),
('/example', 'example/index.html'),
@@ -37,22 +36,21 @@ def test_generate_with_clean_urls(self, expected: str, resource: str):
with App() as app:
app.project.configuration.clean_urls = True
sut = ContentNegotiationPathUrlGenerator(app)
- self.assertEqual(expected, sut.generate(resource, 'text/html'))
+ assert expected == sut.generate(resource, 'text/html')
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, resource', [
('https://example.com', '/'),
('https://example.com/example', 'example'),
])
def test_generate_absolute(self, expected: str, resource: str):
with App() as app:
sut = ContentNegotiationPathUrlGenerator(app)
- self.assertEqual(expected, sut.generate(
- resource, 'text/html', absolute=True))
+ assert expected == sut.generate(resource, 'text/html', absolute=True)
def test_generate_with_invalid_value(self):
with App() as app:
sut = ContentNegotiationPathUrlGenerator(app)
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
sut.generate(9, 'text/html')
def test_generate_multilingual(self):
@@ -64,29 +62,32 @@ def test_generate_multilingual(self):
with app:
sut = ContentNegotiationPathUrlGenerator(app)
with app.acquire_locale('nl'):
- self.assertEqual('/nl/index.html', sut.generate('/index.html', 'text/html'))
+ assert '/nl/index.html' == sut.generate('/index.html', 'text/html')
with app.acquire_locale('en'):
- self.assertEqual('/en/index.html', sut.generate('/index.html', 'text/html'))
+ assert '/en/index.html' == sut.generate('/index.html', 'text/html')
-class EntityUrlGeneratorTest(TestCase):
+class TestEntityUrlGenerator:
class UrlyEntity(Entity):
pass
+ class NonUrlyEntity(Entity):
+ pass
+
def test_generate(self):
with App() as app:
sut = _EntityUrlGenerator(app, self.UrlyEntity, 'prefix/%s/index.%s')
- self.assertEqual('/prefix/I1/index.html', sut.generate(self.UrlyEntity('I1'), 'text/html'))
+ assert '/prefix/I1/index.html' == sut.generate(self.UrlyEntity('I1'), 'text/html')
def test_generate_with_invalid_value(self):
with App() as app:
sut = _EntityUrlGenerator(app, self.UrlyEntity, 'prefix/%s/index.html')
- with self.assertRaises(ValueError):
- sut.generate(9, 'text/html')
+ with pytest.raises(ValueError):
+ sut.generate(self.NonUrlyEntity(), 'text/html')
-class AppUrlGeneratorTest(TestCase):
- @parameterized.expand([
+class TestAppUrlGenerator:
+ @pytest.mark.parametrize('expected, resource', [
('/index.html', '/index.html'),
('/person/P1/index.html', Person('P1')),
('/event/E1/index.html', Event('E1', Death())),
@@ -98,10 +99,10 @@ class AppUrlGeneratorTest(TestCase):
def test_generate(self, expected: str, resource: Any):
with App() as app:
sut = AppUrlGenerator(app)
- self.assertEqual(expected, sut.generate(resource, 'text/html'))
+ assert expected == sut.generate(resource, 'text/html')
def test_generate_with_invalid_value(self):
with App() as app:
sut = AppUrlGenerator(app)
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
sut.generate(9, 'text/html')
diff --git a/betty/tests/trees/test__init__.py b/betty/tests/trees/test___init__.py
similarity index 77%
rename from betty/tests/trees/test__init__.py
rename to betty/tests/trees/test___init__.py
index e818a6253..4ee500157 100644
--- a/betty/tests/trees/test__init__.py
+++ b/betty/tests/trees/test___init__.py
@@ -1,14 +1,12 @@
-from betty.asyncio import sync
+from betty.app import App
from betty.generate import generate
from betty.project import ProjectExtensionConfiguration
+from betty.tests import patch_cache
from betty.trees import Trees
-from betty.app import App
-from betty.tests import patch_cache, TestCase
-class TreesTest(TestCase):
+class TestTrees:
@patch_cache
- @sync
async def test_post_generate_event(self):
with App() as app:
app.project.configuration.debug = True
@@ -16,7 +14,7 @@ async def test_post_generate_event(self):
await generate(app)
with open(app.project.configuration.www_directory_path / 'trees.js', encoding='utf-8') as f:
betty_js = f.read()
- self.assertIn('trees.js', betty_js)
+ assert 'trees.js' in betty_js
with open(app.project.configuration.www_directory_path / 'trees.css', encoding='utf-8') as f:
betty_css = f.read()
- self.assertIn('.tree', betty_css)
+ assert '.tree' in betty_css
diff --git a/betty/tests/wikipedia/test__init__.py b/betty/tests/wikipedia/test___init__.py
similarity index 72%
rename from betty/tests/wikipedia/test__init__.py
rename to betty/tests/wikipedia/test___init__.py
index 9059b9ce2..ae0c8d0ca 100644
--- a/betty/tests/wikipedia/test__init__.py
+++ b/betty/tests/wikipedia/test___init__.py
@@ -1,31 +1,32 @@
from pathlib import Path
from tempfile import TemporaryDirectory
from time import sleep
-from typing import Tuple, Optional
-from unittest.mock import patch, call
+from typing import Tuple, Optional, Any
+from unittest.mock import call
+
+import aiohttp
+import pytest
+from pytest_mock import MockerFixture
from betty.media_type import MediaType
from betty.project import LocaleConfiguration, ProjectExtensionConfiguration
-from betty.tests import TestCase, patch_cache
+from betty.tests import patch_cache
try:
from unittest.mock import AsyncMock
except ImportError:
from mock.mock import AsyncMock
-import aiohttp
from aioresponses import aioresponses
-from parameterized import parameterized
from betty.model.ancestry import Source, Link, Citation
-from betty.asyncio import sync
from betty.load import load
from betty.wikipedia import Entry, _Retriever, NotAnEntryError, _parse_url, RetrievalError, _Populator, Wikipedia
from betty.app import App
-class ParseUrlTest(TestCase):
- @parameterized.expand([
+class TestParseUrl:
+ @pytest.mark.parametrize('expected, url', [
(('en', 'Amsterdam'), 'http://en.wikipedia.org/wiki/Amsterdam',),
(('nl', 'Amsterdam'), 'https://nl.wikipedia.org/wiki/Amsterdam',),
(('en', 'Amsterdam'), 'http://en.wikipedia.org/wiki/Amsterdam',),
@@ -35,36 +36,36 @@ class ParseUrlTest(TestCase):
(('en', 'Amsterdam'), 'https://en.wikipedia.org/wiki/Amsterdam#some-fragment',),
])
def test_should_return(self, expected: Tuple[str, str], url: str) -> None:
- self.assertEqual(expected, _parse_url(url))
+ assert expected == _parse_url(url)
- @parameterized.expand([
- ('',),
- ('ftp://en.wikipedia.org/wiki/Amsterdam',),
- ('https://en.wikipedia.org/w/index.php?title=Amsterdam&action=edit',),
+ @pytest.mark.parametrize('url', [
+ '',
+ 'ftp://en.wikipedia.org/wiki/Amsterdam',
+ 'https://en.wikipedia.org/w/index.php?title=Amsterdam&action=edit',
])
def test_should_error(self, url: str) -> None:
- with self.assertRaises(NotAnEntryError):
+ with pytest.raises(NotAnEntryError):
_parse_url(url)
-class EntryTest(TestCase):
+class TestEntry:
def test_url(self) -> None:
sut = Entry('nl', 'Amsterdam', 'Title for Amsterdam', 'Content for Amsterdam')
- self.assertEqual('https://nl.wikipedia.org/wiki/Amsterdam', sut.url)
+ assert 'https://nl.wikipedia.org/wiki/Amsterdam' == sut.url
def test_title(self) -> None:
title = 'Title for Amsterdam'
sut = Entry('nl', 'Amsterdam', title, 'Content for Amsterdam')
- self.assertEqual(title, sut.title)
+ assert title == sut.title
def test_content(self) -> None:
content = 'Content for Amsterdam'
sut = Entry('nl', 'Amsterdam', 'Title for Amsterdam', content)
- self.assertEqual(content, sut.content)
+ assert content == sut.content
-class RetrieverTest(TestCase):
- @parameterized.expand([
+class TestRetriever:
+ @pytest.mark.parametrize('expected, response_pages_json', [
({}, {},),
({
'nl': 'Amsterdam',
@@ -82,10 +83,8 @@ class RetrieverTest(TestCase):
],
},),
])
- @aioresponses()
- @patch('sys.stderr')
- @sync
- async def test_get_translations_should_return(self, expected, response_pages_json, m_stderr, m_aioresponses) -> None:
+ async def test_get_translations_should_return(self, expected, response_pages_json, aioresponses: aioresponses, mocker: MockerFixture) -> None:
+ mocker.patch('sys.stderr')
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://%s.wikipedia.org/w/api.php?action=query&titles=%s&prop=langlinks&lllimit=500&format=json&formatversion=2' % (entry_language, entry_name)
@@ -94,70 +93,62 @@ async def test_get_translations_should_return(self, expected, response_pages_jso
'pages': [response_pages_json],
},
}
- m_aioresponses.get(api_url, payload=api_response_body)
+ aioresponses.get(api_url, payload=api_response_body)
with TemporaryDirectory() as cache_directory_path:
async with aiohttp.ClientSession() as session:
translations = await _Retriever(session, Path(cache_directory_path)).get_translations(entry_language, entry_name)
- self.assertEqual(expected, translations)
+ assert expected == translations
- @aioresponses()
- @patch('sys.stderr')
- @sync
- async def test_get_translations_with_client_error_should_raise_retrieval_error(self, m_aioresponses, m_stderr) -> None:
+ async def test_get_translations_with_client_error_should_raise_retrieval_error(self, aioresponses: aioresponses, mocker: MockerFixture) -> None:
+ mocker.patch('sys.stderr')
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://%s.wikipedia.org/w/api.php?action=query&titles=%s&prop=langlinks&lllimit=500&format=json&formatversion=2' % (entry_language, entry_name)
- m_aioresponses.get(api_url, exception=aiohttp.ClientError())
+ aioresponses.get(api_url, exception=aiohttp.ClientError())
with TemporaryDirectory() as cache_directory_path:
- with self.assertRaises(RetrievalError):
+ with pytest.raises(RetrievalError):
async with aiohttp.ClientSession() as session:
await _Retriever(session, Path(cache_directory_path)).get_translations(entry_language, entry_name)
- @aioresponses()
- @patch('sys.stderr')
- @sync
- async def test_get_translations_with_invalid_json_response_should_raise_retrieval_error(self, m_aioresponses, m_stderr) -> None:
+ async def test_get_translations_with_invalid_json_response_should_raise_retrieval_error(self, aioresponses: aioresponses, mocker: MockerFixture) -> None:
+ mocker.patch('sys.stderr')
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://%s.wikipedia.org/w/api.php?action=query&titles=%s&prop=langlinks&lllimit=500&format=json&formatversion=2' % (entry_language, entry_name)
- m_aioresponses.get(api_url, body='{Haha Im not rly JSON}')
+ aioresponses.get(api_url, body='{Haha Im not rly JSON}')
with TemporaryDirectory() as cache_directory_path:
- with self.assertRaises(RetrievalError):
+ with pytest.raises(RetrievalError):
async with aiohttp.ClientSession() as session:
await _Retriever(session, Path(cache_directory_path)).get_translations(entry_language, entry_name)
- @parameterized.expand([
- ({},),
- ({
+ @pytest.mark.parametrize('response_json', [
+ {},
+ {
'query': {}
- },),
- ({
+ },
+ {
'query': {
'pages': {}
}
- },),
- ({
+ },
+ {
'query': {
'pages': []
}
- },),
+ },
])
- @aioresponses()
- @patch('sys.stderr')
- @sync
- async def test_get_translations_with_unexpected_json_response_should_raise_retrieval_error(self, response_json, m_stderr, m_aioresponses) -> None:
+ async def test_get_translations_with_unexpected_json_response_should_raise_retrieval_error(self, response_json, mocker: MockerFixture, aioresponses: aioresponses) -> None:
+ mocker.patch('sys.stderr')
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://%s.wikipedia.org/w/api.php?action=query&titles=%s&prop=langlinks&lllimit=500&format=json&formatversion=2' % (entry_language, entry_name)
- m_aioresponses.get(api_url, payload=response_json)
+ aioresponses.get(api_url, payload=response_json)
with TemporaryDirectory() as cache_directory_path:
- with self.assertRaises(RetrievalError):
+ with pytest.raises(RetrievalError):
async with aiohttp.ClientSession() as session:
await _Retriever(session, Path(cache_directory_path)).get_translations(entry_language, entry_name)
- @aioresponses()
- @sync
- async def test_get_entry_should_return(self, m_aioresponses) -> None:
+ async def test_get_entry_should_return(self, aioresponses: aioresponses) -> None:
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=extracts&exintro&format=json&formatversion=2'
@@ -185,9 +176,9 @@ async def test_get_entry_should_return(self, m_aioresponses) -> None:
],
}
}
- m_aioresponses.get(api_url, payload=api_response_body_1)
- m_aioresponses.get(api_url, exception=aiohttp.ClientError())
- m_aioresponses.get(api_url, payload=api_response_body_4)
+ aioresponses.get(api_url, payload=api_response_body_1)
+ aioresponses.get(api_url, exception=aiohttp.ClientError())
+ aioresponses.get(api_url, payload=api_response_body_4)
with TemporaryDirectory() as cache_directory_path:
async with aiohttp.ClientSession() as session:
retriever = _Retriever(session, Path(cache_directory_path), 1)
@@ -203,125 +194,116 @@ async def test_get_entry_should_return(self, m_aioresponses) -> None:
# The fifth retrieval should hit the cache from the fourth request.
entry_5 = await retriever.get_entry(entry_language, entry_name)
for entry in [entry_1, entry_2, entry_3]:
- self.assertEqual(entry_url, entry.url)
- self.assertEqual(title, entry.title)
- self.assertEqual(extract_1, entry.content)
+ assert entry_url == entry.url
+ assert title == entry.title
+ assert extract_1 == entry.content
for entry in [entry_4, entry_5]:
- self.assertEqual(entry_url, entry.url)
- self.assertEqual(title, entry.title)
- self.assertEqual(extract_4, entry.content)
-
- @aioresponses()
- @patch('sys.stderr')
- @sync
- async def test_get_entry_with_client_error_should_raise_retrieval_error(self, m_aioresponses, m_stderr) -> None:
+ assert entry_url == entry.url
+ assert title == entry.title
+ assert extract_4 == entry.content
+
+ async def test_get_entry_with_client_error_should_raise_retrieval_error(self, aioresponses: aioresponses, mocker: MockerFixture) -> None:
+ mocker.patch('sys.stderr')
entry_language = 'en'
entry_name = 'Amsterdam'
api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=extracts&exintro&format=json&formatversion=2'
- m_aioresponses.get(api_url, exception=aiohttp.ClientError())
+ aioresponses.get(api_url, exception=aiohttp.ClientError())
with TemporaryDirectory() as cache_directory_path:
async with aiohttp.ClientSession() as session:
retriever = _Retriever(session, Path(cache_directory_path))
- with self.assertRaises(RetrievalError):
+ with pytest.raises(RetrievalError):
await retriever.get_entry(entry_language, entry_name)
-class PopulatorTest(TestCase):
- @patch('betty.wikipedia._Retriever')
+class TestPopulator:
@patch_cache
- @sync
- async def test_populate_link_should_convert_http_to_https(self, m_retriever) -> None:
+ async def test_populate_link_should_convert_http_to_https(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://en.wikipedia.org/wiki/Amsterdam')
entry_language = 'nl'
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, entry_language)
- self.assertEqual('https://en.wikipedia.org/wiki/Amsterdam', link.url)
+ assert 'https://en.wikipedia.org/wiki/Amsterdam' == link.url
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, media_type', [
(MediaType('text/plain'), MediaType('text/plain')),
(MediaType('text/html'), MediaType('text/html')),
(MediaType('text/html'), None),
])
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_link_should_set_media_type(self, expected: MediaType, media_type: Optional[MediaType], m_retriever) -> None:
+ async def test_populate_link_should_set_media_type(self, expected: MediaType, media_type: Optional[MediaType], mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://en.wikipedia.org/wiki/Amsterdam')
link.media_type = media_type
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, 'en')
- self.assertEqual(expected, link.media_type)
+ assert expected == link.media_type
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, relationship', [
('alternate', 'alternate'),
('external', 'external'),
('external', None),
])
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_link_should_set_relationship(self, expected: str, relationship: Optional[str], m_retriever) -> None:
+ async def test_populate_link_should_set_relationship(self, expected: str, relationship: Optional[str], mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://en.wikipedia.org/wiki/Amsterdam')
link.relationship = relationship
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, 'en')
- self.assertEqual(expected, link.relationship)
+ assert expected == link.relationship
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, entry_language, locale', [
('nl-NL', 'nl', 'nl-NL'),
('nl', 'nl', None),
('nl', 'en', 'nl'),
])
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_link_should_set_locale(self, expected: str, entry_language: str, locale: Optional[str], m_retriever) -> None:
+ async def test_populate_link_should_set_locale(self, expected: str, entry_language: str, locale: Optional[str], mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://%s.wikipedia.org/wiki/Amsterdam' % entry_language)
link.locale = locale
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, entry_language)
- self.assertEqual(expected, link.locale)
+ assert expected == link.locale
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, description', [
('This is the original description', 'This is the original description'),
('Read more on Wikipedia.', None),
])
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_link_should_set_description(self, expected: str, description: str, m_retriever) -> None:
+ async def test_populate_link_should_set_description(self, expected: str, description: str, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://en.wikipedia.org/wiki/Amsterdam')
link.description = description
entry_language = 'en'
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, entry_language)
- self.assertEqual(expected, link.description)
+ assert expected == link.description
- @parameterized.expand([
+ @pytest.mark.parametrize('expected, label', [
('Amsterdam', 'Amsterdam'),
('The city of Amsterdam', None),
])
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_link_should_set_label(self, expected: str, label: Optional[str], m_retriever) -> None:
+ async def test_populate_link_should_set_label(self, expected: str, label: Optional[str], mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('http://en.wikipedia.org/wiki/Amsterdam')
link.label = label
entry = Entry('en', 'The_city_of_Amsterdam', 'The city of Amsterdam', 'Amsterdam, such a lovely place!')
with App() as app:
sut = _Populator(app, m_retriever)
await sut.populate_link(link, 'en', entry)
- self.assertEqual(expected, link.label)
+ assert expected == link.label
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_should_ignore_resource_without_link_support(self, m_retriever) -> None:
+ async def test_populate_should_ignore_resource_without_link_support(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
source = Source('The Source')
resource = Citation('the_citation', source)
with App() as app:
@@ -329,21 +311,19 @@ async def test_populate_should_ignore_resource_without_link_support(self, m_retr
sut = _Populator(app, m_retriever)
await sut.populate()
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_should_ignore_resource_without_links(self, m_retriever) -> None:
+ async def test_populate_should_ignore_resource_without_links(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
resource = Source('the_source', 'The Source')
with App() as app:
app.project.ancestry.entities.append(resource)
sut = _Populator(app, m_retriever)
await sut.populate()
- self.assertSetEqual(set(), resource.links)
+ assert set() == resource.links
- @patch('betty.wikipedia._Retriever')
@patch_cache
- @sync
- async def test_populate_should_ignore_non_wikipedia_links(self, m_retriever) -> None:
+ async def test_populate_should_ignore_non_wikipedia_links(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever')
link = Link('https://example.com')
resource = Source('the_source', 'The Source')
resource.links.add(link)
@@ -351,12 +331,11 @@ async def test_populate_should_ignore_non_wikipedia_links(self, m_retriever) ->
app.project.ancestry.entities.append(resource)
sut = _Populator(app, m_retriever)
await sut.populate()
- self.assertSetEqual({link}, resource.links)
+ assert {link} == resource.links
- @patch('betty.wikipedia._Retriever', spec=_Retriever, new_callable=AsyncMock)
@patch_cache
- @sync
- async def test_populate_should_populate_existing_link(self, m_retriever) -> None:
+ async def test_populate_should_populate_existing_link(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever', spec=_Retriever, new_callable=AsyncMock)
entry_language = 'en'
entry_name = 'Amsterdam'
entry_title = 'Amsterdam'
@@ -372,17 +351,16 @@ async def test_populate_should_populate_existing_link(self, m_retriever) -> None
sut = _Populator(app, m_retriever)
await sut.populate()
m_retriever.get_entry.assert_called_once_with(entry_language, entry_name)
- self.assertEqual(1, len(resource.links))
- self.assertEqual('Amsterdam', link.label)
- self.assertEqual('en', link.locale)
- self.assertEqual(MediaType('text/html'), link.media_type)
- self.assertIsNotNone(link.description)
- self.assertEqual('external', link.relationship)
-
- @patch('betty.wikipedia._Retriever', spec=_Retriever, new_callable=AsyncMock)
+ assert 1 == len(resource.links)
+ assert 'Amsterdam' == link.label
+ assert 'en' == link.locale
+ assert MediaType('text/html') == link.media_type
+ assert link.description is not None
+ assert 'external' == link.relationship
+
@patch_cache
- @sync
- async def test_populate_should_add_translation_links(self, m_retriever) -> None:
+ async def test_populate_should_add_translation_links(self, mocker: MockerFixture) -> None:
+ m_retriever = mocker.patch('betty.wikipedia._Retriever', spec=_Retriever, new_callable=AsyncMock)
entry_language = 'en'
entry_name = 'Amsterdam'
entry_title = 'Amsterdam'
@@ -421,19 +399,18 @@ async def test_populate_should_add_translation_links(self, m_retriever) -> None:
call(added_entry_language, added_entry_name),
])
m_retriever.get_translations.assert_called_once_with(entry_language, entry_name)
- self.assertEqual(2, len(resource.links))
+ assert 2 == len(resource.links)
link_nl = resource.links.difference({link_en}).pop()
- self.assertEqual('Amsterdam', link_nl.label)
- self.assertEqual('nl', link_nl.locale)
- self.assertEqual(MediaType('text/html'), link_nl.media_type)
- self.assertIsNotNone(link_nl.description)
- self.assertEqual('external', link_nl.relationship)
+ assert 'Amsterdam' == link_nl.label
+ assert 'nl' == link_nl.locale
+ assert MediaType('text/html') == link_nl.media_type
+ assert link_nl.description is not None
+ assert 'external' == link_nl.relationship
-class WikipediaTest(TestCase):
- @aioresponses()
+class TestWikipedia:
@patch_cache
- def test_filter(self, m_aioresponses) -> None:
+ def test_filter(self, aioresponses: aioresponses) -> None:
entry_url = 'https://en.wikipedia.org/wiki/Amsterdam'
links = [
Link(entry_url),
@@ -455,18 +432,16 @@ def test_filter(self, m_aioresponses) -> None:
],
}
}
- m_aioresponses.get(api_url, payload=api_response_body)
+ aioresponses.get(api_url, payload=api_response_body)
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Wikipedia))
actual = app.jinja2_environment.from_string(
'{% for entry in (links | wikipedia) %}{{ entry.content }}{% endfor %}').render(links=links)
- self.assertEqual(extract, actual)
+ assert extract == actual
- @aioresponses()
@patch_cache
- @sync
- async def test_post_load(self, m_aioresponses) -> None:
+ async def test_post_load(self, aioresponses: aioresponses) -> None:
resource = Source('the_source', 'The Source')
link = Link('https://en.wikipedia.org/wiki/Amsterdam')
resource.links.add(link)
@@ -483,8 +458,8 @@ async def test_post_load(self, m_aioresponses) -> None:
}
}
entry_api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=extracts&exintro&format=json&formatversion=2'
- m_aioresponses.get(entry_api_url, payload=entry_api_response_body)
- translations_api_response_body = {
+ aioresponses.get(entry_api_url, payload=entry_api_response_body)
+ translations_api_response_body: Any = {
'query': {
'pages': [
{
@@ -494,16 +469,16 @@ async def test_post_load(self, m_aioresponses) -> None:
},
}
translations_api_url = 'https://en.wikipedia.org/w/api.php?action=query&titles=Amsterdam&prop=langlinks&lllimit=500&format=json&formatversion=2'
- m_aioresponses.get(translations_api_url, payload=translations_api_response_body)
+ aioresponses.get(translations_api_url, payload=translations_api_response_body)
with App() as app:
app.project.configuration.extensions.add(ProjectExtensionConfiguration(Wikipedia))
app.project.ancestry.entities.append(resource)
await load(app)
- self.assertEqual(1, len(resource.links))
- self.assertEqual(entry_title, link.label)
- self.assertEqual('en', link.locale)
- self.assertEqual(MediaType('text/html'), link.media_type)
- self.assertIsNotNone(link.description)
- self.assertEqual('external', link.relationship)
+ assert 1 == len(resource.links)
+ assert entry_title == link.label
+ assert 'en' == link.locale
+ assert MediaType('text/html') == link.media_type
+ assert link.description is not None
+ assert 'external' == link.relationship
diff --git a/betty/trees/__init__.py b/betty/trees/__init__.py
index 7565c1ae2..27b4023dc 100644
--- a/betty/trees/__init__.py
+++ b/betty/trees/__init__.py
@@ -5,10 +5,10 @@
from typing import Optional, Iterable, Set, Type
from betty.app.extension import Extension
-from betty.npm import _Npm, NpmBuilder, npm
from betty.generate import Generator
from betty.gui import GuiBuilder
from betty.html import CssProvider, JsProvider
+from betty.npm import _Npm, NpmBuilder, npm
class Trees(Extension, CssProvider, JsProvider, Generator, GuiBuilder, NpmBuilder):
diff --git a/betty/url.py b/betty/url.py
index 4c9c5e15e..ec21f17dc 100644
--- a/betty/url.py
+++ b/betty/url.py
@@ -1,12 +1,13 @@
from __future__ import annotations
+
from contextlib import suppress
from typing import Any, Optional, Type
from betty.app import App
from betty.locale import negotiate_locale
+from betty.media_type import EXTENSIONS
from betty.model import Entity
from betty.model.ancestry import PersonName, Event, Place, File, Source, Citation, Note, Person
-from betty.media_type import EXTENSIONS
from betty.project import ProjectConfiguration
diff --git a/betty/wikipedia/__init__.py b/betty/wikipedia/__init__.py
index 20570d3da..511bc8b22 100644
--- a/betty/wikipedia/__init__.py
+++ b/betty/wikipedia/__init__.py
@@ -25,7 +25,6 @@
from betty.media_type import MediaType
from betty.model.ancestry import Link, HasLinks, Entity
-
if TYPE_CHECKING:
from betty.builtins import _
diff --git a/bin/test b/bin/test
index fa8e0cc91..0742d288e 100755
--- a/bin/test
+++ b/bin/test
@@ -21,7 +21,6 @@ npm run eslint -- -c ./betty/assets/.eslintrc.yaml ./betty/assets/**/*.js
# Run Python tests with coverage.
coverage erase
-PYTHONWARNINGS='error:::betty[.*]' coverage run --append -m nose2
PYTHONWARNINGS='error:::betty[.*]' coverage run --append -m pytest
coverage report -m
diff --git a/mypy.ini b/mypy.ini
index e3ac9d551..5197ea50b 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -17,26 +17,9 @@ exclude = (?x)(
| ^betty/jinja2.py$
| ^betty/serve.py$
| ^betty/openapi.py$
- | ^betty/tests/__init__.py$
- | ^betty/tests/test_readme.py$
| ^betty/npm/__init__.py$
| ^betty/generate.py$
- | ^betty/tests/test_url.py$
- | ^betty/tests/test_locale.py$
- | ^betty/tests/test_json.py$
- | ^betty/tests/test_jinja2.py$
- | ^betty/tests/test_functools.py$
- | ^betty/tests/test_fs.py$
- | ^betty/tests/test_config.py$
- | ^betty/tests/model/test_ancestry.py$
- | ^betty/tests/model/test___init__.py$
- | ^betty/tests/assets/templates/test_event_dimensions_html_j2.py$
- | ^betty/tests/assets/templates/macro/test_person_html_j2.py$
- | ^betty/tests/assets/locale/test_translations.py$
- | ^betty/tests/app/test_extension.py$
- | ^betty/tests/app/test___init__.py$
| ^betty/gramps/loader.py$
- | ^betty/tests/gramps/test_loader.py$
| ^betty/gui/logging\.py$
| ^betty/gui/locale\.py$
| ^betty/gui/model\.py$
@@ -48,14 +31,8 @@ exclude = (?x)(
| ^betty/maps/__init__\.py$
| ^betty/demo/__init__\.py$
| ^betty/cli\.py$
- | ^betty/tests/wikipedia/test__init__\.py$
- | ^betty/pytests/conftest\.py$
| ^betty/privatizer/__init__.py$
| ^betty/gramps/gui\.py$
- | ^betty/tests/test_cli\.py$
- | ^betty/tests/deriver/test__init__\.py$
- | ^betty/tests/anonymizer/test__init__\.py$
- | ^betty/pytests/extension/gramps/test_gui\.py$
| ^betty/cleaner/__init__\.py$
)
@@ -84,9 +61,6 @@ ignore_missing_imports = True
[mypy-lxml.*]
ignore_missing_imports = True
-[mypy-parameterized.*]
-ignore_missing_imports = True
-
[mypy-pdf2image.*]
ignore_missing_imports = True
@@ -99,5 +73,8 @@ ignore_missing_imports = True
[mypy-PyInstaller.*]
ignore_missing_imports = True
+[mypy-pytestqt.*]
+ignore_missing_imports = True
+
[mypy-resizeimage.*]
ignore_missing_imports = True
diff --git a/pytest.ini b/pytest.ini
index a94b7861d..43a47276d 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,4 +1,4 @@
[pytest]
qt_api=pyqt6
-testpaths=betty/pytests
+testpaths=betty/tests
asyncio_mode=auto
diff --git a/setup.py b/setup.py
index 6c9bab557..6d064d3a6 100644
--- a/setup.py
+++ b/setup.py
@@ -35,6 +35,7 @@
'Topic :: Software Development :: Code Generators',
'Natural Language :: Dutch',
'Natural Language :: English',
+ 'Natural Language :: French',
'Natural Language :: Ukrainian',
],
'python_requires': '~= 3.8',
@@ -45,7 +46,6 @@
'click ~= 8.1.2',
'geopy ~= 2.2.0',
'graphlib-backport ~= 1.0.3; python_version < "3.9"',
- 'importlib-metadata ~= 4.11.3',
'jinja2 ~= 3.1.1',
'jsonschema ~= 4.4.0',
'markupsafe ~= 2.1.1',
@@ -67,12 +67,11 @@
'flake8 ~= 4.0.1',
'html5lib ~= 1.1',
'lxml ~= 4.8.0',
- 'nose2 ~= 0.11.0',
'mypy ~= 0.950',
- 'parameterized ~= 0.8.1',
'pip-licenses ~= 3.5.3',
'pyinstaller ~= 5.0',
'pytest ~= 7.1.1',
+ 'pytest-aioresponses ~= 0.2.0 ',
'pytest-asyncio ~= 0.18.3 ',
'pytest-cov ~= 3.0.0',
'pytest-mock ~= 3.7.0',