diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fef370da57..f8cf22fc31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: tox-source: "./core[dev]" package: - name: Python Package + name: Python package uses: beeware/.github/.github/workflows/python-package-create.yml@main with: tox-source: "./core[dev]" @@ -60,8 +60,9 @@ jobs: - "winforms" core: + name: Test core runs-on: ${{ matrix.platform }} - needs: [pre-commit, towncrier, package] + needs: [ pre-commit, towncrier, package ] continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false @@ -69,52 +70,58 @@ jobs: platform: [ "macos-12", "macos-14", "ubuntu-latest", "windows-latest" ] python-version: [ "3.8", "3.12", "3.13-dev" ] include: - - experimental: false - # Test Python 3.9-3.11 on Ubuntu only - - platform: "ubuntu-latest" - python-version: "3.9" - experimental: false - - platform: "ubuntu-latest" - python-version: "3.10" - experimental: false - - platform: "ubuntu-latest" - python-version: "3.11" - experimental: false - # Allow development Python to fail without failing entire job. - - python-version: "3.13-dev" - experimental: true + - experimental: false + # Test Python 3.9-3.11 on Ubuntu only + - platform: "ubuntu-latest" + python-version: "3.9" + experimental: false + - platform: "ubuntu-latest" + python-version: "3.10" + experimental: false + - platform: "ubuntu-latest" + python-version: "3.11" + experimental: false + # Allow development Python to fail without failing entire job. + - python-version: "3.13-dev" + experimental: true exclude: - # macos-14 (i.e. arm64) does not support Python 3.8 - - platform: "macos-14" - python-version: "3.8" - - # Pillow isn't available for Python 3.13 on Windows - - platform: "windows-latest" - python-version: "3.13-dev" + # macos-14 (i.e. arm64) does not support Python 3.8 + - platform: "macos-14" + python-version: "3.8" + # Pillow isn't available for Python 3.13 on Windows + - platform: "windows-latest" + python-version: "3.13-dev" steps: - - uses: actions/checkout@v4.1.1 + - name: Checkout + uses: actions/checkout@v4.1.1 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5.0.0 with: python-version: ${{ matrix.python-version }} - - name: Install dev dependencies + + - name: Install dev Dependencies run: | # We don't actually want to install toga-core; # we just want the dev extras so we have a known version of tox and coverage python -m pip install ./core[dev] - - name: Get packages + + - name: Get Packages uses: actions/download-artifact@v4.1.4 with: pattern: ${{ needs.package.outputs.artifact-name }}-* merge-multiple: true + - name: Test run: | # The $(ls ...) shell expansion is done in the Github environment; - # the value of TOGA_INSTALL_COMMAND will be a literal string, - # without any shell expansions to perform - TOGA_INSTALL_COMMAND="python -m pip install ../$(ls core/dist/toga_core-*.whl)[dev] ../$(ls dummy/dist/toga_dummy-*.whl)" tox -e py + # the value of TOGA_INSTALL_COMMAND will be a literal string without any shell expansions to perform + TOGA_INSTALL_COMMAND="python -m pip install ../$(ls core/dist/toga_core-*.whl)[dev] ../$(ls dummy/dist/toga_dummy-*.whl)" \ + tox -e py-cov + tox -qe coverage$(tr -dc "0-9" <<< "${{ matrix.python-version }}") mv core/.coverage core/.coverage.${{ matrix.platform }}.${{ matrix.python-version }} - - name: Store coverage data + + - name: Store Coverage Data uses: actions/upload-artifact@v4.3.1 with: name: core-coverage-data-${{ matrix.platform }}-${{ matrix.python-version }} @@ -122,35 +129,39 @@ jobs: if-no-files-found: error core-coverage: - name: Combine & check core coverage. - runs-on: ubuntu-latest + name: Coverage needs: core + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - name: Checkout + uses: actions/checkout@v4.1.1 with: fetch-depth: 0 - - uses: actions/setup-python@v5.0.0 + + - name: Set up Python ${{ env.min_python_version }} + uses: actions/setup-python@v5.0.0 with: - # Use latest, so it understands all syntax. - python-version: ${{ env.max_python_version }} + # Use minimum version of python for coverage to avoid phantom branches + # https://github.com/nedbat/coveragepy/issues/1572#issuecomment-1522546425 + python-version: ${{ env.min_python_version }} + - name: Install dev dependencies run: | # We don't actually want to install toga-core; # we just want the dev extras so we have a known version of coverage python -m pip install ./core[dev] - - name: Retrieve coverage data + + - name: Retrieve Coverage Data uses: actions/download-artifact@v4.1.4 with: pattern: core-coverage-data-* path: core merge-multiple: true - - name: Generate coverage report - run: | - cd core - python -m coverage combine - python -m coverage html --skip-covered --skip-empty - python -m coverage report --rcfile ../pyproject.toml --fail-under=100 - - name: Upload HTML report if check failed. + + - name: Generate Coverage Report + run: tox -e coverage-html-fail + + - name: Upload HTML Coverage Report uses: actions/upload-artifact@v4.3.1 if: failure() with: @@ -158,84 +169,91 @@ jobs: path: core/htmlcov testbed: - runs-on: ${{ matrix.runs-on }} + name: Testbed needs: core + runs-on: ${{ matrix.runs-on }} strategy: fail-fast: false matrix: backend: [ "macOS-x86_64", "macOS-arm64", "windows", "linux", "android", "iOS" ] include: - - pre-command: - briefcase-run-prefix: - briefcase-run-args: - setup-python: true - - - backend: "macOS-x86_64" - platform: "macOS" - runs-on: "macos-12" - app-user-data-path: $HOME/Library/Application Support/org.beeware.toga.testbed - - - backend: "macOS-arm64" - platform: "macOS" - runs-on: "macos-14" - app-user-data-path: $HOME/Library/Application Support/org.beeware.toga.testbed - - # We use a fixed Ubuntu version rather than `-latest` because at some point, - # `-latest` will be updated, but it will be a soft changeover, which would cause - # the system Python version to become inconsistent from run to run. - - backend: "linux" - platform: "linux" - runs-on: "ubuntu-22.04" - # The package list should be the same as in tutorial-0.rst, and the BeeWare - # tutorial, plus blackbox to provide a window manager. We need a window - # manager that is reasonably lightweight, honors full screen mode, and - # treats the window position as the top-left corner of the *window*, not the - # top-left corner of the window *content*. The default GNOME window managers of - # most distros meet these requirements, but they're heavyweight; flwm doesn't - # work either. Blackbox is the lightest WM we've found that works. - pre-command: | - sudo apt update -y - sudo apt install -y blackbox pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.0 - - # Start Virtual X server - echo "Start X server..." - Xvfb :99 -screen 0 2048x1536x24 & - sleep 1 - - # Start Window manager - echo "Start window manager..." - DISPLAY=:99 blackbox & - sleep 1 - - briefcase-run-prefix: 'DISPLAY=:99' - setup-python: false # Use the system Python packages. - app-user-data-path: $HOME/.local/share/testbed - - - backend: "windows" - platform: "windows" - runs-on: "windows-latest" - app-user-data-path: $HOME\AppData\Local\Tiberius Yak\Toga Testbed\Data - - - backend: "iOS" - platform: "iOS" - runs-on: "macos-14" - briefcase-run-args: ' -d "iPhone SE (3rd generation)"' - app-user-data-path: $(xcrun simctl get_app_container booted org.beeware.toga.testbed data)/Documents - - - backend: "android" - platform: "android" - runs-on: "ubuntu-latest" - briefcase-run-args: " -d '{\"avd\":\"beePhone\",\"skin\":\"pixel_3a\"}' --Xemulator=-no-window --Xemulator=-no-snapshot --Xemulator=-no-audio --Xemulator=-no-boot-anim --shutdown-on-exit" - pre-command: | - # check if virtualization is supported... - sudo apt install -qq --no-install-recommends cpu-checker coreutils && echo "CPUs=$(nproc --all)" && kvm-ok - # allow access to KVM to run the emulator - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ - | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm + - pre-command: "" + briefcase-run-prefix: "" + briefcase-run-args: "" + setup-python: true + + - backend: "macOS-x86_64" + platform: "macOS" + runs-on: "macos-12" + app-user-data-path: "$HOME/Library/Application Support/org.beeware.toga.testbed" + + - backend: "macOS-arm64" + platform: "macOS" + runs-on: "macos-14" + app-user-data-path: "$HOME/Library/Application Support/org.beeware.toga.testbed" + + # We use a fixed Ubuntu version rather than `-latest` because at some point, + # `-latest` will be updated, but it will be a soft changeover, which would cause + # the system Python version to become inconsistent from run to run. + - backend: "linux" + platform: "linux" + runs-on: "ubuntu-22.04" + # The package list should be the same as in tutorial-0.rst, and the BeeWare + # tutorial, plus blackbox to provide a window manager. We need a window + # manager that is reasonably lightweight, honors full screen mode, and + # treats the window position as the top-left corner of the *window*, not the + # top-left corner of the window *content*. The default GNOME window managers of + # most distros meet these requirements, but they're heavyweight; flwm doesn't + # work either. Blackbox is the lightest WM we've found that works. + pre-command: | + sudo apt update -y + sudo apt install -y --no-install-recommends \ + blackbox pkg-config python3-dev libgirepository1.0-dev libcairo2-dev gir1.2-webkit2-4.0 + + # Start Virtual X server + echo "Start X server..." + Xvfb :99 -screen 0 2048x1536x24 & + sleep 1 + + # Start Window manager + echo "Start window manager..." + DISPLAY=:99 blackbox & + sleep 1 + briefcase-run-prefix: 'DISPLAY=:99' + setup-python: false # Use the system Python packages + app-user-data-path: "$HOME/.local/share/testbed" + + - backend: "windows" + platform: "windows" + runs-on: "windows-latest" + app-user-data-path: '$HOME\AppData\Local\Tiberius Yak\Toga Testbed\Data' + + - backend: "iOS" + platform: "iOS" + runs-on: "macos-14" + briefcase-run-args: "--device 'iPhone SE (3rd generation)'" + app-user-data-path: "$(xcrun simctl get_app_container booted org.beeware.toga.testbed data)/Documents" + + - backend: "android" + platform: "android" + runs-on: "ubuntu-latest" + briefcase-run-prefix: JAVA_HOME=${JAVA_HOME_17_X64} + briefcase-run-args: > + --device '{"avd":"beePhone","skin":"pixel_3a"}' + --Xemulator=-no-window + --Xemulator=-no-snapshot + --Xemulator=-no-audio + --Xemulator=-no-boot-anim + --shutdown-on-exit + pre-command: | + # allow access to KVM to run the emulator + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ + | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm steps: - - uses: actions/checkout@v4.1.1 + - name: Checkout + uses: actions/checkout@v4.1.1 with: fetch-depth: 0 @@ -249,7 +267,7 @@ jobs: # * It doesn't have an Android build of Pillow yet. python-version: "3.10" - - name: Install dependencies + - name: Install Dependencies run: | ${{ matrix.pre-command }} # Use the development version of Briefcase @@ -259,22 +277,24 @@ jobs: - name: Test App working-directory: testbed timeout-minutes: 15 - run: ${{ matrix.briefcase-run-prefix }} briefcase run ${{ matrix.platform }} --test ${{ matrix.briefcase-run-args }} + run: | + ${{ matrix.briefcase-run-prefix }} \ + briefcase run ${{ matrix.platform }} --test ${{ matrix.briefcase-run-args }} - - name: Upload logs + - name: Upload Logs uses: actions/upload-artifact@v4.3.1 if: failure() with: name: testbed-failure-logs-${{ matrix.backend }} path: testbed/logs/* - - name: Copy app generated user data + - name: Copy App Generated User Data if: failure() && matrix.backend != 'android' run: | mkdir -p testbed/app_data cp -r "${{ matrix.app-user-data-path }}" testbed/app_data/testbed-app_data-${{ matrix.backend }} - - name: Upload app data + - name: Upload App Data uses: actions/upload-artifact@v4.3.1 if: failure() && matrix.backend != 'android' with: diff --git a/changes/2440.misc.rst b/changes/2440.misc.rst new file mode 100644 index 0000000000..1a4f164a13 --- /dev/null +++ b/changes/2440.misc.rst @@ -0,0 +1 @@ +The tox environments for tests and coverage were revamped for easier and more effective use. diff --git a/core/pyproject.toml b/core/pyproject.toml index ad432852d5..079446d04b 100644 --- a/core/pyproject.toml +++ b/core/pyproject.toml @@ -63,7 +63,7 @@ dependencies = [ ] [project.optional-dependencies] -# Extras used by developers *of* briefcase are pinned to specific versions to +# Extras used by developers *of* Toga are pinned to specific versions to # ensure environment consistency. dev = [ "coverage[toml] == 7.4.3", @@ -104,6 +104,9 @@ Documentation = "https://toga.readthedocs.io/en/latest/" Tracker = "https://github.com/beeware/toga/issues" Source = "https://github.com/beeware/toga" +[project.entry-points."toga.image_formats"] +pil = "toga.plugins.image_formats.PILConverter" + [tool.setuptools_scm] root = ".." @@ -127,6 +130,3 @@ asyncio_mode = "auto" filterwarnings = [ "error", ] - -[project.entry-points."toga.image_formats"] -pil = "toga.plugins.image_formats.PILConverter" diff --git a/core/src/toga/app.py b/core/src/toga/app.py index 414e8cd5e7..9999591a27 100644 --- a/core/src/toga/app.py +++ b/core/src/toga/app.py @@ -148,7 +148,7 @@ class WidgetRegistry: # doesn't retain a strong reference to the widget, preventing memory cleanup. # # The lookup methods (__getitem__(), __iter__(), __len()__, keys(), items(), and - # values()) are all proxied to to underlying data store. Private methods exist for + # values()) are all proxied to underlying data store. Private methods exist for # internal use, but those methods shouldn't be used by end-users. def __init__(self, *args, **kwargs): @@ -660,7 +660,7 @@ def camera(self) -> Camera: return self._camera except AttributeError: # Instantiate the camera instance for this app on first access - # This will raise a exception if the platform doesn't implement + # This will raise an exception if the platform doesn't implement # the Camera API. self._camera = Camera(self) return self._camera @@ -851,7 +851,7 @@ def __init__( A document-based application is the same as a normal application, with the exception that there is no main window. Instead, each document managed by the - app will create and manage it's own window (or windows). + app will create and manage its own window (or windows). :param document_types: Initial :any:`document_types` mapping. """ @@ -909,7 +909,7 @@ def _open(self, path): :param path: The path to the document to be opened. :raises ValueError: If the document is of a type that can't be opened. Backends can - suppress this exception if necessary to presere platform-native behavior. + suppress this exception if necessary to preserve platform-native behavior. """ try: DocType = self.document_types[path.suffix[1:]] diff --git a/core/src/toga/command.py b/core/src/toga/command.py index 17eb79cf00..5603092e09 100644 --- a/core/src/toga/command.py +++ b/core/src/toga/command.py @@ -22,7 +22,7 @@ def __init__( order: int = 0, ): """ - An collection of commands to display together. + A collection of commands to display together. :param text: A label for the group. :param parent: The parent of this group; use ``None`` to make a root group. diff --git a/core/src/toga/sources/accessors.py b/core/src/toga/sources/accessors.py index 3e1cf4e83f..c080ec8b95 100644 --- a/core/src/toga/sources/accessors.py +++ b/core/src/toga/sources/accessors.py @@ -54,7 +54,7 @@ def build_accessors( :param headings: The list of headings. :param accessors: The list of accessor overrides. Can be specified as: - * A list the same length as headings. Each entry in the list is a a string that + * A list the same length as headings. Each entry in the list is a string that is the override name for the accessor, or :any:`None` if the default accessor for the heading at that index should be used. * A dictionary mapping heading names to accessor names. If a heading name isn't diff --git a/core/src/toga/sources/base.py b/core/src/toga/sources/base.py index 52d195be83..06e9d539c0 100644 --- a/core/src/toga/sources/base.py +++ b/core/src/toga/sources/base.py @@ -29,11 +29,7 @@ def remove(self, index: int, item): """ def clear(self): - """All items have been removed from the data source. - - :param index: The 0-index position in the data. - :param item: The data object that was added. - """ + """All items have been removed from the data source.""" class Source: diff --git a/core/src/toga/sources/tree_source.py b/core/src/toga/sources/tree_source.py index ad488408e7..8b4a5268f5 100644 --- a/core/src/toga/sources/tree_source.py +++ b/core/src/toga/sources/tree_source.py @@ -54,7 +54,7 @@ def __delitem__(self, index: int): child = self._children[index] del self._children[index] - # Child isn't part of this source, or a child of this node any more. + # Child isn't part of this source, or a child of this node anymore. child._parent = None child._source = None diff --git a/core/src/toga/style/pack.py b/core/src/toga/style/pack.py index ac9787feeb..455eb2e70f 100644 --- a/core/src/toga/style/pack.py +++ b/core/src/toga/style/pack.py @@ -85,7 +85,7 @@ def _debug(self, *args): # pragma: no cover @property def _hidden(self): - "Does this style declaration define a object that should be hidden" + """Does this style declaration define an object that should be hidden""" return self.visibility == HIDDEN def apply(self, prop, value): diff --git a/core/src/toga/widgets/base.py b/core/src/toga/widgets/base.py index 4233ac1607..c3aec39de6 100644 --- a/core/src/toga/widgets/base.py +++ b/core/src/toga/widgets/base.py @@ -63,7 +63,7 @@ def tab_index(self) -> int | None: .. note:: This is a beta feature. The ``tab_index`` API may change in - future. + the future. """ return self._impl.get_tab_index() diff --git a/core/src/toga/widgets/canvas.py b/core/src/toga/widgets/canvas.py index 728999497b..bebf336f90 100644 --- a/core/src/toga/widgets/canvas.py +++ b/core/src/toga/widgets/canvas.py @@ -176,7 +176,7 @@ def line_dash(self, value: list[float] | None): # 2023-07 Backwards incompatibility ########################################################################### - # `context.stroke()` used to be a context managger, but is now a primitive. + # `context.stroke()` used to be a context manager, but is now a primitive. # If you try to use the Stroke drawing object as a context, raise an exception. def __enter__(self): raise RuntimeError("Context.stroke() has been renamed Context.Stroke().") diff --git a/core/src/toga/widgets/multilinetextinput.py b/core/src/toga/widgets/multilinetextinput.py index 200edbec84..d4db065abf 100644 --- a/core/src/toga/widgets/multilinetextinput.py +++ b/core/src/toga/widgets/multilinetextinput.py @@ -24,7 +24,7 @@ def __init__( :param readonly: Can the value of the widget be modified by the user? :param placeholder: The content to display as a placeholder when there is no user content to display. - :param on_change: A handler that will be invoked when the the value of + :param on_change: A handler that will be invoked when the value of the widget changes. """ diff --git a/core/src/toga/widgets/numberinput.py b/core/src/toga/widgets/numberinput.py index ba71fcd3aa..83690fd36b 100644 --- a/core/src/toga/widgets/numberinput.py +++ b/core/src/toga/widgets/numberinput.py @@ -88,7 +88,7 @@ def __init__( equal to this maximum. :param value: The initial value for the widget. :param readonly: Can the value of the widget be modified by the user? - :param on_change: A handler that will be invoked when the the value of the + :param on_change: A handler that will be invoked when the value of the widget changes. :param min_value: **DEPRECATED**; alias of ``min``. :param max_value: **DEPRECATED**; alias of ``max``. diff --git a/core/src/toga/widgets/optioncontainer.py b/core/src/toga/widgets/optioncontainer.py index edd2117d71..6923d3ef40 100644 --- a/core/src/toga/widgets/optioncontainer.py +++ b/core/src/toga/widgets/optioncontainer.py @@ -86,7 +86,7 @@ def interface(self) -> OptionContainer: @property def enabled(self) -> bool: - "Is the panel of content available for selection?" + """Is the panel of content available for selection?""" try: return self._enabled except AttributeError: @@ -108,7 +108,7 @@ def enabled(self, value): @property def text(self) -> str: - "The label for the tab of content." + """The label for the tab of content.""" try: return self._text except AttributeError: @@ -441,7 +441,7 @@ def enabled(self, value): pass def focus(self): - "No-op; OptionContainer cannot accept input focus" + """No-op; OptionContainer cannot accept input focus""" pass @property diff --git a/core/src/toga/widgets/table.py b/core/src/toga/widgets/table.py index 0f8f90d600..c63de105c6 100644 --- a/core/src/toga/widgets/table.py +++ b/core/src/toga/widgets/table.py @@ -292,7 +292,7 @@ def accessors(self) -> list[str]: @property def missing_value(self) -> str: - """The value that will be used when a data row doesn't provide an value for an + """The value that will be used when a data row doesn't provide a value for an attribute. """ return self._missing_value diff --git a/core/src/toga/widgets/textinput.py b/core/src/toga/widgets/textinput.py index d36416bcad..74a64bebcb 100644 --- a/core/src/toga/widgets/textinput.py +++ b/core/src/toga/widgets/textinput.py @@ -29,7 +29,7 @@ def __init__( :param readonly: Can the value of the widget be modified by the user? :param placeholder: The content to display as a placeholder when there is no user content to display. - :param on_change: A handler that will be invoked when the the value of + :param on_change: A handler that will be invoked when the value of the widget changes. :param on_confirm: A handler that will be invoked when the user accepts the value of the input (usually by pressing Return on the keyboard). @@ -119,7 +119,7 @@ def value(self, value: str): @property def is_valid(self) -> bool: - "Does the value of the widget currently pass all validators without error?" + """Does the value of the widget currently pass all validators without error?""" return self._impl.is_valid() @property diff --git a/core/src/toga/widgets/tree.py b/core/src/toga/widgets/tree.py index a61af6b1cb..55608225b6 100644 --- a/core/src/toga/widgets/tree.py +++ b/core/src/toga/widgets/tree.py @@ -124,7 +124,7 @@ def data(self) -> TreeSource: * A value of None is turned into an empty TreeSource. - * Otherwise, the value must be an dictionary or an iterable, which is copied + * Otherwise, the value must be a dictionary or an iterable, which is copied into a new TreeSource as shown :ref:`here `. """ return self._data @@ -272,7 +272,7 @@ def accessors(self) -> list[str]: @property def missing_value(self) -> str: - """The value that will be used when a data row doesn't provide an value for an + """The value that will be used when a data row doesn't provide a value for an attribute. """ return self._missing_value diff --git a/core/src/toga/window.py b/core/src/toga/window.py index 04c7e6b2b6..e107307ce4 100644 --- a/core/src/toga/window.py +++ b/core/src/toga/window.py @@ -418,7 +418,7 @@ def hide(self) -> None: @property def visible(self) -> bool: - "Is the window visible?" + """Is the window visible?""" return self._impl.get_visible() @visible.setter @@ -575,7 +575,7 @@ def error_dialog( ) -> Dialog: """Ask the user to acknowledge an error state. - Presents as an error dialog with a "OK" button to close the dialog. + Presents as an error dialog with an "OK" button to close the dialog. **This is an asynchronous method**. If you invoke this method in synchronous context, it will show the dialog, but will return *immediately*. The object diff --git a/core/tests/app/test_app.py b/core/tests/app/test_app.py index 45fbbf68b6..6e73e4b2fd 100644 --- a/core/tests/app/test_app.py +++ b/core/tests/app/test_app.py @@ -209,7 +209,7 @@ def test_create( expected_app_id, expected_app_name, ): - """A simple app can be created""" + """A simple app can be created.""" # Monkeypatch the metadata retrieval function if metadata: metadata_mock = Mock(return_value=metadata) @@ -255,13 +255,13 @@ def test_create( ], ) def test_bad_app_creation(kwargs, exc_type, message): - """Errors are raised""" + """Errors are raised.""" with pytest.raises(exc_type, match=message): toga.App(**kwargs) def test_app_metadata(monkeypatch, event_loop): - """An app can load metadata from the .dist-info file""" + """An app can load metadata from the .dist-info file.""" monkeypatch.setattr( importlib.metadata, "metadata", @@ -292,7 +292,7 @@ def test_app_metadata(monkeypatch, event_loop): def test_explicit_app_metadata(monkeypatch, event_loop): - """App metadata can be provided explicitly, overriding module-level metadata""" + """App metadata can be provided explicitly, overriding module-level metadata.""" monkeypatch.setattr( importlib.metadata, "metadata", @@ -331,7 +331,7 @@ def test_explicit_app_metadata(monkeypatch, event_loop): @pytest.mark.parametrize("construct", [True, False]) def test_icon_construction(construct, event_loop): - """The app icon can be set during construction""" + """The app icon can be set during construction.""" if construct: icon = toga.Icon("path/to/icon") else: @@ -348,7 +348,7 @@ def test_icon_construction(construct, event_loop): @pytest.mark.parametrize("construct", [True, False]) def test_icon(app, construct): - """The app icon can be changed""" + """The app icon can be changed.""" if construct: icon = toga.Icon("path/to/icon") else: @@ -382,7 +382,7 @@ def test_current_window(app): def test_no_current_window(app): - """If there's no current window, current_window reflects this""" + """If there's no current window, current_window reflects this.""" # If all the windows are deleted, and there's no main window (e.g., if it's a document app) # there might be no current window. app._main_window = None @@ -426,7 +426,7 @@ def test_full_screen(event_loop): def test_set_empty_full_screen_window_list(event_loop): - """Setting the full screen window list to [] is an explicit exit""" + """Setting the full screen window list to [] is an explicit exit.""" app = toga.App(formal_name="Test App", app_id="org.example.test") window1 = toga.Window() window2 = toga.Window() @@ -445,7 +445,7 @@ def test_set_empty_full_screen_window_list(event_loop): def test_show_hide_cursor(app): - """The app cursor can be shown and hidden""" + """The app cursor can be shown and hidden.""" app.hide_cursor() assert_action_performed(app, "hide_cursor") @@ -454,7 +454,7 @@ def test_show_hide_cursor(app): def test_startup_method(event_loop): - """If an app provides a startup method, it will be invoked during startup""" + """If an app provides a startup method, it will be invoked during startup.""" startup = Mock() app = toga.App( formal_name="Test App", @@ -466,7 +466,7 @@ def test_startup_method(event_loop): def test_startup_subclass(event_loop): - """App can be subclassed""" + """App can be subclassed.""" class SubclassedApp(toga.App): def startup(self): @@ -479,7 +479,7 @@ def startup(self): def test_startup_subclass_no_main_window(event_loop): - """If a subclassed app doesn't define a main window, an error is raised""" + """If a subclassed app doesn't define a main window, an error is raised.""" class SubclassedApp(toga.App): def startup(self): @@ -490,13 +490,13 @@ def startup(self): def test_about(app): - """The about dialog for the app can be shown""" + """The about dialog for the app can be shown.""" app.about() assert_action_performed(app, "show_about_dialog") def test_visit_homepage(monkeypatch, event_loop): - """The app's homepage can be opened""" + """The app's homepage can be opened.""" app = toga.App( formal_name="Test App", app_id="org.example.test", @@ -512,7 +512,7 @@ def test_visit_homepage(monkeypatch, event_loop): def test_no_homepage(monkeypatch, app): - """If the app doesn't have a home page, visit_homepage is a no-op""" + """If the app doesn't have a home page, visit_homepage is a no-op.""" open_webbrowser = Mock() monkeypatch.setattr(webbrowser, "open", open_webbrowser) @@ -529,7 +529,7 @@ def test_beep(app): def test_exit_direct(app): - """An app can be exited directly""" + """An app can be exited directly.""" on_exit_handler = Mock(return_value=True) app.on_exit = on_exit_handler @@ -542,16 +542,16 @@ def test_exit_direct(app): def test_exit_no_handler(app): - """A app without a exit handler can be exited""" + """An app without an exit handler can be exited.""" # Exit the app app._impl.simulate_exit() - # Window has been exitd, and is no longer in the app's list of windows. + # Window has been exited, and is no longer in the app's list of windows. assert_action_performed(app, "exit") -def test_exit_sucessful_handler(app): - """An app with a successful exit handler can be exited""" +def test_exit_successful_handler(app): + """An app with a successful exit handler can be exited.""" on_exit_handler = Mock(return_value=True) app.on_exit = on_exit_handler @@ -564,7 +564,7 @@ def test_exit_sucessful_handler(app): def test_exit_rejected_handler(app): - """An app can have a exit handler that rejects the exit""" + """An app can have a exit handler that rejects the exit.""" on_exit_handler = Mock(return_value=False) app.on_exit = on_exit_handler @@ -577,13 +577,13 @@ def test_exit_rejected_handler(app): def test_loop(app, event_loop): - """The main thread's event loop can be accessed""" + """The main thread's event loop can be accessed.""" assert isinstance(app.loop, asyncio.AbstractEventLoop) assert app.loop is event_loop def test_background_task(app): - """A background task can be queued""" + """A background task can be queued.""" canary = Mock() async def background(app, **kwargs): @@ -603,8 +603,7 @@ async def waiter(): def test_deprecated_id(event_loop): """The deprecated `id` constructor argument is ignored, and the property of the same - name is redirected to `app_id` - """ + name is redirected to `app_id`""" id_warning = r"App.id is deprecated.* Use app_id instead" with pytest.warns(DeprecationWarning, match=id_warning): app = toga.App("Test App", "org.example.test", id="test_app_id") diff --git a/core/tests/app/test_documentapp.py b/core/tests/app/test_documentapp.py index f4c53af086..fe596dba10 100644 --- a/core/tests/app/test_documentapp.py +++ b/core/tests/app/test_documentapp.py @@ -67,7 +67,8 @@ def test_create_with_cmdline(monkeypatch): def test_create_with_unknown_document_type(monkeypatch): - """If the document specified at the command line is an unknown type, an exception is raised""" + """If the document specified at the command line is an unknown type, an exception is + raised.""" monkeypatch.setattr(sys, "argv", ["app-exe", "/path/to/filename.unknown"]) with pytest.raises( @@ -91,7 +92,7 @@ def test_create_no_document_type(): def test_close_single_document_app(): - """An app in single document mode closes the app when the window is closed""" + """An app in single document mode closes the app when the window is closed.""" # Monkeypatch the dummy impl to use single document mode DummyDocument.SINGLE_DOCUMENT_APP = True @@ -111,7 +112,7 @@ async def _do_close(): def test_close_multiple_document_app(): - """An app in multiple document mode doesn't close when the window is closed""" + """An app in multiple document mode doesn't close when the window is closed.""" # Monkeypatch the dummy impl to use single document mode DummyDocument.SINGLE_DOCUMENT_APP = False diff --git a/core/tests/app/test_mainwindow.py b/core/tests/app/test_mainwindow.py index 69749e9014..18381b6fdd 100644 --- a/core/tests/app/test_mainwindow.py +++ b/core/tests/app/test_mainwindow.py @@ -7,7 +7,7 @@ def test_create(app): - "A MainWindow can be created with minimal arguments" + """A MainWindow can be created with minimal arguments.""" window = toga.MainWindow() assert window.app == app @@ -31,7 +31,7 @@ def test_create(app): def test_no_close(): - "An on_close handler cannot be set on MainWindow" + """An on_close handler cannot be set on MainWindow.""" window = toga.MainWindow() with pytest.raises( diff --git a/core/tests/app/test_screens.py b/core/tests/app/test_screens.py index 566891df09..3827682fe9 100644 --- a/core/tests/app/test_screens.py +++ b/core/tests/app/test_screens.py @@ -5,25 +5,25 @@ def test_name(app): - """The name of the screens can be retrieved""" + """The name of the screens can be retrieved.""" assert app.screens[0].name == "Primary Screen" assert app.screens[1].name == "Secondary Screen" def test_origin(app): - """The origin of the screens can be retrieved""" + """The origin of the screens can be retrieved.""" assert app.screens[0].origin == (0, 0) assert app.screens[1].origin == (-1366, -768) def test_size(app): - """The size of the screens can be retrieved""" + """The size of the screens can be retrieved.""" assert app.screens[0].size == (1920, 1080) assert app.screens[1].size == (1366, 768) def test_as_image(app): - """A screen can be captured as an image""" + """A screen can be captured as an image.""" for screen in app.screens: # `as_image()` should default to `toga.images.Image` as format. toga_image_screenshot = screen.as_image() @@ -35,7 +35,7 @@ def test_as_image(app): def test_as_image_format(app): - """A screen can be captured as an image in a non-default format""" + """A screen can be captured as an image in a non-default format.""" for screen in app.screens: # Capture image in PIL format pil_screenshot = screen.as_image(format=PILImage) diff --git a/core/tests/app/test_widget_registry.py b/core/tests/app/test_widget_registry.py index 07559b501d..a5a25d96f8 100644 --- a/core/tests/app/test_widget_registry.py +++ b/core/tests/app/test_widget_registry.py @@ -26,7 +26,7 @@ def test_empty_registry(widget_registry): def test_add_widget(widget_registry): - "Widgets can be added to the registry" + """Widgets can be added to the registry.""" # Add a widget to the registry widget1 = ExampleWidget(id="widget-1") widget_registry._add(widget1) @@ -46,7 +46,7 @@ def test_add_widget(widget_registry): def test_update_widgets(widget_registry): - "The registry can be bulk updated" + """The registry can be bulk updated.""" # Add a widget to the registry widget1 = ExampleWidget(id="widget-1") widget_registry._add(widget1) @@ -64,7 +64,7 @@ def test_update_widgets(widget_registry): def test_remove_widget(widget_registry): - "A widget can be removed from the repository" + """A widget can be removed from the repository.""" "Widgets can be added to the registry" # Add a widget to the registry widget1 = ExampleWidget(id="widget-1") @@ -80,7 +80,7 @@ def test_remove_widget(widget_registry): def test_add_same_widget_twice(widget_registry): - "A widget cannot be added to the same registry twice" + """A widget cannot be added to the same registry twice.""" # Add a widget to the registry widget1 = ExampleWidget(id="widget-1") widget_registry._add(widget1) @@ -100,7 +100,7 @@ def test_add_same_widget_twice(widget_registry): def test_add_duplicate_id(widget_registry): - "A widget cannot be added to the same registry twice" + """A widget cannot be added to the same registry twice.""" # Add a widget to the registry widget1 = ExampleWidget(id="widget-1") widget_registry._add(widget1) @@ -122,7 +122,7 @@ def test_add_duplicate_id(widget_registry): def test_setitem(widget_registry): - "Widgets cannot be directly assigned to the registry" + """Widgets cannot be directly assigned to the registry.""" widget1 = ExampleWidget(id="widget-1") with pytest.raises( diff --git a/core/tests/app/test_windowset.py b/core/tests/app/test_windowset.py index 1ab0408007..0f7d69add0 100644 --- a/core/tests/app/test_windowset.py +++ b/core/tests/app/test_windowset.py @@ -23,7 +23,7 @@ def test_create(app): def test_add_discard(app, window1, window2): - """An item can be added to a windowset""" + """An item can be added to a windowset.""" # The windowset has 3 windows - the main window, plus 2 extras assert len(app.windows) == 3 @@ -61,7 +61,7 @@ def test_add_discard(app, window1, window2): def test_iadd_isub(app, window1, window2): - """The deprecated += and -= operators are no-ops""" + """The deprecated += and -= operators are no-ops.""" # The windowset has 3 windows - the main window, plus 2 extras assert window2 in app.windows assert len(app.windows) == 3 diff --git a/core/tests/command/test_command.py b/core/tests/command/test_command.py index 5be293010c..708f37f06f 100644 --- a/core/tests/command/test_command.py +++ b/core/tests/command/test_command.py @@ -22,14 +22,14 @@ def assert_order(*items): def test_separator(parent_group_1): - """A separator can be created""" + """A separator can be created.""" separator = Separator(group=parent_group_1) assert repr(separator) == "" def test_separator_eq(parent_group_1, parent_group_2): - """Separator objects can be compared for equality""" + """Separator objects can be compared for equality.""" separator_1a = Separator(parent_group_1) separator_1b = Separator(parent_group_1) @@ -46,7 +46,7 @@ def test_separator_eq(parent_group_1, parent_group_2): def test_create(): - """A command can be created with defaults""" + """A command can be created with defaults.""" cmd = toga.Command(None, "Test command") assert cmd.text == "Test command" @@ -64,7 +64,7 @@ def test_create(): def test_change_action(): - """A command's action can be changed to another handler""" + """A command's action can be changed to another handler.""" action1 = Mock() cmd = toga.Command(action1, "Test command") @@ -89,7 +89,7 @@ def test_change_action(): def test_create_explicit(app): - """A command can be created with explicit arguments""" + """A command can be created with explicit arguments.""" grp = toga.Group("Test group", order=10) handler = Mock() @@ -120,7 +120,7 @@ def test_create_explicit(app): @pytest.mark.parametrize("construct", [True, False]) def test_icon_construction(app, construct): - """The command icon can be set during construction""" + """The command icon can be set during construction.""" if construct: icon = toga.Icon("path/to/icon") else: @@ -133,7 +133,7 @@ def test_icon_construction(app, construct): @pytest.mark.parametrize("construct", [True, False]) def test_icon(app, construct): - """The command icon can be changed""" + """The command icon can be changed.""" if construct: icon = toga.Icon("path/to/icon") else: @@ -184,7 +184,7 @@ def test_enable(action, enabled, initial_state): def test_order_by_text(): - """Commands are ordered by text when group, section and order match""" + """Commands are ordered by text when group, section and order match.""" assert_order( toga.Command(None, "A"), toga.Command(None, "B"), @@ -192,7 +192,7 @@ def test_order_by_text(): def test_order_by_number(): - """Commands are ordered by number when group and section match""" + """Commands are ordered by number when group and section match.""" assert_order( toga.Command(None, "B", order=1), toga.Command(None, "A", order=2), @@ -200,7 +200,7 @@ def test_order_by_number(): def test_order_by_section(parent_group_1): - """Section ordering takes priority over order and text""" + """Section ordering takes priority over order and text.""" assert_order( toga.Command(None, "B", group=parent_group_1, section=1, order=2), toga.Command(None, "A", group=parent_group_1, section=2, order=1), @@ -208,7 +208,7 @@ def test_order_by_section(parent_group_1): def test_order_by_groups(parent_group_1, parent_group_2, child_group_1, child_group_2): - """Commands are ordered by group over""" + """Commands are ordered by group over.""" command_z = toga.Command(None, "Z", group=parent_group_1, order=1) command_y = toga.Command(None, "Y", group=child_group_1, order=1) diff --git a/core/tests/command/test_commandset.py b/core/tests/command/test_commandset.py index 35110f1ca1..cf76b21108 100644 --- a/core/tests/command/test_commandset.py +++ b/core/tests/command/test_commandset.py @@ -8,7 +8,7 @@ def test_create(): - """A CommandSet can be created with defaults""" + """A CommandSet can be created with defaults.""" cs = CommandSet() assert list(cs) == [] @@ -16,7 +16,7 @@ def test_create(): def test_create_with_values(): - """A CommandSet can be created with values""" + """A CommandSet can be created with values.""" change_handler = Mock() cs = CommandSet(on_change=change_handler) @@ -26,7 +26,7 @@ def test_create_with_values(): @pytest.mark.parametrize("change_handler", [(None), (Mock())]) def test_add_clear(app, change_handler): - """Commands can be added and removed from a commandset""" + """Commands can be added and removed from a commandset.""" # Put some commands into the app cmd_a = toga.Command(None, text="App command a") cmd_b = toga.Command(None, text="App command b", order=10) @@ -67,7 +67,7 @@ def test_add_clear(app, change_handler): @pytest.mark.parametrize("change_handler", [(None), (Mock())]) def test_add_clear_with_app(app, change_handler): - """Commands can be added and removed from a commandset that is linked to an app""" + """Commands can be added and removed from a commandset that is linked to an app.""" # Put some commands into the app cmd_a = toga.Command(None, text="App command a") cmd_b = toga.Command(None, text="App command b", order=10) @@ -130,7 +130,7 @@ def test_ordering( child_group_4, child_group_5, ): - """Ordering of groups, separators and commands is preserved""" + """Ordering of groups, separators and commands is preserved.""" # Menu structure is: # - parent group 1 diff --git a/core/tests/command/test_group.py b/core/tests/command/test_group.py index e4519aa8ae..0aad412211 100644 --- a/core/tests/command/test_group.py +++ b/core/tests/command/test_group.py @@ -6,7 +6,7 @@ def test_create(): - """A group can be created with defaults""" + """A group can be created with defaults.""" grp = toga.Group("Group name") assert grp.text == "Group name" assert grp.order == 0 @@ -15,7 +15,7 @@ def test_create(): def test_create_with_params(): - """A fully specified group can be created""" + """A fully specified group can be created.""" parent = toga.Group("Parent name") grp = toga.Group("Group name", order=2, section=3, parent=parent) @@ -92,7 +92,7 @@ def test_group_eq(): def test_parent_creation(): - """Parents can be assigned at creation""" + """Parents can be assigned at creation.""" group_a = toga.Group("A") group_b = toga.Group("B", parent=group_a) group_c = toga.Group("C", parent=group_b) @@ -131,7 +131,7 @@ def test_parent_creation(): def test_parent_assignment(): - """Parents can be assigned at runtime""" + """Parents can be assigned at runtime.""" # Eventually, we'll end up with A->B->C, D. group_a = toga.Group("A") group_b = toga.Group("B") @@ -218,7 +218,7 @@ def test_parent_assignment(): def test_parent_loops(): - """Parent loops are prevented can be assigned at runtime""" + """Parent loops are prevented can be assigned at runtime.""" group_a = toga.Group("A") group_b = toga.Group("B", parent=group_a) group_c = toga.Group("C", parent=group_b) @@ -244,12 +244,12 @@ def test_parent_loops(): def test_order_by_text(): - """Groups are ordered by text if order and section are equivalent""" + """Groups are ordered by text if order and section are equivalent.""" assert_order(toga.Group("A"), toga.Group("B")) def test_order_by_number(): - """Groups are ordered by number""" + """Groups are ordered by number.""" assert_order(toga.Group("B", order=1), toga.Group("A", order=2)) diff --git a/core/tests/hardware/test_camera.py b/core/tests/hardware/test_camera.py index a7731023d1..238bdd4ab4 100644 --- a/core/tests/hardware/test_camera.py +++ b/core/tests/hardware/test_camera.py @@ -21,7 +21,8 @@ def photo(app): def test_no_camera(monkeypatch, app): - """If there's no camera, and no factory implementation, accessing camera raises an exception""" + """If there's no camera, and no factory implementation, accessing camera raises an + exception.""" try: monkeypatch.delattr(app, "_camera") except AttributeError: @@ -42,7 +43,7 @@ def test_no_camera(monkeypatch, app): ], ) def test_request_permission(app, initial, should_request, has_permission): - """An app can request permission to use the camera""" + """An app can request permission to use the camera.""" # The camera instance round-trips the app instance assert app.camera.app == app @@ -63,7 +64,7 @@ def test_request_permission(app, initial, should_request, has_permission): def test_request_permission_sync(app): - """An app can synchronously request permission to use the camera""" + """An app can synchronously request permission to use the camera.""" # Set initial permission app.camera._impl._has_permission = -1 @@ -79,7 +80,7 @@ def test_request_permission_sync(app): def test_device_properties(app): - """Device properties can be checked""" + """Device properties can be checked.""" assert [ { @@ -130,7 +131,8 @@ def test_device_properties(app): [FlashMode.AUTO, FlashMode.ON, FlashMode.OFF], ) def test_take_photo_with_permission(app, device, flash, photo): - """If permission has not been previously requested, it is requested before a photo is taken.""" + """If permission has not been previously requested, it is requested before a photo + is taken.""" # Set permission to potentially allowed app.camera._impl._has_permission = -1 @@ -177,7 +179,7 @@ def test_take_photo_prior_permission(app, photo): def test_take_photo_no_permission(app, photo): - """If permission has been denied, an exception is raised""" + """If permission has been denied, an exception is raised.""" # Deny permission app.camera._impl._has_permission = 0 diff --git a/core/tests/sources/test_accessors.py b/core/tests/sources/test_accessors.py index 1d8b25a80e..5501450a6c 100644 --- a/core/tests/sources/test_accessors.py +++ b/core/tests/sources/test_accessors.py @@ -26,7 +26,7 @@ ], ) def test_to_accessor(heading, accessor): - "Headings can be converted into accessors" + """Headings can be converted into accessors.""" assert to_accessor(heading) == accessor @@ -70,7 +70,7 @@ def test_to_accessor_failures(heading): ], ) def test_build_accessors(headings, overrides, accessors): - "Accessors can be constructed from headings with overrides" + """Accessors can be constructed from headings with overrides.""" assert build_accessors(headings, overrides) == accessors @@ -90,6 +90,6 @@ def test_build_accessors(headings, overrides, accessors): ], ) def test_build_accessor_failure(headings, overrides, error): - "If an accessor list can't be build, an error is raised" + """If an accessor list can't be built, an error is raised.""" with pytest.raises(ValueError, match=error): build_accessors(headings, overrides) diff --git a/core/tests/sources/test_list_source.py b/core/tests/sources/test_list_source.py index f348fb7530..84bed7ecd4 100644 --- a/core/tests/sources/test_list_source.py +++ b/core/tests/sources/test_list_source.py @@ -26,7 +26,7 @@ def source(): ], ) def test_invalid_accessors(value): - "Accessors for a list source must be a list of attribute names" + """Accessors for a list source must be a list of attribute names.""" with pytest.raises( ValueError, match=r"accessors should be a list of attribute names", @@ -35,7 +35,7 @@ def test_invalid_accessors(value): def test_accessors_required(): - "A list source must specify *some* accessors" + """A list source must specify *some* accessors.""" with pytest.raises( ValueError, match=r"ListSource must be provided a list of accessors", @@ -44,7 +44,7 @@ def test_accessors_required(): def test_accessors_copied(): - "A list source must specify *some* accessors" + """A list source must specify *some* accessors.""" accessors = ["foo", "bar"] source = ListSource(accessors) @@ -56,14 +56,14 @@ def test_accessors_copied(): def test_empty_source(): - "A list source can be constructed with no data" + """A list source can be constructed with no data.""" source = ListSource(accessors=["foo", "bar"]) assert len(source) == 0 def test_tuples(): - "A ListSource can be instantiated with tuples" + """A ListSource can be instantiated with tuples.""" source = ListSource( data=[ ("first", 111), @@ -96,7 +96,7 @@ def test_tuples(): def test_list(): - "A ListSource can be instantiated with lists" + """A ListSource can be instantiated with lists.""" source = ListSource( data=[ ["first", 111], @@ -129,7 +129,7 @@ def test_list(): def test_dict(): - "A ListSource can be instantiated with dictionaries" + """A ListSource can be instantiated with dictionaries.""" source = ListSource( data=[ {"val1": "first", "val2": 111}, @@ -162,7 +162,7 @@ def test_dict(): def test_flat_list(): - "A list source can be created from a flat list of objects" + """A list source can be created from a flat list of objects.""" class MyObject: def __init__(self, info): @@ -187,7 +187,7 @@ def __str__(self): def test_flat_list_numbers(): - "A list source can be created from a flat list of numbers" + """A list source can be created from a flat list of numbers.""" data = [ 100, @@ -205,7 +205,7 @@ def test_flat_list_numbers(): def test_flat_list_strings(): - "A list source can be created from a flat list of numbers" + """A list source can be created from a flat list of numbers.""" data = [ "xxx", @@ -223,7 +223,7 @@ def test_flat_list_strings(): def test_iter(source): - "A list source can be iterated over" + """A list source can be iterated over.""" result = 0 for row in source: result += row.val2 @@ -232,7 +232,7 @@ def test_iter(source): def test_clear(source): - "A list source can be cleared" + """A list source can be cleared.""" assert len(source) == 3 @@ -250,7 +250,7 @@ def test_clear(source): def test_insert_kwarg(source): - "You can insert into a list source using kwargs" + """You can insert into a list source using kwargs.""" listener = Mock() source.add_listener(listener) @@ -267,7 +267,7 @@ def test_insert_kwarg(source): def test_insert_positional(source): - "You can insert into a list source using positional args" + """You can insert into a list source using positional args.""" listener = Mock() source.add_listener(listener) @@ -284,7 +284,7 @@ def test_insert_positional(source): def test_append_dict(source): - "You can append onto a list source using a dictionary" + """You can append onto a list source using a dictionary.""" listener = Mock() source.add_listener(listener) @@ -301,7 +301,7 @@ def test_append_dict(source): def test_append_positional(source): - "You can append onto a list source using positional args" + """You can append onto a list source using positional args.""" listener = Mock() source.add_listener(listener) @@ -318,7 +318,7 @@ def test_append_positional(source): def test_del(source): - "You can delete an item from a list source by index" + """You can delete an item from a list source by index.""" listener = Mock() source.add_listener(listener) @@ -337,7 +337,7 @@ def test_del(source): def test_remove(source): - "You can remove an item from a list source" + """You can remove an item from a list source.""" listener = Mock() source.add_listener(listener) @@ -356,7 +356,7 @@ def test_remove(source): def test_index(source): - "You can get the index of any row within a list source" + """You can get the index of any row within a list source.""" for i, row in enumerate(source): assert i == source.index(row) @@ -382,7 +382,7 @@ def test_index(source): def test_find(source): - "You can find the index of any matching row within a list source" + """You can find the index of any matching row within a list source.""" # Duplicate row 1 of the data. source.append(dict(val1="second", val2=222)) diff --git a/core/tests/sources/test_node.py b/core/tests/sources/test_node.py index 2b11ba4d88..f2ed1928dd 100644 --- a/core/tests/sources/test_node.py +++ b/core/tests/sources/test_node.py @@ -7,7 +7,7 @@ def _create_node(source, parent, data, children=None): - "A very simplified _create_node for mock purposes" + """A very simplified _create_node for mock purposes.""" node = Node(**data) node._source = source node._parent = parent @@ -68,7 +68,7 @@ def node(leaf_node, child_a, child_b): def test_node_properties(node): - "An node with children can be created and modified" + """A node with children can be created and modified.""" assert node.val1 == "value 1" assert node.val2 == 42 assert node.can_have_children() @@ -80,7 +80,7 @@ def test_node_properties(node): def test_empty_node_properties(empty_node): - "An empty Node can be created" + """An empty Node can be created.""" assert empty_node.can_have_children() assert len(empty_node) == 0 assert ( @@ -90,7 +90,7 @@ def test_empty_node_properties(empty_node): def test_leaf_node_properties(leaf_node): - "A Leaf Node can be created" + """A Leaf Node can be created.""" assert leaf_node.val1 == "value 1" assert leaf_node.val2 == 42 assert not leaf_node.can_have_children() @@ -101,7 +101,7 @@ def test_leaf_node_properties(leaf_node): def test_modify_attributes(source, node): - """If node attributes are modified, a change notification is sent""" + """If node attributes are modified, a change notification is sent.""" node.val1 = "new value" assert node.val1 == "new value" source.notify.assert_called_once_with("change", item=node) @@ -139,7 +139,7 @@ def test_modify_attributes(source, node): def test_modify_attributes_no_source(node): - """Node attributes can be modified on a node with no source""" + """Node attributes can be modified on a node with no source.""" node.source = None node.val1 = "new value" @@ -169,7 +169,7 @@ def test_modify_attributes_no_source(node): def test_modify_children(source, node): - """Node children can be retrieved and modified""" + """Node children can be retrieved and modified.""" child = node[1] assert node[1].val1 == "value b" @@ -210,7 +210,7 @@ def test_modify_children(source, node): def test_modify_leaf_children(leaf_node): - """Attempts to modifying Leaf Node children raise an error""" + """Attempts to modifying Leaf Node children raise an error.""" with pytest.raises( ValueError, match=r" is a leaf node", @@ -231,12 +231,12 @@ def test_modify_leaf_children(leaf_node): def test_iterate(node): - """Node can be iterated""" + """Node can be iterated.""" assert "|".join(child.val1 for child in node) == "value a|value b" def test_iterate_leaf(leaf_node): - """Node can be iterated""" + """Node can be iterated.""" assert "|".join(child.val1 for child in leaf_node) == "" @@ -250,7 +250,7 @@ def test_iterate_leaf(leaf_node): ], ) def test_insert(source, node, index, actual_index): - """A child can be inserted into a node""" + """A child can be inserted into a node.""" new_child = node.insert(index, {"val1": "new"}) # Node has one more child. @@ -272,7 +272,7 @@ def test_insert(source, node, index, actual_index): def test_insert_with_children(source, node): - """A child with children can be inserted into a node""" + """A child with children can be inserted into a node.""" new_child = node.insert( 1, {"val1": "new"}, @@ -312,7 +312,7 @@ def test_insert_with_children(source, node): def test_insert_leaf(leaf_node, source): - """Inserting a child into a leaf makes the node not a leaf any more""" + """Inserting a child into a leaf makes the node not a leaf any more.""" new_child = leaf_node.insert(0, {"val1": "new"}) # Leaf node isn't a leaf any more @@ -332,7 +332,7 @@ def test_insert_leaf(leaf_node, source): def test_append(source, node): - """A child can be appended onto a node""" + """A child can be appended onto a node.""" new_child = node.append({"val1": "new"}) # Node has one more child. @@ -354,7 +354,7 @@ def test_append(source, node): def test_append_with_children(source, node): - """A child with children can be appended onto a node""" + """A child with children can be appended onto a node.""" new_child = node.append( {"val1": "new"}, children=[ @@ -388,7 +388,7 @@ def test_append_with_children(source, node): def test_append_leaf(leaf_node, source): - """Appending to a leaf makes the node not a leaf any more""" + """Appending to a leaf makes the node not a leaf any more.""" new_child = leaf_node.append({"val1": "new"}) # Leaf node isn't a leaf any more @@ -408,12 +408,12 @@ def test_append_leaf(leaf_node, source): def test_index(node, child_b): - """A child can be found it it's parent""" + """A child can be found it it's parent.""" assert node.index(child_b) == 1 def test_index_not_child(node): - """If a node isn't a child of this node, it can't be found by index""" + """If a node isn't a child of this node, it can't be found by index.""" other = Node(val1="other") with pytest.raises( @@ -424,7 +424,7 @@ def test_index_not_child(node): def test_index_leaf(leaf_node, child_b): - """A child cannot be found in a leaf node""" + """A child cannot be found in a leaf node.""" with pytest.raises( ValueError, match=r" is a leaf node", @@ -433,7 +433,7 @@ def test_index_leaf(leaf_node, child_b): def test_remove(source, node, child_b): - """A node can be removed from it's parent""" + """A node can be removed from it's parent.""" # Child is initially associated with the node assert child_b._parent == node assert child_b._source == node._source @@ -453,7 +453,7 @@ def test_remove(source, node, child_b): def test_remove_leaf(leaf_node, child_b): - """A child cannot be removed from a leaf node""" + """A child cannot be removed from a leaf node.""" with pytest.raises( ValueError, match=r" is a leaf node", @@ -462,7 +462,7 @@ def test_remove_leaf(leaf_node, child_b): def test_find(node, child_b): - """A node can be found by value in it's parent's list of children""" + """A node can be found by value in it's parent's list of children.""" # Append some additional children child_c = node.append({"val1": "value a", "val2": 333}) child_d = node.append({"val1": "value b", "val2": 444}) @@ -478,7 +478,7 @@ def test_find(node, child_b): def test_found_leaf(leaf_node): - """A child cannot be foundd from a leaf node""" + """A child cannot be found from a leaf node.""" with pytest.raises( ValueError, match=r" is a leaf node", diff --git a/core/tests/sources/test_row.py b/core/tests/sources/test_row.py index 3c77897d14..3bd58ea62e 100644 --- a/core/tests/sources/test_row.py +++ b/core/tests/sources/test_row.py @@ -4,7 +4,7 @@ def test_row(): - "A row can be created and modified" + """A row can be created and modified.""" source = Mock() row = Row(val1="value 1", val2=42) # Set a source. @@ -47,7 +47,7 @@ def test_row(): def test_row_without_source(): - "A row with no source can be created and modified" + """A row with no source can be created and modified.""" row = Row(val1="value 1", val2=42) # initial values are as expected @@ -62,7 +62,7 @@ def test_row_without_source(): del row.val1 assert not hasattr(row, "val1") - # Setting an attribute starting with with an underscore isn't a notifiable event + # Setting an attribute starting with an underscore isn't a notifiable event row._secret = "secret value" assert row._secret == "secret value" diff --git a/core/tests/sources/test_source.py b/core/tests/sources/test_source.py index 45d2042a82..00434976e5 100644 --- a/core/tests/sources/test_source.py +++ b/core/tests/sources/test_source.py @@ -53,7 +53,8 @@ def test_notify(): def test_missing_listener_method(): - """If a listener doesn't implement a notification method, the notification is ignored""" + """If a listener doesn't implement a notification method, the notification is + ignored.""" full_listener = Mock() partial_listener = object() source = Source() diff --git a/core/tests/sources/test_tree_source.py b/core/tests/sources/test_tree_source.py index 9b05ee9b73..4438130397 100644 --- a/core/tests/sources/test_tree_source.py +++ b/core/tests/sources/test_tree_source.py @@ -65,7 +65,7 @@ def source(listener): ], ) def test_invalid_accessors(value): - "Accessors for a list source must be a list of attribute names" + """Accessors for a list source must be a list of attribute names.""" with pytest.raises( ValueError, match=r"accessors should be a list of attribute names", @@ -74,7 +74,7 @@ def test_invalid_accessors(value): def test_accessors_required(): - "A list source must specify *some* accessors" + """A list source must specify *some* accessors.""" with pytest.raises( ValueError, match=r"TreeSource must be provided a list of accessors", @@ -83,7 +83,7 @@ def test_accessors_required(): def test_accessors_copied(): - "A list source must specify *some* accessors" + """A list source must specify *some* accessors.""" accessors = ["foo", "bar"] source = TreeSource(accessors) @@ -102,7 +102,7 @@ def test_accessors_copied(): ], ) def test_create_empty(data): - """An empty TreeSource can be created""" + """An empty TreeSource can be created.""" source = TreeSource(data=data, accessors=["val1", "val2"]) assert len(source) == 0 @@ -270,7 +270,7 @@ def test_create_empty(data): ], ) def test_create(data, all_accessor_levels): - """A tree source can be created from data in different formats""" + """A tree source can be created from data in different formats.""" source = TreeSource(data=data, accessors=["val1", "val2"]) # Source has 2 roots @@ -355,7 +355,7 @@ def test_create(data, all_accessor_levels): def test_source_single_object(): - """A single object can be passed as root data""" + """A single object can be passed as root data.""" source = TreeSource(accessors=["val1", "val2"], data="A string") assert len(source) == 1 @@ -363,7 +363,7 @@ def test_source_single_object(): def test_single_object_child(): - """A single object can be passed as child data""" + """A single object can be passed as child data.""" source = TreeSource( accessors=["val1", "val2"], data={("root1", 1): "A string"}, @@ -419,12 +419,12 @@ def test_modify_roots(source, listener): def test_iter_root(source): - """The roots of a source can be iterated over""" + """The roots of a source can be iterated over.""" assert "|".join(root.val1 for root in source) == "group1|group2" def test_clear(source, listener): - """A TreeSource can be cleared""" + """A TreeSource can be cleared.""" source.clear() assert len(source) == 0 @@ -443,7 +443,7 @@ def test_clear(source, listener): ], ) def test_insert(source, listener, index, actual_index): - """A new root node can be inserted""" + """A new root node can be inserted.""" new_child = source.insert(index, {"val1": "new"}) # Source has one more root. @@ -462,7 +462,7 @@ def test_insert(source, listener, index, actual_index): def test_insert_with_children(source, listener): - """A new root node can be inserted with children""" + """A new root node can be inserted with children.""" new_child = source.insert( 1, {"val1": "new"}, @@ -495,7 +495,7 @@ def test_insert_with_children(source, listener): def test_append(source, listener): - """A new root node can be appended""" + """A new root node can be appended.""" new_child = source.append({"val1": "new"}) # Source has one more root. @@ -514,7 +514,7 @@ def test_append(source, listener): def test_append_with_children(source, listener): - """A new root node can be inserted with children""" + """A new root node can be inserted with children.""" new_child = source.append( {"val1": "new"}, children=[ @@ -546,7 +546,7 @@ def test_append_with_children(source, listener): def test_remove_root(source, listener): - """A root node can be removed""" + """A root node can be removed.""" root = source[1] source.remove(root) @@ -561,7 +561,7 @@ def test_remove_root(source, listener): def test_remove_child(source, listener): - """A child node can be removed from a source""" + """A child node can be removed from a source.""" node = source[1][1] source.remove(node) @@ -571,7 +571,7 @@ def test_remove_child(source, listener): assert len(source[1]) == 2 # The child is no longer associated with the source, - # and the child isn't associated with it's parent. + # and the child isn't associated with its parent. assert node._source is None assert node._parent is None @@ -580,7 +580,7 @@ def test_remove_child(source, listener): def test_remove_non_root(source, listener): - """If a node isn't associated with this source, remove raises an error""" + """If a node isn't associated with this source, remove raises an error.""" other = Node(val="other") with pytest.raises( @@ -591,13 +591,13 @@ def test_remove_non_root(source, listener): def test_index(source): - """A root can be found in a TreeSource""" + """A root can be found in a TreeSource.""" root = source[1] assert source.index(root) == 1 def test_find(source): - """A node can be found by value""" + """A node can be found by value.""" root1 = source[1] # Append some additional roots diff --git a/core/tests/sources/test_value_source.py b/core/tests/sources/test_value_source.py index bf7e2a4693..9c44ce23db 100644 --- a/core/tests/sources/test_value_source.py +++ b/core/tests/sources/test_value_source.py @@ -4,7 +4,7 @@ def test_empty(): - """An empty ValueSource can be created""" + """An empty ValueSource can be created.""" source = ValueSource() assert source.accessor == "value" @@ -13,7 +13,7 @@ def test_empty(): def test_value(): - """A ValueSource can be created with a value""" + """A ValueSource can be created with a value.""" source = ValueSource(42) assert source.accessor == "value" @@ -22,7 +22,7 @@ def test_value(): def test_value_with_accessor(): - """A ValueSource can be created with a custom accessro""" + """A ValueSource can be created with a custom accessor.""" source = ValueSource(42, accessor="something") assert source.accessor == "something" @@ -31,7 +31,7 @@ def test_value_with_accessor(): def test_listener(): - """A listener will be notified of ValueSource changes""" + """A listener will be notified of ValueSource changes.""" source = ValueSource(42) listener = Mock() diff --git a/core/tests/style/pack/layout/test_fixed.py b/core/tests/style/pack/layout/test_fixed.py index cec59271e8..2c33bf0a32 100644 --- a/core/tests/style/pack/layout/test_fixed.py +++ b/core/tests/style/pack/layout/test_fixed.py @@ -6,7 +6,8 @@ def test_row_expanding_intrinsic(): - "Children in a row layout with fixed size don't expand, even if their hints allow it" + """Children in a row layout with fixed size don't expand, even if their hints allow + it.""" root = ExampleNode( "app", style=Pack(direction=ROW), @@ -66,7 +67,8 @@ def test_row_expanding_intrinsic(): def test_row_fixed_intrinsic(): - "Children in a row layout without an explicit size, but a fixed intrinsic width, don't expand" + """Children in a row layout without an explicit size, but a fixed intrinsic width, + don't expand.""" root = ExampleNode( "app", style=Pack(direction=ROW), @@ -125,7 +127,8 @@ def test_row_fixed_intrinsic(): def test_column_expanding_intrinsic(): - "Children in a column layout with fixed size don't expand, even if their hints allow it" + """Children in a column layout with fixed size don't expand, even if their hints + allow it.""" root = ExampleNode( "app", style=Pack(direction=COLUMN), @@ -183,7 +186,8 @@ def test_column_expanding_intrinsic(): def test_column_fixed_intrinsic(): - "Children in a column layout without an explicit size, but a fixed intrinsic width, don't expand" + """Children in a column layout without an explicit size, but a fixed intrinsic + width, don't expand.""" root = ExampleNode( "app", style=Pack(direction=COLUMN), diff --git a/core/tests/style/pack/layout/test_flex.py b/core/tests/style/pack/layout/test_flex.py index bf657a6bb4..682746163d 100644 --- a/core/tests/style/pack/layout/test_flex.py +++ b/core/tests/style/pack/layout/test_flex.py @@ -217,8 +217,7 @@ def test_row_flex_insufficient_space(): def test_row_flex_insufficient_space_no_flex(): """Children in a row layout with flexible containers, but insufficient_space for any of the flexible content, and an explicit intrinsic width, doesn't collapse row - height. - """ + height.""" root = ExampleNode( "app", style=Pack(direction=ROW), @@ -277,7 +276,7 @@ def test_row_flex_insufficient_space_no_flex(): def test_row_flex_grandchild_min_size(): - """The minimum intrinsic sizes of grandchild of flex row containers are honored""" + """The minimum intrinsic sizes of grandchild of flex row containers are honored.""" root = ExampleNode( "app", size=(at_least(0), at_least(0)), @@ -329,8 +328,8 @@ def test_row_flex_grandchild_min_size(): def test_column_flex_no_hints(): - """Children in a column layout with flexible containers, but no flex hints, - doesn't collapse column width.""" + """Children in a column layout with flexible containers, but no flex hints, doesn't + collapse column width.""" root = ExampleNode( "app", style=Pack(direction=COLUMN), @@ -459,8 +458,7 @@ def test_column_flex(): def test_column_flex_insufficient_space(): """Children in a column layout with flexible containers, but insufficient space to - accommodate them, and an explicit intrinsic width, doesn't collapse column width. - """ + accommodate them, and an explicit intrinsic width, doesn't collapse column width.""" root = ExampleNode( "app", @@ -542,8 +540,7 @@ def test_column_flex_insufficient_space(): def test_column_flex_insufficient_space_no_flex(): """Children in a column layout with flexible containers, but insufficient space for any of the flexible content, and an explicit intrinsic width, doesn't collapse - column width. - """ + column width.""" root = ExampleNode( "app", @@ -603,7 +600,8 @@ def test_column_flex_insufficient_space_no_flex(): def test_column_flex_grandchild_min_size(): - """The minimum intrinsic sizes of grandchild of flex column containers are honored""" + """The minimum intrinsic sizes of grandchild of flex column containers are + honored.""" root = ExampleNode( "app", size=(at_least(0), at_least(0)), diff --git a/core/tests/style/pack/test_css.py b/core/tests/style/pack/test_css.py index f204c79d1e..b3f924e256 100644 --- a/core/tests/style/pack/test_css.py +++ b/core/tests/style/pack/test_css.py @@ -475,5 +475,5 @@ ], ) def test_rendering(style, expected_css): - """An empty style node can be rendered""" + """An empty style node can be rendered.""" assert style.__css__() == expected_css diff --git a/core/tests/style/pack/utils.py b/core/tests/style/pack/utils.py index 29665caa8d..1c46b41822 100644 --- a/core/tests/style/pack/utils.py +++ b/core/tests/style/pack/utils.py @@ -23,7 +23,7 @@ def __repr__(self): return f"<{self.name}>" def __html__(self, depth=0): - "Debugging helper - output the HTML interpretation of this layout" + """Debugging helper - output the HTML interpretation of this layout""" if depth: tag = "div" else: diff --git a/core/tests/style/test_applicator.py b/core/tests/style/test_applicator.py index 359f7afd8f..b8f2e4bb9b 100644 --- a/core/tests/style/test_applicator.py +++ b/core/tests/style/test_applicator.py @@ -46,14 +46,14 @@ def widget(child): def test_refresh(widget): - "Refresh requests are passed to the widget" + """Refresh requests are passed to the widget.""" widget.applicator.refresh() assert_action_performed_with(widget, "refresh") def test_set_bounds(widget, child, grandchild): - "Bounds changes are passed to all widgets in the tree" + """Bounds changes are passed to all widgets in the tree.""" # Manually set location of the parent widget.layout._origin_left = 100 widget.layout._origin_top = 200 @@ -72,7 +72,7 @@ def test_set_bounds(widget, child, grandchild): grandchild.layout.content_width = 3 grandchild.layout.content_height = 4 - # Propegate the boundsq + # Propagate the bounds widget.applicator.set_bounds() assert_action_performed_with( @@ -83,7 +83,7 @@ def test_set_bounds(widget, child, grandchild): def test_text_alignment(widget): - "Text alignment can be set on a widget" + """Text alignment can be set on a widget.""" widget.applicator.set_text_alignment(RIGHT) assert_action_performed_with(widget, "set alignment", alignment=RIGHT) @@ -136,21 +136,21 @@ def test_set_hidden( def test_set_font(widget): - "A font change can be applied to a widget" + """A font change can be applied to a widget.""" widget.applicator.set_font(FANTASY) assert_action_performed_with(widget, "set font", font=FANTASY) def test_set_color(widget): - "A color change can be applied to a widget" + """A color change can be applied to a widget.""" widget.applicator.set_color(REBECCAPURPLE) assert_action_performed_with(widget, "set color", color=REBECCAPURPLE) def test_set_background_color(child, widget): - "A background color change can be applied to a widget" + """A background color change can be applied to a widget.""" widget.applicator.set_background_color(REBECCAPURPLE) assert_action_performed_with(widget, "set background color", color=REBECCAPURPLE) diff --git a/core/tests/test_fonts.py b/core/tests/test_fonts.py index 5b96ecb513..d59f212b62 100644 --- a/core/tests/test_fonts.py +++ b/core/tests/test_fonts.py @@ -98,7 +98,7 @@ def app(): ], ) def test_builtin_font(family, size, weight, style, variant, as_str): - "A builtin font can be constructed" + """A builtin font can be constructed.""" font = toga.Font( family=family, size=size, @@ -133,7 +133,7 @@ def test_builtin_font(family, size, weight, style, variant, as_str): ], ) def test_registered_font_key(app, family, style, weight, variant, key): - "Registered font keys can be generarted" + """Registered font keys can be generated.""" assert ( toga.Font._registered_font_key( family, style=style, weight=weight, variant=variant @@ -160,7 +160,7 @@ def test_registered_font_key(app, family, style, weight, variant, key): ], ) def test_register_font(app, path, registered): - "A custom font can be registered" + """A custom font can be registered.""" toga.Font.register("Custom Font", path) # Test fixture has paths in Path format; fully resolve for test comparison. This @@ -189,7 +189,7 @@ def test_register_font(app, path, registered): ], ) def test_register_font_variant(app, path, registered): - "A custom font can be registered as a variant" + """A custom font can be registered as a variant.""" toga.Font.register("Custom Font", path, weight=BOLD) # Test fixture has paths in Path format; fully resolve for test comparison. This diff --git a/core/tests/test_handlers.py b/core/tests/test_handlers.py index 2c284780b8..e5a6d547b7 100644 --- a/core/tests/test_handlers.py +++ b/core/tests/test_handlers.py @@ -11,7 +11,7 @@ class ExampleAsyncResult(AsyncResult): def test_noop_handler(): - """None can be wrapped as a valid handler""" + """None can be wrapped as a valid handler.""" obj = Mock() wrapped = wrapped_handler(obj, None) @@ -23,7 +23,7 @@ def test_noop_handler(): def test_noop_handler_with_cleanup(): - """cleanup is still performed when a no-op handler is used""" + """Cleanup is still performed when a no-op handler is used.""" obj = Mock() cleanup = Mock() @@ -39,7 +39,7 @@ def test_noop_handler_with_cleanup(): def test_noop_handler_with_cleanup_error(capsys): - """If cleanup on a no-op handler raises an error, it is logged""" + """If cleanup on a no-op handler raises an error, it is logged.""" obj = Mock() cleanup = Mock(side_effect=Exception("Problem in cleanup")) @@ -61,7 +61,7 @@ def test_noop_handler_with_cleanup_error(capsys): def test_function_handler(): - """A function can be used as a handler""" + """A function can be used as a handler.""" obj = Mock() handler_call = {} @@ -85,7 +85,7 @@ def handler(*args, **kwargs): def test_function_handler_error(capsys): - """A function handler can raise an error""" + """A function handler can raise an error.""" obj = Mock() handler_call = {} @@ -115,7 +115,7 @@ def handler(*args, **kwargs): def test_function_handler_with_cleanup(): - """A function handler can have a cleanup method""" + """A function handler can have a cleanup method.""" obj = Mock() cleanup = Mock() handler_call = {} @@ -179,7 +179,7 @@ def handler(*args, **kwargs): def test_generator_handler(event_loop): - """A generator can be used as a handler""" + """A generator can be used as a handler.""" obj = Mock() handler_call = {} @@ -216,7 +216,7 @@ async def waiter(): def test_generator_handler_error(event_loop, capsys): - """A generator can raise an error""" + """A generator can raise an error.""" obj = Mock() handler_call = {} @@ -255,7 +255,7 @@ async def waiter(): def test_generator_handler_with_cleanup(event_loop): - """A generator can have cleanup""" + """A generator can have cleanup.""" obj = Mock() cleanup = Mock() handler_call = {} @@ -297,7 +297,7 @@ async def waiter(): def test_generator_handler_with_cleanup_error(event_loop, capsys): - """A generator can raise an error during cleanup""" + """A generator can raise an error during cleanup.""" obj = Mock() cleanup = Mock(side_effect=Exception("Problem in cleanup")) handler_call = {} @@ -345,7 +345,7 @@ async def waiter(): def test_coroutine_handler(event_loop): - """A coroutine can be used as a handler""" + """A coroutine can be used as a handler.""" obj = Mock() handler_call = {} @@ -379,7 +379,7 @@ async def waiter(): def test_coroutine_handler_error(event_loop, capsys): - """A coroutine can raise an error""" + """A coroutine can raise an error.""" obj = Mock() handler_call = {} @@ -418,7 +418,7 @@ async def waiter(): def test_coroutine_handler_with_cleanup(event_loop): - """A coroutine can have cleanup""" + """A coroutine can have cleanup.""" obj = Mock() cleanup = Mock() handler_call = {} @@ -457,7 +457,7 @@ async def waiter(): def test_coroutine_handler_with_cleanup_error(event_loop, capsys): - """A coroutine can raise an error during cleanup""" + """A coroutine can raise an error during cleanup.""" obj = Mock() cleanup = Mock(side_effect=Exception("Problem in cleanup")) handler_call = {} @@ -502,7 +502,7 @@ async def waiter(): def test_native_handler(): - """A native function can be used as a handler""" + """A native function can be used as a handler.""" obj = Mock() native_method = Mock() @@ -515,7 +515,7 @@ def test_native_handler(): def test_async_result_non_comparable(event_loop): - """An async result can't be compared or evaluated""" + """An async result can't be compared or evaluated.""" result = ExampleAsyncResult(None) # repr for the result is useful @@ -561,7 +561,7 @@ def test_async_result_non_comparable(event_loop): def test_async_result(event_loop): - """An async result can be set""" + """An async result can be set.""" result = ExampleAsyncResult() result.set_result(42) @@ -574,7 +574,7 @@ def test_async_result(event_loop): def test_async_result_cancelled(event_loop): - """An async result can be set even if the future is cancelled""" + """An async result can be set even if the future is cancelled.""" result = ExampleAsyncResult() # cancel the future. @@ -588,7 +588,7 @@ def test_async_result_cancelled(event_loop): def test_async_exception(event_loop): - """An async result can raise an exception""" + """An async result can raise an exception.""" result = ExampleAsyncResult() result.set_exception(ValueError("Bad stuff")) @@ -599,7 +599,7 @@ def test_async_exception(event_loop): def test_async_exception_cancelled(event_loop): - """An async result can raise an exception even if the future is cancelled""" + """An async result can raise an exception even if the future is cancelled.""" result = ExampleAsyncResult() # Cancel the future @@ -618,7 +618,7 @@ def test_async_exception_cancelled(event_loop): def test_async_result_sync(event_loop): - """The deprecated behavior of using a synchronous result handler is supported""" + """The deprecated behavior of using a synchronous result handler is supported.""" on_result = Mock() with pytest.warns( @@ -638,7 +638,7 @@ def test_async_result_sync(event_loop): def test_async_result_cancelled_sync(event_loop): - """A deprecated on_result handler won't be fired on a cancelled future""" + """A deprecated on_result handler won't be fired on a cancelled future.""" on_result = Mock() with pytest.warns( @@ -661,7 +661,7 @@ def test_async_result_cancelled_sync(event_loop): def test_async_exception_sync(event_loop): - """A deprecated on_result handler can raise an exception""" + """A deprecated on_result handler can raise an exception.""" on_result = Mock() with pytest.warns( @@ -683,7 +683,7 @@ def test_async_exception_sync(event_loop): def test_async_exception_cancelled_sync(event_loop): - """An async result can raise an exception even if the future is cancelled""" + """An async result can raise an exception even if the future is cancelled.""" on_result = Mock() with pytest.warns( diff --git a/core/tests/test_icons.py b/core/tests/test_icons.py index c76e8c660d..8ed5a59407 100644 --- a/core/tests/test_icons.py +++ b/core/tests/test_icons.py @@ -92,7 +92,7 @@ def app(): ], ) def test_create(monkeypatch, app, path, system, sizes, extensions, final_paths): - "Icons can be created" + """Icons can be created.""" # Patch the dummy Icon class to evaluated different lookup strategies. monkeypatch.setattr(DummyIcon, "SIZES", sizes) monkeypatch.setattr(DummyIcon, "EXTENSIONS", extensions) @@ -112,7 +112,7 @@ def test_create(monkeypatch, app, path, system, sizes, extensions, final_paths): def test_create_fallback(app): - "If a resource doesn't exist, a fallback icon is used." + """If a resource doesn't exist, a fallback icon is used.""" icon = toga.Icon("resources/missing") assert icon._impl is not None @@ -127,7 +127,7 @@ def test_create_fallback(app): ], ) def test_cached_icons(app, name, path): - "Default icons exist, and are cached" + """Default icons exist, and are cached.""" icon = getattr(toga.Icon, name) assert icon.path == Path(path) @@ -137,7 +137,7 @@ def test_cached_icons(app, name, path): def test_deprecated_icons(app): - """Deprecated icons are still available""" + """Deprecated icons are still available.""" with pytest.warns(DeprecationWarning): icon = toga.Icon.TOGA_ICON assert icon.path == Path("toga") diff --git a/core/tests/test_images.py b/core/tests/test_images.py index 4490ea4e6f..53b4d4add6 100644 --- a/core/tests/test_images.py +++ b/core/tests/test_images.py @@ -38,7 +38,7 @@ ], ) def test_create_from_file(app, args, kwargs): - """An image can be constructed from a file""" + """An image can be constructed from a file.""" image = toga.Image(*args, **kwargs) # Image is bound @@ -80,7 +80,7 @@ def test_create_from_file(app, args, kwargs): ], ) def test_create_with_nonexistent_file(app, args, kwargs): - """If a file image source doesn't exist, an error is raised""" + """If a file image source doesn't exist, an error is raised.""" with pytest.raises(FileNotFoundError): toga.Image(*args, **kwargs) @@ -101,7 +101,7 @@ def test_create_with_nonexistent_file(app, args, kwargs): ], ) def test_create_from_bytes(args, kwargs): - """An image can be constructed from data""" + """An image can be constructed from data.""" image = toga.Image(*args, **kwargs) # Image is bound @@ -114,7 +114,7 @@ def test_create_from_bytes(args, kwargs): def test_create_from_raw(): - """An image can be created from a raw data source""" + """An image can be created from a raw data source.""" orig = toga.Image(BYTES) copy = toga.Image(orig._impl.native) @@ -128,7 +128,7 @@ def test_create_from_raw(): def test_no_source(): - """If no source is provided, an error is raised""" + """If no source is provided, an error is raised.""" with pytest.raises( TypeError, match=r"Image.__init__\(\) missing 1 required positional argument: 'src'", @@ -137,7 +137,7 @@ def test_no_source(): def test_empty_image(): - """If the image source is provided as None, an error is raised""" + """If the image source is provided as None, an error is raised.""" with pytest.raises( TypeError, match=r"Unsupported source type for Image", @@ -146,7 +146,7 @@ def test_empty_image(): def test_empty_image_explicit(): - """If src is explicitly provided as None, an error is raised""" + """If src is explicitly provided as None, an error is raised.""" with pytest.raises( TypeError, match=r"Unsupported source type for Image", @@ -155,7 +155,7 @@ def test_empty_image_explicit(): def test_invalid_input_format(): - """Trying to create an image with an invalid input should raise an error""" + """Trying to create an image with an invalid input should raise an error.""" with pytest.raises( TypeError, match=r"Unsupported source type for Image", @@ -164,7 +164,7 @@ def test_invalid_input_format(): def test_create_from_pil(app): - """An image can be created from a PIL image""" + """An image can be created from a PIL image.""" with PIL.Image.open(ABSOLUTE_FILE_PATH) as pil_image: pil_image.load() toga_image = toga.Image(pil_image) @@ -174,7 +174,7 @@ def test_create_from_pil(app): def test_create_from_toga_image(app): - """An image can be create from another Toga image""" + """An image can be created from another Toga image.""" toga_image = toga.Image(ABSOLUTE_FILE_PATH) toga_image_2 = toga.Image(toga_image) @@ -202,7 +202,7 @@ def test_deprecated_arguments(kwargs): ], ) def test_too_many_arguments(args, kwargs): - """If multiple arguments are supplied, an error is raised""" + """If multiple arguments are supplied, an error is raised.""" with pytest.raises( TypeError, match=r"Received multiple arguments to constructor.", @@ -211,7 +211,7 @@ def test_too_many_arguments(args, kwargs): def test_dimensions(app): - """The dimensions of the image can be retrieved""" + """The dimensions of the image can be retrieved.""" image = toga.Image(RELATIVE_FILE_PATH) assert image.size == (144, 72) @@ -234,7 +234,7 @@ def test_data(app): def test_image_save(tmp_path): - """An image can be saved""" + """An image can be saved.""" save_path = tmp_path / "save.png" image = toga.Image(BYTES) image.save(save_path) @@ -256,7 +256,8 @@ class ImageSubclass(toga.Image): ], ) def test_as_format_toga(app, Class_1, Class_2): - """as_format can successfully return a "copy" Image, with support for subclassing""" + """as_format can successfully return a "copy" Image, with support for + subclassing.""" image_1 = Class_1(ABSOLUTE_FILE_PATH) image_2 = image_1.as_format(Class_2) @@ -265,7 +266,7 @@ def test_as_format_toga(app, Class_1, Class_2): def test_as_format_pil(app): - """as_format can successfully return a PIL image""" + """as_format can successfully return a PIL image.""" toga_image = toga.Image(ABSOLUTE_FILE_PATH) pil_image = toga_image.as_format(PIL.Image.Image) assert isinstance(pil_image, PIL.Image.Image) @@ -274,7 +275,7 @@ def test_as_format_pil(app): @pytest.mark.parametrize("ImageClass", [CustomImage, CustomImageSubclass]) def test_create_from_custom_class(app, ImageClass): - """toga.Image can be created from custom type""" + """toga.Image can be created from custom type.""" custom_image = ImageClass() toga_image = toga.Image(custom_image) assert isinstance(toga_image, toga.Image) @@ -283,7 +284,7 @@ def test_create_from_custom_class(app, ImageClass): @pytest.mark.parametrize("ImageClass", [CustomImage, CustomImageSubclass]) def test_as_format_custom_class(app, ImageClass): - """as_format can successfully return a registered custom image type""" + """as_format can successfully return a registered custom image type.""" toga_image = toga.Image(ABSOLUTE_FILE_PATH) custom_image = toga_image.as_format(ImageClass) assert isinstance(custom_image, ImageClass) @@ -298,7 +299,7 @@ def test_disabled_image_plugin(app): # None is same as supplying nothing; also test a random unrecognized class @pytest.mark.parametrize("arg", [None, toga.Button]) def test_as_format_invalid_input(app, arg): - """An unsupported format raises an error""" + """An unsupported format raises an error.""" toga_image = toga.Image(ABSOLUTE_FILE_PATH) with pytest.raises(TypeError, match=r"Unknown conversion format for Image:"): diff --git a/core/tests/test_keys.py b/core/tests/test_keys.py index fd5afef715..8eb2b98043 100644 --- a/core/tests/test_keys.py +++ b/core/tests/test_keys.py @@ -2,7 +2,7 @@ def test_is_printable(): - "Key printability can be checked" + """Key printability can be checked.""" assert not Key.is_printable(Key.SHIFT) assert Key.is_printable(Key.LESS_THAN) assert Key.is_printable(Key.GREATER_THAN) @@ -10,7 +10,7 @@ def test_is_printable(): def test_modifiers(): - "Keys can be added with modifiers" + """Keys can be added with modifiers.""" # Mod + Key assert Key.MOD_1 + Key.A == "a" diff --git a/core/tests/test_paths.py b/core/tests/test_paths.py index 9f1bf383d0..30f23b7d66 100644 --- a/core/tests/test_paths.py +++ b/core/tests/test_paths.py @@ -7,7 +7,8 @@ def run_app(args, cwd): - "Run a Toga app as a subprocess with coverage enabled and the Toga Dummy backend" + """Run a Toga app as a subprocess with coverage enabled and the Toga Dummy + backend.""" # We need to do a full copy of the environment, then add our extra bits; # if we don't the Windows interpreter won't inherit SYSTEMROOT env = os.environ.copy() @@ -26,7 +27,7 @@ def run_app(args, cwd): env=env, text=True, ) - # When called as a subprocess, coverage drops it's coverage report in CWD. + # When called as a subprocess, coverage drops its coverage report in CWD. # Move it to the project root for combination with the main test report. for file in cwd.glob(".coverage*"): os.rename(file, Path(__file__).parent.parent / file.name) @@ -34,7 +35,7 @@ def run_app(args, cwd): def assert_paths(output, app_path, app_name): - "Assert the paths for the standalone app are consistent" + """Assert the paths for the standalone app are consistent.""" results = output.splitlines() assert f"app.paths.app={app_path.resolve()}" in results assert ( @@ -57,7 +58,7 @@ def assert_paths(output, app_path, app_name): def test_as_interactive(): - "At an interactive prompt, the app path is the current working directory" + """At an interactive prompt, the app path is the current working directory.""" # Spawn the interactive-mode mocking entry point cwd = Path(__file__).parent / "testbed" output = run_app(["interactive.py"], cwd=cwd) @@ -65,8 +66,8 @@ def test_as_interactive(): def test_simple_as_file_in_module(): - """When a simple app is started as `python app.py` inside a runnable module, the - app path is the folder holding app.py.""" + """When a simple app is started as `python app.py` inside a runnable module, the app + path is the folder holding app.py.""" # Spawn the simple testbed app using `app.py` cwd = Path(__file__).parent / "testbed/simple" output = run_app(["app.py"], cwd=cwd) @@ -74,8 +75,8 @@ def test_simple_as_file_in_module(): def test_simple_as_module(): - """When a simple apps is started as `python -m app` inside a runnable module, - the app path is the folder holding app.py.""" + """When a simple apps is started as `python -m app` inside a runnable module, the + app path is the folder holding app.py.""" # Spawn the simple testbed app using `-m app` cwd = Path(__file__).parent / "testbed/simple" output = run_app(["-m", "app"], cwd=cwd) @@ -83,7 +84,8 @@ def test_simple_as_module(): def test_simple_as_deep_file(): - "When a simple app is started as `python simple/app.py`, the app path is the folder holding app.py" + """When a simple app is started as `python simple/app.py`, the app path is the + folder holding app.py.""" # Spawn the simple testbed app using `simple/app.py` cwd = Path(__file__).parent / "testbed" output = run_app(["simple/app.py"], cwd=cwd) @@ -91,7 +93,8 @@ def test_simple_as_deep_file(): def test_simple_as_deep_module(): - "When a simple app is started as `python -m simple`, the app path is the folder holding app.py" + """When a simple app is started as `python -m simple`, the app path is the folder + holding app.py.""" # Spawn the simple testbed app using `-m simple` cwd = Path(__file__).parent / "testbed" output = run_app(["-m", "simple"], cwd=cwd) @@ -108,8 +111,8 @@ def test_subclassed_as_file_in_module(): def test_subclassed_as_module(): - """When a subclassed app is started as `python -m app` inside a runnable module, - the app path is the folder holding app.py.""" + """When a subclassed app is started as `python -m app` inside a runnable module, the + app path is the folder holding app.py.""" # Spawn the subclassed testbed app using `-m app` cwd = Path(__file__).parent / "testbed/subclassed" output = run_app(["-m", "app"], cwd=cwd) @@ -117,7 +120,8 @@ def test_subclassed_as_module(): def test_subclassed_as_deep_file(): - "When a subclassed app is started as `python simple/app.py`, the app path is the folder holding app.py" + """When a subclassed app is started as `python simple/app.py`, the app path is the + folder holding app.py.""" # Spawn the subclassed testbed app using `subclassed/app.py` cwd = Path(__file__).parent / "testbed" output = run_app(["subclassed/app.py"], cwd=cwd) @@ -125,7 +129,8 @@ def test_subclassed_as_deep_file(): def test_subclassed_as_deep_module(): - "When a subclassed app is started as `python -m simple`, the app path is the folder holding app.py" + """When a subclassed app is started as `python -m simple`, the app path is the + folder holding app.py.""" # Spawn the subclassed testbed app using `-m subclassed` cwd = Path(__file__).parent / "testbed" output = run_app(["-m", "subclassed"], cwd=cwd) diff --git a/core/tests/test_platform.py b/core/tests/test_platform.py index 0e428861cb..423f97c868 100644 --- a/core/tests/test_platform.py +++ b/core/tests/test_platform.py @@ -66,7 +66,7 @@ def test_get_current_platform_desktop(): def test_get_current_platform_android_inferred(monkeypatch): - "Android platform can be inferred from existence of sys.getandroidapilevel" + """Android platform can be inferred from existence of sys.getandroidapilevel.""" monkeypatch.setattr(sys, "platform", "linux") try: # since there isn't an existing attribute of this name, it can't be patched. @@ -77,7 +77,7 @@ def test_get_current_platform_android_inferred(monkeypatch): def test_get_current_platform_android(monkeypatch): - "Android platform can be obtained directly from sys.platform" + """Android platform can be obtained directly from sys.platform.""" monkeypatch.setattr(sys, "platform", "android") try: # since there isn't an existing attribute of this name, it can't be patched. @@ -88,20 +88,20 @@ def test_get_current_platform_android(monkeypatch): def test_get_current_platform_iOS(monkeypatch): - "iOS platform can be obtained directly from sys.platform" + """IOS platform can be obtained directly from sys.platform.""" monkeypatch.setattr(sys, "platform", "ios") assert get_current_platform() == "iOS" def test_get_current_platform_web(monkeypatch): - "Web platform can be obtained directly from sys.platform" + """Web platform can be obtained directly from sys.platform.""" monkeypatch.setattr(sys, "platform", "emscripten") assert get_current_platform() == "web" @pytest.mark.parametrize("value", ["freebsd12", "freebsd13", "freebsd14"]) def test_get_current_platform_freebsd(monkeypatch, value): - "FreeBSD platform can be obtained directly from sys.platform" + """FreeBSD platform can be obtained directly from sys.platform.""" monkeypatch.setattr(sys, "platform", value) assert get_current_platform() == "freeBSD" diff --git a/core/tests/test_validators.py b/core/tests/test_validators.py index e81c24d4ba..66d6a0deb9 100644 --- a/core/tests/test_validators.py +++ b/core/tests/test_validators.py @@ -129,7 +129,7 @@ def test_length_between(value, kwargs, error): def test_invalid_range(): - "Minimum value must be less than maximum value" + """Minimum value must be less than maximum value.""" with pytest.raises( ValueError, match=r"Minimum length cannot be less than maximum length", @@ -640,7 +640,7 @@ def test_integer(value, kwargs, error): ("1.23e+4", dict(), None), # Negative Exponential ("-1.23e-4", dict(), None), - # Exponential (captialized) + # Exponential (capitalized) ("1.23E+4", dict(), None), # Negative (capitalized) ("-1.23E-4", dict(), None), diff --git a/core/tests/test_window.py b/core/tests/test_window.py index 5ea0a05ac4..250e2253c8 100644 --- a/core/tests/test_window.py +++ b/core/tests/test_window.py @@ -17,7 +17,7 @@ def window(app): def test_window_created(app): - "A Window can be created with minimal arguments" + """A Window can be created with minimal arguments.""" window = toga.Window() assert window.app == app @@ -39,7 +39,7 @@ def test_window_created(app): def test_window_created_explicit(app): - "Explicit arguments at construction are stored" + """Explicit arguments at construction are stored.""" on_close_handler = Mock() window = toga.Window( @@ -71,7 +71,7 @@ def test_window_created_explicit(app): def test_window_created_without_app(): - "A window cannot be created without an active app" + """A window cannot be created without an active app.""" toga.App.app = None with pytest.raises( RuntimeError, match="Cannot create a Window before creating an App" @@ -80,7 +80,7 @@ def test_window_created_without_app(): def test_set_app(window, app): - """A window's app cannot be reassigned""" + """A window's app cannot be reassigned.""" assert window.app == app app2 = toga.App("Test App 2", "org.beeware.toga.test-app-2") @@ -89,7 +89,7 @@ def test_set_app(window, app): def test_set_app_with_content(window, app): - """If a window has content, the content is assigned to the app""" + """If a window has content, the content is assigned to the app.""" assert window.app == app content = toga.Box() @@ -110,13 +110,13 @@ def test_set_app_with_content(window, app): ], ) def test_title(window, value, expected): - """The title of the window can be changed""" + """The title of the window can be changed.""" window.title = value assert window.title == expected def test_toolbar_implicit_add(window, app): - """Adding an item to to a toolbar implicitly adds it to the app.""" + """Adding an item to a toolbar implicitly adds it to the app.""" cmd1 = toga.Command(None, "Command 1") cmd2 = toga.Command(None, "Command 2") @@ -141,7 +141,7 @@ def test_toolbar_implicit_add(window, app): def test_change_content(window, app): - """The content of a window can be changed""" + """The content of a window can be changed.""" assert window.content is None assert window.app == app @@ -271,7 +271,7 @@ def test_full_screen(window, app): def test_close_direct(window, app): - """A window can be closed directly""" + """A window can be closed directly.""" on_close_handler = Mock(return_value=True) window.on_close = on_close_handler @@ -291,7 +291,7 @@ def test_close_direct(window, app): def test_close_no_handler(window, app): - """A window without a close handler can be closed""" + """A window without a close handler can be closed.""" window.show() assert window.app == app assert window in app.windows @@ -306,8 +306,8 @@ def test_close_no_handler(window, app): assert_action_performed(window, "close") -def test_close_sucessful_handler(window, app): - """A window with a successful close handler can be closed""" +def test_close_successful_handler(window, app): + """A window with a successful close handler can be closed.""" on_close_handler = Mock(return_value=True) window.on_close = on_close_handler @@ -327,7 +327,7 @@ def test_close_sucessful_handler(window, app): def test_close_rejected_handler(window, app): - """A window can have a close handler that rejects closing""" + """A window can have a close handler that rejects closing.""" on_close_handler = Mock(return_value=False) window.on_close = on_close_handler @@ -347,7 +347,7 @@ def test_close_rejected_handler(window, app): def test_as_image(window): - """A window can be captured as an image""" + """A window can be captured as an image.""" image = window.as_image() assert_action_performed(window, "get image data") # Don't need to check the raw data; just check it's the right size. @@ -355,7 +355,7 @@ def test_as_image(window): def test_screen(window, app): - """A window can be moved to a different screen""" + """A window can be moved to a different screen.""" # Cannot actually change window.screen, so just check # the window positions as a substitute for moving the # window between the screens. @@ -382,7 +382,7 @@ def test_screen_position(window, app): def test_info_dialog(window, app): - """An info dialog can be shown""" + """An info dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -417,7 +417,7 @@ async def run_dialog(dialog): def test_question_dialog(window, app): - """A question dialog can be shown""" + """A question dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -452,7 +452,7 @@ async def run_dialog(dialog): def test_confirm_dialog(window, app): - """A confirm dialog can be shown""" + """A confirm dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -487,7 +487,7 @@ async def run_dialog(dialog): def test_error_dialog(window, app): - """An error dialog can be shown""" + """An error dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -522,7 +522,7 @@ async def run_dialog(dialog): def test_stack_trace_dialog(window, app): - """A stack trace dialog can be shown""" + """A stack trace dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -564,7 +564,7 @@ async def run_dialog(dialog): def test_save_file_dialog(window, app): - """A save file dialog can be shown""" + """A save file dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -607,7 +607,7 @@ async def run_dialog(dialog): def test_save_file_dialog_default_directory(window, app): - """If no path is provided, a save file dialog will use the default directory""" + """If no path is provided, a save file dialog will use the default directory.""" on_result_handler = Mock() with pytest.warns( @@ -651,7 +651,7 @@ async def run_dialog(dialog): def test_open_file_dialog(window, app): - """A open file dialog can be shown""" + """A open file dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -694,7 +694,7 @@ async def run_dialog(dialog): def test_open_file_dialog_default_directory(window, app): - """If no path is provided, a open file dialog will use the default directory""" + """If no path is provided, a open file dialog will use the default directory.""" on_result_handler = Mock() with pytest.warns( @@ -741,7 +741,7 @@ async def run_dialog(dialog): def test_select_folder_dialog(window, app): - """A select folder dialog can be shown""" + """A select folder dialog can be shown.""" on_result_handler = Mock() with pytest.warns( @@ -783,7 +783,7 @@ async def run_dialog(dialog): def test_select_folder_dialog_default_directory(window, app): - """If no path is provided, a select folder dialog will use the default directory""" + """If no path is provided, a select folder dialog will use the default directory.""" on_result_handler = Mock() with pytest.warns( @@ -901,7 +901,7 @@ async def run_dialog(dialog): def test_deprecated_names_resizeable(): - """Deprecated spelling of resizable still works""" + """Deprecated spelling of resizable still works.""" with pytest.warns( DeprecationWarning, match=r"Window.resizeable has been renamed Window.resizable", @@ -916,7 +916,7 @@ def test_deprecated_names_resizeable(): def test_deprecated_names_closeable(): - """Deprecated spelling of closable still works""" + """Deprecated spelling of closable still works.""" with pytest.warns( DeprecationWarning, match=r"Window.closeable has been renamed Window.closable", diff --git a/core/tests/testbed/customize/sitecustomize.py b/core/tests/testbed/customize/sitecustomize.py index 6a1603a850..a57c0f0bd3 100644 --- a/core/tests/testbed/customize/sitecustomize.py +++ b/core/tests/testbed/customize/sitecustomize.py @@ -1,3 +1,9 @@ -import coverage +import os -cov = coverage.process_startup() +# Only set up the app processes for coverage *if* coverage is running. +# Otherwise, just importing coverage will force running coverage. +# The COVERAGE_RUN environment variable is set by coverage.py itself. +if os.environ.get("COVERAGE_RUN"): + import coverage + + coverage.process_startup() diff --git a/core/tests/widgets/canvas/test_canvas.py b/core/tests/widgets/canvas/test_canvas.py index 5d47433480..8fe1f432b5 100644 --- a/core/tests/widgets/canvas/test_canvas.py +++ b/core/tests/widgets/canvas/test_canvas.py @@ -11,7 +11,7 @@ def test_widget_created(): - "An empty canvas can be created" + """An empty canvas can be created.""" widget = toga.Canvas() assert widget._impl.interface == widget assert_action_performed(widget, "create Canvas") @@ -40,7 +40,7 @@ def test_create_with_value( on_alt_release_handler, on_alt_drag_handler, ): - "A Canvas can be created with initial values" + """A Canvas can be created with initial values.""" assert widget._impl.interface == widget assert_action_performed(widget, "create Canvas") @@ -58,7 +58,7 @@ def test_create_with_value( def test_disable_no_op(widget): - """Canvas doesn't have a disabled state""" + """Canvas doesn't have a disabled state.""" # Enabled by default assert widget.enabled @@ -77,7 +77,7 @@ def test_focus_noop(widget): def test_redraw(widget): - """The canvas can be redrawn""" + """The canvas can be redrawn.""" widget.redraw() assert_action_performed(widget, "redraw") @@ -90,7 +90,7 @@ def test_redraw(widget): def test_subcontext(widget): - "A canvas can produce a subcontext" + """A canvas can produce a subcontext.""" with widget.Context() as subcontext: # A fresh context has been created as a subcontext of the canvas. assert isinstance(subcontext, Context) @@ -98,7 +98,7 @@ def test_subcontext(widget): def test_closed_path(widget): - "A canvas can produce a ClosedPath subcontext" + """A canvas can produce a ClosedPath subcontext.""" with widget.ClosedPath(x=10, y=20) as closed_path: # A fresh context has been created as a subcontext of the canvas. assert isinstance(closed_path, ClosedPathContext) @@ -108,7 +108,7 @@ def test_closed_path(widget): def test_fill(widget): - "A canvas can produce a Fill subcontext" + """A canvas can produce a Fill subcontext.""" with widget.Fill( x=10, y=20, color="rebeccapurple", fill_rule=FillRule.EVENODD ) as fill: @@ -123,7 +123,7 @@ def test_fill(widget): def test_stroke(widget): - "A canvas can produce a Stroke subcontext" + """A canvas can produce a Stroke subcontext.""" with widget.Stroke( x=10, y=20, color="rebeccapurple", line_width=5, line_dash=[2, 7] ) as stroke: @@ -149,19 +149,19 @@ def test_stroke(widget): ], ) def test_measure_text(widget, font, expected): - "Canvas can measure rendered text size" + """Canvas can measure rendered text size.""" assert widget.measure_text("Hello world", font=font) == expected def test_as_image(widget): - """A rendered canvas can be retrieved as an image""" + """A rendered canvas can be retrieved as an image.""" image = widget.as_image() assert image is not None assert_action_performed(widget, "get image data") def test_deprecated_drawing_operations(widget): - """Deprecated simple drawing operations raise a warning""" + """Deprecated simple drawing operations raise a warning.""" with pytest.warns( DeprecationWarning, @@ -382,7 +382,7 @@ def test_deprecated_drawing_operations(widget): def test_deprecated_args(widget): - "Deprecated arguments to canvas functions raise warnings." + """Deprecated arguments to canvas functions raise warnings.""" with pytest.warns( DeprecationWarning, match=r"The `tight` argument on Canvas.measure_text\(\) has been deprecated.", diff --git a/core/tests/widgets/canvas/test_context_objects.py b/core/tests/widgets/canvas/test_context_objects.py index e7575bebe0..a4228447e8 100644 --- a/core/tests/widgets/canvas/test_context_objects.py +++ b/core/tests/widgets/canvas/test_context_objects.py @@ -15,7 +15,7 @@ def test_subcontext(widget): - "A context can produce a subcontext" + """A context can produce a subcontext.""" with widget.context.Context() as subcontext: subcontext.line_to(30, 40) # A fresh context has been created as a subcontext of the canvas. @@ -67,7 +67,7 @@ def test_subcontext(widget): ], ) def test_closed_path(widget, kwargs, args_repr, has_move, properties): - "A context can produce a ClosedPath subcontext" + """A context can produce a ClosedPath subcontext.""" with widget.context.ClosedPath(**kwargs) as closed_path: closed_path.line_to(30, 40) @@ -179,7 +179,7 @@ def test_closed_path(widget, kwargs, args_repr, has_move, properties): ], ) def test_fill(widget, kwargs, args_repr, has_move, properties): - "A context can produce a Fill subcontext" + """A context can produce a Fill subcontext.""" with widget.context.Fill(**kwargs) as fill: fill.line_to(30, 40) @@ -343,7 +343,7 @@ def test_fill(widget, kwargs, args_repr, has_move, properties): ], ) def test_stroke(widget, kwargs, args_repr, has_move, properties): - "A context can produce a Stroke subcontext" + """A context can produce a Stroke subcontext.""" with widget.context.Stroke(**kwargs) as stroke: stroke.line_to(30, 40) @@ -380,7 +380,7 @@ def test_stroke(widget, kwargs, args_repr, has_move, properties): def test_deprecated_drawing_operations(widget): - """Deprecated simple drawing operations raise a warning""" + """Deprecated simple drawing operations raise a warning.""" with pytest.warns( DeprecationWarning, @@ -426,7 +426,7 @@ def test_deprecated_drawing_operations(widget): def test_order_change(widget): - """The order of context objects can be changed""" + """The order of context objects can be changed.""" # Initially nothing on the context. assert len(widget.context) == 0 @@ -625,7 +625,7 @@ def test_order_change(widget): def test_stacked_kwargs(widget): - "If contexts are stacked, kwargs for sub operations don't leak" + """If contexts are stacked, kwargs for sub operations don't leak.""" widget.context.line_to(0, 0) with widget.Fill(color=rgb(255, 0, 0)) as fill1: fill1.line_to(10, 20) @@ -834,7 +834,7 @@ def test_stacked_kwargs(widget): def test_deprecated_args(widget): - "Deprecated arguments to canvas functions raise warnings." + """Deprecated arguments to canvas functions raise warnings.""" # fill() raises a warning about preserve being deprecated, then raises an error when # it's used as a context manager. diff --git a/core/tests/widgets/canvas/test_draw_operations.py b/core/tests/widgets/canvas/test_draw_operations.py index 9cd7f6cff4..fc85bcfb98 100644 --- a/core/tests/widgets/canvas/test_draw_operations.py +++ b/core/tests/widgets/canvas/test_draw_operations.py @@ -9,7 +9,7 @@ def test_begin_path(widget): - """A begin path operation can be added""" + """A begin path operation can be added.""" draw_op = widget.context.begin_path() assert_action_performed(widget, "redraw") @@ -22,7 +22,7 @@ def test_begin_path(widget): def test_close_path(widget): - """A close path operation can be added""" + """A close path operation can be added.""" draw_op = widget.context.close_path() assert_action_performed(widget, "redraw") @@ -88,7 +88,7 @@ def test_close_path(widget): ], ) def test_fill(widget, kwargs, args_repr, draw_kwargs): - """A primitive fill operation can be added""" + """A primitive fill operation can be added.""" draw_op = widget.context.fill(**kwargs) assert_action_performed(widget, "redraw") @@ -158,7 +158,7 @@ def test_fill(widget, kwargs, args_repr, draw_kwargs): ], ) def test_stroke(widget, kwargs, args_repr, draw_kwargs): - """A primitive stroke operation can be added""" + """A primitive stroke operation can be added.""" draw_op = widget.context.stroke(**kwargs) assert_action_performed(widget, "redraw") @@ -175,7 +175,7 @@ def test_stroke(widget, kwargs, args_repr, draw_kwargs): def test_move_to(widget): - """A move to operation can be added""" + """A move to operation can be added.""" draw_op = widget.context.move_to(10, 20) assert_action_performed(widget, "redraw") @@ -192,7 +192,7 @@ def test_move_to(widget): def test_line_to(widget): - """A line to operation can be added""" + """A line to operation can be added.""" draw_op = widget.context.line_to(10, 20) assert_action_performed(widget, "redraw") @@ -209,7 +209,7 @@ def test_line_to(widget): def test_bezier_curve_to(widget): - """A Bézier curve to operation can be added""" + """A Bézier curve to operation can be added.""" draw_op = widget.context.bezier_curve_to(10, 20, 30, 40, 50, 60) assert_action_performed(widget, "redraw") @@ -235,7 +235,7 @@ def test_bezier_curve_to(widget): def test_quadratic_curve_to(widget): - """A Quadratic curve to operation can be added""" + """A Quadratic curve to operation can be added.""" draw_op = widget.context.quadratic_curve_to(10, 20, 30, 40) assert_action_performed(widget, "redraw") @@ -362,7 +362,7 @@ def test_quadratic_curve_to(widget): ], ) def test_arc(widget, kwargs, args_repr, draw_kwargs): - """An arc operation can be added""" + """An arc operation can be added.""" draw_op = widget.context.arc(**kwargs) assert_action_performed(widget, "redraw") @@ -519,7 +519,7 @@ def test_arc(widget, kwargs, args_repr, draw_kwargs): ], ) def test_ellipse(widget, kwargs, args_repr, draw_kwargs): - """An ellipse operation can be added""" + """An ellipse operation can be added.""" draw_op = widget.context.ellipse(**kwargs) assert_action_performed(widget, "redraw") @@ -536,7 +536,7 @@ def test_ellipse(widget, kwargs, args_repr, draw_kwargs): def test_rect(widget): - """A rect operation can be added""" + """A rect operation can be added.""" draw_op = widget.context.rect(10, 20, 30, 40) assert_action_performed(widget, "redraw") @@ -599,7 +599,7 @@ def test_rect(widget): ], ) def test_write_text(widget, kwargs, args_repr, draw_kwargs): - """A write text operation can be added""" + """A write text operation can be added.""" draw_op = widget.context.write_text(**kwargs) assert_action_performed(widget, "redraw") @@ -619,7 +619,7 @@ def test_write_text(widget, kwargs, args_repr, draw_kwargs): def test_rotate(widget): - """A rotate operation can be added""" + """A rotate operation can be added.""" draw_op = widget.context.rotate(1.234) assert_action_performed(widget, "redraw") @@ -635,7 +635,7 @@ def test_rotate(widget): def test_scale(widget): - """A scale operation can be added""" + """A scale operation can be added.""" draw_op = widget.context.scale(1.234, 2.345) assert_action_performed(widget, "redraw") @@ -652,7 +652,7 @@ def test_scale(widget): def test_translate(widget): - """A translate operation can be added""" + """A translate operation can be added.""" draw_op = widget.context.translate(10, 20) assert_action_performed(widget, "redraw") @@ -669,7 +669,7 @@ def test_translate(widget): def test_reset_transform(widget): - """A reset transform operation can be added""" + """A reset transform operation can be added.""" draw_op = widget.context.reset_transform() assert_action_performed(widget, "redraw") @@ -682,7 +682,7 @@ def test_reset_transform(widget): def test_deprecated_usage(widget): - """Test that deprecated usage of operations raise errors""" + """Test that deprecated usage of operations raise errors.""" with pytest.raises( RuntimeError, match=r"Context\.fill\(\) has been renamed Context\.Fill\(\)\.", diff --git a/core/tests/widgets/test_activityindicator.py b/core/tests/widgets/test_activityindicator.py index b304f04c47..5a890eccdd 100644 --- a/core/tests/widgets/test_activityindicator.py +++ b/core/tests/widgets/test_activityindicator.py @@ -14,14 +14,14 @@ def activity_indicator(): def test_widget_created(activity_indicator): - "An activity indicator can be created." + """An activity indicator can be created.""" # Round trip the impl/interface assert activity_indicator._impl.interface == activity_indicator assert_action_performed(activity_indicator, "create ActivityIndicator") def test_disable_no_op(activity_indicator): - "ActivityIndicator doesn't have a disabled state" + """ActivityIndicator doesn't have a disabled state.""" # Enabled by default assert activity_indicator.enabled @@ -33,7 +33,7 @@ def test_disable_no_op(activity_indicator): def test_start(activity_indicator): - "An activity indicator can be started" + """An activity indicator can be started.""" # Not running initially assert not activity_indicator.is_running @@ -51,7 +51,7 @@ def test_start(activity_indicator): def test_already_started(activity_indicator): - "If an activity indicator is already started, starting again is a no-op" + """If an activity indicator is already started, starting again is a no-op.""" # Start the activity indicator activity_indicator.start() @@ -69,7 +69,7 @@ def test_already_started(activity_indicator): def test_stop(activity_indicator): - "An indicator can be stopped" + """An indicator can be stopped.""" # Start spinning activity_indicator.start() @@ -87,7 +87,7 @@ def test_stop(activity_indicator): def test_already_stopped(activity_indicator): - "If an indicator is already stopped, stopping again is a no-op" + """If an indicator is already stopped, stopping again is a no-op.""" # The indicator is not running initially assert not activity_indicator.is_running @@ -102,7 +102,7 @@ def test_already_stopped(activity_indicator): def test_initially_running(): - "An activity indicator can be created in a started state" + """An activity indicator can be created in a started state.""" # Creating a new progress bar with running=True so it is already running activity_indicator = toga.ActivityIndicator(running=True) @@ -114,7 +114,7 @@ def test_initially_running(): def test_focus_noop(activity_indicator): - "Focus is a no-op." + """Focus is a no-op.""" activity_indicator.focus() assert_action_not_performed(activity_indicator, "focus") diff --git a/core/tests/widgets/test_base.py b/core/tests/widgets/test_base.py index f23e497fdb..0182924293 100644 --- a/core/tests/widgets/test_base.py +++ b/core/tests/widgets/test_base.py @@ -63,7 +63,7 @@ def test_widget_created(widget): def test_add_child_to_leaf(): - "A child cannot be added to a leaf node" + """A child cannot be added to a leaf node.""" leaf = ExampleLeafWidget() # Widget doesn't have an app or window @@ -82,7 +82,7 @@ def test_add_child_to_leaf(): def test_add_child_without_app(widget): - "A child can be added to a node when there's no underlying app" + """A child can be added to a node when there's no underlying app.""" # Widget doesn't have an app or window assert widget.app is None assert widget.window is None @@ -112,7 +112,7 @@ def test_add_child_without_app(widget): def test_add_child(app, widget): - "A child can be added to a node when there's an app & window" + """A child can be added to a node when there's an app & window.""" # Set the app and window for the widget. window = toga.Window() window.content = widget @@ -164,7 +164,7 @@ def test_add_child(app, widget): def test_add_multiple_children(app, widget): - "Multiple children can be added in one call" + """Multiple children can be added in one call.""" # Set the app and window for the widget. window = toga.Window() window.content = widget @@ -235,7 +235,7 @@ def test_add_multiple_children(app, widget): def test_reparent_child(widget): - "A widget can be reparented" + """A widget can be reparented.""" # Create a second parent widget, and add a child to it other = ExampleWidget(id="other") child = ExampleLeafWidget(id="child_id") @@ -265,7 +265,7 @@ def test_reparent_child(widget): def test_reparent_child_to_self(widget): - "Reparenting a widget to the same parent is a no-op" + """Reparenting a widget to the same parent is a no-op.""" # Add a child to the widget child = ExampleLeafWidget(id="child_id") widget.add(child) @@ -292,7 +292,7 @@ def test_reparent_child_to_self(widget): def test_insert_child_into_leaf(): - "A child cannot be inserted into a leaf node" + """A child cannot be inserted into a leaf node.""" leaf = ExampleLeafWidget() # Widget doesn't have an app or window @@ -311,7 +311,7 @@ def test_insert_child_into_leaf(): def test_insert_child_without_app(widget): - "A child can be inserted into a node when there's no underlying app" + """A child can be inserted into a node when there's no underlying app.""" # Widget doesn't have an app or window assert widget.app is None assert widget.window is None @@ -341,7 +341,7 @@ def test_insert_child_without_app(widget): def test_insert_child(app, widget): - "A child can be inserted into a node when there's an app & window" + """A child can be inserted into a node when there's an app & window.""" # Set the app and window for the widget. window = toga.Window() window.content = widget @@ -396,7 +396,7 @@ def test_insert_child(app, widget): def test_insert_position(app, widget): - "Insert can put a child into a specific position" + """Insert can put a child into a specific position.""" # Set the app and window for the widget. window = toga.Window() window.content = widget @@ -471,7 +471,7 @@ def test_insert_position(app, widget): def test_insert_bad_position(app, widget): - "If the position is invalid, an error is raised" + """If the position is invalid, an error is raised.""" # Set the app and window for the widget. window = toga.Window() window.content = widget @@ -529,7 +529,7 @@ def test_insert_bad_position(app, widget): def test_insert_reparent_child(widget): - "A widget can be reparented by insertion" + """A widget can be reparented by insertion.""" # Create a second parent widget, and add a child to it other = ExampleWidget(id="other") child = ExampleLeafWidget(id="child_id") @@ -559,7 +559,7 @@ def test_insert_reparent_child(widget): def test_insert_reparent_child_to_self(widget): - "Reparenting a widget to the same parent by insertion is a no-op" + """Reparenting a widget to the same parent by insertion is a no-op.""" # Add a child to the widget child = ExampleLeafWidget(id="child_id") widget.add(child) @@ -586,7 +586,7 @@ def test_insert_reparent_child_to_self(widget): def test_remove_child_from_leaf(): - "A child cannot be removed from a leaf node" + """A child cannot be removed from a leaf node.""" leaf = ExampleLeafWidget() # Widget doesn't have an app or window @@ -605,7 +605,7 @@ def test_remove_child_from_leaf(): def test_remove_child_without_app(widget): - "A child without an app or window can be removed from a widget" + """A child without an app or window can be removed from a widget.""" # Add a child to the widget child = ExampleLeafWidget(id="child_id") widget.add(child) @@ -634,7 +634,7 @@ def test_remove_child_without_app(widget): def test_remove_child(app, widget): - "A child associated with an app & window can be removed from a widget" + """A child associated with an app & window can be removed from a widget.""" # Add a child to the widget child = ExampleLeafWidget(id="child_id") widget.add(child) @@ -680,7 +680,7 @@ def test_remove_child(app, widget): def test_remove_multiple_children(app, widget): - "Multiple children can be removed from a widget" + """Multiple children can be removed from a widget.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -739,7 +739,7 @@ def test_remove_multiple_children(app, widget): def test_clear_all_children(app, widget): - "All children can be simultaneously removed from a widget" + """All children can be simultaneously removed from a widget.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -801,7 +801,7 @@ def test_clear_all_children(app, widget): def test_clear_no_children(app, widget): - "No changes are made (no-op) if widget has no children" + """No changes are made (no-op) if widget has no children.""" window = toga.Window() window.content = widget # Clear the event log @@ -823,7 +823,7 @@ def test_clear_no_children(app, widget): def test_clear_leaf(app): - "`clear` cannot be called on a leaf node" + """`clear` cannot be called on a leaf node.""" leaf = ExampleLeafWidget() window = toga.Window() window.content = leaf @@ -847,7 +847,7 @@ def test_clear_leaf(app): def test_remove_from_non_parent(widget): - "Trying to remove a child from a widget other than it's parent is a no-op" + """Trying to remove a child from a widget other than it's parent is a no-op.""" # Create a second parent widget, and add a child to it other = ExampleWidget(id="other") child = ExampleLeafWidget(id="child_id") @@ -873,7 +873,7 @@ def test_remove_from_non_parent(widget): def test_set_app(app, widget): - "A widget can be assigned to an app" + """A widget can be assigned to an app.""" assert len(app.widgets) == 0 # Assign the widget to an app @@ -899,7 +899,7 @@ def test_set_app(app, widget): def test_set_app_with_children(app, widget): - "If a widget has children, the children get the app assignment" + """If a widget has children, the children get the app assignment.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -935,7 +935,7 @@ def test_set_app_with_children(app, widget): def test_set_same_app(app, widget): - "A widget can be re-assigned to the same app" + """A widget can be re-assigned to the same app.""" assert len(app.widgets) == 0 # Assign the widget to an app @@ -947,12 +947,12 @@ def test_set_same_app(app, widget): # Assign the widget to the same app widget.app = app - # The impl has not had it's app property set as a result of the update + # The impl has not had its app property set as a result of the update assert_attribute_not_set(widget, "app") def test_reset_app(app, widget): - "A widget can be re-assigned to no app" + """A widget can be re-assigned to no app.""" assert len(app.widgets) == 0 # Assign the widget to an app @@ -970,12 +970,12 @@ def test_reset_app(app, widget): # The widget index has been updated assert len(app.widgets) == 0 - # The impl has had it's app property set. + # The impl has had its app property set. assert attribute_value(widget, "app") is None def test_set_new_app(app, widget): - "A widget can be assigned to a different app" + """A widget can be assigned to a different app.""" # Assign the widget to an app. It won't appear in the registry, as # it hasn't been assigned to a window widget.app = app @@ -1000,12 +1000,12 @@ def test_set_new_app(app, widget): assert len(new_app.widgets) == 0 assert "widget_id" not in new_app.widgets - # The impl has had it's app property set. + # The impl has had its app property set. assert attribute_value(widget, "app") == new_app def test_set_window(widget): - "A widget can be assigned to a window." + """A widget can be assigned to a window.""" window = toga.Window() assert len(window.widgets) == 0 assert widget.window is None @@ -1022,7 +1022,7 @@ def test_set_window(widget): def test_set_window_with_children(app, widget): - "A widget can be assigned to a window." + """A widget can be assigned to a window.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -1056,7 +1056,7 @@ def test_set_window_with_children(app, widget): def test_reset_window(widget): - "A widget can be assigned to a different window." + """A widget can be assigned to a different window.""" window = toga.Window() assert len(window.widgets) == 0 assert widget.window is None @@ -1081,7 +1081,7 @@ def test_reset_window(widget): def test_unset_window(widget): - "A widget can be assigned to no window." + """A widget can be assigned to no window.""" window = toga.Window() assert len(window.widgets) == 0 assert widget.window is None @@ -1112,7 +1112,7 @@ def test_unset_window(widget): ], ) def test_enabled(widget, value, expected): - "The enabled status of the widget can be changed." + """The enabled status of the widget can be changed.""" # Widget is initially enabled by default. assert widget.enabled @@ -1130,7 +1130,7 @@ def test_enabled(widget, value, expected): def test_refresh_root(widget): - "Refresh can be invoked on the root node" + """Refresh can be invoked on the root node.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -1145,7 +1145,7 @@ def test_refresh_root(widget): def test_refresh_child(widget): - "Refresh can be invoked on child" + """Refresh can be invoked on child.""" # Add children to the widget child1 = ExampleLeafWidget(id="child1_id") child2 = ExampleLeafWidget(id="child2_id") @@ -1163,13 +1163,13 @@ def test_refresh_child(widget): def test_focus(widget): - "A widget can be given focus" + """A widget can be given focus.""" widget.focus() assert_action_performed(widget, "focus") def test_tab_index(widget): - "The tab index of a widget can be set and retrieved" + """The tab index of a widget can be set and retrieved.""" # The initial tab index is None assert widget.tab_index is None diff --git a/core/tests/widgets/test_box.py b/core/tests/widgets/test_box.py index 2a9f5d1ad1..d4e0b80410 100644 --- a/core/tests/widgets/test_box.py +++ b/core/tests/widgets/test_box.py @@ -7,7 +7,7 @@ def test_create_box(): - "A Box can be created." + """A Box can be created.""" box = toga.Box() # Round trip the impl/interface assert box._impl.interface == box @@ -17,7 +17,7 @@ def test_create_box(): def test_create_box_with_children(): - "A Box can be created with children." + """A Box can be created with children.""" child1 = toga.Box() child2 = toga.Box() box = toga.Box(children=[child1, child2]) @@ -34,7 +34,7 @@ def test_create_box_with_children(): def test_disable_no_op(): - "Box doesn't have a disabled state" + """Box doesn't have a disabled state.""" box = toga.Box() # Enabled by default @@ -48,7 +48,7 @@ def test_disable_no_op(): def test_focus_noop(): - "Focus is a no-op." + """Focus is a no-op.""" box = toga.Box() box.focus() diff --git a/core/tests/widgets/test_dateinput.py b/core/tests/widgets/test_dateinput.py index 759d9a0e40..d6c4f79737 100644 --- a/core/tests/widgets/test_dateinput.py +++ b/core/tests/widgets/test_dateinput.py @@ -33,7 +33,7 @@ def test_widget_created(): def test_widget_created_with_values(on_change_handler): - """A DateInput can be created with initial values""" + """A DateInput can be created with initial values.""" # Round trip the impl/interface widget = toga.DateInput( value=date(2015, 6, 15), @@ -64,7 +64,7 @@ def test_widget_created_with_values(on_change_handler): ], ) def test_value(widget, value, expected, on_change_handler): - "The value of the datepicker can be set" + """The value of the datepicker can be set.""" widget.value = value assert widget.value == expected @@ -82,7 +82,7 @@ def test_value(widget, value, expected, on_change_handler): @pytest.mark.parametrize("value, exc, message", INVALID_VALUES) def test_invalid_value(widget, value, exc, message): - "Invalid date values raise an exception" + """Invalid date values raise an exception.""" with pytest.raises(exc, match=message): widget.value = value @@ -104,7 +104,7 @@ def test_invalid_value(widget, value, exc, message): ], ) def test_value_clipping(widget, value, clipped, on_change_handler): - "It the value is inconsistent with min/max, it is clipped." + """It the value is inconsistent with min/max, it is clipped.""" # Set min/max dates, and clear the on_change mock widget.min = date(2010, 1, 1) widget.max = date(2020, 1, 1) @@ -130,7 +130,7 @@ def test_value_clipping(widget, value, clipped, on_change_handler): ], ) def test_min(widget, value, expected): - "The min of the datepicker can be set" + """The min of the datepicker can be set.""" widget.min = value assert widget.min == expected @@ -144,7 +144,7 @@ def test_min(widget, value, expected): @pytest.mark.parametrize("value, exc, message", INVALID_LIMITS) def test_invalid_min(widget, value, exc, message): - "Invalid min values raise an exception" + """Invalid min values raise an exception.""" widget.max = date(2025, 6, 12) with pytest.raises(exc, match=message): @@ -164,7 +164,7 @@ def test_invalid_min(widget, value, exc, message): ], ) def test_min_clip(widget, on_change_handler, min, clip_value, clip_max): - "If the current value or max is before a new min date, it is clipped" + """If the current value or max is before a new min date, it is clipped.""" widget.value = date(2005, 6, 25) widget.max = date(2005, 12, 31) on_change_handler.reset_mock() @@ -195,7 +195,7 @@ def test_min_clip(widget, on_change_handler, min, clip_value, clip_max): ], ) def test_max(widget, value, expected): - "The max of the datepicker can be set" + """The max of the datepicker can be set.""" widget.max = value assert widget.max == expected @@ -203,7 +203,7 @@ def test_max(widget, value, expected): @pytest.mark.parametrize("value, exc, message", INVALID_LIMITS) def test_invalid_max(widget, value, exc, message): - "Invalid max values raise an exception" + """Invalid max values raise an exception.""" widget.min = date(2015, 6, 12) with pytest.raises(exc, match=message): @@ -223,7 +223,7 @@ def test_invalid_max(widget, value, exc, message): ], ) def test_max_clip(widget, on_change_handler, max, clip_value, clip_min): - "If the current value or min is after a new max date, it is clipped" + """If the current value or min is after a new max date, it is clipped.""" widget.min = date(2005, 6, 25) widget.value = date(2005, 12, 31) on_change_handler.reset_mock() diff --git a/core/tests/widgets/test_detailedlist.py b/core/tests/widgets/test_detailedlist.py index eb051a632c..c22fc7d4dc 100644 --- a/core/tests/widgets/test_detailedlist.py +++ b/core/tests/widgets/test_detailedlist.py @@ -62,7 +62,7 @@ def detailedlist( def test_detailedlist_created(): - "An minimal DetailedList can be created" + """A minimal DetailedList can be created.""" detailedlist = toga.DetailedList() assert detailedlist._impl.interface == detailedlist assert_action_performed(detailedlist, "create DetailedList") @@ -91,7 +91,7 @@ def test_create_with_values( on_primary_action_handler, on_secondary_action_handler, ): - "A DetailedList can be created with initial values" + """A DetailedList can be created with initial values.""" detailedlist = toga.DetailedList( data=source, accessors=("key", "value", "icon"), @@ -122,7 +122,7 @@ def test_create_with_values( def test_disable_no_op(detailedlist): - "DetailedList doesn't have a disabled state" + """DetailedList doesn't have a disabled state.""" # Enabled by default assert detailedlist.enabled @@ -134,7 +134,7 @@ def test_disable_no_op(detailedlist): def test_focus_noop(detailedlist): - "Focus is a no-op." + """Focus is a no-op.""" detailedlist.focus() assert_action_not_performed(detailedlist, "focus") @@ -192,7 +192,7 @@ def test_set_data( all_attributes, extra_attributes, ): - "Data can be set from a variety of sources" + """Data can be set from a variety of sources.""" # The selection hasn't changed yet. on_select_handler.assert_not_called() @@ -231,7 +231,7 @@ def test_set_data( def test_selection(detailedlist, on_select_handler): - "The current selection can be retrieved" + """The current selection can be retrieved.""" # Selection is initially empty assert detailedlist.selection is None on_select_handler.assert_not_called() @@ -247,7 +247,7 @@ def test_selection(detailedlist, on_select_handler): def test_refresh(detailedlist, on_refresh_handler): - "Completion of a refresh event triggers the cleanup handler" + """Completion of a refresh event triggers the cleanup handler.""" # Stimulate a refresh. detailedlist._impl.stimulate_refresh() @@ -264,7 +264,7 @@ def test_refresh(detailedlist, on_refresh_handler): def test_scroll_to_top(detailedlist): - "A DetailedList can be scrolled to the top" + """A DetailedList can be scrolled to the top.""" detailedlist.scroll_to_top() assert_action_performed_with(detailedlist, "scroll to row", row=0) @@ -286,14 +286,14 @@ def test_scroll_to_top(detailedlist): ], ) def test_scroll_to_row(detailedlist, row, effective): - "A DetailedList can be scrolled to a specific row" + """A DetailedList can be scrolled to a specific row.""" detailedlist.scroll_to_row(row) assert_action_performed_with(detailedlist, "scroll to row", row=effective) def test_scroll_to_row_no_data(detailedlist): - "If there's no data, scrolling is a no-op" + """If there's no data, scrolling is a no-op.""" detailedlist.data.clear() detailedlist.scroll_to_row(5) @@ -302,7 +302,7 @@ def test_scroll_to_row_no_data(detailedlist): def test_scroll_to_bottom(detailedlist): - "A DetailedList can be scrolled to the top" + """A DetailedList can be scrolled to the top.""" detailedlist.scroll_to_bottom() assert_action_performed_with(detailedlist, "scroll to row", row=2) @@ -312,7 +312,7 @@ def test_scroll_to_bottom(detailedlist): # 2023-07: Backwards compatibility ###################################################################### def test_deprecated_names(on_primary_action_handler): - "Deprecated names still work" + """Deprecated names still work.""" # Can't specify both on_delete and on_primary_action with pytest.raises( diff --git a/core/tests/widgets/test_divider.py b/core/tests/widgets/test_divider.py index 12e02acb9c..bfa2d4fae7 100644 --- a/core/tests/widgets/test_divider.py +++ b/core/tests/widgets/test_divider.py @@ -9,7 +9,7 @@ def test_divider_created(): - "A divider can be created." + """A divider can be created.""" divider = toga.Divider() # Round trip the impl/interface @@ -22,7 +22,7 @@ def test_divider_created(): @pytest.mark.parametrize("direction", [toga.Divider.HORIZONTAL, toga.Divider.VERTICAL]) def test_divider_created_explicit(direction): - "A divider can be created." + """A divider can be created.""" divider = toga.Divider(direction=direction) # Round trip the impl/interface @@ -34,7 +34,7 @@ def test_divider_created_explicit(direction): def test_disable_no_op(): - "Divider doesn't have a disabled state" + """Divider doesn't have a disabled state.""" divider = toga.Divider() # Enabled by default @@ -48,7 +48,7 @@ def test_disable_no_op(): def test_update_direction(): - "The direction of the divider can be altered." + """The direction of the divider can be altered.""" divider = toga.Divider(direction=toga.Divider.HORIZONTAL) # Initial direction is as expected @@ -66,7 +66,7 @@ def test_update_direction(): def test_focus_noop(): - "Focus is a no-op." + """Focus is a no-op.""" divider = toga.Divider(direction=toga.Divider.HORIZONTAL) divider.focus() diff --git a/core/tests/widgets/test_imageview.py b/core/tests/widgets/test_imageview.py index 4128b01806..2b423ccac3 100644 --- a/core/tests/widgets/test_imageview.py +++ b/core/tests/widgets/test_imageview.py @@ -21,7 +21,7 @@ def widget(app): def test_create_empty(widget): - """A empty ImageView can be created""" + """An empty ImageView can be created.""" # interface/impl round trips assert widget._impl.interface is widget assert_action_performed(widget, "create ImageView") @@ -35,7 +35,7 @@ def test_create_empty(widget): def test_create_from_toga_image(app): - """An ImageView can be created from a Toga image""" + """An ImageView can be created from a Toga image.""" image = toga.Image(ABSOLUTE_FILE_PATH) widget = toga.ImageView(image=image) @@ -50,7 +50,7 @@ def test_create_from_toga_image(app): def test_create_from_pil(): - """An ImageView can be created from a PIL image""" + """An ImageView can be created from a PIL image.""" with PIL.Image.open(ABSOLUTE_FILE_PATH) as pil_img: pil_img.load() @@ -60,7 +60,7 @@ def test_create_from_pil(): def test_disable_no_op(widget): - """ImageView doesn't have a disabled state""" + """ImageView doesn't have a disabled state.""" # Enabled by default assert widget.enabled @@ -79,7 +79,7 @@ def test_focus_noop(widget): def test_set_image_str(widget): - """The image can be set with a string""" + """The image can be set with a string.""" widget.image = ABSOLUTE_FILE_PATH assert_action_performed_with(widget, "set image", image=ANY) @@ -90,7 +90,7 @@ def test_set_image_str(widget): def test_set_image_path(widget): - """The image can be set with a Path""" + """The image can be set with a Path.""" widget.image = Path(ABSOLUTE_FILE_PATH) assert_action_performed_with(widget, "set image", image=ANY) @@ -101,7 +101,7 @@ def test_set_image_path(widget): def test_set_image(widget): - "The image can be set with an Image instance" + """The image can be set with an Image instance.""" image = toga.Image(Path(ABSOLUTE_FILE_PATH)) widget.image = image assert_action_performed_with(widget, "set image", image=image) @@ -111,7 +111,7 @@ def test_set_image(widget): def test_set_image_none(app): - "The image can be cleared" + """The image can be cleared.""" widget = toga.ImageView(image=ABSOLUTE_FILE_PATH) assert widget.image is not None diff --git a/core/tests/widgets/test_label.py b/core/tests/widgets/test_label.py index cb77bba260..51d52c592e 100644 --- a/core/tests/widgets/test_label.py +++ b/core/tests/widgets/test_label.py @@ -15,7 +15,7 @@ def label(): def test_label_created(label): - "A label can be created." + """A label can be created.""" # Round trip the impl/interface assert label._impl.interface == label assert_action_performed(label, "create Label") @@ -48,7 +48,7 @@ def test_update_label_text(label, value, expected): def test_focus_noop(label): - "Focus is a no-op." + """Focus is a no-op.""" label.focus() assert_action_not_performed(label, "focus") diff --git a/core/tests/widgets/test_multilinetextinput.py b/core/tests/widgets/test_multilinetextinput.py index f4b507511a..f8b2acf1f5 100644 --- a/core/tests/widgets/test_multilinetextinput.py +++ b/core/tests/widgets/test_multilinetextinput.py @@ -12,7 +12,7 @@ def widget(): def test_widget_created(widget): - "A multiline text input" + """A multiline text input.""" assert widget._impl.interface == widget assert_action_performed(widget, "create MultilineTextInput") @@ -23,7 +23,7 @@ def test_widget_created(widget): def test_create_with_values(): - "A multiline text input can be created with initial values" + """A multiline text input can be created with initial values.""" on_change = Mock() widget = toga.MultilineTextInput( value="Some text", @@ -87,7 +87,7 @@ def test_value(widget, value, expected): ], ) def test_readonly(widget, value, expected): - "The readonly status of the widget can be changed." + """The readonly status of the widget can be changed.""" # Widget is initially not readonly by default. assert not widget.readonly diff --git a/core/tests/widgets/test_numberinput.py b/core/tests/widgets/test_numberinput.py index a9dbef3795..ffb2d0d61f 100644 --- a/core/tests/widgets/test_numberinput.py +++ b/core/tests/widgets/test_numberinput.py @@ -18,7 +18,7 @@ def widget(): def test_widget_created(): - "A NumberInput can be created with minimal arguments" + """A NumberInput can be created with minimal arguments.""" widget = toga.NumberInput() assert widget._impl.interface == widget @@ -33,7 +33,7 @@ def test_widget_created(): def test_create_with_values(): - "A NumberInput can be created with initial values" + """A NumberInput can be created with initial values.""" on_change = Mock() widget = toga.NumberInput( @@ -83,7 +83,7 @@ def test_create_with_values(): ], ) def test_value(widget, value, expected): - "The value of the widget can be set" + """The value of the widget can be set.""" # Clear the event log and validator mock EventLog.reset() @@ -112,7 +112,7 @@ def test_value(widget, value, expected): ], ) def test_bad_value(widget, value): - "If a value can't be converted into a decimal, an error is raised" + """If a value can't be converted into a decimal, an error is raised.""" with pytest.raises(ValueError, match=r"value must be a number or None"): widget.value = value @@ -134,7 +134,7 @@ def test_bad_value(widget, value): ], ) def test_step(widget, value, expected): - "The step of the widget can be set" + """The step of the widget can be set.""" widget.step = value assert widget.step == expected @@ -159,7 +159,7 @@ def test_step(widget, value, expected): @pytest.mark.parametrize(*QUANTIZE_PARAMS) def test_quantization(widget, step, expected): - "The value is quantized to the precision of the step" + """The value is quantized to the precision of the step.""" widget.step = step widget.value = 12.3456 @@ -169,7 +169,7 @@ def test_quantization(widget, step, expected): @pytest.mark.parametrize(*QUANTIZE_PARAMS) def test_quantize_on_retrieval(widget, step, expected): - "A widget's value will be quantized on retrieval." + """A widget's value will be quantized on retrieval.""" widget.step = step # Inject a raw attribute value. @@ -187,7 +187,7 @@ def test_quantize_on_retrieval(widget, step, expected): ], ) def test_bad_step(widget, value): - "If a step can't be converted into a decimal, an error is raised" + """If a step can't be converted into a decimal, an error is raised.""" with pytest.raises(ValueError, match=r"step must be a number"): widget.step = "not a number" @@ -217,7 +217,7 @@ def test_bad_step(widget, value): ], ) def test_min(widget, value, expected): - "The min of the widget can be set" + """The min of the widget can be set.""" widget.min = value assert widget.min == expected @@ -233,13 +233,13 @@ def test_min(widget, value, expected): ], ) def test_bad_min(widget, value): - "If a min can't be converted into a decimal, an error is raised" + """If a min can't be converted into a decimal, an error is raised.""" with pytest.raises(ValueError, match=r"min must be a number or None"): widget.min = value def test_min_greater_than_max(widget): - "If the new min value exceeds the max value, the max value is clipped" + """If the new min value exceeds the max value, the max value is clipped.""" widget.max = 10 widget.min = 100 @@ -249,7 +249,7 @@ def test_min_greater_than_max(widget): @pytest.mark.parametrize(*QUANTIZE_PARAMS) def test_min_quantized(widget, step, expected): - "An existing min value is re-quantized after a change in step" + """An existing min value is re-quantized after a change in step.""" # Set a small step so that the min value isn't quantized widget.step = 0.00000001 widget.min = 12.3456 @@ -286,7 +286,7 @@ def test_min_quantized(widget, step, expected): ], ) def test_max(widget, value, expected): - "The max of the widget can be set" + """The max of the widget can be set.""" widget.max = value assert widget.max == expected @@ -302,13 +302,13 @@ def test_max(widget, value, expected): ], ) def test_bad_max(widget, value): - "If a max can't be converted into a decimal, an error is raised" + """If a max can't be converted into a decimal, an error is raised.""" with pytest.raises(ValueError, match=r"max must be a number or None"): widget.max = value def test_max_less_than_min(widget): - "If the new max value is less than the min value, the min value is clipped" + """If the new max value is less than the min value, the min value is clipped.""" widget.min = 100 widget.max = 10 @@ -318,7 +318,7 @@ def test_max_less_than_min(widget): @pytest.mark.parametrize(*QUANTIZE_PARAMS) def test_max_quantized(widget, step, expected): - "An existing max value is re-quantized after a change in step" + """An existing max value is re-quantized after a change in step.""" # Set a small step so that the max value isn't quantized widget.step = 0.00000001 widget.max = 12.3456 @@ -343,7 +343,7 @@ def test_max_quantized(widget, step, expected): ], ) def test_clip_on_value_change(widget, min, max, provided, clipped): - "A widget's value will be clipped inside the min/max range." + """A widget's value will be clipped inside the min/max range.""" widget.min = min widget.max = max @@ -367,7 +367,8 @@ def test_clip_on_value_change(widget, min, max, provided, clipped): ], ) def test_clip_on_retrieval(widget, min, max, provided, clipped): - "A widget's value will be clipped if the widget has a value outside the min/max range." + """A widget's value will be clipped if the widget has a value outside the min/max + range.""" widget.min = min widget.max = max @@ -387,7 +388,7 @@ def test_clip_on_retrieval(widget, min, max, provided, clipped): ], ) def test_clip_on_max_change(widget, value, new_max, clipped): - "A widget's value will be clipped if the max value changes" + """A widget's value will be clipped if the max value changes.""" # Set an initial max, and a value that is less than it. widget.max = 20 widget.value = value @@ -409,7 +410,7 @@ def test_clip_on_max_change(widget, value, new_max, clipped): ], ) def test_clip_on_min_change(widget, value, new_min, clipped): - "A widget's value will be clipped if the min value changes" + """A widget's value will be clipped if the min value changes.""" # Set an initial max, and a value that is less than it. widget.min = 10 widget.value = value @@ -449,7 +450,7 @@ def test_on_change(widget): ("1.23", "1.23"), ("-1.23", "-1.23"), (".123", ".123"), - # Non alphanumeric + # Non-alphanumeric ("12a3b", "123"), ("12!3@", "123"), # - not at the start @@ -497,7 +498,7 @@ def test_clean_decimal(value, step, clean): def test_deprecated_names(): - """The deprecated min_value/max_value names still work""" + """The deprecated min_value/max_value names still work.""" # Can't specify min and min_value with pytest.raises( ValueError, diff --git a/core/tests/widgets/test_optioncontainer.py b/core/tests/widgets/test_optioncontainer.py index d31b28949b..bdf83fcc4d 100644 --- a/core/tests/widgets/test_optioncontainer.py +++ b/core/tests/widgets/test_optioncontainer.py @@ -68,7 +68,7 @@ def optioncontainer( def test_widget_create(): - "An option container can be created with no arguments" + """An option container can be created with no arguments.""" optioncontainer = toga.OptionContainer() assert_action_performed(optioncontainer, "create OptionContainer") @@ -86,7 +86,7 @@ def test_widget_create_with_args( on_select_handler, tab_icon, ): - "An option container can be created with arguments" + """An option container can be created with arguments.""" assert optioncontainer._impl.interface == optioncontainer assert_action_performed(optioncontainer, "create OptionContainer") @@ -121,7 +121,8 @@ def test_widget_create_with_args( ], ) def test_widget_create_invalid_content(value): - """If the content provided at construction isn't 2- or 3-tuples, an error is raised.""" + """If the content provided at construction isn't 2- or 3-tuples, an error is + raised.""" with pytest.raises( ValueError, match=( @@ -133,7 +134,7 @@ def test_widget_create_invalid_content(value): def test_item_create(content1): - """An OptionItem can be created""" + """An OptionItem can be created.""" item = toga.OptionItem("label", content1) assert item.text == "label" @@ -159,7 +160,7 @@ def test_item_create_invalid_item( content1, tab_icon, ): - """If item details are invalid, an exception is raised""" + """If item details are invalid, an exception is raised.""" with pytest.raises(ValueError, match=message): toga.OptionItem( @@ -171,7 +172,7 @@ def test_item_create_invalid_item( def test_assign_to_app(app, optioncontainer, content1, content2, content3): - """If the widget is assigned to an app, the content is also assigned""" + """If the widget is assigned to an app, the content is also assigned.""" # Option container is initially unassigned assert optioncontainer.app is None @@ -188,7 +189,8 @@ def test_assign_to_app(app, optioncontainer, content1, content2, content3): def test_assign_to_app_no_content(app): - """If the widget is assigned to an app, and there is no content, there's no error""" + """If the widget is assigned to an app, and there is no content, there's no + error.""" optioncontainer = toga.OptionContainer() # Option container is initially unassigned @@ -202,7 +204,7 @@ def test_assign_to_app_no_content(app): def test_assign_to_window(window, optioncontainer, content1, content2, content3): - """If the widget is assigned to a window, the content is also assigned""" + """If the widget is assigned to a window, the content is also assigned.""" # Option container is initially unassigned assert optioncontainer.window is None @@ -218,7 +220,8 @@ def test_assign_to_window(window, optioncontainer, content1, content2, content3) def test_assign_to_window_no_content(window): - """If the widget is assigned to a window, and there is no content, there's no error""" + """If the widget is assigned to a window, and there is no content, there's no + error.""" optioncontainer = toga.OptionContainer() # Option container is initially unassigned @@ -232,7 +235,7 @@ def test_assign_to_window_no_content(window): def test_disable_no_op(optioncontainer): - """OptionContainer doesn't have a disabled state""" + """OptionContainer doesn't have a disabled state.""" # Enabled by default assert optioncontainer.enabled @@ -286,7 +289,7 @@ def test_item_enabled(optioncontainer, value, expected, bare_item): def test_disable_current_item(optioncontainer): - """The currently selected item cannot be disabled""" + """The currently selected item cannot be disabled.""" # Item 0 is selected by default item = optioncontainer.content[0] with pytest.raises( @@ -342,7 +345,7 @@ def test_item_text(optioncontainer, value, expected, bare_item): ], ) def test_invalid_item_text(optioncontainer, value, error, bare_item): - """Invalid item titles are prevented""" + """Invalid item titles are prevented.""" if bare_item: item = toga.OptionItem("title", toga.Box()) else: @@ -438,7 +441,7 @@ def test_item_icon_disabled(monkeypatch, optioncontainer, bare_item): def test_optionlist_repr(optioncontainer): - """OptionContainer content has a helpful repr""" + """OptionContainer content has a helpful repr.""" assert ( repr(optioncontainer.content) == "" @@ -446,7 +449,7 @@ def test_optionlist_repr(optioncontainer): def test_optionlist_iter(optioncontainer): - """OptionContainer content can be iterated""" + """OptionContainer content can be iterated.""" assert [item.text for item in optioncontainer.content] == [ "Item 1", "Item 2", @@ -456,13 +459,13 @@ def test_optionlist_iter(optioncontainer): def test_optionlist_len(optioncontainer): - """OptionContainer content has length""" + """OptionContainer content has length.""" assert len(optioncontainer.content) == 4 @pytest.mark.parametrize("index", [1, "Item 2", None]) def test_getitem(optioncontainer, content2, index): - """An item can be retrieved""" + """An item can be retrieved.""" if index is None: index = optioncontainer.content[1] @@ -502,7 +505,7 @@ def test_delitem(optioncontainer, index): @pytest.mark.parametrize("index", [0, "Item 1", None]) def test_delitem_current(optioncontainer, index): - """The current item can't be deleted""" + """The current item can't be deleted.""" if index is None: index = optioncontainer.content[0] @@ -514,7 +517,7 @@ def test_delitem_current(optioncontainer, index): @pytest.mark.parametrize("index", [1, "Item 2", None]) def test_item_remove(optioncontainer, index): - """An item can be removed with remove""" + """An item can be removed with remove.""" if index is None: index = optioncontainer.content[1] @@ -541,7 +544,7 @@ def test_item_remove(optioncontainer, index): @pytest.mark.parametrize("index", [0, "Item 1", None]) def test_item_remove_current(optioncontainer, index): - """The current item can't be removed""" + """The current item can't be removed.""" if index is None: index = optioncontainer.content[0] @@ -552,7 +555,7 @@ def test_item_remove_current(optioncontainer, index): def test_item_insert_item(optioncontainer): - """The text of an inserted item can be set""" + """The text of an inserted item can be set.""" new_content = toga.Box() item = toga.OptionItem("New Tab", new_content) @@ -601,7 +604,7 @@ def test_item_insert_item(optioncontainer): ], ) def test_item_insert_item_invalid(optioncontainer, args, kwargs, message): - """If both an item and specific details are provided, an error is raised""" + """If both an item and specific details are provided, an error is raised.""" with pytest.raises(ValueError, match=message): optioncontainer.content.insert(1, *args, **kwargs) @@ -615,7 +618,7 @@ def test_item_insert_item_invalid(optioncontainer, args, kwargs, message): ], ) def test_item_insert_text(optioncontainer, value, expected): - """The text of an inserted item can be set""" + """The text of an inserted item can be set.""" new_content = toga.Box() optioncontainer.content.insert(1, value, new_content, enabled=True) @@ -646,7 +649,7 @@ def test_item_insert_text(optioncontainer, value, expected): ], ) def test_item_insert_invalid_text(optioncontainer, value, error): - """The item text must be valid""" + """The item text must be valid.""" new_content = toga.Box() with pytest.raises(ValueError, match=error): optioncontainer.content.insert(1, value, new_content, enabled=True) @@ -654,7 +657,7 @@ def test_item_insert_invalid_text(optioncontainer, value, error): @pytest.mark.parametrize("enabled", [True, False]) def test_item_insert_enabled(optioncontainer, enabled): - """The enabled status of content can be set on insert""" + """The enabled status of content can be set on insert.""" new_content = toga.Box() optioncontainer.content.insert(1, "New content", new_content, enabled=enabled) @@ -678,7 +681,7 @@ def test_item_insert_enabled(optioncontainer, enabled): @pytest.mark.parametrize("enabled", [True, False]) def test_item_append(optioncontainer, enabled): - """An item can be appended to the content list""" + """An item can be appended to the content list.""" # append is implemented using insert; # the bulk of the functionality is tested there. new_content = toga.Box() diff --git a/core/tests/widgets/test_passwordinput.py b/core/tests/widgets/test_passwordinput.py index cca7c8db19..298b1ca67f 100644 --- a/core/tests/widgets/test_passwordinput.py +++ b/core/tests/widgets/test_passwordinput.py @@ -5,7 +5,7 @@ def test_widget_created(): - "A text input can be created" + """A text input can be created.""" widget = toga.PasswordInput() assert widget._impl.interface == widget assert_action_performed(widget, "create PasswordInput") @@ -21,7 +21,7 @@ def test_widget_created(): def test_create_with_values(): - "A multiline text input can be created with initial values" + """A multiline text input can be created with initial values.""" on_change = Mock() on_confirm = Mock() on_gain_focus = Mock() diff --git a/core/tests/widgets/test_progressbar.py b/core/tests/widgets/test_progressbar.py index 9b82f80511..7a73c82031 100644 --- a/core/tests/widgets/test_progressbar.py +++ b/core/tests/widgets/test_progressbar.py @@ -17,7 +17,7 @@ def progressbar(): def test_progressbar_created(progressbar): - "A progressbar can be created." + """A progressbar can be created.""" # Round trip the impl/interface assert progressbar._impl.interface == progressbar assert_action_performed(progressbar, "create ProgressBar") @@ -41,7 +41,7 @@ def test_progressbar_created(progressbar): ], ) def test_set_value_determinate(progressbar, value, actual): - "The value of a determinite progressbar can be set" + """The value of a determinate progressbar can be set.""" # Set the max value progressbar.max = 10 @@ -56,7 +56,7 @@ def test_set_value_determinate(progressbar, value, actual): def test_set_value_indeterminate(progressbar): - "Setting the value of an indeterminate progressbar is a no-op" + """Setting the value of an indeterminate progressbar is a no-op.""" # Make the progressbar indeterminate progressbar.max = None @@ -84,7 +84,7 @@ def test_set_value_indeterminate(progressbar): ], ) def test_set_max(progressbar, value, actual, determinate): - "The max value can be set." + """The max value can be set.""" progressbar.max = value # The maximum value has been applied, and has altered the determinate state. @@ -132,13 +132,13 @@ def test_set_max(progressbar, value, actual, determinate): ], ) def test_invalid_max(progressbar, value, error, msg): - "A max value that isn't positive raises an error" + """A max value that isn't positive raises an error.""" with pytest.raises(error, match=msg): progressbar.max = value def test_start(progressbar): - "An activity indicator can be started" + """An activity indicator can be started.""" # Not running initially assert not progressbar.is_running @@ -156,7 +156,7 @@ def test_start(progressbar): def test_already_started(progressbar): - "If an activity indicator is already started, starting again is a no-op" + """If an activity indicator is already started, starting again is a no-op.""" # Start the activity indicator progressbar.start() @@ -174,7 +174,7 @@ def test_already_started(progressbar): def test_stop(progressbar): - "An indicator can be stopped" + """An indicator can be stopped.""" # Start running progressbar.start() @@ -192,7 +192,7 @@ def test_stop(progressbar): def test_already_stopped(progressbar): - "If an indicator is already stopped, stopping again is a no-op" + """If an indicator is already stopped, stopping again is a no-op.""" # The indicator is not running initially assert not progressbar.is_running @@ -207,7 +207,7 @@ def test_already_stopped(progressbar): def test_initially_running(): - "An activity indicator can be created in a started state" + """An activity indicator can be created in a started state.""" # Creating a new progress bar with running=True so it is already running progressbar = toga.ProgressBar(running=True) @@ -219,7 +219,7 @@ def test_initially_running(): def test_determinate_switch(progressbar): - "A progressbar can switch between determinate and indeterminate" + """A progressbar can switch between determinate and indeterminate.""" # Set initial max and value progressbar.max = 10 progressbar.value = 5 diff --git a/core/tests/widgets/test_scrollcontainer.py b/core/tests/widgets/test_scrollcontainer.py index 024701bce9..cacb76099d 100644 --- a/core/tests/widgets/test_scrollcontainer.py +++ b/core/tests/widgets/test_scrollcontainer.py @@ -32,7 +32,7 @@ def scroll_container(content, on_scroll_handler): def test_widget_created(): - "A scroll container can be created with no arguments" + """A scroll container can be created with no arguments.""" scroll_container = toga.ScrollContainer() assert scroll_container._impl.interface == scroll_container assert_action_performed(scroll_container, "create ScrollContainer") @@ -44,7 +44,7 @@ def test_widget_created(): def test_widget_created_with_values(content, on_scroll_handler): - "A scroll container can be created with arguments" + """A scroll container can be created with arguments.""" scroll_container = toga.ScrollContainer( content=content, on_scroll=on_scroll_handler, @@ -74,7 +74,7 @@ def test_widget_created_with_values(content, on_scroll_handler): def test_assign_to_app(app, scroll_container, content): - """If the widget is assigned to an app, the content is also assigned""" + """If the widget is assigned to an app, the content is also assigned.""" # Scroll container is initially unassigned assert scroll_container.app is None @@ -88,7 +88,8 @@ def test_assign_to_app(app, scroll_container, content): def test_assign_to_app_no_content(app): - """If the widget is assigned to an app, and there is no content, there's no error""" + """If the widget is assigned to an app, and there is no content, there's no + error.""" scroll_container = toga.ScrollContainer() # Scroll container is initially unassigned @@ -102,7 +103,7 @@ def test_assign_to_app_no_content(app): def test_assign_to_window(window, scroll_container, content): - """If the widget is assigned to a window, the content is also assigned""" + """If the widget is assigned to a window, the content is also assigned.""" # Scroll container is initially unassigned assert scroll_container.window is None @@ -116,7 +117,8 @@ def test_assign_to_window(window, scroll_container, content): def test_assign_to_window_no_content(window): - """If the widget is assigned to an app, and there is no content, there's no error""" + """If the widget is assigned to an app, and there is no content, there's no + error.""" scroll_container = toga.ScrollContainer() # Scroll container is initially unassigned @@ -130,7 +132,7 @@ def test_assign_to_window_no_content(window): def test_disable_no_op(scroll_container): - "ScrollContainer doesn't have a disabled state" + """ScrollContainer doesn't have a disabled state.""" # Enabled by default assert scroll_container.enabled @@ -142,14 +144,14 @@ def test_disable_no_op(scroll_container): def test_focus_noop(scroll_container): - "Focus is a no-op." + """Focus is a no-op.""" scroll_container.focus() assert_action_not_performed(scroll_container, "focus") def test_set_content(app, window, scroll_container, content): - """The content of the scroll container can be changed""" + """The content of the scroll container can be changed.""" # Assign the scroll container to an app and window scroll_container.app = app scroll_container.window = window @@ -188,7 +190,7 @@ def test_set_content(app, window, scroll_container, content): def test_clear_content(app, window, scroll_container, content): - """The content of the scroll container can be cleared""" + """The content of the scroll container can be cleared.""" # Assign the scroll container to an app and window scroll_container.app = app scroll_container.window = window @@ -209,7 +211,7 @@ def test_clear_content(app, window, scroll_container, content): # The content has been cleared assert scroll_container.content is None - # The old content isn't assigned any more + # The old content isn't assigned anymore assert content.app is None assert content.window is None @@ -228,7 +230,7 @@ def test_clear_content(app, window, scroll_container, content): ], ) def test_horizontal(scroll_container, on_scroll_handler, content, value, expected): - "Horizontal scrolling can be enabled/disabled." + """Horizontal scrolling can be enabled/disabled.""" scroll_container.horizontal = value scroll_container.horizontal == expected @@ -255,7 +257,7 @@ def test_horizontal(scroll_container, on_scroll_handler, content, value, expecte ], ) def test_vertical(scroll_container, on_scroll_handler, content, value, expected): - "Vertical scrolling can be enabled/disabled." + """Vertical scrolling can be enabled/disabled.""" scroll_container.vertical = value scroll_container.vertical == expected @@ -279,7 +281,7 @@ def test_vertical(scroll_container, on_scroll_handler, content, value, expected) ], ) def test_horizontal_position(scroll_container, on_scroll_handler, position, expected): - "The horizontal position can be set (clipped if necessary) and retrieved" + """The horizontal position can be set (clipped if necessary) and retrieved.""" scroll_container.horizontal_position = position # scroll handler fired @@ -290,7 +292,7 @@ def test_horizontal_position(scroll_container, on_scroll_handler, position, expe def test_disable_horizontal_scrolling(scroll_container, on_scroll_handler): - "When disabling horizontal scrolling, horizontal position resets" + """When disabling horizontal scrolling, horizontal position resets.""" scroll_container.horizontal_position = 100 on_scroll_handler.reset_mock() @@ -313,7 +315,8 @@ def test_disable_horizontal_scrolling(scroll_container, on_scroll_handler): def test_horizontal_position_when_not_horizontal(scroll_container): - "If horizontal scrolling isn't enabled, setting the horizontal position raises an error" + """If horizontal scrolling isn't enabled, setting the horizontal position raises an + error.""" scroll_container.horizontal = False with pytest.raises( ValueError, @@ -339,7 +342,7 @@ def test_horizontal_position_when_not_horizontal(scroll_container): ], ) def test_vertical_position(scroll_container, on_scroll_handler, position, expected): - "The vertical position can be set (clipped if necessary) and retrieved" + """The vertical position can be set (clipped if necessary) and retrieved.""" scroll_container.vertical_position = position # scroll handler fired @@ -350,7 +353,7 @@ def test_vertical_position(scroll_container, on_scroll_handler, position, expect def test_disable_vertical_scrolling(scroll_container, on_scroll_handler): - "When vertical scrolling is disabled, vertical position resets" + """When vertical scrolling is disabled, vertical position resets.""" scroll_container.vertical_position = 100 scroll_container.vertical = False @@ -373,7 +376,8 @@ def test_disable_vertical_scrolling(scroll_container, on_scroll_handler): def test_set_vertical_position_when_not_vertical(scroll_container): - "If vertical scrolling isn't enabled, setting the vertical position raises an error" + """If vertical scrolling isn't enabled, setting the vertical position raises an + error.""" scroll_container.vertical = False with pytest.raises( ValueError, @@ -401,7 +405,7 @@ def test_set_vertical_position_when_not_vertical(scroll_container): ], ) def test_position(scroll_container, on_scroll_handler, position, expected): - "The scroll position can be set (clipped if necessary) and retrieved" + """The scroll position can be set (clipped if necessary) and retrieved.""" scroll_container.position = position assert scroll_container.position == expected diff --git a/core/tests/widgets/test_selection.py b/core/tests/widgets/test_selection.py index 19213bb1ba..edbb067030 100644 --- a/core/tests/widgets/test_selection.py +++ b/core/tests/widgets/test_selection.py @@ -40,7 +40,7 @@ def widget(source, on_change_handler): def test_widget_created(): - "An empty selection can be created" + """An empty selection can be created.""" widget = toga.Selection() assert widget._impl.interface == widget assert_action_performed(widget, "create Selection") @@ -53,7 +53,7 @@ def test_widget_created(): def test_create_with_value(): - "A Selection can be created with initial values" + """A Selection can be created with initial values.""" on_change = Mock() widget = toga.Selection( @@ -106,7 +106,7 @@ def test_create_with_value(): ], ) def test_value_no_accessor(items, title, value): - "If there's no accessor, the items can be set and values will be dereferenced" + """If there's no accessor, the items can be set and values will be dereferenced.""" on_change_handler = Mock() widget = toga.Selection(on_change=on_change_handler) @@ -183,7 +183,7 @@ def test_value_no_accessor(items, title, value): ], ) def test_value_with_accessor(accessor, items, title, value): - "If an accessor is used, item lookup semantics are different" + """If an accessor is used, item lookup semantics are different.""" on_change_handler = Mock() widget = toga.Selection(accessor=accessor, on_change=on_change_handler) @@ -224,7 +224,7 @@ def test_source_no_accessor(): def test_bad_item_no_accessor(): - """The value for the selection must exist in accessor-less data""" + """The value for the selection must exist in accessor-less data.""" selection = toga.Selection(items=["first", "second", "third"]) with pytest.raises( @@ -235,7 +235,7 @@ def test_bad_item_no_accessor(): def test_bad_item_with_accessor(): - """The value for the selection must exist in accessor-based data""" + """The value for the selection must exist in accessor-based data.""" # Create a selection with an extra item and an explicit accessor selection = toga.Selection( accessor="value", items=["first", "bad", "second", "third"] @@ -271,7 +271,7 @@ def test_add_item(widget, source, on_change_handler): def test_insert_item(widget, source, on_change_handler): - """A row can be inserted into the source""" + """A row can be inserted into the source.""" # Store the original selection selection = widget.value @@ -286,7 +286,7 @@ def test_insert_item(widget, source, on_change_handler): def test_remove(widget, source, on_change_handler): - """If you remove an item that isn't selected, no change is generated""" + """If you remove an item that isn't selected, no change is generated.""" # Store the original selection selection = widget.value @@ -303,7 +303,7 @@ def test_remove(widget, source, on_change_handler): def test_remove_selected(widget, source, on_change_handler): - """If you remove the currently selected item, a change is generated""" + """If you remove the currently selected item, a change is generated.""" # Store the original selection selection = widget.value @@ -318,20 +318,20 @@ def test_remove_selected(widget, source, on_change_handler): def test_clear_source(widget, source, on_change_handler): - "If the source is cleared, the selection is cleared" + """If the source is cleared, the selection is cleared.""" # Clear the source source.clear() # The widget has been cleared assert_action_performed(widget, "clear") - # The widget must have cleared it's selection + # The widget must have cleared its selection on_change_handler.assert_called_with(widget) assert widget.value is None def test_change_source_empty(widget, on_change_handler): - """If the source is changed to an empty source, the selection is reset""" + """If the source is changed to an empty source, the selection is reset.""" # Clear the event history EventLog.reset() @@ -342,13 +342,13 @@ def test_change_source_empty(widget, on_change_handler): assert_action_not_performed(widget, "insert item") assert_action_performed(widget, "refresh") - # The widget must have cleared it's selection + # The widget must have cleared its selection on_change_handler.assert_called_once_with(widget) assert widget.value is None def test_change_source(widget, on_change_handler): - """If the source is changed, the selection is set to the first item""" + """If the source is changed, the selection is set to the first item.""" # Clear the event history EventLog.reset() @@ -361,7 +361,7 @@ def test_change_source(widget, on_change_handler): assert_action_performed_with(widget, "insert item", item=widget.items[1]) assert_action_performed(widget, "refresh") - # The widget must have cleared it's selection + # The widget must have cleared its selection on_change_handler.assert_called_once_with(widget) assert widget.value.key == "new 1" @@ -372,7 +372,7 @@ def test_change_source(widget, on_change_handler): def test_deprecated_names(on_change_handler): - "Deprecated names still work" + """Deprecated names still work.""" # Can't specify both on_select and on_change with pytest.raises( diff --git a/core/tests/widgets/test_slider.py b/core/tests/widgets/test_slider.py index 8f53e12f84..f5a5a0e46f 100644 --- a/core/tests/widgets/test_slider.py +++ b/core/tests/widgets/test_slider.py @@ -78,13 +78,13 @@ def test_set_value_to_be_max(slider, on_change): def test_set_value_to_be_too_small(slider, on_change): - "Setting the value below the minimum results in clipping" + """Setting the value below the minimum results in clipping.""" slider.value = INITIAL_MIN - 1 assert_value(slider, on_change, tick_value=1, value=INITIAL_MIN, change_count=1) def test_set_value_to_be_too_big(slider, on_change): - "Setting the value above the maximum results in clipping" + """Setting the value above the maximum results in clipping.""" slider.value = INITIAL_MAX + 1 assert_value(slider, on_change, tick_value=11, value=INITIAL_MAX, change_count=1) @@ -113,13 +113,13 @@ def test_set_tick_value_to_be_max(slider, on_change): def test_set_tick_value_to_be_too_small(slider, on_change): - "Setting the tick value to less than the min results in clipping" + """Setting the tick value to less than the min results in clipping.""" slider.tick_value = 0 assert_value(slider, on_change, tick_value=1, value=INITIAL_MIN, change_count=1) def test_set_tick_value_to_be_too_big(slider, on_change): - "Setting the tick value to greater than the max results in clipping" + """Setting the tick value to greater than the max results in clipping.""" slider.tick_value = INITIAL_TICK_COUNT + 1 assert_value(slider, on_change, tick_value=11, value=INITIAL_MAX, change_count=1) @@ -547,7 +547,7 @@ def test_int_impl_on_change(tick_count, data): def test_deprecated(): - "Check the deprecated min/max naming" + """Check the deprecated min/max naming.""" # Can't specify min and range with pytest.raises( ValueError, diff --git a/core/tests/widgets/test_splitcontainer.py b/core/tests/widgets/test_splitcontainer.py index e53b9a687c..62a61a0572 100644 --- a/core/tests/widgets/test_splitcontainer.py +++ b/core/tests/widgets/test_splitcontainer.py @@ -34,7 +34,7 @@ def splitcontainer(content1, content2): def test_widget_created(): - "A scroll container can be created with no arguments" + """A scroll container can be created with no arguments.""" splitcontainer = toga.SplitContainer() assert splitcontainer._impl.interface == splitcontainer assert_action_performed(splitcontainer, "create SplitContainer") @@ -44,7 +44,7 @@ def test_widget_created(): def test_widget_created_with_values(content1, content2): - "A split container can be created with arguments" + """A split container can be created with arguments.""" splitcontainer = toga.SplitContainer( content=[content1, content2], direction=toga.SplitContainer.HORIZONTAL, @@ -77,7 +77,7 @@ def test_widget_created_with_values(content1, content2): ], ) def test_assign_to_app(app, content1, content2, include_left, include_right): - """If the widget is assigned to an app, the content is also assigned""" + """If the widget is assigned to an app, the content is also assigned.""" splitcontainer = toga.SplitContainer( content=[ content1 if include_left else None, @@ -103,7 +103,8 @@ def test_assign_to_app(app, content1, content2, include_left, include_right): def test_assign_to_app_no_content(app): - """If the widget is assigned to an app, and there is no content, there's no error""" + """If the widget is assigned to an app, and there is no content, there's no + error.""" splitcontainer = toga.SplitContainer() # Scroll container is initially unassigned @@ -126,7 +127,7 @@ def test_assign_to_app_no_content(app): ], ) def test_assign_to_window(window, content1, content2, include_left, include_right): - """If the widget is assigned to a window, the content is also assigned""" + """If the widget is assigned to a window, the content is also assigned.""" splitcontainer = toga.SplitContainer( content=[ content1 if include_left else None, @@ -152,7 +153,8 @@ def test_assign_to_window(window, content1, content2, include_left, include_righ def test_assign_to_window_no_content(window): - """If the widget is assigned to an app, and there is no content, there's no error""" + """If the widget is assigned to an app, and there is no content, there's no + error.""" splitcontainer = toga.SplitContainer() # Scroll container is initially unassigned @@ -166,7 +168,7 @@ def test_assign_to_window_no_content(window): def test_disable_no_op(splitcontainer): - "SplitContainer doesn't have a disabled state" + """SplitContainer doesn't have a disabled state.""" # Enabled by default assert splitcontainer.enabled @@ -178,7 +180,7 @@ def test_disable_no_op(splitcontainer): def test_focus_noop(splitcontainer): - "Focus is a no-op." + """Focus is a no-op.""" splitcontainer.focus() assert_action_not_performed(splitcontainer, "focus") @@ -201,7 +203,7 @@ def test_set_content_widgets( include_left, include_right, ): - """Widget content can be set to a list of widgets""" + """Widget content can be set to a list of widgets.""" splitcontainer.content = [ content2 if include_left else None, content3 if include_right else None, @@ -237,7 +239,7 @@ def test_set_content_flex( include_left, include_right, ): - """Widget content can be set to a list of widgets with flex values""" + """Widget content can be set to a list of widgets with flex values.""" splitcontainer.content = [ (content2 if include_left else None, 2), (content3 if include_right else None, 3), @@ -273,7 +275,7 @@ def test_set_content_flex_mixed( include_left, include_right, ): - """Flex values will be defaulted if missing""" + """Flex values will be defaulted if missing.""" splitcontainer.content = [ content2 if include_left else None, (content3 if include_right else None, 3), @@ -333,14 +335,14 @@ def test_set_content_flex_mixed( ], ) def test_set_content_invalid(splitcontainer, content, message): - """Widget content can only be set to valid values""" + """Widget content can only be set to valid values.""" with pytest.raises(ValueError, match=message): splitcontainer.content = content def test_direction(splitcontainer): - """The direction of the splitcontainer can be changed""" + """The direction of the splitcontainer can be changed.""" splitcontainer.direction = toga.SplitContainer.HORIZONTAL diff --git a/core/tests/widgets/test_switch.py b/core/tests/widgets/test_switch.py index 5e25932b6b..e04a9e043b 100644 --- a/core/tests/widgets/test_switch.py +++ b/core/tests/widgets/test_switch.py @@ -57,7 +57,7 @@ def change_handler(widget, *args, **kwargs): ], ) def test_label_text(switch, value, expected): - "The switch's label can be modified." + """The switch's label can be modified.""" assert switch.text == "Test Switch" # Clear the event log @@ -85,13 +85,13 @@ def test_label_text(switch, value, expected): ], ) def test_value_change(switch, value, expected): - "The value of the switch can be set from almost any value." + """The value of the switch can be set from almost any value.""" switch.value = value assert switch.value == expected def test_toggle(switch): - "Toggle can be used to change the value" + """Toggle can be used to change the value.""" assert not switch.value switch.toggle() @@ -102,7 +102,7 @@ def test_toggle(switch): def test_on_change(switch): - "The on_change handler is invoked whenever the value is changed." + """The on_change handler is invoked whenever the value is changed.""" handler = MagicMock() switch.on_change = handler diff --git a/core/tests/widgets/test_table.py b/core/tests/widgets/test_table.py index 929b8cb5c5..2da512e0fa 100644 --- a/core/tests/widgets/test_table.py +++ b/core/tests/widgets/test_table.py @@ -45,7 +45,7 @@ def table(source, on_select_handler, on_activate_handler): def test_table_created(): - "An minimal Table can be created" + """A minimal Table can be created.""" table = toga.Table(["First", "Second"]) assert table._impl.interface == table assert_action_performed(table, "create Table") @@ -60,7 +60,7 @@ def test_table_created(): def test_create_with_values(source, on_select_handler, on_activate_handler): - "A Table can be created with initial values" + """A Table can be created with initial values.""" table = toga.Table( ["First", "Second"], data=source, @@ -83,7 +83,7 @@ def test_create_with_values(source, on_select_handler, on_activate_handler): def test_create_with_accessor_overrides(): - "A Table can partially override accessors" + """A Table can partially override accessors.""" table = toga.Table( ["First", "Second"], accessors={"First": "override"}, @@ -97,7 +97,7 @@ def test_create_with_accessor_overrides(): def test_create_no_headings(): - "A Table can be created with no headings" + """A Table can be created with no headings.""" table = toga.Table( headings=None, accessors=["primus", "secondus"], @@ -111,7 +111,7 @@ def test_create_no_headings(): def test_create_headings_required(): - "A Table requires either headingscan be created with no headings" + """A Table requires either headings can be created with no headings.""" with pytest.raises( ValueError, match=r"Cannot create a table without either headings or accessors", @@ -120,7 +120,7 @@ def test_create_headings_required(): def test_disable_no_op(table): - "Table doesn't have a disabled state" + """Table doesn't have a disabled state.""" # Enabled by default assert table.enabled @@ -132,7 +132,7 @@ def test_disable_no_op(table): def test_focus_noop(table): - "Focus is a no-op." + """Focus is a no-op.""" table.focus() assert_action_not_performed(table, "focus") @@ -184,7 +184,7 @@ def test_focus_noop(table): ], ) def test_set_data(table, on_select_handler, data, all_attributes, extra_attributes): - "Data can be set from a variety of sources" + """Data can be set from a variety of sources.""" # The selection hasn't changed yet. on_select_handler.assert_not_called() @@ -218,7 +218,7 @@ def test_set_data(table, on_select_handler, data, all_attributes, extra_attribut def test_single_selection(table, on_select_handler): - "The current selection can be retrieved" + """The current selection can be retrieved.""" # Selection is initially empty assert table.selection is None on_select_handler.assert_not_called() @@ -234,7 +234,7 @@ def test_single_selection(table, on_select_handler): def test_multiple_selection(source, on_select_handler): - "A multi-select table can have the selection retrieved" + """A multi-select table can have the selection retrieved.""" table = toga.Table( ["Title", "Value"], data=source, @@ -256,7 +256,7 @@ def test_multiple_selection(source, on_select_handler): def test_activation(table, on_activate_handler): - "A row can be activated" + """A row can be activated.""" # Activate an item table._impl.simulate_activate(1) @@ -266,7 +266,7 @@ def test_activation(table, on_activate_handler): def test_scroll_to_top(table): - "A table can be scrolled to the top" + """A table can be scrolled to the top.""" table.scroll_to_top() assert_action_performed_with(table, "scroll to row", row=0) @@ -288,14 +288,14 @@ def test_scroll_to_top(table): ], ) def test_scroll_to_row(table, row, effective): - "A table can be scrolled to a specific row" + """A table can be scrolled to a specific row.""" table.scroll_to_row(row) assert_action_performed_with(table, "scroll to row", row=effective) def test_scroll_to_row_no_data(table): - "If there's no data, scrolling is a no-op" + """If there's no data, scrolling is a no-op.""" table.data.clear() table.scroll_to_row(5) @@ -304,14 +304,14 @@ def test_scroll_to_row_no_data(table): def test_scroll_to_bottom(table): - "A table can be scrolled to the top" + """A table can be scrolled to the top.""" table.scroll_to_bottom() assert_action_performed_with(table, "scroll to row", row=2) def test_insert_column_accessor(table): - """A column can be inserted at an accessor""" + """A column can be inserted at an accessor.""" table.insert_column("value", "New Column", accessor="extra") # The column was added @@ -327,13 +327,13 @@ def test_insert_column_accessor(table): def test_insert_column_unknown_accessor(table): - """If the insertion index accessor is unknown, an error is raised""" + """If the insertion index accessor is unknown, an error is raised.""" with pytest.raises(ValueError, match=r"'unknown' is not in list"): table.insert_column("unknown", "New Column", accessor="extra") def test_insert_column_index(table): - """A column can be inserted""" + """A column can be inserted.""" table.insert_column(1, "New Column", accessor="extra") @@ -350,7 +350,7 @@ def test_insert_column_index(table): def test_insert_column_big_index(table): - """A column can be inserted at an index bigger than the number of columns""" + """A column can be inserted at an index bigger than the number of columns.""" table.insert_column(100, "New Column", accessor="extra") @@ -367,7 +367,7 @@ def test_insert_column_big_index(table): def test_insert_column_negative_index(table): - """A column can be inserted at a negative index""" + """A column can be inserted at a negative index.""" table.insert_column(-2, "New Column", accessor="extra") @@ -384,7 +384,8 @@ def test_insert_column_negative_index(table): def test_insert_column_big_negative_index(table): - """A column can be inserted at a negative index larger than the number of columns""" + """A column can be inserted at a negative index larger than the number of + columns.""" table.insert_column(-100, "New Column", accessor="extra") @@ -401,7 +402,7 @@ def test_insert_column_big_negative_index(table): def test_insert_column_no_accessor(table): - """A column can be inserted with a default accessor""" + """A column can be inserted with a default accessor.""" table.insert_column(1, "New Column") @@ -418,7 +419,7 @@ def test_insert_column_no_accessor(table): def test_insert_column_no_headings(source): - """A column can be inserted into a table with no headings""" + """A column can be inserted into a table with no headings.""" table = toga.Table(headings=None, accessors=["key", "value"], data=source) table.insert_column(1, "New Column", accessor="extra") @@ -436,7 +437,7 @@ def test_insert_column_no_headings(source): def test_insert_column_no_headings_missing_accessor(source): - """An accessor is mandatory when adding a column to a table with no headings""" + """An accessor is mandatory when adding a column to a table with no headings.""" table = toga.Table(headings=None, accessors=["key", "value"], data=source) with pytest.raises( @@ -447,7 +448,7 @@ def test_insert_column_no_headings_missing_accessor(source): def test_append_column(table): - """A column can be appended""" + """A column can be appended.""" table.append_column("New Column", accessor="extra") # The column was added @@ -463,7 +464,7 @@ def test_append_column(table): def test_remove_column_accessor(table): - "A column can be removed by accessor" + """A column can be removed by accessor.""" table.remove_column("value") @@ -478,19 +479,19 @@ def test_remove_column_accessor(table): def test_remove_column_unknown_accessor(table): - "If the column named for removal doesn't exist, an error is raised" + """If the column named for removal doesn't exist, an error is raised.""" with pytest.raises(ValueError, match=r"'unknown' is not in list"): table.remove_column("unknown") def test_remove_column_invalid_index(table): - "If the index specified doesn't exist, an error is raised" + """If the index specified doesn't exist, an error is raised.""" with pytest.raises(IndexError, match=r"list assignment index out of range"): table.remove_column(100) def test_remove_column_index(table): - "A column can be removed by index" + """A column can be removed by index.""" table.remove_column(1) @@ -505,7 +506,7 @@ def test_remove_column_index(table): def test_remove_column_negative_index(table): - "A column can be removed by index" + """A column can be removed by index.""" table.remove_column(-2) @@ -520,7 +521,7 @@ def test_remove_column_negative_index(table): def test_remove_column_no_headings(table): - "A column can be removed when there are no headings" + """A column can be removed when there are no headings.""" table = toga.Table( headings=None, accessors=["primus", "secondus"], @@ -539,7 +540,7 @@ def test_remove_column_no_headings(table): def test_deprecated_names(on_activate_handler): - "Deprecated names still work" + """Deprecated names still work.""" # Can't specify both on_double_click and on_activate with pytest.raises( diff --git a/core/tests/widgets/test_textinput.py b/core/tests/widgets/test_textinput.py index aaf73b7112..1c0f7271fa 100644 --- a/core/tests/widgets/test_textinput.py +++ b/core/tests/widgets/test_textinput.py @@ -23,7 +23,7 @@ def widget(validator): def test_widget_created(): - "A text input can be created" + """A text input can be created.""" widget = toga.TextInput() assert widget._impl.interface == widget assert_action_performed(widget, "create TextInput") @@ -39,7 +39,7 @@ def test_widget_created(): def test_create_with_values(): - "A multiline text input can be created with initial values" + """A multiline text input can be created with initial values.""" on_change = Mock() on_confirm = Mock() on_gain_focus = Mock() @@ -125,7 +125,7 @@ def test_value(widget, value, expected, validator): ], ) def test_readonly(widget, value, expected): - "The readonly status of the widget can be changed." + """The readonly status of the widget can be changed.""" # Widget is initially not readonly by default. assert not widget.readonly @@ -243,7 +243,7 @@ def test_on_lose_focus(widget): def test_change_validators(widget, validator): - "If the validator list is changed, the new validators are invoked" + """If the validator list is changed, the new validators are invoked.""" new_validator1 = Mock(return_value=None) new_validator2 = Mock(return_value=None) @@ -263,7 +263,7 @@ def test_change_validators(widget, validator): def test_remove_validators(widget, validator): - "The validator list can be cleared" + """The validator list can be cleared.""" widget.value = "Some text" # Clear the event log and validator mock @@ -278,7 +278,7 @@ def test_remove_validators(widget, validator): def test_is_valid(widget): - "Widget validity can be evaluated" + """Widget validity can be evaluated.""" validator1 = Mock(return_value=None) validator2 = Mock(return_value=None) diff --git a/core/tests/widgets/test_timeinput.py b/core/tests/widgets/test_timeinput.py index de93d5de88..934820ac2c 100644 --- a/core/tests/widgets/test_timeinput.py +++ b/core/tests/widgets/test_timeinput.py @@ -31,7 +31,7 @@ def test_widget_created(): def test_widget_created_with_values(on_change_handler): - """A TimeInput can be created with initial values""" + """A TimeInput can be created with initial values.""" # Round trip the impl/interface widget = toga.TimeInput( value=time(13, 37, 42), @@ -62,7 +62,7 @@ def test_widget_created_with_values(on_change_handler): ], ) def test_value(widget, value, expected, on_change_handler): - "The value of the datepicker can be set" + """The value of the datepicker can be set.""" widget.value = value assert widget.value == expected @@ -80,7 +80,7 @@ def test_value(widget, value, expected, on_change_handler): @pytest.mark.parametrize("value, exc, message", INVALID_VALUES) def test_invalid_value(widget, value, exc, message): - "Invalid time values raise an exception" + """Invalid time values raise an exception.""" with pytest.raises(exc, match=message): widget.value = value @@ -98,7 +98,7 @@ def test_invalid_value(widget, value, exc, message): ], ) def test_value_clipping(widget, value, clipped, on_change_handler): - "It the value is inconsistent with min/max, it is clipped." + """It the value is inconsistent with min/max, it is clipped.""" # Set min/max dates, and clear the on_change mock widget.min = time(6, 0, 0) widget.max = time(18, 0, 0) @@ -124,7 +124,7 @@ def test_value_clipping(widget, value, clipped, on_change_handler): ], ) def test_min(widget, value, expected): - "The min of the datepicker can be set" + """The min of the datepicker can be set.""" widget.min = value assert widget.min == expected @@ -132,7 +132,7 @@ def test_min(widget, value, expected): @pytest.mark.parametrize("value, exc, message", INVALID_VALUES) def test_invalid_min(widget, value, exc, message): - "Invalid min values raise an exception" + """Invalid min values raise an exception.""" widget.max = time(18, 0, 0) with pytest.raises(exc, match=message): @@ -152,7 +152,7 @@ def test_invalid_min(widget, value, exc, message): ], ) def test_min_clip(widget, on_change_handler, min, clip_value, clip_max): - "If the current value or max is before a new min time, it is clipped" + """If the current value or max is before a new min time, it is clipped.""" widget.value = time(3, 42, 37) widget.max = time(12, 0, 0) on_change_handler.reset_mock() @@ -183,7 +183,7 @@ def test_min_clip(widget, on_change_handler, min, clip_value, clip_max): ], ) def test_max(widget, value, expected): - "The max of the datepicker can be set" + """The max of the datepicker can be set.""" widget.max = value assert widget.max == expected @@ -191,7 +191,7 @@ def test_max(widget, value, expected): @pytest.mark.parametrize("value, exc, message", INVALID_VALUES) def test_invalid_max(widget, value, exc, message): - "Invalid max values raise an exception" + """Invalid max values raise an exception.""" widget.min = time(18, 0, 0) with pytest.raises(exc, match=message): @@ -211,7 +211,7 @@ def test_invalid_max(widget, value, exc, message): ], ) def test_max_clip(widget, on_change_handler, max, clip_value, clip_min): - "If the current value is after a new max date, the value is clipped" + """If the current value is after a new max date, the value is clipped.""" widget.min = time(3, 42, 37) widget.value = time(12, 0, 0) on_change_handler.reset_mock() diff --git a/core/tests/widgets/test_tree.py b/core/tests/widgets/test_tree.py index c938910da6..b5d01d9bef 100644 --- a/core/tests/widgets/test_tree.py +++ b/core/tests/widgets/test_tree.py @@ -77,7 +77,7 @@ def tree(source, on_select_handler, on_activate_handler): def test_tree_created(): - "An minimal Tree can be created" + """A minimal Tree can be created.""" tree = toga.Tree(["First", "Second"]) assert tree._impl.interface is tree assert_action_performed(tree, "create Tree") @@ -92,7 +92,7 @@ def test_tree_created(): def test_create_with_values(source, on_select_handler, on_activate_handler): - "A Tree can be created with initial values" + """A Tree can be created with initial values.""" tree = toga.Tree( ["First", "Second"], data=source, @@ -114,8 +114,8 @@ def test_create_with_values(source, on_select_handler, on_activate_handler): assert tree.on_activate._raw == on_activate_handler -def test_create_with_acessor_overrides(): - "A Tree can partially override accessors" +def test_create_with_accessor_overrides(): + """A Tree can partially override accessors.""" tree = toga.Tree( ["First", "Second"], accessors={"First": "override"}, @@ -129,7 +129,7 @@ def test_create_with_acessor_overrides(): def test_create_no_headings(): - "A Tree can be created with no headings" + """A Tree can be created with no headings.""" tree = toga.Tree( headings=None, accessors=["primus", "secondus"], @@ -143,7 +143,7 @@ def test_create_no_headings(): def test_create_headings_required(): - "A Tree requires either headingscan be created with no headings" + """A Tree requires either headings can be created with no headings.""" with pytest.raises( ValueError, match=r"Cannot create a tree without either headings or accessors", @@ -152,7 +152,7 @@ def test_create_headings_required(): def test_disable_no_op(tree): - "Tree doesn't have a disabled state" + """Tree doesn't have a disabled state.""" # Enabled by default assert tree.enabled @@ -164,7 +164,7 @@ def test_disable_no_op(tree): def test_focus_noop(tree): - "Focus is a no-op." + """Focus is a no-op.""" tree.focus() assert_action_not_performed(tree, "focus") @@ -230,7 +230,7 @@ def test_focus_noop(tree): ], ) def test_set_data(tree, on_select_handler, data, all_attributes, extra_attributes): - "Data can be set from a variety of sources" + """Data can be set from a variety of sources.""" # The selection hasn't changed yet. on_select_handler.assert_not_called() @@ -265,7 +265,7 @@ def test_set_data(tree, on_select_handler, data, all_attributes, extra_attribute def test_single_selection(tree, on_select_handler): - "The current selection can be retrieved" + """The current selection can be retrieved.""" # Selection is initially empty assert tree.selection is None on_select_handler.assert_not_called() @@ -281,7 +281,7 @@ def test_single_selection(tree, on_select_handler): def test_multiple_selection(source, on_select_handler): - "A multi-select tree can have the selection retrieved" + """A multi-select tree can have the selection retrieved.""" tree = toga.Tree( ["Title", "Value"], data=source, @@ -303,7 +303,7 @@ def test_multiple_selection(source, on_select_handler): def test_expand_collapse(tree): - """The rows on a tree can be expanded and collapsed""" + """The rows on a tree can be expanded and collapsed.""" # Expand the full tree tree.expand() @@ -331,7 +331,7 @@ def test_expand_collapse(tree): def test_activation(tree, on_activate_handler): - """A row can be activated""" + """A row can be activated.""" # Activate an item tree._impl.simulate_activate((0, 1)) @@ -341,7 +341,7 @@ def test_activation(tree, on_activate_handler): def test_insert_column_accessor(tree): - """A column can be inserted at an accessor""" + """A column can be inserted at an accessor.""" tree.insert_column("value", "New Column", accessor="extra") # The column was added @@ -357,13 +357,13 @@ def test_insert_column_accessor(tree): def test_insert_column_unknown_accessor(tree): - """If the insertion index accessor is unknown, an error is raised""" + """If the insertion index accessor is unknown, an error is raised.""" with pytest.raises(ValueError, match=r"'unknown' is not in list"): tree.insert_column("unknown", "New Column", accessor="extra") def test_insert_column_index(tree): - """A column can be inserted""" + """A column can be inserted.""" tree.insert_column(1, "New Column", accessor="extra") @@ -380,7 +380,7 @@ def test_insert_column_index(tree): def test_insert_column_big_index(tree): - """A column can be inserted at an index bigger than the number of columns""" + """A column can be inserted at an index bigger than the number of columns.""" tree.insert_column(100, "New Column", accessor="extra") @@ -397,7 +397,7 @@ def test_insert_column_big_index(tree): def test_insert_column_negative_index(tree): - """A column can be inserted at a negative index""" + """A column can be inserted at a negative index.""" tree.insert_column(-2, "New Column", accessor="extra") @@ -414,7 +414,8 @@ def test_insert_column_negative_index(tree): def test_insert_column_big_negative_index(tree): - """A column can be inserted at a negative index larger than the number of columns""" + """A column can be inserted at a negative index larger than the number of + columns.""" tree.insert_column(-100, "New Column", accessor="extra") @@ -431,7 +432,7 @@ def test_insert_column_big_negative_index(tree): def test_insert_column_no_accessor(tree): - """A column can be inserted with a default accessor""" + """A column can be inserted with a default accessor.""" tree.insert_column(1, "New Column") @@ -448,7 +449,7 @@ def test_insert_column_no_accessor(tree): def test_insert_column_no_headings(source): - """A column can be inserted into a tree with no headings""" + """A column can be inserted into a tree with no headings.""" tree = toga.Tree(headings=None, accessors=["key", "value"], data=source) tree.insert_column(1, "New Column", accessor="extra") @@ -466,7 +467,7 @@ def test_insert_column_no_headings(source): def test_insert_column_no_headings_missing_accessor(source): - """An accessor is mandatory when adding a column to a tree with no headings""" + """An accessor is mandatory when adding a column to a tree with no headings.""" tree = toga.Tree(headings=None, accessors=["key", "value"], data=source) with pytest.raises( @@ -477,7 +478,7 @@ def test_insert_column_no_headings_missing_accessor(source): def test_append_column(tree): - """A column can be appended""" + """A column can be appended.""" tree.append_column("New Column", accessor="extra") # The column was added @@ -493,7 +494,7 @@ def test_append_column(tree): def test_remove_column_accessor(tree): - "A column can be removed by accessor" + """A column can be removed by accessor.""" tree.remove_column("value") @@ -508,19 +509,19 @@ def test_remove_column_accessor(tree): def test_remove_column_unknown_accessor(tree): - "If the column named for removal doesn't exist, an error is raised" + """If the column named for removal doesn't exist, an error is raised.""" with pytest.raises(ValueError, match=r"'unknown' is not in list"): tree.remove_column("unknown") def test_remove_column_invalid_index(tree): - "If the index specified doesn't exist, an error is raised" + """If the index specified doesn't exist, an error is raised.""" with pytest.raises(IndexError, match=r"list assignment index out of range"): tree.remove_column(100) def test_remove_column_index(tree): - "A column can be removed by index" + """A column can be removed by index.""" tree.remove_column(1) @@ -535,7 +536,7 @@ def test_remove_column_index(tree): def test_remove_column_negative_index(tree): - "A column can be removed by index" + """A column can be removed by index.""" tree.remove_column(-2) @@ -550,7 +551,7 @@ def test_remove_column_negative_index(tree): def test_remove_column_no_headings(tree): - "A column can be removed when there are no headings" + """A column can be removed when there are no headings.""" tree = toga.Tree( headings=None, accessors=["primus", "secondus"], @@ -569,7 +570,7 @@ def test_remove_column_no_headings(tree): def test_deprecated_names(on_activate_handler): - "Deprecated names still work" + """Deprecated names still work.""" # Can't specify both on_double_click and on_activate with pytest.raises( diff --git a/core/tests/widgets/test_webview.py b/core/tests/widgets/test_webview.py index c8d66c6a5e..b8f1dcada8 100644 --- a/core/tests/widgets/test_webview.py +++ b/core/tests/widgets/test_webview.py @@ -19,7 +19,7 @@ def widget(): def test_widget_created(): - "A WebView can be created with minimal arguments" + """A WebView can be created with minimal arguments.""" widget = toga.WebView() assert widget._impl.interface == widget @@ -31,7 +31,7 @@ def test_widget_created(): def test_create_with_values(): - "A WebView can be created with initial values" + """A WebView can be created with initial values.""" on_webview_load = Mock() widget = toga.WebView( @@ -80,7 +80,7 @@ def test_webview_load_disabled(monkeypatch): ], ) def test_url(widget, url): - "The URL of a webview can be set" + """The URL of a webview can be set.""" # Set up a load handler on_webview_load_handler = Mock() widget.on_webview_load = on_webview_load_handler @@ -110,7 +110,7 @@ def test_url(widget, url): ], ) async def test_load_url(widget, url): - "The URL of a webview can be loaded asynchronously" + """The URL of a webview can be loaded asynchronously.""" # Set up a load handler on_webview_load_handler = Mock() widget.on_webview_load = on_webview_load_handler @@ -152,7 +152,7 @@ async def delayed_page_load(): ], ) async def test_invalid_url(widget, url): - "URLs must start with https:// or http://" + """URLs must start with https:// or http://""" with pytest.raises( ValueError, match=r"WebView can only display http:// and https:// URLs", @@ -167,7 +167,7 @@ async def test_invalid_url(widget, url): def test_set_content(widget): - "Static HTML content can be loaded into the page" + """Static HTML content can be loaded into the page.""" widget.set_content("https://example.com", "

