diff --git a/.gitignore b/.gitignore index 676a7f9..c78213e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ __pycache__/ *.egg-info/ build/ dist/ -.pytest_cache/ .vscode/* !.vscode/launch.json !.vscode/tasks.json diff --git a/.vscode/tasks.json b/.vscode/tasks.json index b0c7914..81356ee 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,31 +1,6 @@ { "version": "2.0.0", "tasks": [ - { - "label": "Tests: Run pytest", - "type": "shell", - "command": "${command:python.interpreterPath}", - "args": [ - "-m", - "pytest" - ], - "options": { - "cwd": "${workspaceFolder}", - "env": { - "PYTHONPATH": "${workspaceFolder}/src" - } - }, - "group": { - "kind": "test", - "isDefault": true - }, - "presentation": { - "clear": true, - "reveal": "always", - "panel": "dedicated" - }, - "problemMatcher": [] - }, { "label": "Qt: Open Designer", "type": "shell", @@ -69,4 +44,4 @@ "problemMatcher": [] } ] -} \ No newline at end of file +} diff --git a/pyproject.toml b/pyproject.toml index 29e7b47..36b9d56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,17 +21,8 @@ bedit = "bedit_gui.application:main" [project.optional-dependencies] dev = [ - "pytest>=8", "ruff>=0.5", ] [tool.setuptools.packages.find] where = ["src"] - -[tool.pytest.ini_options] -addopts = "--strict-config --strict-markers -ra" -testpaths = ["tests"] -markers = [ - "unit: fast tests without GUI event-loop interaction", - "gui: tests that create or interact with Qt objects", -] diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 45fdee5..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Shared pytest fixtures for bedit tests. - -Qt-specific fixtures can be added here later when GUI testing starts. Keeping -the model fixtures independent from Qt lets the core suite stay lightweight. -""" - -from __future__ import annotations - -import pytest - -from bedit_core.models import ( - Component, - ComponentID, - Document, - Graph, - GraphImplementation, - ID, - Interface, -) - - -@pytest.fixture -def minimal_document() -> Document: - """Return the smallest useful graph document for core tests.""" - root_id = ComponentID("root") - root = Component( - name="Root", - interface=Interface(), - parameters={}, - implementation=GraphImplementation(Graph()), - ) - return Document( - format_version=1, - id=ID("document"), - name="Test document", - root={root_id: root}, - ) diff --git a/tests/unit/test_application_logging.py b/tests/unit/test_application_logging.py deleted file mode 100644 index 4ff153a..0000000 --- a/tests/unit/test_application_logging.py +++ /dev/null @@ -1,96 +0,0 @@ -from __future__ import annotations - -import logging -from pathlib import Path - -import pytest -from PySide6.QtCore import Qt -from PySide6.QtGui import QUndoCommand -from PySide6.QtWidgets import QApplication - -from bedit_gui.controllers.document_controller import DocumentController -from bedit_gui.controllers.log_controller import LogController -from bedit_gui.controllers.undo_controller import UndoController -from bedit_gui.documents import Document -from bedit_gui.services.application_logging import get_logger, set_log_level -from bedit_gui.views.dialogs.document_dialogs import SaveChangesChoice -from bedit_gui.views.main_window import MainWindow - - -class FakeDialogs: - def __init__(self, path: Path) -> None: - self.path = path - - def choose_open_path(self, _current_path: Path | None) -> Path: - return self.path - - def choose_save_path(self, _current_path: Path | None) -> Path: - return self.path - - def ask_save_changes(self) -> SaveChangesChoice: - return SaveChangesChoice.DISCARD - - def show_file_error(self, _title: str, error: Exception) -> None: - raise AssertionError("unexpected file error") from error - - -@pytest.fixture(scope="module") -def qt_app() -> QApplication: - return QApplication.instance() or QApplication([]) - - -@pytest.mark.unit -def test_log_level_and_qt_log_model(qt_app: QApplication) -> None: - window = MainWindow() - controller = LogController(window, logging.WARNING) - logger = get_logger("tests") - - logger.info("hidden message") - logger.warning("visible message") - qt_app.processEvents() - - assert controller.model.rowCount() == 1 - assert "WARNING visible message" in controller.model.data( - controller.model.index(0), - Qt.ItemDataRole.DisplayRole, - ) - - set_log_level(logging.INFO) - logger.info("now visible") - qt_app.processEvents() - - assert controller.model.rowCount() == 2 - - -@pytest.mark.unit -def test_document_and_undo_actions_are_logged( - qt_app: QApplication, - tmp_path: Path, -) -> None: - path = tmp_path / "document.json" - window = MainWindow() - log_controller = LogController(window) - document = Document(qt_app) - dialogs = FakeDialogs(path) - document_controller = DocumentController(document, window, dialogs) - undo_controller = UndoController(document, window) - - document_controller.new_document() - document_controller.save_document_as() - document_controller.save_document() - document_controller.open_document() - document.undo_stack.push(QUndoCommand("test change")) - undo_controller.undo() - undo_controller.redo() - qt_app.processEvents() - - messages = [ - log_controller.model.data(log_controller.model.index(row)) - for row in range(log_controller.model.rowCount()) - ] - assert any("Created new document" in message for message in messages) - assert any("Opened document:" in message for message in messages) - assert any("Saved document:" in message for message in messages) - assert any("Saved document as:" in message for message in messages) - assert any("Undo: test change" in message for message in messages) - assert any("Redo: test change" in message for message in messages) diff --git a/tests/unit/test_bondgraph_flatten.py b/tests/unit/test_bondgraph_flatten.py deleted file mode 100644 index 4f56b0b..0000000 --- a/tests/unit/test_bondgraph_flatten.py +++ /dev/null @@ -1,88 +0,0 @@ -from __future__ import annotations - -import pytest - -from bedit_core.bondgraph import flatten_bondgraph -from bedit_core.models import ( - BondConnection, - BondPort, - Component, - ComponentID, - ConnectionID, - EquationImplementation, - Graph, - GraphImplementation, - Interface, - PortID, - SignalDirection, -) - - -def _leaf(name: str, port_id: PortID) -> Component: - return Component( - name=name, - interface=Interface( - {port_id: BondPort(name="p", direction=SignalDirection.INPUT)} - ), - parameters={}, - implementation=EquationImplementation(), - ) - - -@pytest.mark.unit -def test_flattens_bonds_across_a_component_boundary() -> None: - boundary_id = PortID("boundary") - inner_id = PortID("inner") - outer_id = PortID("outer") - inner = _leaf("Inner", inner_id) - subsystem = Component( - name="Subsystem", - interface=Interface( - { - boundary_id: BondPort( - name="boundary", - direction=SignalDirection.INPUT, - ) - } - ), - parameters={}, - implementation=GraphImplementation( - Graph( - components={ComponentID("inner"): inner}, - connections={ - ConnectionID("inside"): BondConnection( - boundary_id, - inner_id, - ) - }, - ) - ), - ) - outer = _leaf("Outer", outer_id) - root = Component( - name="Root", - interface=Interface(), - parameters={}, - implementation=GraphImplementation( - Graph( - components={ - ComponentID("subsystem"): subsystem, - ComponentID("outer"): outer, - }, - connections={ - ConnectionID("outside"): BondConnection( - outer_id, - boundary_id, - ) - }, - ) - ), - ) - - network = flatten_bondgraph(root) - boundary = next(port for port in network.ports if port.port_id == boundary_id) - - assert len(network.components) == 4 - assert len(network.ports) == 3 - assert len(network.bonds) == 2 - assert len(network.bonds_for(boundary)) == 2 diff --git a/tests/unit/test_document_controller.py b/tests/unit/test_document_controller.py deleted file mode 100644 index 17e17e0..0000000 --- a/tests/unit/test_document_controller.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest -from PySide6.QtGui import QUndoCommand -from PySide6.QtWidgets import QApplication - -from bedit_gui.controllers.document_controller import DocumentController -from bedit_gui.documents import Document -from bedit_gui.views.dialogs.document_dialogs import SaveChangesChoice -from bedit_gui.views.main_window import MainWindow - - -class FakeDialogs: - def __init__(self) -> None: - self.open_path: Path | None = None - self.save_path: Path | None = None - self.save_choice = SaveChangesChoice.CANCEL - self.errors: list[tuple[str, Exception]] = [] - - def choose_open_path(self, _current_path: Path | None) -> Path | None: - return self.open_path - - def choose_save_path(self, _current_path: Path | None) -> Path | None: - return self.save_path - - def ask_save_changes(self) -> SaveChangesChoice: - return self.save_choice - - def show_file_error(self, title: str, error: Exception) -> None: - self.errors.append((title, error)) - - -@pytest.fixture(scope="module") -def qt_app() -> QApplication: - return QApplication.instance() or QApplication([]) - - -@pytest.mark.unit -def test_new_document_respects_unsaved_changes(qt_app: QApplication) -> None: - document = Document(qt_app) - window = MainWindow() - dialogs = FakeDialogs() - controller = DocumentController(document, window, dialogs) - original_id = document.model.id - document.undo_stack.push(QUndoCommand("change")) - - controller.new_document() - assert document.model.id == original_id - - dialogs.save_choice = SaveChangesChoice.DISCARD - controller.new_document() - assert document.model.id != original_id - assert not document.modified - - -@pytest.mark.unit -def test_save_as_then_open_document( - qt_app: QApplication, - tmp_path: Path, -) -> None: - document = Document(qt_app) - window = MainWindow() - dialogs = FakeDialogs() - controller = DocumentController(document, window, dialogs) - original_id = document.model.id - path = tmp_path / "document.json" - - dialogs.save_path = path - assert controller.save_document_as() - - document.new() - dialogs.open_path = path - controller.open_document() - - assert document.model.id == original_id - assert document.path == path - assert not dialogs.errors diff --git a/tests/unit/test_graphviz_util.py b/tests/unit/test_graphviz_util.py deleted file mode 100644 index 5e3f11c..0000000 --- a/tests/unit/test_graphviz_util.py +++ /dev/null @@ -1,14 +0,0 @@ -from __future__ import annotations - -import pytest - -from bedit_core.models import Document -from bedit_util.graphviz import document_to_dot - - -@pytest.mark.unit -def test_dot_contains_component_name_path(minimal_document: Document) -> None: - dot = document_to_dot(minimal_document, "Root") - - assert 'label="Root"' in dot - assert 'xlabel="Root"' in dot diff --git a/tests/unit/test_gui_document.py b/tests/unit/test_gui_document.py deleted file mode 100644 index 17e65ef..0000000 --- a/tests/unit/test_gui_document.py +++ /dev/null @@ -1,37 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest -from PySide6.QtGui import QUndoCommand - -from bedit_gui.documents import Document - - -@pytest.mark.unit -@pytest.mark.parametrize("suffix", [".beb", ".json"]) -def test_document_save_and_open(tmp_path: Path, suffix: str) -> None: - path = tmp_path / f"document{suffix}" - document = Document() - original_id = document.model.id - - document.save_as(path) - document.new() - assert document.model.id != original_id - - document.open(path) - - assert document.model.id == original_id - assert document.path == path - assert not document.modified - - -@pytest.mark.unit -def test_document_modified_state_uses_undo_stack() -> None: - document = Document() - - document.undo_stack.push(QUndoCommand("change")) - assert document.modified - - document.undo_stack.setClean() - assert not document.modified diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py deleted file mode 100644 index f4db6c4..0000000 --- a/tests/unit/test_models.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Basic smoke tests for the core model.""" - -from __future__ import annotations - -import pytest - -from bedit_core.models import Document, GraphImplementation - - -@pytest.mark.unit -def test_minimal_document_has_graph_root(minimal_document: Document) -> None: - root = next(iter(minimal_document.root.values())) - - assert isinstance(root.implementation, GraphImplementation) diff --git a/tests/unit/test_port_editor.py b/tests/unit/test_port_editor.py deleted file mode 100644 index e56f6fa..0000000 --- a/tests/unit/test_port_editor.py +++ /dev/null @@ -1,115 +0,0 @@ -from __future__ import annotations - -from copy import deepcopy - -import pytest -from PySide6.QtWidgets import QApplication - -from bedit_core import models -from bedit_core.models import ( - BondPort, - Component, - Interface, - PortID, - SignalDirection, - SignalPort, - ValueType, -) -from bedit_gui.documents import Document -from bedit_gui.views.port_editor_widget import PortEditorWidget - - -@pytest.fixture(scope="module") -def qt_app() -> QApplication: - return QApplication.instance() or QApplication([]) - - -@pytest.mark.unit -def test_port_editor_uses_a_detached_draft(qt_app: QApplication) -> None: - port_id = PortID("input") - original = { - port_id: SignalPort( - name="Input", - direction=SignalDirection.INPUT, - ) - } - editor = PortEditorWidget() - editor.set_ports(original) - - editor.ui.nameEdit.setText("Changed") - editor.ui.nameEdit.textEdited.emit("Changed") - - assert original[port_id].name == "Input" - assert editor.ports()[port_id].name == "Changed" - - -@pytest.mark.unit -def test_port_type_controls_option_visibility(qt_app: QApplication) -> None: - editor = PortEditorWidget() - editor.set_ports( - { - PortID("port"): SignalPort( - name="Port", - direction=SignalDirection.INPUT, - value_type=ValueType.REAL, - ) - } - ) - - assert not editor.ui.signalTypeComboBox.isHidden() - assert editor.ui.domainComboBox.isHidden() - - editor.ui.typeBond.setChecked(True) - - assert editor.ui.signalTypeComboBox.isHidden() - assert not editor.ui.domainComboBox.isHidden() - - -@pytest.mark.unit -def test_bond_causality_is_loaded_and_changed(qt_app: QApplication) -> None: - port_id = PortID("bond") - editor = PortEditorWidget() - editor.set_ports({port_id: BondPort("Bond", SignalDirection.INPUT, causality_preference=models.PortCausality.PREFERRED_FLOW_OUT)}) - - assert editor.ui.causalityComboBox.currentData() is models.PortCausality.PREFERRED_FLOW_OUT - - index = editor.ui.causalityComboBox.findData(models.PortCausality.FIXED_EFFORT_OUT) - editor.ui.causalityComboBox.setCurrentIndex(index) - - assert editor.ports()[port_id].causality_preference is models.PortCausality.FIXED_EFFORT_OUT - - -@pytest.mark.unit -def test_port_changes_are_one_undoable_operation( - qt_app: QApplication, - minimal_document, -) -> None: - component: Component = next(iter(minimal_document.root.values())) - removed_id = PortID("removed") - changed_id = PortID("changed") - added_id = PortID("added") - component.interface = Interface( - { - removed_id: SignalPort("Remove", SignalDirection.INPUT), - changed_id: SignalPort("Before", SignalDirection.INPUT), - } - ) - original = deepcopy(component.interface.ports) - updated = { - changed_id: BondPort( - "After", - SignalDirection.OUTPUT, - domain="electrical", - ), - added_id: SignalPort("Added", SignalDirection.INPUT), - } - document = Document(qt_app) - - document.update_component_ports(component, updated) - assert component.interface.ports == updated - - document.undo_stack.undo() - assert component.interface.ports == original - - document.undo_stack.redo() - assert component.interface.ports == updated diff --git a/tests/unit/test_serialization.py b/tests/unit/test_serialization.py deleted file mode 100644 index 8d91cea..0000000 --- a/tests/unit/test_serialization.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Basic smoke tests for document serialization.""" - -from __future__ import annotations - -import pytest - -from bedit_core.models import Document -from bedit_core.serialization import load, save - - -@pytest.mark.unit -def test_json_round_trip(tmp_path, minimal_document: Document) -> None: - path = tmp_path / "document.json" - - save(minimal_document, path) - - assert load(path) == minimal_document diff --git a/tests/unit/test_settings_controller.py b/tests/unit/test_settings_controller.py deleted file mode 100644 index bf5a479..0000000 --- a/tests/unit/test_settings_controller.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -import logging -from pathlib import Path - -import pytest -from PySide6.QtCore import QSettings -from PySide6.QtWidgets import QApplication, QDialog - -from bedit_gui.controllers.settings_controller import SettingsController -from bedit_gui.services.application_logging import get_logger -from bedit_gui.services.application_settings import ApplicationSettings -from bedit_gui.views.main_window import MainWindow - - -class FakeSettingsDialog: - def __init__(self, log_level: int) -> None: - self.initial_log_level = log_level - self.log_level = logging.ERROR - - def exec(self) -> QDialog.DialogCode: - return QDialog.DialogCode.Accepted - - -@pytest.fixture(scope="module") -def qt_app() -> QApplication: - return QApplication.instance() or QApplication([]) - - -@pytest.mark.unit -def test_log_level_is_persisted(tmp_path: Path) -> None: - path = tmp_path / "settings.ini" - backend = QSettings(str(path), QSettings.Format.IniFormat) - settings = ApplicationSettings(backend) - - settings.log_level = logging.DEBUG - backend.sync() - - reloaded = ApplicationSettings( - QSettings(str(path), QSettings.Format.IniFormat) - ) - assert reloaded.log_level == logging.DEBUG - - -@pytest.mark.unit -def test_settings_action_applies_log_level( - qt_app: QApplication, - tmp_path: Path, -) -> None: - backend = QSettings( - str(tmp_path / "settings.ini"), - QSettings.Format.IniFormat, - ) - settings = ApplicationSettings(backend) - window = MainWindow() - dialogs: list[FakeSettingsDialog] = [] - - def make_dialog( - log_level: int, - _window: MainWindow, - ) -> FakeSettingsDialog: - dialog = FakeSettingsDialog(log_level) - dialogs.append(dialog) - return dialog - - SettingsController(window, settings, make_dialog) - window.ui.actionSettings.trigger() - - assert dialogs[0].initial_log_level == logging.INFO - assert settings.log_level == logging.ERROR - assert get_logger("tests").isEnabledFor(logging.ERROR) - assert not get_logger("tests").isEnabledFor(logging.WARNING) diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py deleted file mode 100644 index a358181..0000000 --- a/tests/unit/test_simulation.py +++ /dev/null @@ -1,174 +0,0 @@ -from __future__ import annotations - -import asyncio -import re -import socket -import threading -from pathlib import Path -from typing import Mapping, Sequence - -import pytest - -from bedit_simulation import ( - OpenModelicaRunner, - ProcessResult, - Simulation, - SimulationOptions, - SimulationStateError, -) - - -def _fake_omc( - command: Sequence[str], - working_directory: Path, - timeout: float | None, - environment: Mapping[str, str] | None, -) -> ProcessResult: - del timeout, environment - script = Path(command[-1]).read_text(encoding="utf-8") - if "buildModel(Example" in script: - (working_directory / "Example").touch() - if "system(" in script: - port_match = re.search(r"-port=(\d+)", script) - assert port_match is not None - with socket.create_connection( - ("127.0.0.1", int(port_match.group(1))) - ) as connection: - connection.sendall( - b'\n' - ) - (working_directory / "Example_res.csv").write_text( - '"time","x"\n0,1\n1,2\n', - encoding="utf-8", - ) - return ProcessResult( - tuple(command), - 0, - '"Check of Example completed successfully.\n' - 'Class Example has 1 equation(s) and 1 variable(s)."\n' - '""\n', - "", - ) - - -@pytest.mark.unit -def test_runs_omc_through_configured_command(tmp_path: Path) -> None: - runner = OpenModelicaRunner( - ["./run-omc-in-docker"], - executor=_fake_omc, - ) - - async def run() -> object: - simulation = Simulation(runner) - await simulation.load_modelica( - "model Example\nend Example;\n", - "Example", - ) - await simulation.build(tmp_path) - return await simulation.run( - SimulationOptions(stop_time=2, number_of_intervals=20), - ) - - result = asyncio.run(run()) - - assert result.data == {"time": [0.0, 1.0], "x": [1.0, 2.0]} - assert result.result_file == tmp_path / "Example_res.csv" - script = (tmp_path / "run.mos").read_text(encoding="utf-8") - assert f'cd("{tmp_path}")' in script - assert "stepSize=0.1" in script - assert "-logFormat=xmltcp" in script - assert "simulate(Example" not in script - - -@pytest.mark.unit -def test_rejects_invalid_time_range() -> None: - with pytest.raises(ValueError, match="stop_time"): - SimulationOptions(start_time=1, stop_time=1) - - -@pytest.mark.unit -def test_run_requires_a_built_model() -> None: - simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc)) - - async def run() -> None: - await simulation.load_modelica( - "model Example\nend Example;\n", - "Example", - ) - with pytest.raises(SimulationStateError, match=r"call build\(\) first"): - await simulation.run() - - asyncio.run(run()) - - -@pytest.mark.unit -def test_checks_existing_modelica_source(tmp_path: Path) -> None: - simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc)) - - async def check() -> object: - await simulation.load_modelica( - "model Example\nend Example;\n", - "Example", - ) - return await simulation.check(working_directory=tmp_path) - - result = asyncio.run(check()) - - assert result.successful - assert result.output == [ - "Check of Example completed successfully.", - "Class Example has 1 equation(s) and 1 variable(s).", - "", - ] - script = (tmp_path / "model-command.mos").read_text(encoding="utf-8") - assert "checkModel(Example);" in script - - -@pytest.mark.unit -def test_async_execution_uses_a_worker_thread(tmp_path: Path) -> None: - caller_thread = threading.get_ident() - execution_threads: list[int] = [] - - def executor( - command: Sequence[str], - working_directory: Path, - timeout: float | None, - environment: Mapping[str, str] | None, - ) -> ProcessResult: - del working_directory, timeout, environment - execution_threads.append(threading.get_ident()) - return ProcessResult(tuple(command), 0, '"OpenModelica test"', "") - - simulation = Simulation(OpenModelicaRunner("omc", executor=executor)) - - result = asyncio.run( - simulation.execute( - "getVersion()", - working_directory=tmp_path, - ) - ) - - assert result.return_code == 0 - assert len(execution_threads) == 1 - assert execution_threads[0] != caller_thread - - -@pytest.mark.unit -def test_requires_and_retains_a_loaded_model(tmp_path: Path) -> None: - simulation = Simulation(OpenModelicaRunner("omc", executor=_fake_omc)) - - async def use_session() -> None: - with pytest.raises(SimulationStateError, match="no model is loaded"): - await simulation.check() - - await simulation.load_modelica( - "model Example\nend Example;\n", - "Example", - ) - check = await simulation.check(working_directory=tmp_path) - - assert simulation.model_name == "Example" - assert simulation.last_check is check - - asyncio.run(use_session()) diff --git a/tests/unit/test_simulation_util.py b/tests/unit/test_simulation_util.py deleted file mode 100644 index 3da1e93..0000000 --- a/tests/unit/test_simulation_util.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -import matplotlib -import pytest - -from bedit_simulation import SimulationResult -from bedit_util.simulate import create_results_figure - -matplotlib.use("Agg") - - -@pytest.mark.unit -def test_creates_result_figure_with_trace_controls() -> None: - import matplotlib.pyplot as plt - - result = SimulationResult( - model_name="Example", - data={ - "time": [0.0, 1.0], - "x": [1.0, 2.0], - "y": [3.0, 4.0], - }, - ) - - figure = create_results_figure(result) - - assert {line.get_label() for line in figure.axes[0].lines} == {"x", "y"} - assert len(figure._bedit_widgets) == 3 # type: ignore[attr-defined] - plt.close(figure) diff --git a/tests/unit/test_view_menu_controller.py b/tests/unit/test_view_menu_controller.py deleted file mode 100644 index c45eba0..0000000 --- a/tests/unit/test_view_menu_controller.py +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import annotations - -import pytest -from PySide6.QtWidgets import QApplication - -from bedit_gui.controllers.view_menu_controller import ViewMenuController -from bedit_gui.views.main_window import MainWindow - - -@pytest.fixture(scope="module") -def qt_app() -> QApplication: - return QApplication.instance() or QApplication([]) - - -@pytest.mark.unit -def test_view_submenus_contain_visibility_actions( - qt_app: QApplication, -) -> None: - window = MainWindow() - controller = ViewMenuController(window) - - assert window.ui.actionPanels.menu() is controller.panels_menu - assert window.ui.actionToolbars.menu() is controller.toolbars_menu - assert [action.text() for action in controller.panels_menu.actions()] == [ - "Document Tree", - "Log", - ] - assert [action.text() for action in controller.toolbars_menu.actions()] == [ - "File", - "Undo", - ] - assert all( - action.isCheckable() - for action in ( - controller.panels_menu.actions() - + controller.toolbars_menu.actions() - ) - )