"""Frontend detection: where an existing frontend is, its stack and its colour tokens.""" import importlib.util import json import subprocess import sys from pathlib import Path DETECT = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py" spec = importlib.util.spec_from_file_location("detect_frontend", DETECT) detect_frontend = importlib.util.module_from_spec(spec) spec.loader.exec_module(detect_frontend) def project(tmp_path, files): for name, text in files.items(): (tmp_path / name).parent.mkdir(parents=True, exist_ok=True) (tmp_path / name).write_text(text, encoding="utf-8") return tmp_path def cli(root): return subprocess.run([sys.executable, str(DETECT), str(root)], capture_output=True, text=True, check=True).stdout def test_no_frontend_when_only_tooling_and_installed_skills_have_html(tmp_path): root = project(tmp_path, { "main.py": "print('[step 1] Login')\n", "node_modules/pkg/index.html": "", ".venv/lib/site.html": "
", ".claude/skills/rpa-dashboard-no-ai/templates/dashboard.html": "", "dist/index.html": "
", }) assert detect_frontend.detect(root) == [] assert cli(root).strip() == "No existing frontend found." FIXTURE_C = Path(__file__).resolve().parent / "fixtures" / "c-web-existing-frontend" def test_plain_html_site_reports_location_stack_light_and_dark_tokens(): [f] = detect_frontend.detect(FIXTURE_C) assert f["path"] == "web/" assert f["stack"] == "plain HTML (web)" light, dark = f["blocks"] assert (light["file"], light["selector"], light["dark"]) == ("web/css/theme.css", ":root", False) assert light["tokens"]["--color-brand"] == "#0f766e" assert light["tokens"]["--font-sans"] == '"Trebuchet MS", "Segoe UI", sans-serif' assert (dark["selector"], dark["dark"]) == ('[data-theme="dark"]', True) assert dark["tokens"]["--color-brand"] == "#0d9488" out = cli(FIXTURE_C) first = out.splitlines()[0] assert first.startswith("Frontend found at `web/`, stack `plain HTML (web)`, colours `--color-brand #0f766e,") assert 'fonts `"Trebuchet MS", "Segoe UI", sans-serif`' in first assert "web/css/theme.css" in out and '[data-theme="dark"]' in out def test_react_app_is_one_frontend_with_its_media_query_dark_tokens(tmp_path): root = project(tmp_path, { "server.py": "from fastapi import FastAPI\n", "frontend/package.json": json.dumps({"dependencies": {"react": "^19.0.0", "react-dom": "^19.0.0"}, "devDependencies": {"vite": "^6.0.0"}}), "frontend/index.html": '
', "frontend/src/index.css": ":root { --primary: 222.2 47.4% 11.2%; --radius: .5rem }\n" "@media (prefers-color-scheme: dark) { :root { --primary: 210 40% 98% } }", "frontend/node_modules/react/index.html": "",
})
[f] = detect_frontend.detect(root)
assert (f["path"], f["stack"]) == ("frontend/", "React (Vite, web)")
light, dark = f["blocks"]
assert (light["file"], light["tokens"]["--primary"], light["dark"]) == ("frontend/src/index.css", "222.2 47.4% 11.2%", False)
assert (dark["selector"], dark["tokens"], dark["dark"]) == (
"@media (prefers-color-scheme: dark) :root", {"--primary": "210 40% 98%"}, True)
assert "colours `--primary 222.2 47.4% 11.2%`" in cli(root)
def test_vue_app_reads_component_styles_and_tailwind_theme(tmp_path):
root = project(tmp_path, {
"package.json": json.dumps({"dependencies": {"vue": "^3.5.0"}, "devDependencies": {"tailwindcss": "^3.4.0"}}),
"tailwind.config.js": "module.exports = { theme: { extend: { colors: { brand: '#e11d48', 'brand-dark': \"#9f1239\" } } } }",
"src/App.vue": " {{ msg }}
Hi
')\n"}) [f] = detect_frontend.detect(inline) assert (f["path"], f["stack"]) == ("main.py", "PyQt web view (desktop, host `main.py`)") assert f["blocks"][0]["tokens"] == {"--accent": "#16a34a"}