Fancy page

") assert_action_performed_with( widget, @@ -178,13 +178,13 @@ def test_set_content(widget): def test_user_agent(widget): - "The user agent can be customized" + """The user agent can be customized.""" widget.user_agent = "New user agent" assert widget.user_agent == "New user agent" def test_evaluate_javascript(widget): - "Javascript can be evaluated" + """Javascript can be evaluated.""" result = widget.evaluate_javascript("test(1);") assert_action_performed(widget, "evaluate_javascript") @@ -199,7 +199,8 @@ def test_evaluate_javascript(widget): async def test_evaluate_javascript_async(widget): - "Javascript can be evaluated asynchronously, and an asynchronous result returned" + """Javascript can be evaluated asynchronously, and an asynchronous result + returned.""" # An async task that simulates evaluation of Javascript after a delay async def delayed_page_load(): @@ -217,7 +218,7 @@ async def delayed_page_load(): async def test_evaluate_javascript_sync(widget): - """Deprecated sync handlers can be used for Javascript evaluation""" + """Deprecated sync handlers can be used for Javascript evaluation.""" # An async task that simulates evaluation of Javascript after a delay async def delayed_page_load(): diff --git a/core/tests/window/test_filtered_widget_registry.py b/core/tests/window/test_filtered_widget_registry.py index aefb5aa3d4..7cdb6662c8 100644 --- a/core/tests/window/test_filtered_widget_registry.py +++ b/core/tests/window/test_filtered_widget_registry.py @@ -153,7 +153,7 @@ def test_reuse_id(app): assert "magic" not in win_1.widgets assert "magic" not in win_2.widgets - # Create a widget with the magic ID. The wiget still isn't in the registry, because + # Create a widget with the magic ID. The widget still isn't in the registry, because # it's not part of a window first = ExampleWidget(id="magic") diff --git a/docs/how-to/contribute-code.rst b/docs/how-to/contribute-code.rst index c1a3d7f1e2..0b5905f717 100644 --- a/docs/how-to/contribute-code.rst +++ b/docs/how-to/contribute-code.rst @@ -41,6 +41,9 @@ First, ensure that you have Python 3 and pip installed. To do this, run: C:\...>python3 --version C:\...>pip3 --version +Create a virtual environment +---------------------------- + The recommended way of setting up your development environment for Toga is to install a virtual environment, install the required dependencies and start coding. To set up a virtual environment, run: @@ -70,6 +73,9 @@ start coding. To set up a virtual environment, run: Your prompt should now have a ``(venv)`` prefix in front of it. +Install system dependencies +--------------------------- + Next, install any additional dependencies for your operating system: .. tabs:: @@ -110,6 +116,9 @@ Next, install any additional dependencies for your operating system: No additional dependencies +Clone the Toga repository +------------------------- + Next, go to `the Toga page on GitHub `__, fork the repository into your own account, and then clone a copy of that repository onto your computer by clicking on "Clone or Download". If you have the GitHub @@ -145,6 +154,9 @@ command line: (substituting your GitHub username) +Install Toga +------------ + Now that you have the source code, you can install Toga into your development environment. The Toga source repository contains multiple packages. Since we're installing from source, we can't rely on pip to resolve the dependencies to @@ -173,6 +185,9 @@ source packages, so we have to manually install each package: (venv) C:\...>cd toga (venv) C:\...>pip install -e ./core[dev] -e ./dummy -e ./winforms +Pre-commit automatically runs during the commit +----------------------------------------------- + Toga uses a tool called `Pre-Commit `__ to identify simple issues and standardize code formatting. It does this by installing a git hook that automatically runs a series of code linters prior to finalizing any @@ -510,19 +525,19 @@ To run the core test suite: .. code-block:: console - (venv) $ tox -e py + (venv) $ tox -m test .. group-tab:: Linux .. code-block:: console - (venv) $ tox -e py + (venv) $ tox -m test .. group-tab:: Windows .. code-block:: doscon - (venv) C:\...>tox -e py + (venv) C:\...>tox -m test You should get some output indicating that tests have been run. You may see ``SKIPPED`` tests, but shouldn't ever get any ``FAIL`` or ``ERROR`` test @@ -557,6 +572,9 @@ This tells us that line 211, and lines 238-240 are not being executed by the tes suite. You'll need to add new tests (or modify an existing test) to restore this coverage. +Run a subset of tests +--------------------- + When you're developing your new test, it may be helpful to run *just* that one test. To do this, you can pass in the name of a specific test file (or a specific test, using `pytest specifiers @@ -587,6 +605,58 @@ coverage report when running a part of the test suite - but the coverage results will only report the lines of code that were executed by the specific tests you ran. +Running the test suite for multiple Python versions +--------------------------------------------------- + +Tox can also run the test suite for all supported version of Python. This +requires that each version of Python is available from ``Path``. + +.. tabs:: + + .. group-tab:: macOS + + .. code-block:: console + + (venv) $ tox + + .. group-tab:: Linux + + .. code-block:: console + + (venv) $ tox + + .. group-tab:: Windows + + .. code-block:: doscon + + (venv) C:\...>tox + +Running CI checks +----------------- + +Tox can also be used to run many of the same checks that run in CI; this is +most useful prior to committing and pushing your changes. + +.. tabs:: + + .. group-tab:: macOS + + .. code-block:: console + + (venv) $ tox -m ci + + .. group-tab:: Linux + + .. code-block:: console + + (venv) $ tox -m ci + + .. group-tab:: Windows + + .. code-block:: doscon + + (venv) C:\...>tox -m ci + .. _run-testbed: Running the testbed diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index ba3df15415..55a8709978 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -74,6 +74,7 @@ substrings Sur testbed Todo +Tox toolbar toolbars Toolbars diff --git a/dummy/pyproject.toml b/dummy/pyproject.toml index 94106a9267..640747dc00 100644 --- a/dummy/pyproject.toml +++ b/dummy/pyproject.toml @@ -54,6 +54,10 @@ Source = "https://github.com/beeware/toga" [project.entry-points."toga.backends"] dummy = "toga_dummy" +[project.entry-points."toga.image_formats"] +dummy = "toga_dummy.plugins.image_formats.CustomImageConverter" +disabled = "toga_dummy.plugins.image_formats.DisabledImageConverter" + [tool.setuptools_scm] root = ".." @@ -61,7 +65,3 @@ root = ".." dependencies = [ "toga-core == {version}", ] - -[project.entry-points."toga.image_formats"] -dummy = "toga_dummy.plugins.image_formats.CustomImageConverter" -disabled = "toga_dummy.plugins.image_formats.DisabledImageConverter" diff --git a/testbed/tests/app/test_app.py b/testbed/tests/app/test_app.py index 11043a98c3..067c6466cf 100644 --- a/testbed/tests/app/test_app.py +++ b/testbed/tests/app/test_app.py @@ -17,7 +17,7 @@ def mock_app_exit(monkeypatch, app): return app_exit -# Mobile platforms have different windowing characterics, so they have different tests. +# Mobile platforms have different windowing characteristics, so they have different tests. if toga.platform.current_platform in {"iOS", "android"}: #################################################################################### # Mobile platform tests diff --git a/tox.ini b/tox.ini index 1e989cc585..623756231e 100644 --- a/tox.ini +++ b/tox.ini @@ -6,20 +6,65 @@ extend-ignore = # See https://github.com/PyCQA/pycodestyle/issues/373 E203, -# The leading comma generates the "py" environment. -[testenv:py{,38,39,310,311,312,313}] +[tox] +# Add Python 3.13 once a wheel for Pillow is available +envlist = py{38,39,310,311,312}-cov,coverage +labels = + test = py-cov,coverage + test38 = py38-cov,coverage38 + test39 = py39-cov,coverage39 + test310 = py310-cov,coverage310 + test311 = py311-cov,coverage311 + test312 = py312-cov,coverage312 + test313 = py313-cov,coverage313 + ci = towncrier-check,docs-lint,pre-commit,py{38,39,310,311,312}-cov,coverage +skip_missing_interpreters = True + +[testenv:pre-commit] +skip_install = True +deps = {tox_root}{/}core[dev] +commands = pre-commit run --all-files --show-diff-on-failure --color=always + +# The leading comma generates the "py" environment +[testenv:py{,38,39,310,311,312,313}{,-cov}] +depends = pre-commit +changedir = core skip_install = True setenv = TOGA_BACKEND = toga_dummy -changedir = core allowlist_externals = bash commands = - # TOGA_INSTALL_COMMAND is set to a bash command by the CI workflow. - {env:TOGA_INSTALL_COMMAND:python -m pip install .[dev] {tox_root}{/}dummy} - {env:test_command_prefix:} coverage run -m pytest -vv {posargs} - coverage combine - coverage report --rcfile {tox_root}{/}pyproject.toml + # TOGA_INSTALL_COMMAND is set to a bash command by the CI workflow + # Install as editable so parallel runs don't clobber the build directory for each other + {env:TOGA_INSTALL_COMMAND:python -m pip install {tox_root}{/}core[dev] {tox_root}{/}dummy} + !cov: python -m pytest {posargs:-vv --color yes} + cov : python -m coverage run -m pytest {posargs:-vv --color yes} + +[testenv:coverage{,38,39,310,311,312,313}{,-html}{,-keep}{,-fail}] +depends = pre-commit,py{,38,39,310,311,312,313}{,-cov} +changedir = core +skip_install = True +# by default, coverage should run on oldest supported Python for testing platform coverage. +# however, coverage for a particular Python version should match the version used for pytest. +base_python = + coverage38: py38 + coverage39: py39 + coverage310: py310 + coverage311: py311 + coverage312: py312 + coverage313: py313 +deps = {tox_root}{/}core[dev] +setenv = + keep: COMBINE_KEEP = --keep + fail: REPORT_FAIL_COND = --fail-under=100 + CORE_RCFILE = --rcfile {tox_root}{/}core{/}pyproject.toml + PROJECT_RCFILE = --rcfile {tox_root}{/}pyproject.toml +commands_pre = python --version +commands = + -python -m coverage combine {env:CORE_RCFILE} {env:COMBINE_KEEP} + html: python -m coverage html {env:PROJECT_RCFILE} --skip-covered --skip-empty + python -m coverage report {env:PROJECT_RCFILE} {env:REPORT_FAIL_COND} [testenv:towncrier{,-check}] skip_install = True