From 1ec636f3b033bb00c8e4b45fda01d22b59c6a69c Mon Sep 17 00:00:00 2001 From: Dominik Stahl Date: Tue, 1 Jul 2025 08:42:15 +0200 Subject: [PATCH] test: add event creation test --- cypress/e2e/auth-user.ts | 12 - cypress/e2e/event-create.cy.ts | 39 +- cypress/support/commands.ts | 19 + src/app/(main)/events/page.tsx | 5 +- src/components/custom-ui/event-list-entry.tsx | 5 +- src/components/custom-ui/labeled-input.tsx | 4 + src/components/forms/event-form.tsx | 346 +++++++++--------- src/components/misc/header.tsx | 5 +- src/components/time-picker.tsx | 5 +- 9 files changed, 251 insertions(+), 189 deletions(-) delete mode 100644 cypress/e2e/auth-user.ts diff --git a/cypress/e2e/auth-user.ts b/cypress/e2e/auth-user.ts deleted file mode 100644 index 5b02ab9..0000000 --- a/cypress/e2e/auth-user.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default function authUser() { - cy.visit('http://127.0.0.1:3000/login'); - cy.getBySel('login-header').should('exist'); - cy.getBySel('login-form').should('exist'); - cy.getBySel('email-input').should('exist'); - cy.getBySel('password-input').should('exist'); - cy.getBySel('login-button').should('exist'); - cy.getBySel('email-input').type('cypress@example.com'); - cy.getBySel('password-input').type('Password123!'); - cy.getBySel('login-button').click(); - cy.url().should('include', '/home'); -} diff --git a/cypress/e2e/event-create.cy.ts b/cypress/e2e/event-create.cy.ts index a74f770..2a8385a 100644 --- a/cypress/e2e/event-create.cy.ts +++ b/cypress/e2e/event-create.cy.ts @@ -1,9 +1,40 @@ -import authUser from './auth-user'; - describe('event creation', () => { it('loads', () => { - authUser(); + cy.login(); - // cy.visit('http://127.0.0.1:3000/events/new'); // TODO: Add event creation tests + cy.visit('http://127.0.0.1:3000/events/new'); + cy.getBySel('event-form').should('exist'); + cy.getBySel('event-form').within(() => { + cy.getBySel('event-name-input').should('exist'); + cy.getBySel('event-start-time-picker').should('exist'); + cy.getBySel('event-end-time-picker').should('exist'); + cy.getBySel('event-location-input').should('exist'); + cy.getBySel('event-description-input').should('exist'); + cy.getBySel('event-save-button').should('exist'); + }); + }); + + it('creates an event', () => { + cy.login(); + cy.visit( + 'http://127.0.0.1:3000/events/new?start=2025-07-01T01:00:00.000Z&end=2025-07-01T04:30:00.000Z', + ); + + cy.getBySel('event-form').should('exist'); + cy.getBySel('event-form').within(() => { + cy.getBySel('event-name-input').type('Cypress Test Event'); + cy.getBySel('event-location-input').type('Cypress Park'); + cy.getBySel('event-description-input').type( + 'This is a test event created by Cypress.', + ); + cy.getBySel('event-save-button').click(); + }); + cy.wait(1000); + cy.visit('http://127.0.0.1:3000/events'); + cy.getBySel('event-list-entry').should('exist'); + cy.getBySel('event-list-entry') + .contains('Cypress Test Event') + .should('exist'); + cy.getBySel('event-list-entry').contains('Cypress Park').should('exist'); }); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 59717f5..994b7ef 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-namespace */ /// // *********************************************** // This example commands.ts shows you how to @@ -44,6 +46,22 @@ Cypress.Commands.add('getBySelLike', (selector, ...args) => { return cy.get(`[data-cy*=${selector}]`, ...args); }); +Cypress.Commands.add('login', () => { + cy.session('auth', () => { + cy.visit('http://127.0.0.1:3000/login'); + cy.getBySel('login-header').should('exist'); + cy.getBySel('login-form').should('exist'); + cy.getBySel('email-input').should('exist'); + cy.getBySel('password-input').should('exist'); + cy.getBySel('login-button').should('exist'); + cy.getBySel('email-input').type('cypress@example.com'); + cy.getBySel('password-input').type('Password123!'); + cy.getBySel('login-button').click(); + cy.url().should('include', '/home'); + cy.getBySel('header').should('exist'); + }); +}); + declare global { namespace Cypress { interface Chainable { @@ -55,6 +73,7 @@ declare global { selector: string, ...args: any[] ): Chainable>; + login(): Chainable; } } } diff --git a/src/app/(main)/events/page.tsx b/src/app/(main)/events/page.tsx index bcd1e57..741f19b 100644 --- a/src/app/(main)/events/page.tsx +++ b/src/app/(main)/events/page.tsx @@ -17,7 +17,10 @@ export default function Events() { const events = eventsData?.data?.events || []; return ( -
+
{/* Heading */}

My Events diff --git a/src/components/custom-ui/event-list-entry.tsx b/src/components/custom-ui/event-list-entry.tsx index edc4a2f..b52d438 100644 --- a/src/components/custom-ui/event-list-entry.tsx +++ b/src/components/custom-ui/event-list-entry.tsx @@ -43,7 +43,10 @@ export default function EventListEntry({ return ( -
+
diff --git a/src/components/custom-ui/labeled-input.tsx b/src/components/custom-ui/labeled-input.tsx index 5505e14..6db0691 100644 --- a/src/components/custom-ui/labeled-input.tsx +++ b/src/components/custom-ui/labeled-input.tsx @@ -17,6 +17,7 @@ export default function LabeledInput({ variantSize = 'default', autocomplete, error, + 'data-cy': dataCy, ...rest }: { label: string; @@ -30,6 +31,7 @@ export default function LabeledInput({ variantSize?: 'default' | 'big' | 'textarea'; autocomplete?: string; error?: string; + 'data-cy'?: string; } & React.InputHTMLAttributes) { const [passwordVisible, setPasswordVisible] = React.useState(false); const [inputValue, setInputValue] = React.useState( @@ -58,6 +60,7 @@ export default function LabeledInput({ id={name} name={name} rows={3} + data-cy={dataCy} /> ) : ( @@ -76,6 +79,7 @@ export default function LabeledInput({ id={name} name={name} autoComplete={autocomplete} + data-cy={dataCy} {...rest} onChange={handleInputChange} /> diff --git a/src/components/forms/event-form.tsx b/src/components/forms/event-form.tsx index 11bf820..26824df 100644 --- a/src/components/forms/event-form.tsx +++ b/src/components/forms/event-form.tsx @@ -191,11 +191,6 @@ const EventForm: React.FC = (props) => { router.back(); } - // Calculate values for organiser, created, and updated - const organiserValue = isLoading - ? 'Loading...' - : data?.data.user?.name || 'Unknown User'; - // Use DB values for created_at/updated_at in edit mode const createdAtValue = props.type === 'edit' && event?.created_at @@ -210,188 +205,203 @@ const EventForm: React.FC = (props) => { const createdAtDisplay = new Date(createdAtValue).toLocaleDateString(); const updatedAtDisplay = new Date(updatedAtValue).toLocaleDateString(); + const [isClient, setIsClient] = React.useState(false); + React.useEffect(() => { + setIsClient(true); + }, []); + if (props.type === 'edit' && isLoading) return
Loading...
; if (props.type === 'edit' && fetchError) return
Error loading event.
; return ( - <> - -
-
-
-
- -
-
- setTitle(e.target.value)} - /> -
-
+ + +
+
+
+
-
-
- +
+ setTitle(e.target.value)} + data-cy='event-name-input' + /> +
+
+
+
+
+ +
+
+ +
+
+ setLocation(e.target.value)} + data-cy='event-location-input' + /> +
+
+
+ +
-
- +
+ +

+ {updatedAtDisplay} +

-
- setLocation(e.target.value)} - /> -
-
+
+
+
+
+
- - -
-
- +

- {updatedAtDisplay} + {!isClient || isLoading + ? 'Loading...' + : data?.data.user?.name || 'Unknown User'}

-
-
-
-
-
- - -
-
-
- setDescription(e.target.value)} - > -
-
- - { - setSelectedParticipants((current) => - current.find((u) => u.id === user.id) - ? current - : [...current, user], - ); - }} - removeUserAction={(user) => { - setSelectedParticipants((current) => - current.filter((u) => u.id !== user.id), - ); - }} - /> - - - -
- {selectedParticipants.map((user) => ( - - ))} -
+ setDescription(e.target.value)} + data-cy='event-description-input' + >
- -
-
- -
-
- +
+ + { + setSelectedParticipants((current) => + current.find((u) => u.id === user.id) + ? current + : [...current, user], + ); + }} + removeUserAction={(user) => { + setSelectedParticipants((current) => + current.filter((u) => u.id !== user.id), + ); + }} + /> + + + +
+ {selectedParticipants.map((user) => ( + + ))}
- {isSuccess &&

Event created!

} - {error &&

Error: {error.message}

}
- - - - Calendar - - Calendar for selected participants - - - - u.id)} - additionalEvents={[ - { - id: 'temp-event', - title: title || 'New Event', - start: startDate ? new Date(startDate) : new Date(), - end: endDate ? new Date(endDate) : new Date(), - type: 'event', - userId: 'create-event', - colorOverride: '#ff9800', - }, - ]} - height='600px' - /> - - -
- + +
+
+ +
+
+ +
+
+ {isSuccess &&

Event created!

} + {error &&

Error: {error.message}

} +
+ + + + Calendar + + Calendar for selected participants + + + + u.id)} + additionalEvents={[ + { + id: 'temp-event', + title: title || 'New Event', + start: startDate ? new Date(startDate) : new Date(), + end: endDate ? new Date(endDate) : new Date(), + type: 'event', + userId: 'create-event', + colorOverride: '#ff9800', + }, + ]} + height='600px' + /> + + +
); }; diff --git a/src/components/misc/header.tsx b/src/components/misc/header.tsx index 5d9d438..0421563 100644 --- a/src/components/misc/header.tsx +++ b/src/components/misc/header.tsx @@ -25,7 +25,10 @@ export default function Header({ }>) { return (
-
+
diff --git a/src/components/time-picker.tsx b/src/components/time-picker.tsx index 4402b88..1f36367 100644 --- a/src/components/time-picker.tsx +++ b/src/components/time-picker.tsx @@ -20,6 +20,7 @@ export default function TimePicker({ setDate, time, setTime, + ...props }: { dateLabel?: string; timeLabel?: string; @@ -27,11 +28,11 @@ export default function TimePicker({ setDate?: (date: Date | undefined) => void; time?: string; setTime?: (time: string) => void; -}) { +} & React.HTMLAttributes) { const [open, setOpen] = React.useState(false); return ( -
+