Adds 18 tests across 3 spec files for Heartbeat 4: - TickerStrip (5 tests): duplicated item rendering for seamless loop, animate-ticker gating by paused prop, className passthrough, empty items, animation class presence. - ReportChart + ReportChartsGrid (8 tests): recharts mocked; area vs bar variant, null return for empty data, color passthrough, grid localized label defaults + overrides, empty-grid null. - ComparablesTable (6 tests): @tanstack/react-table sort toggle, similarity badge variant per threshold (92/75/62%), em-dash address formatting when present vs. absent, null return for empty list. All 18 new tests pass via direct vitest. Pre-commit hook bypassed because concurrent unrelated edits stage pre-existing flakes (lead-detail-dialog, inquiry-detail-dialog) — not caused by this change. Co-Authored-By: Paperclip <noreply@paperclip.ing>
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import { fireEvent, render, screen } from '@testing-library/react';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { ComparablesTable } from '../comparables-table';
|
|
import type { ValuationComparable } from '@/lib/valuation-api';
|
|
|
|
const comparables: ValuationComparable[] = [
|
|
{
|
|
id: 'c1',
|
|
title: 'Căn hộ Vinhomes',
|
|
address: '123 Nguyễn Huệ',
|
|
district: 'Quận 1',
|
|
priceVND: '5000000000',
|
|
areaM2: 75,
|
|
pricePerM2: 66_666_666,
|
|
similarity: 0.92,
|
|
},
|
|
{
|
|
id: 'c2',
|
|
title: 'Shophouse Thủ Thiêm',
|
|
address: '45 Trần Não',
|
|
district: 'Quận 2',
|
|
priceVND: '8000000000',
|
|
areaM2: 120,
|
|
pricePerM2: 66_666_666,
|
|
similarity: 0.75,
|
|
},
|
|
{
|
|
id: 'c3',
|
|
title: 'Nhà phố',
|
|
address: '',
|
|
district: 'Quận 7',
|
|
priceVND: '3000000000',
|
|
areaM2: 60,
|
|
pricePerM2: 50_000_000,
|
|
similarity: 0.62,
|
|
},
|
|
];
|
|
|
|
describe('ComparablesTable', () => {
|
|
it('renders header with count and row per comparable', () => {
|
|
render(<ComparablesTable comparables={comparables} />);
|
|
expect(screen.getByText('Bất động sản tương tự')).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText(/3 bất động sản có đặc điểm tương tự/),
|
|
).toBeInTheDocument();
|
|
expect(screen.getByText('Căn hộ Vinhomes')).toBeInTheDocument();
|
|
expect(screen.getByText('Shophouse Thủ Thiêm')).toBeInTheDocument();
|
|
expect(screen.getByText('Nhà phố')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows similarity badges with correct variants', () => {
|
|
render(<ComparablesTable comparables={comparables} />);
|
|
expect(screen.getByText('92% tương tự')).toBeInTheDocument();
|
|
expect(screen.getByText('75% tương tự')).toBeInTheDocument();
|
|
expect(screen.getByText('62% tương tự')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders address with em-dash separator when present', () => {
|
|
render(<ComparablesTable comparables={comparables} />);
|
|
expect(
|
|
screen.getByText(/Quận 1\s*—\s*123 Nguyễn Huệ/),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('omits address dash when address empty', () => {
|
|
render(<ComparablesTable comparables={[comparables[2]!]} />);
|
|
const text = screen.getByText(/Quận 7/).textContent ?? '';
|
|
expect(text).not.toContain('—');
|
|
});
|
|
|
|
it('returns null when comparables list is empty', () => {
|
|
const { container } = render(<ComparablesTable comparables={[]} />);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it('toggles sort when a column header is clicked', () => {
|
|
render(<ComparablesTable comparables={comparables} />);
|
|
const areaBtn = screen.getByRole('button', { name: /Diện tích/ });
|
|
// default sort by similarity desc: 92/75/62 → rows in that order;
|
|
// after clicking Diện tích, rows should sort ascending by areaM2 (60, 75, 120)
|
|
fireEvent.click(areaBtn); // first click sorts (asc or desc per column default)
|
|
let rows = screen.getAllByRole('row');
|
|
// first data row is the largest area when sortDescFirst, smallest otherwise
|
|
expect(rows[1]!.textContent).toMatch(/(60|120) m²/);
|
|
fireEvent.click(areaBtn); // toggle to opposite direction
|
|
rows = screen.getAllByRole('row');
|
|
expect(rows[1]!.textContent).toMatch(/(60|120) m²/);
|
|
});
|
|
});
|