from pathlib import Path from types import SimpleNamespace from PyQt6.QtCore import QSettings from PyQt6.QtWidgets import ( QApplication, QWidget, ) import pyqtgraph as pg import pytest from pytestqt.qtbot import QtBot from piker import config @pytest.fixture(scope='module') def module_qapp( qapp: QApplication, ) -> QApplication: ''' Pin pytest-qt's session application for this test module. ''' return qapp def test_ui_state_is_process_isolated_and_restored( isolated_ui_state: SimpleNamespace, module_qapp: QApplication, pytestconfig: pytest.Config, qtbot: QtBot, ) -> None: ''' Prove imports and one UI test cannot reach user config state. Previously `qapp_args` assigned `XDG_CONFIG_HOME` only after Piker was imported, so `piker.config` cached real user paths before Qt setup. This test verifies every cached path starts below the process-owned temporary XDG root, then redirects those globals and writes both a file and `QSettings` key. The fixture's exact-state teardown proves those mutations disappear while its process sentinels survive for the next test. ''' state: SimpleNamespace = isolated_ui_state assert module_qapp is state.qapp assert QApplication.instance() is state.qapp if pytestconfig.getoption('headless'): assert state.qapp.platformName() == 'offscreen' assert config._click_config_dir.is_relative_to( state.process_config_home, ) assert config._config_dir.is_relative_to( state.process_config_home, ) assert config._watchlists_data_path.is_relative_to( state.process_config_home, ) widget: QWidget = QWidget() qtbot.addWidget(widget) widget.show() owned_dir: Path = state.test_config_dir config._click_config_dir = owned_dir config._config_dir = owned_dir config._watchlists_data_path = owned_dir / 'watchlists.json' (owned_dir / 'owned').write_text('remove-me', encoding='utf-8') settings: QSettings = QSettings('pikers', 'piker') settings.setFallbacksEnabled(False) settings_path: Path = Path(settings.fileName()) assert settings_path.is_relative_to(state.process_config_home) assert settings.value(state.preexisting_setting) == 'keep-me' settings.setValue(state.owned_setting, 'remove-me') settings.sync() def test_ui_state_reuses_qapp_without_previous_test_leaks( isolated_ui_state: SimpleNamespace, module_qapp: QApplication, qtbot: QtBot, ) -> None: ''' Prove repeated tests reuse Qt without inheriting mutable state. A session `QApplication` can retain widgets, settings and PyQtGraph's `ViewBox.AllViews` entries from an earlier test. This test runs after the mutation case, verifies the same application and preserved process sentinel but no test-owned key, then creates a real named `PlotWidget` and mutates Qt/PyQtGraph options. Normal pytest-qt widget closure plus the isolation fixture's registry assertions prove the second test also leaves no state behind. ''' state: SimpleNamespace = isolated_ui_state assert module_qapp is state.qapp assert QApplication.instance() is state.qapp settings: QSettings = QSettings('pikers', 'piker') settings.setFallbacksEnabled(False) assert settings.value(state.preexisting_setting) == 'keep-me' assert not settings.contains(state.owned_setting) assert state.preexisting_config.read_text( encoding='utf-8', ) == 'keep-me' assert list(state.test_config_dir.iterdir()) == [] plot: pg.PlotWidget = pg.PlotWidget(name='pytest-owned-view') qtbot.addWidget(plot) plot.show() assert pg.ViewBox.NamedViews['pytest-owned-view'] is ( plot.plotItem.vb ) pg.setConfigOption( 'antialias', not pg.getConfigOption('antialias'), ) state.qapp.setQuitOnLastWindowClosed( not state.qapp.quitOnLastWindowClosed(), )