55 lines
2.1 KiB
Python
55 lines
2.1 KiB
Python
"""
|
|
Source guards for frontend pitfalls that unit tests cannot catch.
|
|
|
|
Native browser dialogs (window.confirm / alert / prompt) are silently suppressed in
|
|
embedded and sandboxed browser contexts: confirm() returns false without ever showing a
|
|
dialog, so any action gated behind it becomes a no-op with no error. This actually
|
|
happened — the Dashboard delete button did nothing because of it. Use ConfirmDialog
|
|
(components/ui.tsx) instead.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
SRC = Path(__file__).resolve().parents[2] / "frontend" / "src"
|
|
|
|
# `confirm(`, `alert(`, `prompt(` as bare calls or on window.
|
|
_NATIVE_DIALOG = re.compile(r"(?:\bwindow\s*\.\s*)?\b(confirm|alert|prompt)\s*\(")
|
|
_ALLOWED = {"ConfirmDialog"} # our own component, not the native dialog
|
|
|
|
|
|
def _sources() -> list[Path]:
|
|
return [p for p in SRC.rglob("*.ts*") if p.is_file()]
|
|
|
|
|
|
def test_no_native_browser_dialogs():
|
|
offenders: list[str] = []
|
|
for path in _sources():
|
|
for i, line in enumerate(path.read_text().splitlines(), 1):
|
|
stripped = line.strip()
|
|
if stripped.startswith("//") or stripped.startswith("*"):
|
|
continue
|
|
m = _NATIVE_DIALOG.search(line)
|
|
if m and not any(a in line for a in _ALLOWED):
|
|
offenders.append(f"{path.relative_to(SRC)}:{i}: {stripped[:90]}")
|
|
assert not offenders, (
|
|
"Native browser dialogs are suppressed in embedded contexts and silently "
|
|
"cancel the action. Use <ConfirmDialog> instead:\n " + "\n ".join(offenders)
|
|
)
|
|
|
|
|
|
def test_confirm_dialog_component_exists():
|
|
ui = (SRC / "components" / "ui.tsx").read_text()
|
|
assert "export function ConfirmDialog" in ui
|
|
|
|
|
|
def test_destructive_actions_use_confirm_dialog():
|
|
"""Anything calling deleteSession must route through the in-app dialog."""
|
|
for path in _sources():
|
|
text = path.read_text()
|
|
if "deleteSession" in text and "api.ts" not in path.name and "client.ts" not in path.name:
|
|
assert "ConfirmDialog" in text, (
|
|
f"{path.relative_to(SRC)} deletes a closing without <ConfirmDialog>"
|
|
)
|