main
py 214 lines 11.2 KB
Raw
1 from pathlib import Path
2
3
4 PROJECT_ROOT = Path(__file__).resolve().parents[1]
5
6
7 def _read(relative_path: str) -> str:
8 return (PROJECT_ROOT / relative_path).read_text(encoding="utf-8")
9
10
11 def test_welcome_screen_embeds_shared_new_chat_composer() -> None:
12 welcome = _read("webui/components/welcome/welcome-screen.html")
13 welcome_store = _read("webui/components/welcome/welcome-store.js")
14 index = _read("webui/index.html")
15 discovery_cards = _read(
16 "plugins/_discovery/extensions/webui/welcome-actions-end/discovery-cards.html"
17 )
18 discovery_store = _read("plugins/_discovery/webui/discovery-store.js")
19
20 assert 'class="welcome-composer"' in welcome
21 assert 'path="chat/attachments/inputPreview.html"' in welcome
22 assert 'path="chat/input/chat-bar-input.html"' in welcome
23 assert "x-text=\"$store.welcomeStore.heroSubtitle\"" in welcome
24 assert "Hello! I'm Agent Zero" in welcome
25 assert "is-setup-required" not in welcome
26 assert "welcome-setup-composer" not in welcome
27 assert "Configure your models to start chatting" not in welcome
28 assert "Start Onboarding" not in welcome
29 assert "openBlockingSetup()" not in welcome
30 assert '.filter((b) => b.id !== "missing-api-key")' in welcome_store
31 assert "get heroSubtitle()" in welcome_store
32 assert 'return "How can I help you today?";' in welcome_store
33 assert '<h2>Quick Actions</h2>' in welcome
34 assert 'class="welcome-lower-grid"' in welcome
35 assert 'x-extension id="welcome-actions-end"' in welcome
36 assert "grid-template-columns: repeat(4, minmax(0, 1fr));" in welcome
37 assert '"accounts accounts channels channels"' in welcome
38 assert '"system system system system"' in welcome
39 assert "align-items: stretch;" in welcome
40 assert ".welcome-lower-grid .discovery-slot {\n display: contents;" in welcome
41 assert ".welcome-lower-grid .discovery-account-card {\n grid-area: accounts;" in welcome
42 assert "grid-area: system;" in welcome
43 assert ".welcome-composer .input-row {\n min-height: 4.9rem;" in welcome
44 assert "border-radius: 8px;" in welcome
45 assert 'aria-label="Refresh System Resources"' in welcome
46 assert "refreshBanners(true)" in welcome
47 assert 'aria-label="Dismiss System Resources"' in welcome
48 assert "dismissBanner($store.welcomeStore.systemResourceBanner?.id || 'system-resources')" in welcome
49 assert "welcome-panel-status-icon" not in welcome
50 assert "monitoring" not in welcome
51 assert ".welcome-banner {\n --welcome-banner-accent: #2f6bff;" in welcome
52 assert "grid-template-columns: auto minmax(0, 1fr) auto auto;" in welcome
53 assert ".welcome-banner.banner-warning {\n --welcome-banner-accent: #f59e0b;" in welcome
54 assert "border-left: 4px" not in welcome
55 assert 'class="welcome-banner-cta"' in welcome
56 assert "executeBannerAction(banner.cta_action)" in welcome
57 assert "executeBannerAction(action)" in welcome_store
58 assert "handleBannerHtmlClick($event)" in welcome
59 assert "handleBannerHtmlClick(event)" in welcome_store
60 assert "[data-banner-action]" in welcome_store
61 assert 'action.startsWith("open-modal:")' in welcome_store
62 assert 'const hashIndex = path.indexOf("#");' in welcome_store
63 assert 'history.replaceState(null, "", `#${hash}`);' in welcome_store
64 assert ".welcome-banner-html .btn,\n .welcome-banner-html button" in welcome
65 assert "background-color: #4248f1;" in welcome
66 assert 'aria-label="Dismiss Connect Channels"' in discovery_cards
67 assert "dismissFeatureCards()" in discovery_cards
68 assert "dismissFeatureCards() {" in discovery_store
69 assert 'class="discovery-feature-card"\n type="button"' in discovery_cards
70 assert "`${card.cta_text || 'Connect'} ${card.title}`" in discovery_cards
71 assert "discovery-feature-card > .btn" not in discovery_cards
72 assert "discovery-feature-head" not in discovery_cards
73 assert "discovery-account-header" in discovery_cards
74 assert "discovery-account-cta" in discovery_cards
75 assert "discovery-account-icon" not in discovery_cards
76 assert "some(b => b.id === 'missing-api-key')" not in discovery_cards
77
78 assert "x-if=\"$store.welcomeStore && $store.welcomeStore.isVisible\"" in index
79 assert "x-if=\"!$store.welcomeStore || !$store.welcomeStore.isVisible\"" in index
80 assert "Connect Channels" in discovery_cards
81 assert "oauthAccountCards" in discovery_cards
82 assert "discovery-account-card" in discovery_cards
83 assert "topHeroCards" not in discovery_cards
84 assert "bottomHeroCards" not in discovery_cards
85 assert "background: var(--color-background);" in welcome
86 assert "radial-gradient" not in welcome
87
88
89 def test_welcome_composer_can_create_a_chat_before_sending() -> None:
90 input_store = _read("webui/components/chat/input/input-store.js")
91 chats_store = _read("webui/components/sidebar/chats/chats-store.js")
92 index_js = _read("webui/index.js")
93 gate_store = _read("webui/components/chat/model-gate-store.js")
94 gate_component = _read("webui/components/chat/model-setup-gate.html")
95
96 assert 'return "Ask anything to start a new chat";' in input_store
97 assert "if (!chatsStore.selected" in input_store
98 assert "await chatsStore.newChat()" in input_store
99 optimistic_user = 'await setMessages([{ id: messageId, type: "user"'
100 assert index_js.count(optimistic_user) == 1
101 assert index_js.index(optimistic_user) < index_js.index("if (hasAttachments)")
102 assert "return response.ctxid;" in chats_store
103 assert 'return "arrow_forward";' in input_store
104 assert "modelGateStore.canSendToModel()" in index_js
105 assert "modelGateStore.mergeSyntheticMessages(snapshot.logs, context)" in index_js
106 assert 'type: "model_setup_gate"' in gate_store
107 assert 'STORAGE_KEY = "a0:model-gate-pending:v1"' in gate_store
108 assert "SYNTHETIC_MESSAGE_NO_BASE = Number.MAX_SAFE_INTEGER - 2" in gate_store
109 assert "return synthetic.length ? [...(logs || []), ...synthetic] : logs;" in gate_store
110 assert "restorePending()" in gate_store
111 assert "savePending()" in gate_store
112 assert "clearSavedPending()" in gate_store
113 assert "sessionStorage.setItem(STORAGE_KEY" in gate_store
114 assert "sessionStorage.getItem(STORAGE_KEY)" in gate_store
115 assert "sessionStorage.removeItem(STORAGE_KEY)" in gate_store
116 assert 'import("/plugins/_oauth/webui/oauth-config-store.js")' in gate_store
117 assert "refreshConnectedAccountState()" in gate_store
118 assert "accountConnected" in gate_store
119 assert "accountLabel" in gate_store
120 assert "oauthConfigStore.connectedProviderCards()[0] || null" in gate_store
121 assert "autoApplyConnectedProviderIfNeeded" not in gate_store
122 assert "void this.dispatchPendingIfConfigured();" in gate_store
123 assert "this.choice = \"\";" in gate_store
124 assert 'document.addEventListener("model-configured"' in gate_store
125 assert 'document.addEventListener("model-setup-changed"' in gate_store
126 assert "bypassModelGate: true" in gate_store
127 assert "modelConfigStore.openPresetEditor(" in gate_store
128 assert 'openPluginConfig("_oauth"' not in gate_store
129 assert "Your message sends automatically once a model is connected." in gate_component
130 assert "openOnboarding('cloud')" in gate_component
131 assert "openOnboarding('account')" in gate_component
132 assert "openOnboarding('local')" in gate_component
133 assert gate_component.index("openOnboarding('cloud')") < gate_component.index("openOnboarding('account')") < gate_component.index("openOnboarding('local')")
134 assert "openOauthConfiguration" not in gate_component
135 assert "Show accounts" not in gate_component
136 assert "model-gate-accounts" not in gate_component
137 assert "Choose models" in gate_component
138 assert "Advanced model configuration" in gate_component
139 assert "Advanced settings" not in gate_component
140 assert "model-gate-fields" not in gate_component
141 assert "Connect model" not in gate_component
142 assert "accountsOpen" not in gate_store
143 assert "saveInlineSetup" not in gate_store
144 assert """.model-gate-card {
145 display: grid;
146 gap: 0.75rem;
147 }""" in gate_component
148 # The composer never hard-blocks sending: the in-chat gate guides users instead.
149 assert "Connect a model to send" not in input_store
150 assert "blocked" not in input_store
151 assert "sendDisabled" not in input_store
152 assert "isBlockingSend" not in gate_store
153 assert "isBlockingSend" not in index_js
154
155
156 def test_welcome_composer_does_not_overlap_idle_progress_placeholder() -> None:
157 input_store = _read("webui/components/chat/input/input-store.js")
158
159 assert "!!chatsStore.selected &&\n state !== \"all\"" in input_store
160 assert '!(state === "stop" && messageQueueStore?.hasQueue)' in input_store
161
162
163 def test_welcome_composer_buttons_keep_target_geometry_without_glow() -> None:
164 chat_bar = _read("webui/components/chat/input/chat-bar-input.html")
165 mic_css = _read("plugins/_whisper_stt/webui/whisper-stt.css")
166
167 assert "#send-button {\n order: 2;\n border-radius: 12px;" in chat_bar
168 assert "background-color: #4248f1;" in chat_bar
169 assert "background-color: #e67e22;" in chat_bar
170 assert "linear-gradient" not in chat_bar
171 assert "#microphone-button {\n order: 1;" in mic_css
172 assert "border-radius: 12px;" in mic_css
173 assert "background-color: transparent;" in mic_css
174 assert "#microphone-button.mic-inactive {\n color: grey;" in mic_css
175 assert "background-color: grey;" not in mic_css
176 assert "#microphone-button.mic-listening {\n color: red;" in mic_css
177 assert "#microphone-button.mic-recording {\n color: green;" in mic_css
178 assert "#microphone-button.mic-activating svg" in mic_css
179 assert "rgba(34, 68, 255" not in chat_bar
180 assert "rgba(217, 119, 6" not in chat_bar
181
182
183 def test_welcome_hover_and_focus_borders_use_neutral_tokens() -> None:
184 surfaces = "\n".join(
185 [
186 _read("webui/components/welcome/welcome-screen.html"),
187 _read("webui/components/chat/input/chat-bar-input.html"),
188 _read("plugins/_discovery/extensions/webui/welcome-actions-end/discovery-cards.html"),
189 ]
190 )
191
192 assert "#315cf6" not in surfaces
193 assert "rgba(34, 68, 255" not in surfaces
194 assert "linear-gradient" not in surfaces
195 assert "border-color: color-mix(in srgb, var(--color-primary" not in surfaces
196 assert "border-color: var(--color-primary" not in surfaces
197 assert "border-color: color-mix(in srgb, var(--color-border) 88%, var(--color-text) 12%)" in surfaces
198
199
200 def test_composer_creates_visual_code_blocks_from_entered_fence() -> None:
201 chat_bar = _read("webui/components/chat/input/chat-bar-input.html")
202 input_store = _read("webui/components/chat/input/input-store.js")
203
204 assert 'contenteditable="true"' in chat_bar
205 assert "font-family: var(--font-family-main" in chat_bar
206 assert ".composer-code-content" in chat_bar
207 assert "font-family: var(--font-family-code" in chat_bar
208 assert "FENCE_LINE_RE" in input_store
209 assert "_tryCreateCodeBlock($event)" in input_store
210 assert 'data-code-block' in input_store
211 assert "handlePaste($event)" in input_store
212 assert "_insertPlainText(text)" in input_store
213 assert "is-code-context" not in chat_bar
214 assert "_isCaretInsideFence" not in input_store