feat: 完善 N7 智能座舱交互与控制链路
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from cognitive_agent.application.mode_profile import ModeProfileRegistry
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
WEB = ROOT / "apps" / "web-console"
|
||||
|
||||
|
||||
def test_runtime_mode_catalog_exposes_expected_static_and_dynamic_membership() -> None:
|
||||
registry = ModeProfileRegistry(ROOT / "config" / "modes")
|
||||
modes = registry.public_modes(vehicle_id="denza_n7")
|
||||
grouped = {
|
||||
behavior: {mode["id"] for mode in modes if mode["behavior"] == behavior}
|
||||
for behavior in {"static", "cyclic"}
|
||||
}
|
||||
|
||||
assert grouped["static"] == {
|
||||
"zero_gravity",
|
||||
"office",
|
||||
"nap",
|
||||
"queen_passenger_driving",
|
||||
"leisure_lounger",
|
||||
"cruise_sofa",
|
||||
"easy_entry_exit",
|
||||
"electric_beach_chair",
|
||||
"calf_decompression_float",
|
||||
"baby_nap",
|
||||
"meditation_space",
|
||||
}
|
||||
assert grouped["cyclic"] == {"rocking_horse", "dynamic_cradle"}
|
||||
assert len(modes) == len(grouped["static"] | grouped["cyclic"])
|
||||
|
||||
|
||||
def node_runtime() -> str:
|
||||
candidates = [
|
||||
shutil.which("node"),
|
||||
Path.home()
|
||||
/ ".cache"
|
||||
/ "codex-runtimes"
|
||||
/ "codex-primary-runtime"
|
||||
/ "dependencies"
|
||||
/ "node"
|
||||
/ "bin"
|
||||
/ "node.exe",
|
||||
Path.home()
|
||||
/ ".cache"
|
||||
/ "codex-runtimes"
|
||||
/ "codex-primary-runtime"
|
||||
/ "dependencies"
|
||||
/ "node"
|
||||
/ "bin"
|
||||
/ "node",
|
||||
]
|
||||
for candidate in candidates:
|
||||
if candidate and Path(candidate).is_file():
|
||||
return str(candidate)
|
||||
raise AssertionError("Node.js is required to execute the mode gallery grouping contract")
|
||||
|
||||
|
||||
def test_mode_gallery_classifies_runtime_profiles_into_two_behavior_groups() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
start = app.index("const MODE_GALLERY_GROUPS")
|
||||
end = app.index("\n\nfunction createModeGalleryCard", start)
|
||||
snippet = app[start:end]
|
||||
script = "const snippet = " + json.dumps(snippet) + ";\n" + r"""
|
||||
const vm = require("node:vm");
|
||||
const modes = [
|
||||
{ id: "user-static", origin: "user", deletable: true, behavior: "static" },
|
||||
{ id: "office", origin: "preset", behavior: "static" },
|
||||
{ id: "future-cycle", origin: "user", deletable: true, behavior: "cyclic" },
|
||||
{ id: "timeline-only", origin: "preset", timeline: { repeat: "once" } },
|
||||
{ id: "rocking_horse", origin: "preset" },
|
||||
{ id: "unknown", origin: "preset" },
|
||||
{ id: "future-behavior", origin: "preset", behavior: "scheduled", timeline: { repeat: "loop" } },
|
||||
];
|
||||
const context = { state: { runtime: { modes } } };
|
||||
vm.createContext(context);
|
||||
vm.runInContext(
|
||||
"function modeProfiles() { return state.runtime.modes; }\n"
|
||||
+ snippet
|
||||
+ "\nthis.result = {"
|
||||
+ "groups: modeGalleryBuckets().map((group) => ({ id: group.id, title: group.title, modeIds: group.modes.map((mode) => mode.id) })),"
|
||||
+ "dynamicFlags: state.runtime.modes.map((mode) => isDynamicModeProfile(mode))"
|
||||
+ "};",
|
||||
context,
|
||||
);
|
||||
console.log(JSON.stringify(context.result));
|
||||
"""
|
||||
completed = subprocess.run(
|
||||
[node_runtime(), "-e", script],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = json.loads(completed.stdout)
|
||||
|
||||
assert [(group["id"], group["title"]) for group in result["groups"]] == [
|
||||
("static", "静态模式"),
|
||||
("dynamic", "动态模式"),
|
||||
]
|
||||
assert result["groups"][0]["modeIds"] == ["user-static", "office", "unknown"]
|
||||
assert result["groups"][1]["modeIds"] == [
|
||||
"future-cycle",
|
||||
"rocking_horse",
|
||||
"timeline-only",
|
||||
"future-behavior",
|
||||
]
|
||||
assert result["dynamicFlags"] == [False, False, True, True, True, False, True]
|
||||
|
||||
|
||||
def test_mode_gallery_layout_and_copy_expose_only_static_and_dynamic_sections() -> None:
|
||||
app = (WEB / "app.js").read_text(encoding="utf-8")
|
||||
html = (WEB / "index.html").read_text(encoding="utf-8")
|
||||
styles = (WEB / "styles.css").read_text(encoding="utf-8")
|
||||
definitions = app[
|
||||
app.index("const MODE_GALLERY_GROUPS")
|
||||
: app.index("const MODE_DOMAIN_LABELS")
|
||||
]
|
||||
|
||||
assert definitions.count('title: "静态模式"') == 1
|
||||
assert definitions.count('title: "动态模式"') == 1
|
||||
for legacy_group in ('id: "personal"', 'id: "frequent"', 'id: "comfort"'):
|
||||
assert legacy_group not in definitions
|
||||
assert 'if (group.id === "static") grid.append(createModeGalleryAddCard())' in app
|
||||
assert "function createModeGalleryEmpty(group)" in app
|
||||
assert 'section.setAttribute("aria-labelledby", headingId)' in app
|
||||
assert "静态模式 · 动态模式" in html
|
||||
assert "按运行方式整理,预设与自定义模式统一归类" in html
|
||||
assert "grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));" in styles
|
||||
Reference in New Issue
Block a user