293 lines
11 KiB
Python
293 lines
11 KiB
Python
import re
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
WEB = ROOT / "apps" / "web-console"
|
|
|
|
|
|
def source(name: str) -> str:
|
|
return (WEB / name).read_text(encoding="utf-8")
|
|
|
|
|
|
def test_shadcn_semantic_theme_tokens_drive_the_console() -> None:
|
|
styles = source("styles.css")
|
|
console = source("console-v2.css")
|
|
speech = source("speech-input.css")
|
|
|
|
expected_tokens = {
|
|
"--background: oklch(0.968 0.019 244)",
|
|
"--foreground: oklch(0.29 0.042 252)",
|
|
"--card-foreground: oklch(0.34 0.032 250)",
|
|
"--primary: oklch(0.57 0.19 258)",
|
|
"--primary-foreground: oklch(0.99 0.004 245)",
|
|
"--destructive: oklch(0.61 0.19 24)",
|
|
"--sidebar-primary: oklch(0.57 0.19 258)",
|
|
"--background: oklch(0.2 0.035 252)",
|
|
"--foreground: oklch(0.94 0.02 245)",
|
|
"--primary: oklch(0.7 0.15 252)",
|
|
"--sidebar-primary: oklch(0.7 0.15 252)",
|
|
}
|
|
token_lines = {line.strip() for line in styles.replace(";", "").splitlines()}
|
|
assert expected_tokens <= token_lines
|
|
assert ".dark {" in styles
|
|
assert "--v2-accent: var(--primary)" in console
|
|
assert "--v2-sidebar: var(--sidebar)" in console
|
|
assert "color: var(--destructive)" in speech
|
|
|
|
|
|
def test_theme_asset_versions_are_consistent_with_the_pwa_shell() -> None:
|
|
html = source("index.html")
|
|
service_worker = source("service-worker.js")
|
|
|
|
for asset in (
|
|
"/styles.css?v=dual-seat-23",
|
|
"/console-v2.css?v=delivery-state-69",
|
|
"/self-extension.css?v=pet-settings-52",
|
|
"/speech-input.css?v=sensevoice-7",
|
|
"/codepen-glass-theme.css?v=creative-cloud-2",
|
|
):
|
|
assert asset in html
|
|
assert f'"{asset}"' in service_worker
|
|
assert 'const CACHE_VERSION = "cognitive-agent-pad-v305"' in service_worker
|
|
|
|
|
|
def test_reference_glass_theme_covers_the_complete_workspace() -> None:
|
|
html = source("index.html")
|
|
theme = source("codepen-glass-theme.css")
|
|
app = source("app.js")
|
|
|
|
assert '<meta name="theme-color" content="#17182b" id="themeColorMeta" />' in html
|
|
assert 'const key = "cognitive-agent.glass-theme"' in html
|
|
assert 'id="glassThemeToggle"' in html
|
|
assert 'data-theme-icon="sun"' in html
|
|
assert 'data-theme-icon="moon"' in html
|
|
assert 'const GLASS_THEME_STORAGE_KEY = "cognitive-agent.glass-theme"' in app
|
|
assert "localStorage.setItem(GLASS_THEME_STORAGE_KEY, normalized)" in app
|
|
assert "document.cookie = `${GLASS_THEME_STORAGE_KEY}=${normalized}" in app
|
|
assert 'new CustomEvent("cognitive-agent:theme-change"' in app
|
|
for contract in (
|
|
"--glass-shell:",
|
|
"--glass-panel:",
|
|
"--glass-card:",
|
|
"backdrop-filter: blur(22px) saturate(1.25)",
|
|
".main-panel > .v2-mode-rail",
|
|
"#assistantDrawer",
|
|
".joint-twin-shell > .twin-band",
|
|
"#v2ExtensionPanel .v2-extension-sync-mount",
|
|
".v2-pet-card",
|
|
".v2-pet-presence-card",
|
|
".v2-pet-settings-dialog",
|
|
".mode-gallery",
|
|
".v2-nfc-dialog",
|
|
".energy-monitor-page",
|
|
):
|
|
assert contract in theme
|
|
assert 'html[data-glass-theme="light"] body.console-v2' in theme
|
|
assert ".glass-theme-toggle" in theme
|
|
assert "width: 100vw !important" in theme
|
|
assert "height: 100dvh !important" in theme
|
|
assert "margin: 0 !important" in theme
|
|
assert "border: 0 !important" in theme
|
|
assert "border-radius: 0 !important" in theme
|
|
|
|
|
|
def test_vehicle_twin_icons_and_nested_overlays_use_semantic_tokens() -> None:
|
|
styles = source("styles.css")
|
|
console = source("console-v2.css")
|
|
extension = source("self-extension.css")
|
|
|
|
expected_console_rules = {
|
|
".console-v2 .v2-twin-hotspot-label",
|
|
".console-v2 .v2-twin-device-popover",
|
|
".console-v2 .v2-reasoning-field",
|
|
".console-v2 .device-power-drawer",
|
|
".console-v2 .v2-sync-log li[data-log-status=\"error\"] > span",
|
|
"background: var(--primary)",
|
|
"background: var(--popover)",
|
|
}
|
|
expected_overlay_rules = {
|
|
".mode-gallery-backdrop",
|
|
".mode-editor-backdrop",
|
|
".gesture-drawer",
|
|
"background: var(--card)",
|
|
"border-color: var(--border)",
|
|
}
|
|
expected_extension_rules = {
|
|
".v2-extension-dialog::backdrop",
|
|
".v2-gscu-tcp-dialog",
|
|
".v2-nfc-dialog",
|
|
".v2-extension-toast.error",
|
|
".console-v2 #v2ExtensionPanel .v2-extension-gateway > button",
|
|
"background: var(--destructive)",
|
|
}
|
|
|
|
assert all(rule in console for rule in expected_console_rules)
|
|
assert all(rule in styles for rule in expected_overlay_rules)
|
|
assert all(rule in extension for rule in expected_extension_rules)
|
|
|
|
|
|
def test_operator_controls_share_status_and_typography_tokens() -> None:
|
|
html = source("index.html")
|
|
styles = source("styles.css")
|
|
console = source("console-v2.css")
|
|
script = source("console-v2.js")
|
|
|
|
for token in (
|
|
"--status-online:",
|
|
"--status-offline:",
|
|
"--status-info:",
|
|
"--status-success:",
|
|
"--status-warning:",
|
|
"--status-error:",
|
|
"--heading-foreground:",
|
|
"--body-foreground:",
|
|
"--subtle-foreground:",
|
|
"--text-caption:",
|
|
"--text-control:",
|
|
"--text-input:",
|
|
"--text-label:",
|
|
):
|
|
assert token in styles
|
|
assert '<div class="mode-rail" aria-label="功能模式">' in html
|
|
assert 'id="modeRailToggle"' in html
|
|
assert 'aria-controls="modeRailContent"' in html
|
|
assert 'id="modeRailContent"' in html
|
|
assert 'role="region"' in html
|
|
assert 'aria-label="功能模式列表"' in html
|
|
assert '<span>AI 新建</span>' in html
|
|
assert "模拟下发" in html
|
|
assert "真实下发" in html
|
|
assert 'id="simulationMode"' not in html
|
|
assert 'class="v2-sync-link-stack"' not in script
|
|
assert '.mode-option[data-connection-state="online"]::after' in styles
|
|
assert '.mode-option[data-connection-state="offline"]::after' in styles
|
|
assert "font-size: var(--text-control) !important" in console
|
|
|
|
|
|
def test_wired_can_link_requires_real_device_connection_evidence() -> None:
|
|
app = source("app.js")
|
|
|
|
assert 'runtime?.state_sync?.state === "online"' in app
|
|
assert '["online", "ready"].includes(runtime?.state_sync?.state)' not in app
|
|
assert 'wiredCanProbe: { passenger: false, vehicle: false }' in app
|
|
assert 'updateWiredCanProbe("passenger", true, requestedTransport)' in app
|
|
assert 'updateWiredCanProbe("vehicle", true, requestedTransport)' in app
|
|
assert 'updateWiredCanProbe("passenger", false, requestedTransport)' in app
|
|
assert 'updateWiredCanProbe("vehicle", false, requestedTransport)' in app
|
|
assert "resetTransportConnectionEvidence();" in app
|
|
assert 'label.textContent = `${transportLabel} · 信号链路断开`' in app
|
|
|
|
|
|
def test_command_fields_use_distinct_semantic_colors() -> None:
|
|
styles = source("styles.css")
|
|
console = source("console-v2.css")
|
|
|
|
for token in (
|
|
"--command-channel:",
|
|
"--command-source:",
|
|
"--command-delivered:",
|
|
"--command-pending:",
|
|
"--command-category:",
|
|
"--command-control:",
|
|
"--command-frame:",
|
|
):
|
|
assert token in styles
|
|
for mapping in (
|
|
"color: var(--command-channel)",
|
|
"color: var(--command-source)",
|
|
"color: var(--command-pending)",
|
|
"color: var(--command-delivered)",
|
|
"color: var(--command-category)",
|
|
"color: var(--command-control)",
|
|
"color: var(--command-frame)",
|
|
):
|
|
assert mapping in console
|
|
|
|
|
|
def test_function_modes_use_an_inline_accessible_collapsible() -> None:
|
|
html = source("index.html")
|
|
app = source("app.js")
|
|
console = source("console-v2.css")
|
|
|
|
assert '<div class="mode-rail-bar">' in html
|
|
assert 'id="modeRailSelection">静态模式</span>' in html
|
|
assert 'id="modeGalleryToggle"' in html
|
|
assert 'class="mode-rail-identity"' in html
|
|
assert '<span>AI 新建</span>' in html
|
|
assert 'aria-expanded="true"' in html
|
|
assert '<details class="mode-rail"' not in html
|
|
assert 'class="mode-rail-dropdown"' not in html
|
|
assert 'toggle.setAttribute("aria-expanded", String(open))' in app
|
|
assert "content.hidden = !open" in app
|
|
assert 'mainPanel.insertBefore(modeRail, output)' in source("console-v2.js")
|
|
assert 'grid-column: 1 / -1' in source("assistant-chat.css")
|
|
assert 'document.body.classList.toggle("mode-rail-expanded", open)' not in app
|
|
compact_styles = console.split("/* Compact function-mode strip", 1)[1]
|
|
assert '.mode-rail-content[hidden]' in compact_styles
|
|
assert ".mode-rail-expanded .main-panel" not in console
|
|
assert "position: static" in compact_styles
|
|
assert "overflow-x: auto" in compact_styles
|
|
assert "min-height: 54px" in compact_styles
|
|
|
|
|
|
def test_vehicle_device_cards_hide_stale_values_without_signal() -> None:
|
|
console = source("console-v2.js")
|
|
|
|
assert "function normalizeDeviceReadout(value)" in console
|
|
assert "function deviceFeedbackAvailable(deviceId)" in console
|
|
assert 'return rows.map(([label]) => [label, "-"])' in console
|
|
assert 'const waiting = rows.some(([, value]) => value === "-")' in console
|
|
assert '"cognitive-agent:vehicle-feedback-state"' in console
|
|
|
|
|
|
def test_final_theme_layers_do_not_reintroduce_raw_ui_colors() -> None:
|
|
sections = (
|
|
source("styles.css").split("/* Final semantic theme layer", 1)[1],
|
|
source("console-v2.css").split("/* Final semantic theme cascade", 1)[1],
|
|
source("self-extension.css").split(
|
|
"/* Final semantic theme layer for extension surfaces", 1
|
|
)[1],
|
|
)
|
|
raw_color = re.compile(r"#[0-9a-fA-F]{3,8}\b|rgba?\(")
|
|
|
|
for section in sections:
|
|
assert not raw_color.search(section)
|
|
|
|
|
|
def test_all_five_output_modules_are_covered_by_the_final_theme_layer() -> None:
|
|
console = source("console-v2.css")
|
|
final_layer = console.split("/* Final semantic theme cascade", 1)[1]
|
|
|
|
for selector in (
|
|
".joint-twin-shell.v2-output-page",
|
|
".decision-list li",
|
|
".result-workspace #panel-config.config-panel",
|
|
".plan-panel .frame-summary",
|
|
".execution-summary",
|
|
):
|
|
assert selector in final_layer
|
|
|
|
assert "--config-accent: var(--primary)" in final_layer
|
|
assert "background: var(--card)" in final_layer
|
|
assert "border-color: var(--border)" in final_layer
|
|
|
|
|
|
def test_ai_light_blue_theme_closes_after_legacy_color_layers() -> None:
|
|
console = source("console-v2.css")
|
|
assistant = source("assistant-chat.css")
|
|
extension = source("self-extension.css")
|
|
|
|
assert console.rfind("/* AI assistant light-blue final override") > console.rfind(
|
|
"/* Final semantic theme cascade"
|
|
)
|
|
assert assistant.rfind("/* Shared AI light-blue palette") > assistant.rfind(
|
|
"@media (prefers-reduced-motion: reduce)"
|
|
)
|
|
assert extension.rfind("/* AI light-blue closure") > extension.rfind(
|
|
"/* Final semantic theme layer for extension surfaces"
|
|
)
|
|
assert "color: var(--body-foreground)" in console
|
|
assert "color: var(--subtle-foreground)" in console
|
|
assert "background: var(--status-success-soft)" in assistant
|
|
assert "background: var(--primary)" in assistant
|