Files
Cognitive_Agent/tests/unit/test_assistant_frontend_contract.py
T

554 lines
27 KiB
Python

from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
WEB = ROOT / "apps" / "web-console"
def vue_shell() -> str:
return (WEB / "src" / "App.vue").read_text(encoding="utf-8")
def test_console_contains_ai_assistant_surface():
html = (WEB / "index.html").read_text(encoding="utf-8")
template = vue_shell()
vite_entry = (WEB / "src" / "main.js").read_text(encoding="utf-8")
assert 'id="assistantTranscript"' in template
assert 'id="assistantMessages"' in template
assert "你好,我是 AI助手。你可以向我提问" in template
assert 'aria-label="AI助手对话"' in template
assert 'placeholder="向 AI助手提问或交代工作"' in template
assert 'aria-label="发送消息"' in template
assert 'import "../assistant-chat.css";' in vite_entry
assert 'src="/assistant-chat.js?v=codex-assistant-41-unified-preview"' in html
def test_codex_assistant_assets_are_versioned_together_in_the_service_worker():
service_worker = (ROOT / "apps/web-console/service-worker.js").read_text(encoding="utf-8")
assert (
'const CACHE_VERSION = "cognitive-agent-pad-v338-unified-preview"'
in service_worker
)
assert '"/assistant-chat.css?v=codex-assistant-31-unified-preview"' in service_worker
assert '"/assistant-chat.js?v=codex-assistant-41-unified-preview"' in service_worker
def test_codex_frontend_uses_one_streaming_endpoint_and_agent_work_events():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert '"/api/assistant/stream"' in script
assert 'Accept: "application/x-ndjson"' in script
assert '"action.started", "action.progress"' in script
assert 'event.type === "action.completed"' in script
assert 'event.type === "answer.delta"' in script
assert '"work.started", "work.progress"' in script
assert 'event.type === "work.completed"' in script
assert 'event.type === "validated_control_commands"' in script
assert '"cognitive-agent:validated-can-commands"' in script
assert '"cognitive-agent:control-request-start"' in script
assert "speech_utterance_id:" in script
assert "speech_value:" in script
assert 'Object.prototype.hasOwnProperty.call(event, "answer")' in script
assert 'pendingController.abort("user_stop")' in script
assert 'signal.addEventListener("abort", cancelReader, { once: true })' in script
assert "reader.cancel(signal.reason)" in script
assert 'signal.removeEventListener("abort", cancelReader)' in script
assert "reader.releaseLock()" in script
assert 'if (!answerCompleted) throw new Error("助手响应意外中断,请重试。")' in script
assert "event.stopImmediatePropagation()" in script
assert "confirmation" not in script.lower()
def test_unsupported_control_suggestions_are_clickable_accessible_and_deduplicated():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert 'event.type === "control.suggestions"' in script
assert "event.suggestions || event.recommended_commands" in script
assert "function normalizeControlSuggestions(rawSuggestions)" in script
assert "seenCommands.has(commandKey)" in script
assert "MAX_CONTROL_SUGGESTIONS = 4" in script
assert "function appendControlSuggestions(message, rawSuggestions)" in script
assert 'message.article?.classList.add("assistant-message-control-suggestions")' in script
assert 'group.setAttribute("role", "group")' in script
assert 'group.setAttribute("aria-label", "可以尝试的其他控制指令")' in script
assert 'button.type = "button"' in script
assert 'button.setAttribute("aria-label", `执行控制指令:${suggestion.label}`)' in script
assert "submitSuggestedCommand(suggestion.command, group, button)" in script
assert "setSuggestionGroupPending(group, button, true)" in script
assert "await submitAssistant(command)" in script
assert ".assistant-control-suggestions" in css
assert ".assistant-message-control-suggestions .assistant-message-body" in css
assert ".assistant-control-suggestion:focus-visible" in css
assert ".assistant-control-suggestion.is-sending" in css
def test_validated_controls_are_mirrored_into_a_compact_assistant_card():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert "function appendDispatchedControls(message, result = {})" in script
assert "window.CognitiveAgentConsoleV2?.getParsedCanCommands?.(result)" in script
assert "fallbackAssistantControlCommands(result)" in script
assert 'card.className = "assistant-dispatched-controls"' in script
assert 'const headingText = mergedResult.mode_profile_draft?.name' in script
assert 'previewOnly ? "控制指令预览(未下发)" : "下发控制指令"' in script
assert 'count.className = "assistant-dispatched-controls-count"' in script
assert 'list.className = "assistant-dispatched-controls-list"' in script
assert "value.textContent = assistantControlValue(item)" in script
assert 'return ({ 1: "3档", 2: "1档" })[level]' in script
assert '{ 1: "3档(高)", 2: "1档(低)" }' in script
assert "语义控制指令:" in script
assert "实际控制指令:" in script
assert "appendDispatchedControls(message, event.payload)" in script
assert "function announceModeExecution(" in script
assert "announceModeExecution," in script
assert "function beginModeExecution(" in script
assert "function completeModeExecution(" in script
assert "appendDispatchedControls(message, modeExecutionPreviewResult(profile))" in script
assert "beginModeExecution," in script
assert "completeModeExecution," in script
assert "功能模式部分完成" not in script
assert "功能模式指令已提交" in script
assert "未由 PC 执行的" in script
assert "const operationByRef = new Map()" in script
assert 'deliveryState: "submitting"' in script
assert 'deliveryLabel: "正在下发"' in script
assert '"seat.set_cushion_tilt": "坐盆"' in script
assert '"seat.set_backrest_recline": "靠背"' in script
assert '"seat.set_legrest_height": "腿托"' in script
assert '"seat.set_legrest_extension": "腿托"' in script
assert '"ambient_light.set_illumination": "氛围灯颜色与亮度"' in script
assert '"android.launch_app": "打开视频媒体"' in script
assert 'if (capability === "android.launch_app")' in script
assert "parameters.package_name" in script
assert "parameters.activity_name" in script
assert '.replaceAll("_", "-")' in script
assert ".assistant-dispatched-controls > summary" in css
assert ".assistant-dispatched-controls-list > li" in css
assert ".assistant-dispatched-control-value" in css
assert ".assistant-dispatched-controls[open]" in css
def test_explicit_nfc_authorization_block_is_rendered_as_safety_blocked():
console = (ROOT / "apps/web-console/console-v2.js").read_text(encoding="utf-8")
assert 'executionStatus === "blocked"' in console
assert "Boolean(telemetry.authorization_reason)" in console
assert 'deliveryState = aborted' in console
assert '? "blocked"' in console
assert '? "安全阻止"' in console
def test_n7_video_launch_uses_chinese_mode_card_labels():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assert 'android: "车机媒体"' in app
assert 'headunit: "车机"' in app
assert '"android.launch_app": "打开视频媒体"' in app
assert 'package_name: "应用包名"' in app
assert 'activity_name: "启动页面"' in app
def test_latest_dispatched_control_group_is_the_only_group_expanded():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(
encoding="utf-8"
)
vue_ui = (ROOT / "apps/web-console/vue-ui.js").read_text(encoding="utf-8")
assert "function focusLatestDispatchedControls(container)" in assistant
assert 'transcript.querySelectorAll(".assistant-dispatched-controls")' in assistant
assert "card.open = card === activeCard" in assistant
assert "focusLatestDispatchedControls(host)" in assistant
assert "focusLatestDispatchedControls(card)" in assistant
assert 'querySelectorAll(".assistant-dispatched-controls[open]")' in assistant
assert 'querySelectorAll(".assistant-dispatched-controls[open]")' in vue_ui
assert "if (card !== event.currentTarget) card.open = false" in vue_ui
def test_saved_mode_execution_opens_with_pending_flow_before_http_completion():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
flow = app.split(
"async function executeSavedModeProfile", 1
)[1].split("function applyModeCommand", 1)[0]
begin_index = flow.index("assistant?.beginModeExecution?.(mode")
request_index = flow.index("await postControlPhase(")
assert begin_index < request_index
assert "assistant.completeModeExecution(assistantModeHandle, mode, payload" in flow
assert "assistant.completeModeExecution(assistantModeHandle, mode, null" in flow
def test_seat_control_cards_name_the_controlled_seat_component():
console = (ROOT / "apps/web-console/console-v2.js").read_text(encoding="utf-8")
assert '"seat.set_cushion_tilt": ["副驾座椅坐盆调节"' in console
assert 'backrest: "主驾座椅靠背调节"' in console
assert 'basin: "主驾座椅坐盆调节"' in console
assert 'legrest: "主驾座椅腿托调节"' in console
assert 'horizontal: "主驾座椅前后调节"' in console
def test_codex_request_keeps_trusted_vehicle_context_without_legacy_model_routing():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
request_body = script.split("function requestBody(message)", 1)[1].split(
"async function readErrorResponse", 1
)[0]
assert 'client_type: "web_console_codex_assistant"' in request_body
for field in (
"execution_mode",
"control_transport",
"vehicle_id",
"hardware_client_id",
"simulation_fault",
):
assert field in request_body
assert "history" not in request_body
assert "provider" not in request_body
assert "reasoning_effort" not in request_body
assert 'const executionMode = modeConfigurationPreview ? "dry_run"' in request_body
def test_codex_request_reuses_only_the_console_validated_hardware_client_id():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert "const HARDWARE_CLIENT_ID_PATTERN" in app
assert "function validatedHardwareClientId()" in app
assert "getHardwareClientId()" in app
assert "return validatedHardwareClientId();" in app
assert "window.CognitiveAgentConsole?.getHardwareClientId?.()" in assistant
assert "/^hwc-[a-z0-9-]{8,80}$/.test(value)" in assistant
def test_starting_a_new_codex_conversation_rotates_the_conversation_id():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'const CONVERSATION_STORAGE_KEY = "cognitive-agent.conversation-id"' in script
assert "function createConversationId()" in script
assert "const nextConversationId = createConversationId()" in script
assert (
"sessionStorage.setItem(CONVERSATION_STORAGE_KEY, nextConversationId)"
in script
)
def test_assistant_uses_the_shared_top_right_dock_and_opens_by_default():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert 'trigger.id = "assistantDrawerToggle"' in script
assert 'drawer.id = "assistantDrawer"' in script
assert 'drawer.setAttribute("role", "dialog")' in script
assert 'drawer.setAttribute("aria-label", "AI助手")' in script
assert 'trigger.setAttribute("aria-controls", drawer.id)' in script
assert 'trigger.setAttribute("aria-pressed", "false")' in script
assert 'trigger.classList.toggle("active", open)' in script
assert 'trigger.setAttribute("aria-pressed", String(open))' in script
assert 'drawer.toggleAttribute("inert", !open)' in script
assert 'trigger.className = "assistant-drawer-toggle v2-panel-control-button"' in script
assert 'const panelDock = $("#v2PanelControlDock")' in script
assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in script
assert "document.body.append(trigger)" in script
assert 'id = "assistantDrawerClose"' not in script
assert "setDrawerOpen(true)" in script
assert 'event.key === "Escape"' in script
assert "#v2PanelControlDock > .assistant-drawer-toggle" in css
assert ".assistant-drawer-open .v2-operation-shell" in css
assert "position: fixed" in css
assert "position: relative" in css
assert "grid-template-rows: 56px auto minmax(0, 1fr)" in css
assert ".v2-output-shell" in css
def test_assistant_mode_creation_previews_then_saves_to_the_compact_mode_strip():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert "function startModeCreation()" in assistant
assert 'input.value = "请帮我创建一个"' in assistant
assert "function appendModeDraftState(message, draft, actions)" in assistant
assert "function modeDraftRows(draft, rawActions)" in assistant
assert "event.payload.mode_profile_draft" in assistant
assert 'event.payload.orchestration_route === "mode_profile_create_preview"' in assistant
assert "appendModeDraftState(" in assistant
assert "保存为功能模式" in assistant
assert "重新校验(不下发)" in assistant
assert "创建阶段仅生成并校验控制方案,不会向 CAN 或红外外设发送指令" in assistant
assert 'label = String(action?.target?.name || action?.target?.device || "红外外设")' in assistant
save_flow = assistant.split(
"async function saveModeProfileDraft", 1
)[1].split("async function tryModeProfileDraft", 1)[0]
assert 'const existingModeId = String(draft.existing_mode_id || "").trim()' in save_flow
assert "encodeURIComponent(existingModeId)" in save_flow
assert '"/api/mode-profiles"' in save_flow
assert "request(endpoint" in save_flow
assert 'method: "POST"' in save_flow
for field in ("name", "instruction", "source_request_id", "vehicle_id"):
assert field in save_flow
assert "response.ok" in save_flow
assert "refreshModeProfiles?.(payload.mode_profile?.id)" in save_flow
assert "appendModeCreatedState(message, payload.mode_profile" in save_flow
assert "updated: Boolean(existingModeId)" in save_flow
assert "确认更新此模式" in assistant
assert "已载入现有功能模式" in assistant
for action_label in (
"座椅前后位置",
"坐垫位置",
"靠背位置",
"腿托位置",
"空调循环",
"内循环",
):
assert action_label in assistant
assert "tryModeDraft?.({ ...draft, actions })" in assistant
assert '["mode_profile_create", "mode_profile_update"].includes(' in assistant
assert "appendModeCreatedState(message, event.payload.mode_profile, {" in assistant
assert (
'updated: event.payload.orchestration_route === "mode_profile_update"'
in assistant
)
assert "refreshModeProfiles?.(" in assistant
assert "revealModeProfile?.(String(profile.id), { focus: true })" in assistant
assert "function revealModeProfile(modeProfileId" in app
assert "refreshModeProfiles" in app
assert "tryModeDraft" in app
assert "const actionSignature = (action) =>" in app
assert 'apiRequest("/api/mode-profile-drafts/trial"' in app
assert "source_request_id: draft.source_request_id" in app
assert "const expectedSignatures = Array.isArray(draft?.actions)" in app
assert "signature === actualSignatures[index]" in app
assert "|| !sameActions" in app
assert 'button.classList.add("just-created")' in app
assert 'window.addEventListener("cognitive-agent:request-complete"' in app
assert ".assistant-mode-draft" in css
assert ".assistant-mode-draft-row" in css
assert ".assistant-mode-draft-save" in css
assert ".assistant-mode-draft-status.failed" in css
assert ".assistant-mode-created" in css
def test_assistant_carries_only_the_active_mode_draft_reference_to_follow_up_turns():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
request_body = assistant.split("function requestBody(message)", 1)[1].split(
"async function readErrorResponse", 1
)[0]
context_flow = assistant.split("function sanitizeModeDraftContext", 1)[1].split(
"function loadStoredMessages", 1
)[0]
assert (
'const ACTIVE_MODE_DRAFT_STORAGE_KEY = "cognitive-agent.active-mode-draft"'
in assistant
)
assert "mode_profile_draft_context: modeProfileDraftContext" in request_body
assert "requestModeDraftContext(message)" in request_body
assert "name: context.name" in context_flow
assert "source_request_id: context.source_request_id" in context_flow
assert "vehicle_id: context.vehicle_id" in context_flow
assert "actions" not in context_flow
assert "rememberModeDraftContext(draft)" in assistant
assert "activeModeDraftContext = loadActiveModeDraftContext()" in assistant
assert "function isModeDraftUpdateRequest(message, context)" in assistant
assert "normalizeModeName(matched[nameIndex]) === normalizeModeName(context.name)" in assistant
def test_latest_same_name_mode_draft_retires_stale_card_and_lifecycle_clears_context():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
replace_flow = assistant.split("function supersedeModeDraftCards", 1)[1].split(
"function appendModeDraftState", 1
)[0]
save_flow = assistant.split("async function saveModeProfileDraft", 1)[1].split(
"async function tryModeProfileDraft", 1
)[0]
new_conversation_flow = assistant.split(
"function resetConversation()", 1
)[1].split("function initialize()", 1)[0]
assert "card.dataset.assistantModeName !== nextName" in replace_flow
assert "card.dataset.assistantModeDraft === nextRequestId" in replace_flow
assert 'card.classList.add("is-superseded")' in replace_flow
assert "button.disabled = true" in replace_flow
assert "已由下方最新草稿替换" in replace_flow
assert "clearActiveModeDraftContext(draft.source_request_id)" in save_flow
assert "clearActiveModeDraftContext()" in new_conversation_flow
assert ".assistant-mode-draft.is-superseded" in css
assert ".assistant-mode-draft-status.superseded" in css
def test_new_conversation_resets_chat_in_place_and_keeps_panel_order_spatial():
app = (ROOT / "apps/web-console/app.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(
encoding="utf-8"
)
reset_flow = assistant.split("function resetConversation()", 1)[1].split(
"function initialize()", 1
)[0]
app_flow = app.split("function startNewConversation", 1)[1].split(
"window.CognitiveAgentConsole", 1
)[0]
assert 'activeController.abort("new_conversation")' in reset_flow
assert "sessionStorage.removeItem(STORAGE_KEY)" in reset_flow
assert "renderInitialMessages()" in reset_flow
assert "setComposerPending(false)" in reset_flow
assert '"cognitive-agent:new-conversation"' in reset_flow
assert "window.location.reload()" not in app_flow
assert (
'window.addEventListener("cognitive-agent:new-conversation", '
"startNewConversation)"
) in app
assert 'const firstRightPanelToggle = $("[data-panel-toggle]", panelDock)' in assistant
assert "panelDock.insertBefore(trigger, firstRightPanelToggle)" in assistant
def test_assistant_composer_grows_to_a_cap_then_scrolls_its_own_content():
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
resize_flow = assistant.split(
"function resizeComposerInput", 1
)[1].split("function createMessage", 1)[0]
assert "const COMPOSER_MIN_HEIGHT = 64" in assistant
assert "const COMPOSER_MAX_HEIGHT = 116" in assistant
assert 'input.style.height = "auto"' in resize_flow
assert "input.scrollHeight" in resize_flow
assert "Math.min(contentHeight, COMPOSER_MAX_HEIGHT)" in resize_flow
assert 'input.style.height = `${nextHeight}px`' in resize_flow
assert 'input.classList.toggle("is-scrollable", scrollable)' in resize_flow
assert "if (!scrollable) input.scrollTop = 0" in resize_flow
assert 'input.addEventListener("input", () => resizeComposerInput(input))' in assistant
assert "resizeComposerInput(input)" in assistant
composer_styles = css.split(
".v2-request-form .command-composer", 1
)[1].split("}", 1)[0]
assert "height: auto" in composer_styles
input_styles = css.split(
".v2-request-form #utterance", 1
)[1].split("}", 1)[0]
assert "max-height: 116px" in input_styles
assert "overflow-y: hidden" in input_styles
scrollable_styles = css.split("#utterance.is-scrollable", 1)[1].split("}", 1)[0]
assert "overflow-y: auto" in scrollable_styles
assert "scrollbar-width: thin" in scrollable_styles
assert "overscroll-behavior: contain" in scrollable_styles
def test_assistant_composer_keeps_the_microphone_visible_and_aligned():
html = vue_shell()
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert 'id="speechDictationButton"' in html
assert 'data-lucide="mic"' in html
assert ".v2-request-form .speech-dictation" in css
assert "right: 53px" in css
assert "bottom: auto" in css
assert "display: grid !important" in css
assert "transform: translateY(-50%)" in css
assert ".v2-request-form .speech-status:empty" in css
def test_voice_input_supports_direct_manual_capture_and_background_wake_standby():
html = vue_shell()
script = (ROOT / "apps/web-console/speech-input.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'aria-label="开始语音输入"' in html
assert 'const DEFAULT_WAKE_WORD = "你好,小迪"' in script
assert 'new Set(["arming", "armed", "capturing", "finalizing"])' in script
assert "async function startManualDictation()" in script
assert "async function startWakeStandby({ automatic = true } = {})" in script
assert 'return startCapture("manual")' in script
assert 'return startCapture("wake", { automatic })' in script
assert 'else void startManualDictation()' in script
assert 'const manualInput = speech.inputMode === "manual"' in script
assert "const detectedWake = manualInput || wakeWord.detected === true" in script
assert 'fetch("/api/speech/preview"' in script
assert '"X-Speech-Preview-Sequence"' in script
assert '"X-Speech-Input-Mode": speech.inputMode' in script
assert 'result.stale === true' in script
assert 'sequence <= speech.lastAppliedPreviewSequence' in script
assert "wakeWord.remainder ?? result.live_text" in script
assert "status.wake_word.primary" in script
assert 'if (speech.wakeDetected && liveText)' in script
assert 'replaceInput(liveText, "partial")' in script
assert '"X-Speech-Input-Mode": inputMode' in script
assert '"X-Speech-Wake-Word-Required": inputMode === "wake" ? "1" : "0"' in script
assert (
"result.assistant_text || result.clean_text || result.live_text || "
"result.raw_text || result.text"
) in script
assert "commands.length" not in script
assert "input.dataset.speechExecutionEligible =" not in script
assert "void speech.setAssistantOpen(open)" in assistant
assert '["capturing", "finalizing"].includes(speechPhase)' in assistant
assert 'speechPhase === "arming" && speechInputMode === "manual"' in assistant
assert "请等待语音整理完成后再发送" in assistant
def test_voice_lifecycle_releases_hidden_or_collapsed_microphone():
script = (ROOT / "apps/web-console/speech-input.js").read_text(encoding="utf-8")
assistant = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert 'document.addEventListener("visibilitychange"' in script
assert "ACTIVE_PHASES.has(speech.phase)" in script
assert 'window.addEventListener("pagehide"' in script
assert "speech.stream?.getTracks().forEach((track) => track.stop())" in script
assert "speech.previewController?.abort" in script
assert "speech.finalController?.abort" in script
assert 'typeof speech?.setAssistantOpen === "function"' in assistant
assert "void speech.setAssistantOpen(open)" in assistant
def test_model_text_is_rendered_through_text_nodes_not_raw_html():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
assert "document.createTextNode(part)" in script
assert "code.textContent =" in script
assert "contentNode.textContent = content" in script
assert "innerHTML = content" not in script
def test_chat_styles_include_accessible_responsive_and_reduced_motion_states():
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert ".assistant-message-user" in css
assert ".assistant-action-status.success" in css
assert "@media (max-width: 900px)" in css
assert "@media (max-width: 560px)" in css
assert ".v2-unified-right-rail" in css
assert "grid-template-columns: minmax(0, 1fr) !important" in css
assert "@media (prefers-reduced-motion: reduce)" in css
def test_dispatched_control_card_spans_the_full_assistant_message_row():
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
layout = css.split("/* Compact command receipt", 1)[1].split(
"/* The function-mode strip", 1
)[0]
assert "grid-template-columns: 30px minmax(0, 1fr)" in layout
assert "display: contents" in layout
assert "grid-column: 1 / -1" in layout
assert "max-width: none" in layout
def test_assistant_does_not_restore_collapsed_sidebars_and_uses_mobile_overlays():
script = (ROOT / "apps/web-console/assistant-chat.js").read_text(encoding="utf-8")
css = (ROOT / "apps/web-console/assistant-chat.css").read_text(encoding="utf-8")
assert "--assistant-drawer-width: clamp(340px, 26vw, 410px);" in css
assert "@media (min-width: 901px)" in css
assert ".assistant-drawer-open.v2-sidebar-collapsed .workspace" in css
assert "grid-template-columns: 0 minmax(0, 1fr);" in css
assert ".assistant-drawer-open.v2-sidebar-collapsed .command-panel.v2-sidebar" in css
assert "visibility: hidden;" in css
assert "grid-template-columns: var(--assistant-drawer-width) minmax(0, 1fr);" in css
assert "#v2PanelControlDock > .assistant-drawer-toggle" in css
assert "opacity: 1 !important;" in css
assert "pointer-events: auto !important;" in css
assert "@media (max-width: 900px)" in css
assert "width: min(86vw, 320px);" in css
assert "width: min(86vw, 360px);" in css
assert "top: 60px;" in css
assert 'event.key === "Escape"' in script