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