From c71930f79b102f5a3cade7877dba425462ed758a Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Fri, 18 Sep 2026 09:52:14 +0800 Subject: test(frontend): 第二轮首页聚合真实渲染集成测试 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [变更性质] 测试补充,归入第二轮验证件。 [新增内容] mock listProjects/listDatasets/listRuns/getAIUsage 后渲染 ProjectsPage,断言 4 张 metric-card 与 2 处 sparkline-cell 的真实 DOM 输出。 [影响范围] frontend/src/lib/round2DashboardIntegration.test.ts(2 用例)。 --- .../src/lib/round2DashboardIntegration.test.ts | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 frontend/src/lib/round2DashboardIntegration.test.ts (limited to 'frontend/src/lib') diff --git a/frontend/src/lib/round2DashboardIntegration.test.ts b/frontend/src/lib/round2DashboardIntegration.test.ts new file mode 100644 index 0000000..363e1da --- /dev/null +++ b/frontend/src/lib/round2DashboardIntegration.test.ts @@ -0,0 +1,58 @@ +// @ts-nocheck +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import { render, cleanup, waitFor } from '@testing-library/svelte'; + +vi.mock('../lib/client', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + listProjects: vi.fn(), + listDatasets: vi.fn(), + listRuns: vi.fn(), + getAIUsage: vi.fn() + }; +}); + +import ProjectsPage from '../pages/ProjectsPage.svelte'; +import { listProjects, listDatasets, listRuns, getAIUsage } from '../lib/client'; + +beforeEach(() => { + vi.mocked(listProjects).mockReset().mockResolvedValue({ items: [ + { id: 'pA', name: '项目A', description: null, draft_code: '', draft_generation: 1, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z' }, + { id: 'pB', name: '项目B', description: null, draft_code: '', draft_generation: 0, created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z' } + ]}); + vi.mocked(listDatasets).mockReset().mockResolvedValue({ items: [{ id: 'd1' }, { id: 'd2' }, { id: 'd3' }] }); + vi.mocked(listRuns).mockReset().mockResolvedValue({ items: [ + { id: 'r1', project_id: 'pA', status: 'succeeded', created_at: '2026-02-01T00:00:00Z', result: { equity: [{ equity: 100 }, { equity: 101 }, { equity: 112 }] } }, + { id: 'r2', project_id: 'pB', status: 'succeeded', created_at: '2026-02-01T00:00:00Z', result: null } + ]}); + vi.mocked(getAIUsage).mockReset().mockResolvedValue({ totals: { input_tokens: 500, output_tokens: 200, requests: 2 } }); +}); + +afterEach(() => cleanup()); + +// Leader round2: 登录后默认页 = 指标卡行 + 项目行迷你权益缩略图。 +describe('ProjectsPage dashboard round2 (metric row + row sparkline, real render)', () => { + it('renders the 4-card metric row with真实聚合 numbers', async () => { + render(ProjectsPage); + await waitFor(() => expect(vi.mocked(getAIUsage)).toHaveBeenCalled()); + const cards = document.querySelectorAll('[data-testid="metric-card"]'); + await waitFor(() => expect(cards.length).toBe(4)); + const values = [...document.querySelectorAll('.metric-row .mvalue')].map((n) => n.textContent!.replace(/\s+/g, ' ').trim()); + expect(values[0]).toBe('2'); + expect(values[1]).toBe('3'); + expect(values[2]).toContain('已完成'); + expect(values[2]).toContain('2'); + }); + + it('renders an inline sparkline row cell for projects whose latest run has equity, none for data-less ones', async () => { + render(ProjectsPage); + await waitFor(() => expect(vi.mocked(listRuns)).toHaveBeenCalled()); + const rows = await waitFor(() => document.querySelectorAll('tbody tr')); + await waitFor(() => expect(document.querySelector('svg[data-testid="sparkline"]')).toBeTruthy()); + const rowA = [...document.querySelectorAll('tbody tr')].find((r) => r.textContent!.includes('项目A'))!; + const rowB = [...document.querySelectorAll('tbody tr')].find((r) => r.textContent!.includes('项目B'))!; + expect(rowA.querySelector('svg[data-testid="sparkline"]'), '项目A has equity → sparkline renders').toBeTruthy(); + expect(rowB.querySelector('svg[data-testid="sparkline"]'), '项目B has no equity → stays empty').toBeNull(); + }); +}); -- cgit v1.2.3