import { test, expect } from '@playwright/test'; const mockListings = { data: [ { id: 'listing-1', transactionType: 'SALE', priceVND: '5000000000', pricePerM2: 66666667, rentPriceMonthly: null, commissionPct: null, status: 'ACTIVE', viewCount: 120, saveCount: 15, inquiryCount: 8, publishedAt: '2026-01-15T00:00:00Z', property: { id: 'prop-1', propertyType: 'APARTMENT', title: 'Căn hộ cao cấp Quận 1', description: 'Căn hộ đẹp view sông', address: '123 Nguyễn Huệ', ward: 'Bến Nghé', district: 'Quận 1', city: 'Hồ Chí Minh', latitude: 10.7769, longitude: 106.7009, areaM2: 75, bedrooms: 2, bathrooms: 2, floors: 1, direction: 'SOUTH', yearBuilt: null, legalStatus: null, projectName: null, amenities: [], media: [], }, seller: { id: 's1', fullName: 'Nguyen Van A', phone: '0912345678' }, agent: null, }, { id: 'listing-2', transactionType: 'RENT', priceVND: '15000000', pricePerM2: null, rentPriceMonthly: '15000000', commissionPct: null, status: 'ACTIVE', viewCount: 50, saveCount: 5, inquiryCount: 3, publishedAt: '2026-02-01T00:00:00Z', property: { id: 'prop-2', propertyType: 'HOUSE', title: 'Nhà phố Quận 7', description: 'Nhà phố đẹp khu an ninh', address: '456 Nguyễn Thị Thập', ward: 'Tân Phú', district: 'Quận 7', city: 'Hồ Chí Minh', latitude: 10.7385, longitude: 106.7218, areaM2: 120, bedrooms: 4, bathrooms: 3, floors: 3, direction: 'EAST', yearBuilt: 2020, legalStatus: null, projectName: null, amenities: [], media: [], }, seller: { id: 's2', fullName: 'Tran Thi B', phone: '0987654321' }, agent: null, }, ], total: 2, page: 1, limit: 12, totalPages: 1, }; test.describe('Search Page', () => { test.beforeEach(async ({ page }) => { // Mock the listings API to return consistent data await page.route('**/listings**', (route) => { if (route.request().method() === 'GET') { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockListings), }); } return route.continue(); }); }); test('renders search page with title and filters', async ({ page }) => { await page.goto('/search'); await expect(page.getByRole('heading', { name: 'Tìm kiếm bất động sản' })).toBeVisible(); await expect( page.getByText('Tìm bất động sản phù hợp với nhu cầu của bạn'), ).toBeVisible(); }); test('displays view mode toggle buttons', async ({ page }) => { await page.goto('/search'); await expect(page.getByRole('button', { name: /Danh sách/i })).toBeVisible(); await expect(page.getByRole('button', { name: /Bản đồ/i })).toBeVisible(); }); test('displays listing results', async ({ page }) => { await page.goto('/search'); await expect(page.getByText('Căn hộ cao cấp Quận 1')).toBeVisible({ timeout: 10000 }); await expect(page.getByText('Nhà phố Quận 7')).toBeVisible(); }); test('switches to map view mode', async ({ page }) => { await page.goto('/search'); await page.getByRole('button', { name: /Bản đồ/i }).click(); // Map view should be active — list results should not be visible await expect(page.getByRole('button', { name: /Bản đồ/i })).toHaveAttribute( 'data-state', /.*/, ); }); test('syncs filters to URL query parameters', async ({ page }) => { await page.goto('/search?transactionType=SALE'); // The URL should contain the filter await expect(page).toHaveURL(/transactionType=SALE/); }); test('shows error state on API failure', async ({ page }) => { await page.route('**/listings**', (route) => route.fulfill({ status: 500, body: 'Internal Server Error' }), ); await page.goto('/search'); // Should show some error indication await page.waitForTimeout(2000); // The page should still be navigable (not crash) await expect(page.getByRole('heading', { name: 'Tìm kiếm bất động sản' })).toBeVisible(); }); test('shows loading spinner initially', async ({ page }) => { await page.route('**/listings**', async (route) => { await new Promise((r) => setTimeout(r, 2000)); await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockListings), }); }); await page.goto('/search'); // Should show loading indication (spinner or skeleton) const spinner = page.locator('.animate-spin, .animate-pulse'); await expect(spinner.first()).toBeVisible({ timeout: 3000 }); }); });