Compare commits

..

No commits in common. "67e4ad5d163b875a5c6420f448b1606e90a22c9b" and "02760e5068b7a8bcea3641f92f0f22cbe2d75157" have entirely different histories.

5 changed files with 112 additions and 161 deletions

View File

@ -18,7 +18,6 @@ import { Toast } from 'primereact/toast';
import { Suspense, useEffect, useRef, useState } from 'react'; import { Suspense, useEffect, useRef, useState } from 'react';
import InvitationsBoard from '@/app/ui/invitations/board'; import InvitationsBoard from '@/app/ui/invitations/board';
import { Invitation, InvitationSerializer } from '@/app/lib/invitation'; import { Invitation, InvitationSerializer } from '@/app/lib/invitation';
import { Entity } from '@/app/lib/definitions';
export default function Page() { export default function Page() {
@ -30,9 +29,9 @@ export default function Page() {
refreshGuests(); refreshGuests();
refreshInvitations(); refreshInvitations();
}, []); }, []);
const toast = useRef<Toast>(null); const toast = useRef<Toast>(null);
function refreshGuests() { function refreshGuests() {
new AbstractApi<Guest>().getAll(new GuestSerializer(), (objects: Guest[]) => { new AbstractApi<Guest>().getAll(new GuestSerializer(), (objects: Guest[]) => {
setGuests(objects); setGuests(objects);
@ -55,20 +54,20 @@ export default function Page() {
fetch(`/api/${slug}/groups/affinities/reset`, { fetch(`/api/${slug}/groups/affinities/reset`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'X-CSRF-TOKEN': getCsrfToken(), 'X-CSRF-TOKEN': getCsrfToken(),
} }
}) })
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
showAffinitiesResetSuccess(); showAffinitiesResetSuccess();
} else { } else {
console.error('Failed to reset affinities'); console.error('Failed to reset affinities');
} }
}) })
.catch(error => { .catch(error => {
console.error('Error resetting affinities:', error); console.error('Error resetting affinities:', error);
}); });
} }
function showAffinitiesResetSuccess() { function showAffinitiesResetSuccess() {
@ -89,22 +88,7 @@ export default function Page() {
const [invitations, setInvitations] = useState<Array<Invitation>>([]); const [invitations, setInvitations] = useState<Array<Invitation>>([]);
function updateList<T extends Entity>(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 ( return (
<div className="w-full"> <div className="w-full">
@ -115,9 +99,22 @@ export default function Page() {
<GuestFormDialog <GuestFormDialog
key={guestBeingEdited?.id} key={guestBeingEdited?.id}
groups={groups} groups={groups}
onCreate={(newGuest) => { onCreate={(newGuest) => {
setGuests(guests => updateList(guests, newGuest)); setGuests(prevGuests => {
setGuestBeingEdited(undefined); 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} guest={guestBeingEdited}
visible={guestBeingEdited !== undefined} visible={guestBeingEdited !== undefined}
@ -144,10 +141,7 @@ export default function Page() {
<GroupFormDialog <GroupFormDialog
key={groupBeingEdited?.id} key={groupBeingEdited?.id}
groups={groups} groups={groups}
onCreate={(newGroup) => { onCreate={() => { refreshGroups(); setGroupBeingEdited(undefined) }}
setGroups(groups => updateList(groups, newGroup));
setGroupBeingEdited(undefined)
}}
group={groupBeingEdited} group={groupBeingEdited}
visible={groupBeingEdited !== undefined} visible={groupBeingEdited !== undefined}
onHide={() => { setGroupBeingEdited(undefined) }} onHide={() => { setGroupBeingEdited(undefined) }}
@ -171,7 +165,7 @@ export default function Page() {
</div> </div>
</ TabPanel> </ TabPanel>
<TabPanel header="Invitations" leftIcon="pi pi-envelope mx-2"> <TabPanel header="Invitations" leftIcon="pi pi-envelope mx-2">
<InvitationsBoard guests={guests} invitations={invitations} /> <InvitationsBoard guests={guests} invitations={invitations}/>
</TabPanel> </TabPanel>
</ TabView> </ TabView>
</div> </div>

View File

@ -14,7 +14,7 @@ import { useState } from 'react';
export default function GroupFormDialog({ groups, onCreate, onHide, group, visible }: { export default function GroupFormDialog({ groups, onCreate, onHide, group, visible }: {
groups: Group[], groups: Group[],
onCreate?: (newGroup: Group) => void, onCreate?: () => void,
onHide: () => void, onHide: () => void,
group?: Group, group?: Group,
visible: boolean, visible: boolean,
@ -46,15 +46,15 @@ export default function GroupFormDialog({ groups, onCreate, onHide, group, visib
group.color = color; group.color = color;
group.parentId = parentId; group.parentId = parentId;
api.update(serializer, group, (newGroup) => { api.update(serializer, group, () => {
resetForm(); resetForm();
onCreate && onCreate(newGroup); onCreate && onCreate();
}); });
} else { } else {
api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), (newGroup) => { api.create(serializer, new Group(undefined, name, undefined, icon, undefined, parentId, color), () => {
resetForm(); resetForm();
onCreate && onCreate(newGroup); onCreate && onCreate();
}); });
} }
} }

View File

@ -1,65 +0,0 @@
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');
await page.getByRole('tab', { name: 'Groups' }).click();
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');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(1)).toHaveText('Color');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(2)).toHaveText('Confirmed');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(3)).toHaveText('Tentative');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(4)).toHaveText('Pending');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(5)).toHaveText('Declined');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(6)).toHaveText('Considered');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(7)).toHaveText('Total');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(8)).toHaveText('Actions');
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toContainText('Pam\'s family');
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('1');
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('2');
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');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(4)).toHaveText('0');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(5)).toHaveText('0');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(6)).toHaveText('0');
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);
});

View File

@ -73,3 +73,45 @@ test('should allow CRUD on guests', async ({ page }) => {
await page.getByRole('row').nth(1).locator('svg').nth(0).click(); // Click delete icon 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(); await expect(page.getByText('There are 2 elements in the list')).toBeVisible();
}); });
test('should display the list of groups', async ({ page }) => {
await mockGroupsAPI({ page });
await page.goto('/default/dashboard/guests');
await page.getByRole('tab', { name: 'Groups' }).click();
await expect(page.getByRole('button', { name: 'Add new' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Reset affinities' })).toBeVisible();
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');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(1)).toHaveText('Color');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(2)).toHaveText('Confirmed');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(3)).toHaveText('Tentative');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(4)).toHaveText('Pending');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(5)).toHaveText('Declined');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(6)).toHaveText('Considered');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(7)).toHaveText('Total');
await expect(page.getByRole('row').nth(0).getByRole('columnheader').nth(8)).toHaveText('Actions');
await expect(page.getByRole('row').nth(1).getByRole('cell').nth(0)).toContainText('Pam\'s family');
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('1');
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('2');
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');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(4)).toHaveText('0');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(5)).toHaveText('0');
await expect(page.getByRole('row').nth(2).getByRole('cell').nth(6)).toHaveText('0');
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);
});

View File

@ -2,59 +2,39 @@ import { Page } from "@playwright/test";
export default async function mockGroupsAPI({ page }: { page: Page }): Promise<void> { export default async function mockGroupsAPI({ page }: { page: Page }): Promise<void> {
page.route('*/**/api/default/groups', async route => { page.route('*/**/api/default/groups', async route => {
if (route.request().method() === 'GET') { const json = [
const json = [ {
{ "id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a",
"id": "ee44ffb9-1147-4842-a378-9eaeb0f0871a", "name": "Pam's family",
"name": "Pam's family", "icon": "pi pi-users",
"icon": "pi pi-users", "parent_id": null,
"parent_id": null, "color": "#ff0000",
"color": "#ff0000", "attendance": {
"attendance": { "total": 3,
"total": 3, "considered": 2,
"considered": 2, "invited": 1,
"invited": 1, "confirmed": 0,
"confirmed": 0, "declined": 0,
"declined": 0, "tentative": 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 })
} 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
}
} }
},
{
"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 })
}
}) })
} }