main
py 145 lines 5.59 KB
Raw
1 import re
2 from pathlib import Path
3
4
5 PROJECT_ROOT = Path(__file__).resolve().parents[1]
6
7
8 def read(*parts: str) -> str:
9 return (PROJECT_ROOT / Path(*parts)).read_text(encoding="utf-8")
10
11
12 def test_full_log_replays_replace_existing_message_dom():
13 index_js = read("webui", "index.js")
14 messages_js = read("webui", "js", "messages.js")
15
16 assert "snapshot.logs?.[0]?.no === 0" in index_js
17 assert "msgs.resetMessageRenderState();" in index_js
18 assert "export function resetMessageRenderState" in messages_js
19 assert "normalized.sort(" in messages_js
20
21
22 def test_root_responses_render_markdown_during_streaming():
23 messages_js = read("webui", "js", "messages.js")
24
25 assert "const renderMarkdown = kvps?.finished !== false;" not in messages_js
26 assert "markdown: true," in messages_js
27 assert "latex: true," in messages_js
28
29
30 def test_message_ordering_uses_a_bounded_tail_first_renderer_cache():
31 messages_js = read("webui", "js", "messages.js")
32 message_window_js = read("webui", "js", "message-window.js")
33
34 assert 'from "./message-window.js"' in messages_js
35 assert "_messageWindow.compactTailIfNeeded()" in messages_js
36 assert "_messageWindow.visibleMessages()" in messages_js
37 assert "class MessageWindow" in message_window_js
38 assert "showTail()" in message_window_js
39 assert "shiftOlder()" in message_window_js
40 assert "shiftNewer()" in message_window_js
41 assert "_messageWindowFollowTail" in messages_js
42 assert "_messageWindow.showTail()" in messages_js
43
44
45 def test_virtual_paging_uses_passive_loaders_and_cancels_stale_scrolling():
46 messages_js = read("webui", "js", "messages.js")
47 scroller_js = read("webui", "js", "scroller.js")
48 loading_css = read("webui", "css", "loading-indicators.css")
49
50 assert "createMessageWindowIndicator" in messages_js
51 assert 'indicator.setAttribute("role", "status")' in messages_js
52 assert "createThreeBubbleLoader({ active: isLoading })" in messages_js
53 assert "@keyframes three-bubble-loader-jump" in loading_css
54 assert "Load ${Math.min" not in messages_js
55 assert "export function cancelPendingScroll" in scroller_js
56 assert "cancelPendingScroll(history)" in messages_js
57
58
59 def test_context_switch_uses_three_bubble_chat_loading_splash():
60 index_html = read("webui", "index.html")
61 index_js = read("webui", "index.js")
62 messages_js = read("webui", "js", "messages.js")
63 messages_css = read("webui", "css", "messages.css")
64 loading_js = read("webui", "js", "loading-indicators.js")
65 loading_css = read("webui", "css", "loading-indicators.css")
66 welcome_html = read("webui", "components", "welcome", "welcome-screen.html")
67
68 splash = re.search(
69 r'<div id="chat-loading-splash"(?P<body>.*?)</div>',
70 index_html,
71 flags=re.DOTALL,
72 )
73 assert splash
74 assert "for (let index = 0; index < 3; index += 1)" in loading_js
75 assert "createThreeBubbleLoader({ active: true })" in index_js
76 assert "createThreeBubbleLoader({ active: isLoading })" in messages_js
77 assert "beginChatLoading(id)" in index_js
78 assert "id !== loadingContext" in index_js
79 assert "finishChatLoading(snapshot.context)" in index_js
80 assert "CHAT_LOADING_TEST_MIN_DURATION_MS" not in index_js
81 assert "const CHAT_LOADING_SPLASH_DELAY_MS = 300" in index_js
82 assert "chatLoadingSplashVisible = true" in index_js
83 assert "@keyframes chat-loading-splash-fade-in" in messages_css
84 index_css = read("webui", "index.css")
85 assert "'chat-active': $store.welcomeStore" in index_html
86 assert "#right-panel.chat-active" in index_css
87 assert "background-color 0.2s ease-out" in index_css
88 assert "background: var(--color-background)" not in welcome_html
89 assert "--three-bubble-loader-delay: 0.16s" in loading_css
90 assert "--three-bubble-loader-delay: 0.32s" in loading_css
91 assert "animation-delay: var(--three-bubble-loader-delay)" in loading_css
92
93
94 def test_utility_prefixed_process_groups_are_not_hidden_from_partial_dom_state():
95 messages_js = read("webui", "js", "messages.js")
96 preferences_js = read(
97 "webui",
98 "components",
99 "sidebar",
100 "bottom",
101 "preferences",
102 "preferences-store.js",
103 )
104 process_group_css = read(
105 "webui",
106 "components",
107 "messages",
108 "process-group",
109 "process-group.css",
110 )
111
112 assert 'else if (log.type === "util")' in messages_js
113 assert 'group.classList.remove("utility-only")' in messages_js
114 assert "isUtilityOnlyProcessGroup" not in messages_js
115 assert "group.hidden" not in messages_js
116 assert '"show-utility-messages"' in preferences_js
117 assert ".show-utility-messages .process-group.utility-only" in process_group_css
118 assert ".process-step.message-util {" in process_group_css
119 assert ".show-utility-messages .process-step.message-util" in process_group_css
120
121
122 def test_message_actions_put_copy_before_speak():
123 sources = [
124 read("webui", "js", "messages.js"),
125 read(
126 "plugins",
127 "_browser",
128 "extensions",
129 "webui",
130 "get_tool_message_handler",
131 "browser-tool-handler.js",
132 ),
133 ]
134
135 for source in sources:
136 lines = source.splitlines()
137 for index, line in enumerate(lines):
138 if 'createActionButton("speak"' not in line:
139 continue
140 preceding_actions = [
141 match.group(1)
142 for candidate in lines[max(0, index - 12) : index]
143 if (match := re.search(r'createActionButton\("(detail|copy|speak)"', candidate))
144 ]
145 assert preceding_actions and preceding_actions[-1] == "copy"