"""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] / "skills" / "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": "\n", }) [f] = detect_frontend.detect(root) assert (f["path"], f["stack"]) == ("./", "Vue (Tailwind, web)") blocks = {b["file"]: b for b in f["blocks"]} assert blocks["src/App.vue"]["tokens"] == {"--surface": "#ffffff"} assert blocks["tailwind.config.js"]["tokens"] == {"brand": "#e11d48", "brand-dark": "#9f1239"} assert "brand #e11d48" in cli(root).splitlines()[0] def test_pyqt_web_view_is_a_desktop_frontend(tmp_path): host = "from PyQt6.QtWebEngineWidgets import QWebEngineView\nview = QWebEngineView()\n" root = project(tmp_path / "file", { "app.py": host + "view.load(QUrl.fromLocalFile('ui/page.html'))\n", "bot.py": "print('[step 1] Login')\n", "ui/page.html": "", }) [f] = detect_frontend.detect(root) assert (f["path"], f["stack"]) == ("ui/", "PyQt web view (desktop, host `app.py`)") assert f["blocks"][0]["tokens"] == {"--accent": "#2563eb"} inline = project(tmp_path / "inline", {"main.py": host + "view.setHtml('

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"}