diff --git a/app/[slug]/dashboard/guests/page.tsx b/app/[slug]/dashboard/guests/page.tsx index bc5f59a..61088ca 100644 --- a/app/[slug]/dashboard/guests/page.tsx +++ b/app/[slug]/dashboard/guests/page.tsx @@ -18,6 +18,7 @@ import { Toast } from 'primereact/toast'; import { Suspense, useEffect, useRef, useState } from 'react'; import InvitationsBoard from '@/app/ui/invitations/board'; import { Invitation, InvitationSerializer } from '@/app/lib/invitation'; +import { Entity } from '@/app/lib/definitions'; export default function Page() { @@ -29,9 +30,9 @@ export default function Page() { refreshGuests(); refreshInvitations(); }, []); - + const toast = useRef(null); - + function refreshGuests() { new AbstractApi().getAll(new GuestSerializer(), (objects: Guest[]) => { setGuests(objects); @@ -54,20 +55,20 @@ export default function Page() { fetch(`/api/${slug}/groups/affinities/reset`, { method: 'POST', headers: { - 'Accept': 'application/json', - 'X-CSRF-TOKEN': getCsrfToken(), + 'Accept': 'application/json', + 'X-CSRF-TOKEN': getCsrfToken(), } }) - .then(response => { - if (response.ok) { - showAffinitiesResetSuccess(); - } else { - console.error('Failed to reset affinities'); - } - }) - .catch(error => { - console.error('Error resetting affinities:', error); - }); + .then(response => { + if (response.ok) { + showAffinitiesResetSuccess(); + } else { + console.error('Failed to reset affinities'); + } + }) + .catch(error => { + console.error('Error resetting affinities:', error); + }); } function showAffinitiesResetSuccess() { @@ -88,7 +89,22 @@ export default function Page() { const [invitations, setInvitations] = useState>([]); - + function updateList(originalList: T[], element: T): T[] { + { + const index = originalList.findIndex(g => g.id === element?.id); + if (index !== -1) { + // Replace existing element + return [ + element!, + ...originalList.slice(0, index), + ...originalList.slice(index + 1) + ]; + } else { + // Add new element at the start + return [element!, ...originalList]; + } + } + } return (
@@ -99,22 +115,9 @@ export default function Page() { { - 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) ; + onCreate={(newGuest) => { + setGuests(guests => updateList(guests, newGuest)); + setGuestBeingEdited(undefined); }} guest={guestBeingEdited} visible={guestBeingEdited !== undefined} @@ -141,7 +144,10 @@ export default function Page() { { refreshGroups(); setGroupBeingEdited(undefined) }} + onCreate={(newGroup) => { + setGroups(groups => updateList(groups, newGroup)); + setGroupBeingEdited(undefined) + }} group={groupBeingEdited} visible={groupBeingEdited !== undefined} onHide={() => { setGroupBeingEdited(undefined) }} @@ -165,7 +171,7 @@ export default function Page() {
- + diff --git a/app/ui/components/group-form-dialog.tsx b/app/ui/components/group-form-dialog.tsx index 03d5c92..70cdd36 100644 --- a/app/ui/components/group-form-dialog.tsx +++ b/app/ui/components/group-form-dialog.tsx @@ -14,7 +14,7 @@ import { useState } from 'react'; export default function GroupFormDialog({ groups, onCreate, onHide, group, visible }: { groups: Group[], - onCreate?: () => void, + onCreate?: (newGroup: Group) => void, onHide: () => void, group?: Group, visible: boolean, @@ -46,15 +46,15 @@ export default function GroupFormDialog({ groups, onCreate, onHide, group, visib group.color = color; group.parentId = parentId; - api.update(serializer, group, () => { + api.update(serializer, group, (newGroup) => { resetForm(); - onCreate && onCreate(); + onCreate && onCreate(newGroup); }); } else { - api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), () => { + api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), (newGroup) => { resetForm(); - onCreate && onCreate(); + onCreate && onCreate(newGroup); }); } } diff --git a/tests/groups.spec.ts b/tests/groups.spec.ts index baf13d4..87ab922 100644 --- a/tests/groups.spec.ts +++ b/tests/groups.spec.ts @@ -1,7 +1,9 @@ import { test, expect, Page } from '@playwright/test' import mockGroupsAPI from './mocks/groups'; +import mockGuestsAPI from './mocks/guests'; test('should allow CRUD on groups', async ({ page }) => { + await mockGuestsAPI({ page }); await mockGroupsAPI({ page }); await page.goto('/default/dashboard/guests'); @@ -10,6 +12,8 @@ test('should allow CRUD on groups', async ({ page }) => { await expect(page.getByRole('button', { name: 'Add new' })).toBeVisible(); await expect(page.getByRole('button', { name: 'Reset affinities' })).toBeVisible(); + + // List all groups await expect(page.getByRole('row')).toHaveCount(3); // 1 header row + 2 data rows await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(0)).toHaveText('Name'); @@ -31,7 +35,6 @@ test('should allow CRUD on groups', async ({ page }) => { await expect(page.getByRole('row').nth(1).getByRole('cell').nth(7)).toHaveText('3'); await expect(page.getByRole('row').nth(1).locator('svg:visible')).toHaveCount(3); - await expect(page.getByRole('row').nth(2).getByRole('cell').nth(0)).toContainText('Pam\'s work'); await expect(page.getByRole('row').nth(2).getByRole('cell').nth(2)).toHaveText('0'); await expect(page.getByRole('row').nth(2).getByRole('cell').nth(3)).toHaveText('2'); @@ -41,4 +44,22 @@ test('should allow CRUD on groups', async ({ page }) => { await expect(page.getByRole('row').nth(2).getByRole('cell').nth(7)).toHaveText('2'); await expect(page.getByRole('row').nth(2).locator('svg:visible')).toHaveCount(3); + // Add a new group + await page.getByRole('button', { name: 'Add new' }).click(); + const dialog = page.getByRole('dialog'); + await expect(dialog).toBeVisible(); + + await dialog.getByLabel('Name').fill("Pam's friends"); + await dialog.getByRole('button', { name: 'Create' }).click(); + + await expect(dialog).not.toBeVisible(); + + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toContainText('Pam\'s friends'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(2)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(3)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(4)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(5)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(6)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).getByRole('cell').nth(7)).toHaveText('0'); + await expect(page.getByRole('row').nth(1).locator('svg:visible')).toHaveCount(3); }); \ No newline at end of file diff --git a/tests/mocks/groups.tsx b/tests/mocks/groups.tsx index bc17bc1..317761e 100644 --- a/tests/mocks/groups.tsx +++ b/tests/mocks/groups.tsx @@ -2,39 +2,59 @@ import { Page } from "@playwright/test"; export default async function mockGroupsAPI({ page }: { page: Page }): Promise { page.route('*/**/api/default/groups', async route => { - const json = [ - { - "id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a", - "name": "Pam's family", - "icon": "pi pi-users", - "parent_id": null, - "color": "#ff0000", - "attendance": { - "total": 3, - "considered": 2, - "invited": 1, - "confirmed": 0, - "declined": 0, - "tentative": 0 - } - }, - { - "id": "c8bda6ca-d8af-4bb8-b2bf-e6ec1c21b1e6", - "name": "Pam's work", - "icon": "pi pi-desktop", - "parent_id": null, - "color": "#00ff00", - "attendance": { - "total": 2, - "considered": 0, - "invited": 0, - "confirmed": 0, - "declined": 0, - "tentative": 2 - } - }, - ]; + if (route.request().method() === 'GET') { + const json = [ + { + "id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a", + "name": "Pam's family", + "icon": "pi pi-users", + "parent_id": null, + "color": "#ff0000", + "attendance": { + "total": 3, + "considered": 2, + "invited": 1, + "confirmed": 0, + "declined": 0, + "tentative": 0 + } + }, + { + "id": "c8bda6ca-d8af-4bb8-b2bf-e6ec1c21b1e6", + "name": "Pam's work", + "icon": "pi pi-desktop", + "parent_id": null, + "color": "#00ff00", + "attendance": { + "total": 2, + "considered": 0, + "invited": 0, + "confirmed": 0, + "declined": 0, + "tentative": 2 + } + }, + ]; - await route.fulfill({ json }) + await route.fulfill({ json }) + } else if (route.request().method() === 'POST') { + const json = { + "id": "4d55bc34-6f42-4e2e-82a1-71ae32da2466", + "name": "Pam's friends", + "icon": "pi pi-desktop", + "parent_id": null, + "color": "#0000ff", + "attendance": { + "total": 0, + "considered": 0, + "invited": 0, + "confirmed": 0, + "declined": 0, + "tentative": 0 + } + } + + await route.fulfill({ json }) + } }) } \ No newline at end of file