import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import type { ListingDetail, PaginatedResult } from '@/lib/listings-api';
import { SearchResults } from '../search-results';
// Mock PropertyCard
vi.mock('../property-card', () => ({
PropertyCard: ({ listing }: { listing: ListingDetail }) => (
{listing.property.title}
),
}));
function makeListing(id: string): ListingDetail {
return {
id,
status: 'ACTIVE',
transactionType: 'SALE',
priceVND: '3500000000',
pricePerM2: 40_000_000,
rentPriceMonthly: null,
commissionPct: null,
viewCount: 100,
saveCount: 10,
inquiryCount: 5,
publishedAt: '2026-01-01T00:00:00Z',
createdAt: '2025-12-01T00:00:00Z',
property: {
id: `prop-${id}`,
propertyType: 'APARTMENT',
title: `Listing ${id}`,
description: 'Test listing',
address: '123 Test St',
ward: 'Ward',
district: 'District',
city: 'HCMC',
areaM2: 75,
bedrooms: 2,
bathrooms: 2,
floors: null,
direction: null,
yearBuilt: null,
legalStatus: null,
amenities: [],
projectName: null,
latitude: null,
longitude: null,
media: [],
},
seller: { id: 's1', fullName: 'Seller', phone: '0901234567' },
agent: null,
};
}
function makeResult(count: number, page = 1, totalPages = 1): PaginatedResult {
return {
data: Array.from({ length: count }, (_, i) => makeListing(`${i + 1}`)),
total: count,
page,
limit: 10,
totalPages,
};
}
const defaultProps = {
page: 1,
sort: '',
onPageChange: vi.fn(),
onSortChange: vi.fn(),
};
describe('SearchResults', () => {
it('renders loading spinner when loading', () => {
const { container } = render(
,
);
expect(container.querySelector('.animate-spin')).toBeInTheDocument();
});
it('renders error state', () => {
render(
,
);
expect(screen.getByText('Không thể tải kết quả tìm kiếm')).toBeInTheDocument();
});
it('renders retry button in error state', () => {
const onRetry = vi.fn();
render(
,
);
expect(screen.getByText('Thử lại')).toBeInTheDocument();
});
it('calls onRetry when retry button clicked', async () => {
const user = userEvent.setup();
const onRetry = vi.fn();
render(
,
);
await user.click(screen.getByText('Thử lại'));
expect(onRetry).toHaveBeenCalled();
});
it('renders empty state when no results', () => {
render(
,
);
expect(screen.getByText('Không tìm thấy kết quả')).toBeInTheDocument();
});
it('renders empty state with null result', () => {
render(
,
);
expect(screen.getByText('Không tìm thấy kết quả')).toBeInTheDocument();
});
it('renders property cards for results', () => {
render(
,
);
expect(screen.getByTestId('property-card-1')).toBeInTheDocument();
expect(screen.getByTestId('property-card-2')).toBeInTheDocument();
expect(screen.getByTestId('property-card-3')).toBeInTheDocument();
});
it('renders total results count', () => {
render(
,
);
expect(screen.getByText('3 kết quả')).toBeInTheDocument();
});
it('renders sort select', () => {
render(
,
);
expect(screen.getByText('Mới nhất')).toBeInTheDocument();
expect(screen.getByText('Giá: Thấp đến cao')).toBeInTheDocument();
});
it('renders pagination buttons for multi-page results', () => {
render(
,
);
expect(screen.getByText('Trước')).toBeInTheDocument();
expect(screen.getByText('Tiếp')).toBeInTheDocument();
});
it('disables Trước button on first page', () => {
render(
,
);
expect(screen.getByText('Trước')).toBeDisabled();
});
it('disables Tiếp button on last page', () => {
render(
,
);
expect(screen.getByText('Tiếp')).toBeDisabled();
});
it('calls onPageChange when Tiếp clicked', async () => {
const user = userEvent.setup();
const onPageChange = vi.fn();
render(
,
);
await user.click(screen.getByText('Tiếp'));
expect(onPageChange).toHaveBeenCalledWith(2);
});
it('does not render pagination for single page', () => {
render(
,
);
expect(screen.queryByText('Trước')).not.toBeInTheDocument();
});
});