import { test, expect } from '@playwright/test'; import { getPortId, login, navigateTo, waitForSheet } from './helpers'; const RES_CLIENT_NAME = `E2E Res Client ${Date.now()}`; const RES_CLIENT_EMAIL = `e2e-res-${Date.now()}@test.example`; test.describe('Residential', () => { test.beforeEach(async ({ page }) => { await login(page, 'super_admin'); }); test('residential clients page loads with empty state or table', async ({ page }) => { await navigateTo(page, '/residential/clients'); await expect(page.getByRole('heading', { name: /residential clients/i })).toBeVisible({ timeout: 10_000, }); // Either table or empty state should render — both ok. const hasTableOrEmpty = await Promise.race([ page .locator('table') .first() .isVisible({ timeout: 5_000 }) .catch(() => false), page .getByRole('button', { name: /new/i }) .first() .isVisible({ timeout: 5_000 }) .catch(() => false), ]); expect(hasTableOrEmpty).toBeTruthy(); }); test('create a residential client via UI', async ({ page }) => { await navigateTo(page, '/residential/clients'); await page.locator('main').getByRole('button', { name: /^new$/i }).first().click(); await waitForSheet(page); const sheet = page.locator('[role="dialog"]'); await sheet.locator('#rc-name').fill(RES_CLIENT_NAME); await sheet.locator('#rc-email').fill(RES_CLIENT_EMAIL); await sheet.getByRole('button', { name: /create|save/i }).click(); await expect(sheet).not.toBeVisible({ timeout: 30_000 }); await page.waitForTimeout(1500); // Reload list and confirm row is present. await navigateTo(page, '/residential/clients'); await expect(page.getByText(RES_CLIENT_NAME).first()).toBeVisible({ timeout: 30_000 }); }); test('residential interests page loads', async ({ page }) => { await navigateTo(page, '/residential/interests'); await expect(page.getByRole('heading', { name: /residential interests/i })).toBeVisible({ timeout: 10_000, }); }); test('residential nav section is visible in sidebar', async ({ page }) => { await navigateTo(page, '/dashboard'); // Both residential links should appear for super_admin. await expect(page.getByRole('link', { name: /residential clients/i }).first()).toBeVisible({ timeout: 10_000, }); await expect(page.getByRole('link', { name: /residential interests/i }).first()).toBeVisible({ timeout: 10_000, }); }); test('create a residential interest via API and see it on the interests list', async ({ page, }) => { const portId = await getPortId(page); const stamp = Date.now(); // 1) Create a residential client via the v1 API. const clientRes = await page.request.post('/api/v1/residential/clients', { data: { fullName: `E2E Res ${stamp}`, email: `e2e-res-${stamp}@test.example`, phone: '+1-555-0199', }, headers: { 'Content-Type': 'application/json', 'x-port-id': portId }, }); expect(clientRes.status()).toBe(201); const clientBody = await clientRes.json(); const residentialClientId = clientBody.data?.id ?? clientBody.id; expect(residentialClientId).toBeTruthy(); // 2) Create a residential interest tied to that client. const interestRes = await page.request.post('/api/v1/residential/interests', { data: { residentialClientId, preferences: `2BR oceanview, budget €${stamp}`, notes: `Smoke interest ${stamp}`, source: 'manual', }, headers: { 'Content-Type': 'application/json', 'x-port-id': portId }, }); expect(interestRes.status()).toBe(201); const interestBody = await interestRes.json(); const residentialInterestId = interestBody.data?.id ?? interestBody.id; expect(residentialInterestId).toBeTruthy(); // 3) Confirm it shows up on the residential interests list page. // The list renders preferences/notes (not the client name), so match // against the unique stamp embedded in the preferences string. await navigateTo(page, '/residential/interests'); await expect(page.getByText(new RegExp(String(stamp))).first()).toBeVisible({ timeout: 15_000, }); }); test('residential interest detail page renders for a freshly-created interest', async ({ page, }) => { const portId = await getPortId(page); const stamp = Date.now(); const clientRes = await page.request.post('/api/v1/residential/clients', { data: { fullName: `E2E ResDetail ${stamp}`, email: `e2e-res-detail-${stamp}@test.example`, }, headers: { 'Content-Type': 'application/json', 'x-port-id': portId }, }); expect(clientRes.status()).toBe(201); const clientId = (await clientRes.json()).data?.id; const interestRes = await page.request.post('/api/v1/residential/interests', { data: { residentialClientId: clientId, preferences: 'penthouse, sea view', source: 'manual' }, headers: { 'Content-Type': 'application/json', 'x-port-id': portId }, }); expect(interestRes.status()).toBe(201); const interestId = (await interestRes.json()).data?.id; // Navigate to the detail route — the page should render the standard // residential-interest detail layout (eyebrow + client name h1 + sections). await navigateTo(page, `/residential/interests/${interestId}`); await expect(page.getByText('Residential interest', { exact: true }).first()).toBeVisible({ timeout: 15_000, }); await expect(page.getByRole('heading', { name: /E2E ResDetail/ }).first()).toBeVisible({ timeout: 5_000, }); }); test('public residential inquiry endpoint accepts a submission', async ({ page }) => { // Direct API call to the public endpoint — confirms wiring without UI. // Public endpoint takes portId via query string (no auth, but the helper // resolves the port ID for us via the admin API). const portId = await getPortId(page); const res = await page.request.post( `/api/public/residential-inquiries?portId=${encodeURIComponent(portId)}`, { data: { firstName: `Public${Date.now()}`, lastName: 'Smoke', email: `public-res-${Date.now()}@test.example`, phone: '+1-555-0100', notes: 'Smoke test inquiry', }, headers: { 'Content-Type': 'application/json' }, }, ); // 201 success, 429 if rate-limit hit (also acceptable as proof endpoint exists). expect([201, 429]).toContain(res.status()); }); });