129 lines
4.9 KiB
TypeScript
129 lines
4.9 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { mockAuthenticatedUser } from './support/auth';
|
|
|
|
/**
|
|
* E2E coverage for the listing inquiry modal (TEC-2751 / TEC-2738.10).
|
|
*
|
|
* The backend route is `POST /api/v1/inquiries` and is guarded by JwtAuthGuard.
|
|
* The web app talks to it via `apiClient.post('/inquiries', ...)`, so the
|
|
* request URL contains `/inquiries` — we intercept it and stub both the
|
|
* profile fetch (to make the user look authenticated) and the inquiry POST.
|
|
*/
|
|
|
|
const seededListingId = 'seed-listing-001';
|
|
const seededListingTitle = /Căn hộ Vinhomes Central Park|Vinhomes Central Park/i;
|
|
|
|
test.describe('Listing inquiry modal', () => {
|
|
test('opens the inquiry modal when clicking "Nhắn tin"', async ({ page }) => {
|
|
await page.goto(`/listings/${seededListingId}`);
|
|
|
|
await expect(page.getByRole('heading', { name: seededListingTitle })).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.getByRole('button', { name: /Nhắn tin|Nhan tin/i }).click();
|
|
|
|
await expect(
|
|
page.getByRole('heading', { name: /Nhắn tin cho người bán/ }),
|
|
).toBeVisible();
|
|
await expect(page.getByLabel(/Nội dung tin nhắn/)).toBeVisible();
|
|
await expect(page.getByLabel(/Số điện thoại/)).toBeVisible();
|
|
});
|
|
|
|
test('shows validation errors when fields are missing or invalid', async ({ page, context, baseURL }) => {
|
|
await mockAuthenticatedUser(page, context, baseURL, { role: 'BUYER' });
|
|
|
|
await page.goto(`/listings/${seededListingId}`);
|
|
await page.getByRole('button', { name: /Nhắn tin|Nhan tin/i }).click();
|
|
|
|
// Native required validation keeps the modal open before zod validation runs.
|
|
await page.getByRole('button', { name: 'Gửi tin nhắn' }).click();
|
|
await expect(
|
|
page.getByRole('heading', { name: /Nhắn tin cho người bán/ }),
|
|
).toBeVisible();
|
|
|
|
// Provide message but an obviously-invalid phone.
|
|
await page.getByLabel(/Nội dung tin nhắn/).fill('Tôi quan tâm tin đăng này.');
|
|
await page.getByLabel(/Số điện thoại/).fill('123');
|
|
await page.getByRole('button', { name: 'Gửi tin nhắn' }).click();
|
|
await expect(
|
|
page.getByText(/Vui lòng nhập số điện thoại hợp lệ|Số điện thoại không hợp lệ/),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test('submits the inquiry and calls POST /api/v1/inquiries (201)', async ({
|
|
page,
|
|
context,
|
|
baseURL,
|
|
}) => {
|
|
await mockAuthenticatedUser(page, context, baseURL, { role: 'BUYER' });
|
|
|
|
let inquiryRequestBody: Record<string, unknown> | null = null;
|
|
await page.route('**/api/v1/inquiries', async (route) => {
|
|
if (route.request().method() !== 'POST') {
|
|
return route.fallback();
|
|
}
|
|
inquiryRequestBody = route.request().postDataJSON() as Record<string, unknown>;
|
|
await route.fulfill({
|
|
status: 201,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
id: 'inq-1',
|
|
listingId: seededListingId,
|
|
listingTitle: 'Căn hộ Vinhomes Central Park 3PN view sông Sài Gòn',
|
|
userId: 'e2e-buyer-user',
|
|
userName: 'E2E BUYER',
|
|
userPhone: '+84900000002',
|
|
message: 'Tôi quan tâm tin đăng này.',
|
|
phone: '0911222333',
|
|
isRead: false,
|
|
createdAt: new Date().toISOString(),
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.goto(`/listings/${seededListingId}`);
|
|
await page.getByRole('button', { name: /Nhắn tin|Nhan tin/i }).click();
|
|
|
|
await page.getByLabel(/Nội dung tin nhắn/).fill('Tôi quan tâm tin đăng này.');
|
|
// Phone pre-fills from the mocked profile; overwrite to ensure stability.
|
|
await page.getByLabel(/Số điện thoại/).fill('0911222333');
|
|
|
|
const [request] = await Promise.all([
|
|
page.waitForRequest(
|
|
(req) => req.url().includes('/inquiries') && req.method() === 'POST',
|
|
),
|
|
page.getByRole('button', { name: 'Gửi tin nhắn' }).click(),
|
|
]);
|
|
|
|
expect(request.postDataJSON()).toMatchObject({
|
|
listingId: seededListingId,
|
|
message: 'Tôi quan tâm tin đăng này.',
|
|
phone: '0911222333',
|
|
});
|
|
|
|
// Modal should close after success.
|
|
await expect(
|
|
page.getByRole('heading', { name: /Nhắn tin cho người bán/ }),
|
|
).toBeHidden();
|
|
|
|
// Sonner success toast appears.
|
|
await expect(page.getByText('Đã gửi thành công!')).toBeVisible();
|
|
|
|
expect(inquiryRequestBody).not.toBeNull();
|
|
});
|
|
|
|
test('redirects anonymous users to /login on submit', async ({ page }) => {
|
|
await page.goto(`/listings/${seededListingId}`);
|
|
await page.getByRole('button', { name: /Nhắn tin|Nhan tin/i }).click();
|
|
|
|
await page.getByLabel(/Nội dung tin nhắn/).fill('Tôi quan tâm tin đăng này.');
|
|
await page.getByLabel(/Số điện thoại/).fill('0911222333');
|
|
|
|
await Promise.all([
|
|
page.waitForURL(/\/login/),
|
|
page.getByRole('button', { name: 'Gửi tin nhắn' }).click(),
|
|
]);
|
|
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
});
|