From 5c524c2559c82d5955d3454c7dae7abeb40f1743 Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 14 Jun 2025 07:28:03 +0200 Subject: [PATCH 1/2] Merge create and list guests into a single test --- tests/guests.spec.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/guests.spec.ts b/tests/guests.spec.ts index d7309a0..ccce687 100644 --- a/tests/guests.spec.ts +++ b/tests/guests.spec.ts @@ -81,8 +81,10 @@ const mockGroupsAPI = ({ page }: { page: Page }) => { }) } -test('should display the list of guests', async ({ page }) => { +test('should allow CRUD on guests', async ({ page }) => { await mockGuestsAPI({ page }); + await mockGroupsAPI({ page }); + await page.goto('/default/dashboard/guests'); @@ -105,14 +107,7 @@ test('should display the list of guests', async ({ page }) => { await expect(page.getByRole('row').nth(2).getByRole('cell', { name: 'Work' })).toBeVisible(); await expect(page.getByRole('row').nth(2).getByRole('cell', { name: 'Invited' })).toBeVisible(); await expect(page.getByRole('row').nth(2).locator('svg')).toHaveCount(2); -}); -test('should allow creating a new guest', async ({ page }) => { - await mockGuestsAPI({ page }); - await mockGroupsAPI({ page }); - - await page.goto('/default/dashboard/guests'); - await expect(page.getByText('There are 2 elements in the list')).toBeVisible(); await page.getByRole('button', { name: 'Add new' }).click(); await page.getByRole('dialog').getByLabel('Name').fill('John Snow'); From c50263f760f3817b45d662de63dec3fc6f26339d Mon Sep 17 00:00:00 2001 From: Manuel Bustillo Date: Sat, 14 Jun 2025 08:17:42 +0200 Subject: [PATCH 2/2] Fix bug where updated guest is duplicated --- app/[slug]/dashboard/guests/page.tsx | 15 ++++- app/api/abstract-api.tsx | 7 +- app/ui/components/guest-form-dialog.tsx | 8 +-- tests/guests.spec.ts | 88 ++++++++++++++++++------- 4 files changed, 87 insertions(+), 31 deletions(-) diff --git a/app/[slug]/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx index 3a62a69..bc5f59a 100644 --- a/app/[slug]/dashboard/guests/page.tsx +++ b/app/[slug]/dashboard/guests/page.tsx @@ -100,7 +100,20 @@ export default function Page() { key={guestBeingEdited?.id} groups={groups} onCreate={(newGuest) => { - setGuests([newGuest!, ...guests]); + setGuests(prevGuests => { + const index = prevGuests.findIndex(g => g.id === newGuest?.id); + if (index !== -1) { + // Replace existing guest + return [ + newGuest!, + ...prevGuests.slice(0, index), + ...prevGuests.slice(index + 1) + ]; + } else { + // Add new guest at the start + return [newGuest!, ...prevGuests]; + } + }); setGuestBeingEdited(undefined) ; }} guest={guestBeingEdited} diff --git a/app/api/abstract-api.tsx b/app/api/abstract-api.tsx index 5f8ebc7..b49e763 100644 --- a/app/api/abstract-api.tsx +++ b/app/api/abstract-api.tsx @@ -41,7 +41,7 @@ export class AbstractApi implements Api { }); } - update(serializable: Serializable, object: T, callback: () => void): void { + update(serializable: Serializable, object: T, callback: (updatedObject: T) => void): void { const endpoint = object.id ? `/api/${getSlug()}/${serializable.apiPath()}/${object.id}` : `/api/${getSlug()}/${serializable.apiPath()}`; fetch(endpoint, { @@ -52,7 +52,10 @@ export class AbstractApi implements Api { 'Accept': 'application/json', 'X-CSRF-TOKEN': getCsrfToken(), } - }).then(callback) + }).then((response) => response.json()) + .then((data) => { + callback(serializable.fromJson(data)); + }) .catch((error) => console.error(error)); } diff --git a/app/ui/components/guest-form-dialog.tsx b/app/ui/components/guest-form-dialog.tsx index 0dca253..9b3c729 100644 --- a/app/ui/components/guest-form-dialog.tsx +++ b/app/ui/components/guest-form-dialog.tsx @@ -44,9 +44,9 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib guest.group!.id = group; guest.status = status; - api.update(serializer, guest, () => { + api.update(serializer, guest, (updatedGuest) => { resetForm(); - onCreate && onCreate(guest); + onCreate && onCreate(updatedGuest); }); } else { api.create(serializer, new Guest(undefined, name, undefined, status, [], groups.find((g) => g.id === group)), (newGuest)=> { @@ -62,8 +62,8 @@ export default function GuestFormDialog({ groups, onCreate, onHide, guest, visib
- setName(e.target.value)} /> - + setName(e.target.value)} /> + setGroup(e.target.value)} options={ diff --git a/tests/guests.spec.ts b/tests/guests.spec.ts index ccce687..6d27594 100644 --- a/tests/guests.spec.ts +++ b/tests/guests.spec.ts @@ -10,8 +10,8 @@ const mockGuestsAPI = ({ page }: { page: Page }) => { "status": "tentative", "name": "Kristofer Rohan DVM", "group": { - "id": "2fcb8b22-6b07-4c34-92e3-a2535dbc5b14", - "name": "Childhood friends", + "id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a", + "name": "Pam's family", } }, { @@ -19,8 +19,8 @@ const mockGuestsAPI = ({ page }: { page: Page }) => { "status": "invited", "name": "Olevia Quigley Jr.", "group": { - "id": "da8edf26-3e1e-4cbb-b985-450c49fffe01", - "name": "Work", + "id": "c8bda6ca-d8af-4bb8-b2bf-e6ec1c21b1e6", + "name": "Pam's work", } }, ]; @@ -28,18 +28,37 @@ const mockGuestsAPI = ({ page }: { page: Page }) => { await route.fulfill({ json }) } else if (route.request().method() === 'POST') { const json = { - "id":"ff58aa2d-643d-4c29-be9c-50e10ae6853c", - "name":"John Snow", - "status":"invited", + "id": "ff58aa2d-643d-4c29-be9c-50e10ae6853c", + "name": "John Snow", + "status": "invited", "group": { - "id": "da8edf26-3e1e-4cbb-b985-450c49fffe01", - "name": "Work", - } - }; + "id": "c8bda6ca-d8af-4bb8-b2bf-e6ec1c21b1e6", + "name": "Pam's work", + } + }; await route.fulfill({ json }); } }) + + page.route("*/**/api/default/guests/*", async route => { + if (route.request().method() === 'PUT') { + const json = { + "id": "ff58aa2d-643d-4c29-be9c-50e10ae6853c", + "name": "John Fire", + "status": "declined", + "group": { + "id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a", + "name": "Pam's family", + } + } + await route.fulfill({ json }); + + } else if (route.request().method() === 'DELETE') { + const json = {} + await route.fulfill({ json }); + } + }); } const mockGroupsAPI = ({ page }: { page: Page }) => { @@ -85,7 +104,6 @@ test('should allow CRUD on guests', async ({ page }) => { await mockGuestsAPI({ page }); await mockGroupsAPI({ page }); - await page.goto('/default/dashboard/guests'); await expect(page.getByRole('button', { name: 'Add new' })).toBeVisible(); @@ -94,42 +112,64 @@ test('should allow CRUD on guests', async ({ page }) => { await expect(page.getByRole('tab', { name: 'Groups' })).toBeVisible(); await expect(page.getByRole('tab', { name: 'Invitations' })).toBeVisible(); + // List all guests await expect(page.getByText('There are 2 elements in the list')).toBeVisible(); await expect(page.getByRole('row')).toHaveCount(3); // 1 header row + 2 data rows await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Kristofer Rohan DVM' })).toBeVisible(); - await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Childhood friends' })).toBeVisible(); + await expect(page.getByRole('row').nth(1).getByRole('cell', { name: "Pam's family" })).toBeVisible(); await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Tentative' })).toBeVisible(); await expect(page.getByRole('row').nth(1).locator('svg')).toHaveCount(2); - await expect(page.getByRole('row').nth(2).getByRole('cell', { name: 'Olevia Quigley Jr.' })).toBeVisible(); - await expect(page.getByRole('row').nth(2).getByRole('cell', { name: 'Work' })).toBeVisible(); + await expect(page.getByRole('row').nth(2).getByRole('cell', { name: "Pam's work" })).toBeVisible(); await expect(page.getByRole('row').nth(2).getByRole('cell', { name: 'Invited' })).toBeVisible(); await expect(page.getByRole('row').nth(2).locator('svg')).toHaveCount(2); + // Add a new guest await page.getByRole('button', { name: 'Add new' }).click(); await page.getByRole('dialog').getByLabel('Name').fill('John Snow'); - await page.keyboard.press('Tab'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('Enter'); + await page.locator('#group').click(); + await page.getByRole('option', { name: "Pam's work" }).click(); - await page.keyboard.press('Tab'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('Enter'); + await page.locator('#status').click(); + await page.getByRole('option', { name: 'Invited' }).click(); await page.getByRole('dialog').getByRole('button', { name: 'Create' }).click(); await expect(page.getByText('There are 3 elements in the list')).toBeVisible(); await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'John Snow' })).toBeVisible(); - await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Work' })).toBeVisible(); + await expect(page.getByRole('row').nth(1).getByRole('cell', { name: "Pam\'s work" })).toBeVisible(); await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Invited' })).toBeVisible(); await expect(page.getByRole('row').nth(1).locator('svg')).toHaveCount(2); + + // Edit the just-added John Snow + await page.getByRole('row').nth(1).locator('svg').nth(1).click(); // Click edit icon + + const dialog = page.getByRole('dialog'); + await expect(dialog.getByLabel('Name')).toHaveValue('John Snow'); + await dialog.getByLabel('Name').fill('John Fire'); + + await dialog.locator('#group').click(); + await page.getByRole('option', { name: "Pam's family" }).click(); + + await dialog.locator('#status').click(); + await page.getByRole('option', { name: 'Declined' }).click(); + + await dialog.getByRole('button', { name: 'Update' }).click(); + + await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'John Fire' })).toBeVisible(); + await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Pam\'s Family' })).toBeVisible(); + await expect(page.getByRole('row').nth(1).getByRole('cell', { name: 'Declined' })).toBeVisible(); + + await expect(page.getByText('There are 3 elements in the list')).toBeVisible(); + + // Delete John Fire + await page.getByRole('row').nth(1).locator('svg').nth(0).click(); // Click delete icon + await expect(page.getByText('There are 2 elements in the list')).toBeVisible(); }); test('should display the list of groups', async ({ page }) => {