

























@@ -15,6 +15,7 @@ import { renderChatSessionSelect } from "../chat/session-controls.ts";
1515import type { GatewayBrowserClient } from "../gateway.ts";
1616import type { ModelCatalogEntry } from "../types.ts";
1717import type { ChatQueueItem } from "../ui-types.ts";
18+import { renderChat } from "./chat.ts";
18191920const refreshVisibleToolsEffectiveForCurrentSessionMock = vi.hoisted(() =>
2021vi.fn(async (state: AppViewState) => {
@@ -220,12 +221,140 @@ async function flushTasks() {
220221await vi.dynamicImportSettled();
221222}
222223224+function renderChatView(overrides: Partial<Parameters<typeof renderChat>[0]> = {}) {
225+const container = document.createElement("div");
226+render(
227+renderChat({
228+sessionKey: "main",
229+onSessionKeyChange: () => undefined,
230+thinkingLevel: null,
231+showThinking: false,
232+showToolCalls: true,
233+loading: false,
234+sending: false,
235+compactionStatus: null,
236+fallbackStatus: null,
237+messages: [],
238+sideResult: null,
239+toolMessages: [],
240+streamSegments: [],
241+stream: null,
242+streamStartedAt: null,
243+assistantAvatarUrl: null,
244+draft: "",
245+queue: [],
246+realtimeTalkActive: false,
247+realtimeTalkStatus: "idle",
248+realtimeTalkDetail: null,
249+realtimeTalkTranscript: null,
250+connected: true,
251+canSend: true,
252+disabledReason: null,
253+error: null,
254+sessions: null,
255+focusMode: false,
256+sidebarOpen: false,
257+sidebarContent: null,
258+sidebarError: null,
259+splitRatio: 0.6,
260+canvasHostUrl: null,
261+embedSandboxMode: "scripts",
262+allowExternalEmbedUrls: false,
263+assistantName: "Val",
264+assistantAvatar: null,
265+userName: null,
266+userAvatar: null,
267+localMediaPreviewRoots: [],
268+assistantAttachmentAuthToken: null,
269+autoExpandToolCalls: false,
270+attachments: [],
271+onAttachmentsChange: () => undefined,
272+showNewMessages: false,
273+onScrollToBottom: () => undefined,
274+onRefresh: () => undefined,
275+onToggleFocusMode: () => undefined,
276+getDraft: () => "",
277+onDraftChange: () => undefined,
278+onRequestUpdate: () => undefined,
279+onSend: () => undefined,
280+onCompact: () => undefined,
281+onToggleRealtimeTalk: () => undefined,
282+onAbort: () => undefined,
283+onQueueRemove: () => undefined,
284+onQueueSteer: () => undefined,
285+onDismissSideResult: () => undefined,
286+onNewSession: () => undefined,
287+onClearHistory: () => undefined,
288+agentsList: null,
289+currentAgentId: "main",
290+onAgentChange: () => undefined,
291+onNavigateToAgent: () => undefined,
292+onSessionSelect: () => undefined,
293+onOpenSidebar: () => undefined,
294+onCloseSidebar: () => undefined,
295+onSplitRatioChange: () => undefined,
296+onChatScroll: () => undefined,
297+basePath: "",
298+ ...overrides,
299+}),
300+container,
301+);
302+return container;
303+}
304+223305afterEach(() => {
224306loadSessionsMock.mockClear();
225307refreshVisibleToolsEffectiveForCurrentSessionMock.mockClear();
226308vi.unstubAllGlobals();
227309});
228310311+describe("chat loading skeleton", () => {
312+it("shows the skeleton while the initial history load has no rendered content", () => {
313+const container = renderChatView({ loading: true });
314+315+expect(container.querySelector(".chat-loading-skeleton")).not.toBeNull();
316+expect(container.querySelector(".agent-chat__welcome")).toBeNull();
317+});
318+319+it("keeps existing messages visible without the skeleton during a background reload", () => {
320+const container = renderChatView({
321+loading: true,
322+messages: [
323+{
324+role: "assistant",
325+content: "Already loaded answer",
326+timestamp: 1,
327+},
328+],
329+});
330+331+expect(container.querySelector(".chat-loading-skeleton")).toBeNull();
332+expect(container.textContent).toContain("Already loaded answer");
333+});
334+335+it("keeps active stream content visible without the skeleton during a background reload", () => {
336+const container = renderChatView({
337+loading: true,
338+stream: "Partial streamed answer",
339+streamStartedAt: 1,
340+});
341+342+expect(container.querySelector(".chat-loading-skeleton")).toBeNull();
343+expect(container.textContent).toContain("Partial streamed answer");
344+});
345+346+it("keeps the reading indicator visible without the skeleton before stream text arrives", () => {
347+const container = renderChatView({
348+loading: true,
349+stream: "",
350+streamStartedAt: 1,
351+});
352+353+expect(container.querySelector(".chat-loading-skeleton")).toBeNull();
354+expect(container.querySelector(".chat-reading-indicator")).not.toBeNull();
355+});
356+});
357+229358describe("chat queue", () => {
230359it("renders Steer only for queued messages during an active run", () => {
231360const onQueueSteer = vi.fn();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。