101 lines
4.1 KiB
Python
101 lines
4.1 KiB
Python
|
|
"""Regression coverage for settings navigation and master-detail refresh state."""
|
||
|
|
import pathlib
|
||
|
|
import re
|
||
|
|
|
||
|
|
|
||
|
|
REPO_ROOT = pathlib.Path(__file__).parent.parent
|
||
|
|
PANELS_JS = (REPO_ROOT / "static" / "panels.js").read_text(encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
class TestSettingsNavigationGuard:
|
||
|
|
"""Leaving Settings through the rail must still honor save/discard semantics."""
|
||
|
|
|
||
|
|
def test_switch_panel_checks_settings_guard(self):
|
||
|
|
assert "if (!opts.bypassSettingsGuard && !_beforePanelSwitch(nextPanel)) return false;" in PANELS_JS, (
|
||
|
|
"switchPanel() must consult the settings guard before leaving Settings "
|
||
|
|
"so rail/sidebar navigation cannot bypass the unsaved-changes flow"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_dirty_settings_capture_requested_destination(self):
|
||
|
|
m = re.search(
|
||
|
|
r"function _beforePanelSwitch\(nextPanel\)\s*\{.*?"
|
||
|
|
r"_pendingSettingsTargetPanel = nextPanel \|\| 'chat';.*?"
|
||
|
|
r"_showSettingsUnsavedBar\(\);.*?"
|
||
|
|
r"return false;",
|
||
|
|
PANELS_JS,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, (
|
||
|
|
"_beforePanelSwitch() must remember the requested destination and "
|
||
|
|
"block navigation while settings are dirty"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_hiding_settings_resumes_pending_target_with_bypass(self):
|
||
|
|
m = re.search(
|
||
|
|
r"function _hideSettingsPanel\(\)\s*\{.*?"
|
||
|
|
r"const target = _consumeSettingsTargetPanel\('chat'\);.*?"
|
||
|
|
r"switchPanel\(target, \{bypassSettingsGuard:true\}\);",
|
||
|
|
PANELS_JS,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, (
|
||
|
|
"_hideSettingsPanel() must resume the pending target after save/discard "
|
||
|
|
"instead of always falling back to chat"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_entering_settings_starts_fresh_preview_session(self):
|
||
|
|
assert "if (prevPanel !== 'settings' && nextPanel === 'settings') _beginSettingsPanelSession();" in PANELS_JS, (
|
||
|
|
"switchPanel() must snapshot the preview baseline when entering Settings "
|
||
|
|
"through the main navigation"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestMasterDetailRefreshClearsRemovedSelections:
|
||
|
|
"""Refreshes must not leave dead detail panes visible after a selection disappears."""
|
||
|
|
|
||
|
|
def test_tasks_clear_empty_state_detail(self):
|
||
|
|
assert "if (_cronMode !== 'create' && _cronMode !== 'edit') _clearCronDetail();" in PANELS_JS, (
|
||
|
|
"loadCrons() must clear the detail pane when the jobs list becomes empty"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_tasks_clear_missing_selected_job(self):
|
||
|
|
m = re.search(
|
||
|
|
r"if \(_currentCronDetail && _cronMode !== 'create' && _cronMode !== 'edit'\) \{.*?"
|
||
|
|
r"if \(refreshed\) _renderCronDetail\(refreshed\);\s*else _clearCronDetail\(\);",
|
||
|
|
PANELS_JS,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, (
|
||
|
|
"loadCrons() must clear the detail pane when the selected job disappears "
|
||
|
|
"during refresh"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_workspaces_clear_missing_selected_workspace(self):
|
||
|
|
m = re.search(
|
||
|
|
r"if \(_currentWorkspaceDetail && _workspaceMode !== 'create' && _workspaceMode !== 'edit'\) \{.*?"
|
||
|
|
r"if \(refreshed\) _renderWorkspaceDetail\(refreshed\);\s*else _clearWorkspaceDetail\(\);",
|
||
|
|
PANELS_JS,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, (
|
||
|
|
"renderWorkspacesPanel() must clear the detail pane when the selected "
|
||
|
|
"workspace disappears during refresh"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_profiles_clear_empty_state_detail(self):
|
||
|
|
assert "if (_profileMode !== 'create') _clearProfileDetail();" in PANELS_JS, (
|
||
|
|
"loadProfilesPanel() must clear the detail pane when there are no profiles"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_profiles_clear_missing_selected_profile(self):
|
||
|
|
m = re.search(
|
||
|
|
r"if \(_currentProfileDetail && _profileMode !== 'create'\) \{.*?"
|
||
|
|
r"if \(refreshed\) _renderProfileDetail\(refreshed, data.active\);\s*else _clearProfileDetail\(\);",
|
||
|
|
PANELS_JS,
|
||
|
|
re.DOTALL,
|
||
|
|
)
|
||
|
|
assert m, (
|
||
|
|
"loadProfilesPanel() must clear the detail pane when the selected "
|
||
|
|
"profile disappears during refresh"
|
||
|
|
)
|