import { describe, expect, it } from 'vitest'; import { render, screen } from '@testing-library/react'; import type { FunnelQueryResponse } from '../../../api/types'; import { FunnelResult } from '../components/FunnelResult'; function renderResult(props: Parameters[0]) { return render(); } const readyData: FunnelQueryResponse = { period: 'last_7d', snapshot_dt: '20260623', data_status: 'ready', results: [ { step_index: 1, name: '启动', event_key: 'start', uv: 10000, conversion_rate: null, dropoff_rate: null, }, { step_index: 2, name: '曝光', event_key: 'show', uv: 8200, conversion_rate: 0.82, dropoff_rate: 0.18, }, ], }; const baseProps = { period: 'last_7d' as const, isLoading: false, isError: false, }; describe('FunnelResult states', () => { it('ready: renders funnel chart and result table', async () => { renderResult({ ...baseProps, data: readyData }); // FunnelChart is lazy-loaded — await its appearance. expect(await screen.findByTestId('funnel-chart')).toBeInTheDocument(); expect(screen.getByText('UV')).toBeInTheDocument(); expect(screen.getByText('转化率')).toBeInTheDocument(); expect(screen.getByText('流失率')).toBeInTheDocument(); expect(screen.getByText(/启动/)).toBeInTheDocument(); expect(screen.getByText(/曝光/)).toBeInTheDocument(); }); it('ready (7d): shows the rolling "数据截至 … (近 7 天)" caption', () => { renderResult({ ...baseProps, data: readyData }); expect( screen.getByText(/数据截至 2026-06-23(近 7 天)/), ).toBeInTheDocument(); expect(screen.queryByText(/数据快照日/)).not.toBeInTheDocument(); }); it('ready (30d): caption says (近 30 天)', () => { renderResult({ ...baseProps, period: 'last_30d', data: { ...readyData, period: 'last_30d' }, }); expect( screen.getByText(/数据截至 2026-06-23(近 30 天)/), ).toBeInTheDocument(); }); it('ready (day): shows the "数据快照日" caption', () => { renderResult({ ...baseProps, period: 'day', data: { ...readyData, period: 'day' }, }); expect(screen.getByText(/数据快照日:2026-06-23/)).toBeInTheDocument(); expect(screen.queryByText(/数据截至/)).not.toBeInTheDocument(); }); it('ready: step 1 null conversion renders as "—", not 0%', () => { renderResult({ ...baseProps, data: readyData }); const dashes = screen.getAllByText('—'); // step 1 conversion + dropoff = two em-dashes expect(dashes.length).toBeGreaterThanOrEqual(2); expect(screen.queryByText('0%')).not.toBeInTheDocument(); expect(screen.queryByText('0.00%')).not.toBeInTheDocument(); // the real value still shows expect(screen.getByText('82.00%')).toBeInTheDocument(); }); it('missing: explicit 数据缺失 empty state, no zeros / no chart', () => { renderResult({ ...baseProps, data: { ...readyData, results: [], data_status: 'missing' }, }); expect(screen.getByText(/数据缺失/)).toBeInTheDocument(); expect(screen.queryByTestId('funnel-chart')).not.toBeInTheDocument(); expect(screen.queryByText('0')).not.toBeInTheDocument(); }); it('request error: shows error state with message', () => { renderResult({ ...baseProps, isError: true, errorMessage: '网络请求失败', }); expect(screen.getByText('查询失败')).toBeInTheDocument(); expect(screen.getByText('网络请求失败')).toBeInTheDocument(); }); it('loading / no data yet: shows loading card', () => { renderResult({ ...baseProps, isLoading: true }); expect(screen.getByTestId('funnel-loading')).toBeInTheDocument(); }); });