Files
metona-ai-desktop/src/components/layout/__tests__/Sidebar.test.tsx
T
thzxx 4cd6e997b5
CI / 类型检查 + Lint + 单元测试 (push) Failing after 9m45s
CI / 全量测试 (Electron ABI) (push) Failing after 6m28s
CI / 产物编译验证 (push) Successful in 11m18s
feat: v0.8.2 安全纵深补全 · 协议保真 · 断链修复 — 图片SSRF/根MEMORY.md保护根治 · Anthropic thinking回传+pause_turn续传 · 2523 用例全量回归 + E2E 扩充
2026-09-08 14:30:27 +08:00

126 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @vitest-environment jsdom
/**
* Sidebar 组件测试(v0.8.2 P3-3 补齐设置面板/侧栏测试空白)
*
* 覆盖契约:
* - 挂载时拉取会话列表并渲染标题
* - v0.8.2 P2-4: session:updated 实时刷新 messageCount/updatedAt/title
* - 搜索框(标题 + 内容全文搜索入口)存在
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Sidebar } from '../Sidebar';
import { useSessionStore } from '@renderer/stores/session-store';
const noop = (): void => {};
const sessionUpdatedHandlers: Array<(data: unknown) => void> = [];
function makeSession(overrides: Record<string, unknown> = {}): {
id: string;
title: string;
createdAt: number;
updatedAt: number;
messageCount: number;
totalTokens: number;
pinned: boolean;
archived: boolean;
} {
return {
id: 's1',
title: '第一个会话',
createdAt: Date.now(),
updatedAt: Date.now(),
messageCount: 2,
totalTokens: 0,
pinned: false,
archived: false,
...overrides,
};
}
beforeEach(() => {
sessionUpdatedHandlers.length = 0;
useSessionStore.getState().setSessions([]);
useSessionStore.getState().setCurrentSession(null);
const bridge = (window as unknown as { metona: Record<string, unknown> }).metona;
bridge.sessions = {
...(bridge.sessions as Record<string, unknown>),
list: vi.fn().mockResolvedValue([makeSession(), makeSession({ id: 's2', title: 'Second' })]),
searchContent: vi.fn().mockResolvedValue({ success: true, data: [] }),
onSessionUpdated: vi.fn((cb: (data: unknown) => void) => {
sessionUpdatedHandlers.push(cb);
return noop;
}),
};
});
describe('Sidebar — 会话列表', () => {
it('挂载时拉取并渲染会话标题', async () => {
render(<Sidebar />);
await waitFor(() => {
expect(screen.getByText('第一个会话')).toBeInTheDocument();
expect(screen.getByText('Second')).toBeInTheDocument();
});
});
it('渲染搜索框与新建会话按钮', () => {
render(<Sidebar />);
expect(screen.getByPlaceholderText(/搜索|Search/)).toBeInTheDocument();
expect(screen.getAllByRole('button').length).toBeGreaterThan(0);
});
it('接收 session:updated 后实时刷新条数(P2-4', async () => {
render(<Sidebar />);
await waitFor(() => {
expect(screen.getByText('第一个会话')).toBeInTheDocument();
});
expect(sessionUpdatedHandlers.length).toBeGreaterThan(0);
act(() => {
sessionUpdatedHandlers.forEach((h) =>
h({ sessionId: 's1', messageCount: 42, updatedAt: Date.now() }),
);
});
await waitFor(() => {
// t('sidebar.messageCount') zh 文案为 "{{count}} 条"
expect(screen.getByText(/42\s*条|42 messages/)).toBeInTheDocument();
});
});
it('接收 session:updated 的 title 补丁 → 标题刷新(LLM 标题生成链路)', async () => {
render(<Sidebar />);
await waitFor(() => {
expect(screen.getByText('第一个会话')).toBeInTheDocument();
});
act(() => {
sessionUpdatedHandlers.forEach((h) => h({ sessionId: 's1', title: 'LLM 生成的新标题' }));
});
await waitFor(() => {
expect(screen.getByText('LLM 生成的新标题')).toBeInTheDocument();
});
});
});
describe('Sidebar — 搜索', () => {
it('输入搜索词触发内容全文搜索(FTS5 通道,300ms 防抖)', async () => {
const user = userEvent.setup();
render(<Sidebar />);
const input = screen.getByPlaceholderText(/搜索|Search/);
await user.type(input, '关键词');
await waitFor(
() => {
const bridge = (window as unknown as { metona: Record<string, unknown> }).metona;
expect(bridge.sessions).toBeDefined();
expect(
(bridge.sessions as { searchContent: { mock: { calls: unknown[][] } } }).searchContent
.mock.calls.length,
).toBeGreaterThan(0);
},
{ timeout: 2_000 },
);
});
});