优化后端和前端逻辑
This commit is contained in:
@@ -288,6 +288,35 @@ operations:
|
||||
self.assertTrue(
|
||||
(user_root / "vehicles" / "denza_n7" / "user" / "office.yaml").is_file()
|
||||
)
|
||||
|
||||
create_request = urllib.request.Request(
|
||||
f"{self.base_url}/api/mode-profiles",
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json"},
|
||||
data=json.dumps(
|
||||
{
|
||||
"name": "通勤模式",
|
||||
"instruction": instruction,
|
||||
"source_request_id": request_id,
|
||||
"vehicle_id": "denza_n7",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8"),
|
||||
)
|
||||
with self.opener.open(create_request, timeout=3) as response:
|
||||
created = json.loads(response.read().decode("utf-8"))
|
||||
self.assertEqual(response.status, 201)
|
||||
self.assertEqual(created["mode_profile"]["origin"], "user")
|
||||
self.assertEqual(created["mode_profile"]["name"], "通勤模式")
|
||||
self.assertTrue(
|
||||
(
|
||||
user_root
|
||||
/ "vehicles"
|
||||
/ "denza_n7"
|
||||
/ "user"
|
||||
/ f"{created['mode_profile']['id']}.yaml"
|
||||
).is_file()
|
||||
)
|
||||
finally:
|
||||
self.service.mode_profiles = original_registry
|
||||
self.service.artifact_root = original_artifact_root
|
||||
@@ -588,18 +617,18 @@ operations:
|
||||
def test_static_console_and_runtime_endpoint(self) -> None:
|
||||
with self.opener.open(f"{self.base_url}/", timeout=3) as response:
|
||||
html = response.read().decode("utf-8")
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-109", timeout=3) as response:
|
||||
with self.opener.open(f"{self.base_url}/app.js?v=dual-seat-114", timeout=3) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/gesture-control.js?v=android-pad-9", timeout=3
|
||||
) as response:
|
||||
gesture_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/console-v2.js?v=delivery-state-50", timeout=3
|
||||
f"{self.base_url}/console-v2.js?v=delivery-state-54", timeout=3
|
||||
) as response:
|
||||
console_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/console-v2.css?v=delivery-state-43", timeout=3
|
||||
f"{self.base_url}/console-v2.css?v=delivery-state-50", timeout=3
|
||||
) as response:
|
||||
console_styles = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -607,7 +636,7 @@ operations:
|
||||
) as response:
|
||||
speech_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/speech-input.css?v=sensevoice-4", timeout=3
|
||||
f"{self.base_url}/speech-input.css?v=sensevoice-5", timeout=3
|
||||
) as response:
|
||||
speech_styles = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
@@ -615,11 +644,11 @@ operations:
|
||||
) as response:
|
||||
speech_worklet = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/self-extension.js?v=gscu-tcp-40", timeout=3
|
||||
f"{self.base_url}/self-extension.js?v=gscu-tcp-41", timeout=3
|
||||
) as response:
|
||||
extension_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/self-extension.css?v=gscu-tcp-39", timeout=3
|
||||
f"{self.base_url}/self-extension.css?v=gscu-tcp-42", timeout=3
|
||||
) as response:
|
||||
extension_styles = response.read().decode("utf-8").replace("\r\n", "\n")
|
||||
with self.opener.open(
|
||||
@@ -652,21 +681,21 @@ operations:
|
||||
self.assertIn("CMD 0x431 · FB 0x41A · CH1", html)
|
||||
self.assertIn("CMD 0x431 · FB 0x388 · CH1", html)
|
||||
self.assertNotIn("透明车体 · 实体设备", html)
|
||||
self.assertIn('src="/app.js?v=dual-seat-109"', html)
|
||||
self.assertIn('href="/styles.css?v=dual-seat-16"', html)
|
||||
self.assertIn('src="/app.js?v=dual-seat-114"', html)
|
||||
self.assertIn('href="/styles.css?v=dual-seat-21"', html)
|
||||
self.assertIn('src="/gesture-control.js?v=android-pad-9"', html)
|
||||
self.assertNotIn('class="vehicle-system-hud"', html)
|
||||
self.assertIn('src="/console-v2.js?v=delivery-state-50"', html)
|
||||
self.assertIn('href="/console-v2.css?v=delivery-state-43"', html)
|
||||
self.assertIn('src="/console-v2.js?v=delivery-state-54"', html)
|
||||
self.assertIn('href="/console-v2.css?v=delivery-state-50"', html)
|
||||
self.assertIn('src="/speech-input.js?v=sensevoice-6"', html)
|
||||
self.assertIn('href="/speech-input.css?v=sensevoice-4"', html)
|
||||
self.assertIn('href="/speech-input.css?v=sensevoice-5"', html)
|
||||
self.assertIn('id="speechDictationButton"', html)
|
||||
self.assertIn('class="speech-waveform"', html)
|
||||
self.assertIn('<body class="console-v2">', html)
|
||||
self.assertIn('id="v2BootScreen"', html)
|
||||
self.assertIn('正在加载车机控制与实时状态', html)
|
||||
self.assertIn('src="/self-extension.js?v=gscu-tcp-40"', html)
|
||||
self.assertIn('href="/self-extension.css?v=gscu-tcp-39"', html)
|
||||
self.assertIn('src="/self-extension.js?v=gscu-tcp-41"', html)
|
||||
self.assertIn('href="/self-extension.css?v=gscu-tcp-42"', html)
|
||||
self.assertNotIn("self-calibration", html)
|
||||
self.assertNotIn("self-repair", html)
|
||||
self.assertNotIn("self-evolution", html)
|
||||
@@ -680,7 +709,7 @@ operations:
|
||||
'panelHeading.classList.add("v2-operation-heading", "v2-module-heading")',
|
||||
console_script,
|
||||
)
|
||||
self.assertIn('kicker.textContent = "REMOTE ORCHESTRATION · LIVE SYNC"', console_script)
|
||||
self.assertIn('if (title) title.textContent = "一句话远程编排"', console_script)
|
||||
self.assertIn('fetch("/api/vehicle-display-sync"', console_script)
|
||||
self.assertIn('document.body.classList.add("v2-passive-display", "v2-interactive-sync")', console_script)
|
||||
self.assertIn('input.readOnly = false', console_script)
|
||||
@@ -713,7 +742,7 @@ operations:
|
||||
self.assertNotIn('id="v2VehicleState"', console_script)
|
||||
self.assertIn('actionRail.append(cameraToggle)', console_script)
|
||||
self.assertNotIn("function enhanceConfiguredDropdowns()", console_script)
|
||||
self.assertIn("function updateModeEdgeHints()", console_script)
|
||||
self.assertNotIn('modeChips?.addEventListener("wheel"', console_script)
|
||||
self.assertIn('classList.toggle("v2-twin-active", definition.index === 0)', console_script)
|
||||
self.assertIn('label: "执行结果与硬件回读"', console_script)
|
||||
self.assertIn("function keepRunButtonAvailable()", console_script)
|
||||
@@ -793,15 +822,33 @@ operations:
|
||||
)
|
||||
self.assertIn('data-control-transport="wired_can"', html)
|
||||
self.assertIn('data-control-transport="wireless_mqtt"', html)
|
||||
self.assertIn('<div class="mode-rail" aria-label="功能模式">', html)
|
||||
self.assertIn('id="modeRailToggle"', html)
|
||||
self.assertIn('id="modeRailContent"', html)
|
||||
self.assertIn('role="region"', html)
|
||||
self.assertIn('aria-label="功能模式列表"', html)
|
||||
self.assertIn('<span>自定义</span>', html)
|
||||
self.assertIn('模拟下发', html)
|
||||
self.assertIn('真实下发', html)
|
||||
self.assertNotIn('id="simulationMode"', html)
|
||||
self.assertIn('control_transport: state.controlTransport', app_script)
|
||||
self.assertIn('button.dataset.connectionState = connectionState', app_script)
|
||||
self.assertIn('信号链路${connectionState === "online" ? "正常" : "断开"}', app_script)
|
||||
self.assertIn('const transportField = $("#vehicleTransportField", originalForm)', console_script)
|
||||
self.assertNotIn('runtimeControls.append(transportField)', console_script)
|
||||
self.assertIn('insertVisibleControl(transportField, "v2-sync-transport-field")', console_script)
|
||||
self.assertNotIn('class="v2-sync-link-stack"', console_script)
|
||||
self.assertNotIn('id="v2HeadUnitState"', console_script)
|
||||
self.assertIn('.v2-sync-execution-field .execution-mode-options', console_styles)
|
||||
self.assertIn('grid-template-columns: repeat(3, minmax(0, 1fr))', console_styles)
|
||||
self.assertIn(
|
||||
'.v2-sync-execution-field .execution-mode-options {\n grid-template-columns: repeat(2, minmax(0, 1fr))',
|
||||
console_styles,
|
||||
)
|
||||
self.assertNotIn('class="v2-sync-protocol"', console_script)
|
||||
self.assertIn('.console-v2 .v2-transport-field .vehicle-transport-options', console_styles)
|
||||
self.assertIn('.v2-sync-transport-field .mode-option', console_styles)
|
||||
self.assertIn('.v2-mode-rail > .mode-rail-content[hidden]', console_styles)
|
||||
self.assertIn('font-size: var(--text-control) !important', console_styles)
|
||||
self.assertIn('overflow-wrap: anywhere', console_styles)
|
||||
self.assertIn('button.disabled = false', app_script)
|
||||
self.assertIn('button.setAttribute("aria-pressed", String(selected))', app_script)
|
||||
@@ -846,7 +893,7 @@ operations:
|
||||
self.assertIn('外设玩具能力学习', extension_script)
|
||||
self.assertIn('外设玩具与车端联动', extension_script)
|
||||
self.assertGreaterEqual(extension_script.count('class="v2-module-heading"'), 2)
|
||||
self.assertGreaterEqual(extension_script.count('SELF-EXTENSION'), 2)
|
||||
self.assertNotIn('class="section-kicker"', extension_script)
|
||||
self.assertIn('id="extensionExecuteButton"', extension_script)
|
||||
self.assertIn('data-lucide="play"', extension_script)
|
||||
self.assertNotIn('id="extensionControlHealth"', extension_script)
|
||||
@@ -912,10 +959,10 @@ operations:
|
||||
self.assertIn("CAN 指令已提交,等待车辆状态确认", console_script)
|
||||
self.assertIn('canvas.addEventListener("webglcontextlost"', app_script)
|
||||
self.assertIn('"cognitive-agent:module-change"', app_script)
|
||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v221"', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-109"', service_worker)
|
||||
self.assertIn('const CACHE_VERSION = "cognitive-agent-pad-v227"', service_worker)
|
||||
self.assertIn('"/app.js?v=dual-seat-114"', service_worker)
|
||||
self.assertIn('"/speech-input.js?v=sensevoice-6"', service_worker)
|
||||
self.assertIn('"/speech-input.css?v=sensevoice-4"', service_worker)
|
||||
self.assertIn('"/speech-input.css?v=sensevoice-5"', service_worker)
|
||||
self.assertIn('"/speech-audio-worklet.js?v=sensevoice-4"', service_worker)
|
||||
self.assertIn('fetch("/api/speech/transcribe"', speech_script)
|
||||
self.assertIn('"X-Speech-Utterance-Id"', speech_script)
|
||||
@@ -956,7 +1003,7 @@ operations:
|
||||
self.assertNotIn("gestureDebug", gesture_script)
|
||||
self.assertIn("HARDWARE_REQUEST_TIMEOUT_MS = 15 * 60 * 1000", app_script)
|
||||
self.assertIn(
|
||||
"requestTimeoutForExecutionMode(state.executionMode)", app_script
|
||||
"requestTimeoutForExecutionMode(executionMode)", app_script
|
||||
)
|
||||
self.assertIn('error.code === "hardware_busy"', app_script)
|
||||
self.assertIn("BENCH BUSY", app_script)
|
||||
@@ -977,12 +1024,15 @@ operations:
|
||||
self.assertIn('id="saveModeToggle"', html)
|
||||
self.assertIn("根据指令创建", html)
|
||||
self.assertIn('aria-label="快捷功能模式"', html)
|
||||
self.assertIn('id="modeRailPrev"', html)
|
||||
self.assertIn('id="modeRailNext"', html)
|
||||
self.assertIn('id="modeRailContent"', html)
|
||||
self.assertIn('aria-label="功能模式列表"', html)
|
||||
self.assertNotIn('id="modeRailPrev"', html)
|
||||
self.assertNotIn('id="modeRailNext"', html)
|
||||
self.assertLess(html.index('id="saveModeToggle"'), html.index('id="modeRailContent"'))
|
||||
self.assertIn('id="modeGalleryToggle"', html)
|
||||
self.assertIn('id="modeGallery"', html)
|
||||
self.assertIn('id="modeGalleryGroups"', html)
|
||||
self.assertIn("全部功能模式", html)
|
||||
self.assertIn("自定义模式", html)
|
||||
self.assertIn('id="modeEditorBackdrop"', html)
|
||||
self.assertIn('id="modeEditorName"', html)
|
||||
self.assertIn('id="modeEditorInstruction"', html)
|
||||
@@ -990,7 +1040,7 @@ operations:
|
||||
self.assertIn('id="modeEditorComplete"', html)
|
||||
self.assertIn('id="modeEditorFlowPreview"', html)
|
||||
self.assertIn('class="mode-editor-flow-preview empty"', html)
|
||||
self.assertIn('id="saveModeSource"', html)
|
||||
self.assertNotIn('id="saveModeSource"', html)
|
||||
self.assertNotIn("主驾 24℃", html)
|
||||
populate_modes = app_script[
|
||||
app_script.index("function populateModeChips()")
|
||||
@@ -998,13 +1048,18 @@ operations:
|
||||
]
|
||||
self.assertNotIn("ch1_body_can_driver_only", populate_modes)
|
||||
self.assertIn("function populateModeGallery", app_script)
|
||||
self.assertIn("function updateModeRailNavigation", app_script)
|
||||
self.assertIn("function scrollModeRail", app_script)
|
||||
self.assertNotIn("function updateModeRailNavigation", app_script)
|
||||
self.assertNotIn("function scrollModeRail", app_script)
|
||||
self.assertIn('className = "mode-gallery-card"', app_script)
|
||||
self.assertIn("function createModeGalleryAddCard", app_script)
|
||||
self.assertIn('button.dataset.modeCreate = "true"', app_script)
|
||||
self.assertIn("function openModeEditorForCreate", app_script)
|
||||
self.assertIn('apiRequest(endpoint', app_script)
|
||||
self.assertIn('? "/api/mode-profiles"', app_script)
|
||||
self.assertIn("#modeGallery button[data-mode-profile-id]", app_script)
|
||||
self.assertIn("function setModeExecutionState", app_script)
|
||||
self.assertIn("function requestCommandAndWait", app_script)
|
||||
self.assertIn("已创建并添加到功能模式栏", app_script)
|
||||
self.assertNotIn("function requestCommandAndWait", app_script)
|
||||
self.assertIn("已永久保存到模式卡片", app_script)
|
||||
self.assertLess(
|
||||
app_script.index('id: "personal"'),
|
||||
app_script.index('id: "frequent"'),
|
||||
@@ -1039,7 +1094,7 @@ operations:
|
||||
self.assertIn('id="cabinCarouselNext"', app_script)
|
||||
self.assertIn('aria-label="左右浏览设备卡片"', app_script)
|
||||
self.assertNotIn('class="cabin-category-tabs"', app_script)
|
||||
self.assertIn('<span class="section-kicker">LIVE VEHICLE DIGITAL TWIN</span>', html)
|
||||
self.assertNotIn('class="section-kicker"', html)
|
||||
self.assertIn('.joint-twin-shell > .twin-band {', console_styles)
|
||||
self.assertIn('grid-template-rows: 62px minmax(0, 1fr)', console_styles)
|
||||
self.assertIn('grid-template-columns: repeat(2, minmax(0, 1fr))', console_styles)
|
||||
@@ -1327,7 +1382,7 @@ operations:
|
||||
) as response:
|
||||
extension_script = response.read().decode("utf-8")
|
||||
with self.opener.open(
|
||||
f"{self.base_url}/app.js?v=dual-seat-109", timeout=3
|
||||
f"{self.base_url}/app.js?v=dual-seat-114", timeout=3
|
||||
) as response:
|
||||
app_script = response.read().decode("utf-8")
|
||||
|
||||
|
||||
@@ -621,6 +621,25 @@ operations:
|
||||
if item["id"] == "office"
|
||||
)
|
||||
self.assertEqual(persisted["name"], "专注办公模式")
|
||||
created = self.service.create_mode_profile_card(
|
||||
"自然舒享模式",
|
||||
instruction,
|
||||
generated["request_id"],
|
||||
"denza_n7",
|
||||
)
|
||||
self.assertEqual(created["status"], "succeeded")
|
||||
self.assertEqual(created["mode_profile"]["origin"], "user")
|
||||
self.assertEqual(created["mode_profile"]["instruction"], instruction)
|
||||
self.assertNotEqual(created["mode_profile"]["id"], "office")
|
||||
self.assertTrue(
|
||||
(
|
||||
user_root
|
||||
/ "vehicles"
|
||||
/ "denza_n7"
|
||||
/ "user"
|
||||
/ f"{created['mode_profile']['id']}.yaml"
|
||||
).is_file()
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "生成指令"):
|
||||
self.service.save_mode_profile_card(
|
||||
"office",
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
|
||||
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(1 0 0)",
|
||||
"--foreground: oklch(0.145 0.008 326)",
|
||||
"--primary: oklch(0.488 0.243 264.376)",
|
||||
"--primary-foreground: oklch(0.97 0.014 254.604)",
|
||||
"--destructive: oklch(0.577 0.245 27.325)",
|
||||
"--sidebar-primary: oklch(0.546 0.245 262.881)",
|
||||
"--background: oklch(0.145 0.008 326)",
|
||||
"--foreground: oklch(0.985 0 0)",
|
||||
"--primary: oklch(0.424 0.199 265.638)",
|
||||
"--sidebar-primary: oklch(0.623 0.214 259.815)",
|
||||
}
|
||||
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-21",
|
||||
"/console-v2.css?v=delivery-state-50",
|
||||
"/self-extension.css?v=gscu-tcp-42",
|
||||
"/speech-input.css?v=sensevoice-5",
|
||||
):
|
||||
assert asset in html
|
||||
assert f'"{asset}"' in service_worker
|
||||
assert 'const CACHE_VERSION = "cognitive-agent-pad-v227"' in service_worker
|
||||
|
||||
|
||||
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:",
|
||||
"--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>自定义</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 '<span>自定义</span>' 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 'document.body.classList.toggle("mode-rail-expanded", open)' not in app
|
||||
assert 'document.addEventListener("pointerdown"' in app
|
||||
assert '.v2-mode-rail > .mode-rail-content[hidden]' in console
|
||||
assert ".mode-rail-expanded .main-panel" not in console
|
||||
assert "position: absolute" in console
|
||||
assert "top: calc(100% + 8px)" in console
|
||||
assert "overflow-y: auto" in console
|
||||
assert "scrollbar-gutter: stable" in console
|
||||
|
||||
|
||||
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
|
||||
@@ -17,7 +17,7 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
|
||||
|
||||
assert 'data-open-nfc-binding' in script
|
||||
assert '靠近读卡器后' in script
|
||||
assert 'NFC · DEVICE AUTHORIZATION' in script
|
||||
assert '<h2>NFC 设备授权</h2>' in script
|
||||
assert 'NFC 设备授权' in script
|
||||
assert 'api("nfc/status")' in script
|
||||
assert 'function bindCardToDevice(device, cardUid)' in script
|
||||
@@ -60,8 +60,8 @@ def test_unified_console_exposes_device_id_based_nfc_authorization() -> None:
|
||||
assert '.v2-nfc-command-list' in styles
|
||||
assert '.v2-nfc-command-option' in styles
|
||||
assert '.v2-nfc-select-all' in styles
|
||||
assert 'self-extension.js?v=gscu-tcp-40' in html
|
||||
assert 'self-extension.css?v=gscu-tcp-39' in html
|
||||
assert 'self-extension.js?v=gscu-tcp-41' in html
|
||||
assert 'self-extension.css?v=gscu-tcp-42' in html
|
||||
|
||||
|
||||
def test_unified_console_exposes_gscu_tcp_configuration_and_actions() -> None:
|
||||
@@ -139,4 +139,4 @@ def test_main_parsed_control_panel_renders_validated_infrared_operations() -> No
|
||||
assert 'frameLabel: "GSCU1B TCP"' in console
|
||||
assert "已解析控制指令" in console
|
||||
assert 'li[data-can-channel="IR"]' in styles
|
||||
assert "console-v2.js?v=delivery-state-50" in html
|
||||
assert "console-v2.js?v=delivery-state-54" in html
|
||||
|
||||
Reference in New Issue
Block a user