fix(web): recover vehicle option lookups
This commit is contained in:
@@ -117,6 +117,21 @@ test('supports searching and selecting license plates before querying exact VINs
|
||||
expect(screen.getByTitle('LTEST000000000001')).toHaveTextContent('粤A12345');
|
||||
});
|
||||
|
||||
test('shows and recovers from a failed license plate candidate query', async () => {
|
||||
prepareData();
|
||||
mocks.vehicles.mockRejectedValueOnce(new Error('车辆目录查询超时')).mockResolvedValueOnce({ items: [{ vin: 'LTEST000000000001', plate: '粤A12345' }], total: 1, limit: 12, offset: 0 });
|
||||
renderPage();
|
||||
|
||||
fireEvent.focus(screen.getByRole('textbox', { name: '搜索车牌' }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('车辆目录查询超时');
|
||||
expect(screen.queryByText('没有匹配的车牌')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试' }));
|
||||
|
||||
expect(await screen.findByRole('option', { name: /粤A12345/ })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(mocks.vehicles).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('lets users disable mileage sources and persists the source priority', async () => {
|
||||
prepareData();
|
||||
renderPage('/statistics?vins=LTEST000000000001&dateFrom=2026-07-13&dateTo=2026-07-14');
|
||||
|
||||
@@ -174,10 +174,11 @@ function VehicleMultiSelect({ value, onChange }: { value: VehicleOption[]; onCha
|
||||
{open ? <div className="v2-mileage-options" role="listbox">
|
||||
<header><span>车牌候选</span><em>{value.length}/{MAX_SELECTED_VEHICLES} 已选</em></header>
|
||||
{candidates.isLoading ? <p>正在搜索车辆…</p> : null}
|
||||
{!candidates.isLoading && candidates.isError ? <div className="v2-vehicle-option-error" role="alert"><span>{candidates.error instanceof Error ? candidates.error.message : '车牌候选加载失败'}</span><button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => void candidates.refetch()}>重试</button></div> : null}
|
||||
{!candidates.isLoading && options.map((vehicle) => <button type="button" role="option" aria-selected={selected.has(vehicle.vin)} key={vehicle.vin} disabled={selected.has(vehicle.vin)} onMouseDown={(event) => event.preventDefault()} onClick={() => add(vehicle)}>
|
||||
<strong>{vehicle.plate || '未绑定车牌'}</strong><span>{vehicle.vin}</span>{selected.has(vehicle.vin) ? <em>已选择</em> : null}
|
||||
</button>)}
|
||||
{!candidates.isLoading && !options.length ? <p>没有匹配的车牌</p> : null}
|
||||
{!candidates.isLoading && !candidates.isError && !options.length ? <p>没有匹配的车牌</p> : null}
|
||||
</div> : null}
|
||||
</div>
|
||||
<small>支持车牌关键字搜索,最多选择 {MAX_SELECTED_VEHICLES} 辆</small>
|
||||
|
||||
@@ -66,6 +66,21 @@ test('keeps committed track criteria authoritative to same-route navigation and
|
||||
expect(screen.getByTestId('track-map')).toHaveAttribute('data-point-count', '0');
|
||||
});
|
||||
|
||||
test('distinguishes a failed vehicle lookup from an empty result and retries in place', async () => {
|
||||
mocks.vehicles.mockRejectedValueOnce(new Error('车辆目录暂时不可用')).mockResolvedValueOnce({ items: [{ vin: 'LTEST000000000001', plate: '粤A12345' }], total: 1, limit: 10, offset: 0 });
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks']}><TrackPage /></MemoryRouter></QueryClientProvider>);
|
||||
|
||||
fireEvent.focus(screen.getByRole('textbox', { name: '搜索轨迹车辆' }));
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('车辆目录暂时不可用');
|
||||
expect(screen.queryByText('没有匹配车辆')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '重试' }));
|
||||
|
||||
expect(await screen.findByRole('option', { name: /粤A12345/ })).toBeInTheDocument();
|
||||
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
||||
expect(mocks.vehicles).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('renders a map-first replay workspace and connects stop, event, and panel interactions', async () => {
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
const view = render(<QueryClientProvider client={client}><MemoryRouter future={ROUTER_FUTURE} initialEntries={['/tracks?keyword=LTEST000000000001&dateFrom=2026-07-15T00:00&dateTo=2026-07-15T23:59']}><TrackPage /></MemoryRouter></QueryClientProvider>);
|
||||
|
||||
@@ -113,11 +113,12 @@ function VehiclePicker({ value, onChange, onSelect }: { value: string; onChange:
|
||||
{open ? <div className="v2-track-vehicle-options" role="listbox">
|
||||
<header><span>车辆候选</span><em>支持车牌或 VIN</em></header>
|
||||
{candidates.isFetching ? <p><span className="v2-spinner" />正在搜索车辆</p> : null}
|
||||
{!candidates.isFetching && candidates.isError ? <div className="v2-vehicle-option-error" role="alert"><span>{candidates.error instanceof Error ? candidates.error.message : '车辆候选加载失败'}</span><button type="button" onMouseDown={(event) => event.preventDefault()} onClick={() => void candidates.refetch()}>重试</button></div> : null}
|
||||
{!candidates.isFetching && (candidates.data?.items ?? []).map((vehicle) => <button
|
||||
type="button" role="option" aria-selected={false} key={vehicle.vin}
|
||||
onMouseDown={(event) => event.preventDefault()} onClick={() => { onSelect(vehicle); setOpen(false); }}
|
||||
><strong>{vehicle.plate || '未绑定车牌'}</strong><span>{vehicle.vin}</span><em>选择</em></button>)}
|
||||
{!candidates.isFetching && !(candidates.data?.items.length) ? <p>没有匹配车辆</p> : null}
|
||||
{!candidates.isFetching && !candidates.isError && !(candidates.data?.items.length) ? <p>没有匹配车辆</p> : null}
|
||||
</div> : null}
|
||||
</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user