From 482517c91b5ae28be6e27cdea155dc77e18fdf5d Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Mon, 1 Jun 2026 14:01:35 +0300 Subject: [PATCH 1/2] fix(types): use ButtonVariant & ButtonPresetTheme, fix playwright types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix "yarn typecheck": ``` $ yarn typecheck yarn run v1.22.22 $ tsc --project ./tsconfig.json --noEmit src/domain/headless-cms/components/__tests__/CmsPage.test.tsx:113:11 - error TS2322: Type '"primary"' is not assignable to type 'ButtonVariant.Primary | ButtonVariant.Secondary | ButtonVariant.Success | ButtonVariant.Danger | ButtonVariant.Clear | undefined'. 113 loadMoreButtonVariant: 'primary', ~~~~~~~~~~~~~~~~~~~~~ src/domain/headless-cms/components/__tests__/CmsPage.test.tsx:114:11 - error TS2322: Type '"coat"' is not assignable to type 'ButtonPresetTheme | ButtonTheme | undefined'. 114 loadMoreButtonTheme: 'coat', ~~~~~~~~~~~~~~~~~~~ src/playwright/tests/search.spec.ts:55:41 - error TS2345: Argument of type '"Detaljerad sökning"' is not assignable to parameter of type 'SearchButton'. 55 await frontPageSv.clickSearchButton('Detaljerad sökning'); ~~~~~~~~~~~~~~~~~~~~ src/playwright/tests/search.spec.ts:71:41 - error TS2345: Argument of type '"Advanced search"' is not assignable to parameter of type 'SearchButton'. 71 await frontPageEn.clickSearchButton('Advanced search'); ~~~~~~~~~~~~~~~~~ ``` Refs: PT-453 --- .../headless-cms/components/__tests__/CmsPage.test.tsx | 5 +++-- src/playwright/pages/searchCapable.page.ts | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/domain/headless-cms/components/__tests__/CmsPage.test.tsx b/src/domain/headless-cms/components/__tests__/CmsPage.test.tsx index af4a29a1..b169b0c2 100644 --- a/src/domain/headless-cms/components/__tests__/CmsPage.test.tsx +++ b/src/domain/headless-cms/components/__tests__/CmsPage.test.tsx @@ -3,6 +3,7 @@ import { ConfigProvider as RHHCConfigProvider, defaultConfig as rhhcDefaultConfig, } from '@city-of-helsinki/react-helsinki-headless-cms'; +import { ButtonPresetTheme, ButtonVariant } from 'hds-react'; import * as React from 'react'; import { @@ -110,8 +111,8 @@ test('renders with sidebar layout when sidebar has content', async () => { openInExternalDomainAriaLabel: 'Avautuu uudella sivustolla.', }, customCopy: { - loadMoreButtonVariant: 'primary', - loadMoreButtonTheme: 'coat', + loadMoreButtonVariant: ButtonVariant.Primary, + loadMoreButtonTheme: ButtonPresetTheme.Coat, }, internalHrefOrigins, utils: { diff --git a/src/playwright/pages/searchCapable.page.ts b/src/playwright/pages/searchCapable.page.ts index 3c1603a9..b4baf2f3 100644 --- a/src/playwright/pages/searchCapable.page.ts +++ b/src/playwright/pages/searchCapable.page.ts @@ -7,7 +7,10 @@ type ZeroToTwoYears = '0-2 vuotiaat' | '0-2 år' | '0-2 years'; type TargetGroupOption = ZeroToTwoYears; type SearchOption = TargetGroupOption; type SearchEvents = 'Hae tapahtumia' | 'Sök evenemang' | 'Search events'; -type AdvancedSearch = 'Tarkennettu haku' | 'Redigera sökning' | 'Edit search'; +type AdvancedSearch = + | 'Tarkennettu haku' + | 'Detaljerad sökning' + | 'Advanced search'; type SearchButton = SearchEvents | AdvancedSearch; type SearchTextBox = SearchEvents | TargetGroupButton; type TargetGroupField = TargetGroupButton; From f37539f872f39bd3f28ad57464f36715069d2194 Mon Sep 17 00:00:00 2001 From: Kari Salminen Date: Wed, 27 May 2026 12:53:28 +0300 Subject: [PATCH 2/2] feat: add "Is part of cultural route" selection to enrolment form also: - with the request of the product owner don't restore any previous answer to "Is part of cultural route" question from local storage persisted Formik form's data, but instead always reset the question to its initial value i.e. no data given at all - update generated GraphQL types using backend running City-of-Helsinki/palvelutarjotin#509 i.e. the backend support for the `is_part_of_cultural_route` field in enrolments and queue enrolments - add tests - update playwright tests Refs: PT-453 --- public/locales/en/enrolment.json | 7 + public/locales/fi/enrolment.json | 7 + public/locales/sv/enrolment.json | 7 + .../formikPersist/FormikPersist.tsx | 13 +- .../__tests__/FormikPersist.test.tsx | 53 ++++++++ src/domain/enrolment/__tests__/utils.test.ts | 27 ++++ .../enrolment/enrolmentForm/EnrolmentForm.tsx | 6 + .../IsPartOfCulturalRouteField.tsx | 62 +++++++++ .../enrolmentForm/ValidationSchema.tsx | 7 + .../__tests__/EnrolmentForm.test.tsx | 31 ++++- .../IsPartOfCulturalRouteField.test.tsx | 124 ++++++++++++++++++ .../__snapshots__/EnrolmentForm.test.tsx.snap | 4 + .../enrolment/enrolmentForm/constants.ts | 8 ++ src/domain/enrolment/query.ts | 1 + src/domain/enrolment/utils.ts | 24 +++- .../enrolmentFormIntegration.test.tsx.snap | 6 + .../enrolmentFormIntegration.test.tsx | 11 ++ src/domain/eventQueueEnrolment/query.ts | 1 + src/generated/graphql.tsx | 21 ++- src/playwright/mocks.ts | 1 + src/playwright/pages/event.page.ts | 32 +++++ src/playwright/tests/eventEnrolling.spec.ts | 1 + src/utils/mockDataUtils.ts | 1 + 23 files changed, 447 insertions(+), 8 deletions(-) create mode 100644 src/domain/enrolment/__tests__/utils.test.ts create mode 100644 src/domain/enrolment/enrolmentForm/IsPartOfCulturalRouteField.tsx create mode 100644 src/domain/enrolment/enrolmentForm/__tests__/IsPartOfCulturalRouteField.test.tsx diff --git a/public/locales/en/enrolment.json b/public/locales/en/enrolment.json index 65dd89b9..e6b929ce 100644 --- a/public/locales/en/enrolment.json +++ b/public/locales/en/enrolment.json @@ -17,6 +17,13 @@ "labelIsSharingDataAccepted": "I agree to share my information with the event organizer", "labelLanguage": "Language", "labelNotificationLanguage": "Language used in notifications", + "culturalRoute": { + "labelIsPartOf": "The visit is part of the cultural route", + "labelNoOrUnknown": "No / I don't know", + "labelYes": "Yes", + "helperText": "Cultural routes are implementation proposals for the City of Helsinki's cultural education plan from early childhood education to upper secondary education. Read more", + "readMoreUrl": "/en/cms-page/cultural-education/cultural-routes" + }, "person": { "labelEmailAddress": "Email address", "labelName": "Name", diff --git a/public/locales/fi/enrolment.json b/public/locales/fi/enrolment.json index 0f1506b3..db66230c 100644 --- a/public/locales/fi/enrolment.json +++ b/public/locales/fi/enrolment.json @@ -17,6 +17,13 @@ "labelIsSharingDataAccepted": "Hyväksyn tietojeni jakamisen tapahtuman järjestäjän kanssa", "labelLanguage": "Kieli", "labelNotificationLanguage": "Ilmoitusten kieli", + "culturalRoute": { + "labelIsPartOf": "Käynti sisältyy kulttuuripolkuun", + "labelNoOrUnknown": "Ei / en tiedä", + "labelYes": "Kyllä", + "helperText": "Kulttuuripolut ovat Helsingin kaupungin kulttuurikasvatussuunnitelman toteutusehdotuksia varhaiskasvatuksesta toiselle asteelle. Lue lisää kulttuuripoluista", + "readMoreUrl": "/fi/cms-page/kulttuurikasvatus/kulttuuripolulla-kulttuuria-kaikille" + }, "person": { "labelEmailAddress": "Sähköpostiosoite", "labelName": "Nimi", diff --git a/public/locales/sv/enrolment.json b/public/locales/sv/enrolment.json index 19a9da88..1b6437d0 100644 --- a/public/locales/sv/enrolment.json +++ b/public/locales/sv/enrolment.json @@ -17,6 +17,13 @@ "labelIsSharingDataAccepted": "Jag samtycker till att dela mina uppgifter med arrangören av evenemanget.", "labelLanguage": "Språk", "labelNotificationLanguage": "Språket för meddelanden", + "culturalRoute": { + "labelIsPartOf": "Besöket ingår i kulturstigen", + "labelNoOrUnknown": "Nej / jag vet inte", + "labelYes": "Ja", + "helperText": "Kulturstigarna är förslag på genomförande av Helsingfors stads kulturfostransplan från småbarnspedagogik till gymnasieutbildning. Läs mer om kulturstigarna", + "readMoreUrl": "/sv/cms-page/kulturpedagogik/kulturstigen-kultur-for-alla" + }, "person": { "labelEmailAddress": "E-post", "labelName": "Namn", diff --git a/src/common/components/formikPersist/FormikPersist.tsx b/src/common/components/formikPersist/FormikPersist.tsx index 956a26bd..41ab1dc5 100644 --- a/src/common/components/formikPersist/FormikPersist.tsx +++ b/src/common/components/formikPersist/FormikPersist.tsx @@ -1,5 +1,8 @@ import { FormikProps, useFormikContext } from 'formik'; +import cloneDeep from 'lodash/cloneDeep'; +import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; +import set from 'lodash/set'; import * as React from 'react'; import useIsMounted from '../../../hooks/useIsMounted'; @@ -26,6 +29,8 @@ export interface PersistProps { debounceTime?: number; isSessionStorage?: boolean; initialValues: Record; + // Dot-notation paths of fields that are restored using initial values: + alwaysFreshFields?: string[]; } const FormikPersist = ({ @@ -33,6 +38,7 @@ const FormikPersist = ({ isSessionStorage = false, name, initialValues, + alwaysFreshFields = [], }: PersistProps): null => { const isMounted = useIsMounted(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -78,7 +84,12 @@ const FormikPersist = ({ storedFormikStateObject && objectStructureMatches(initialValues, storedFormikStateObject.values) ) { - formik.setValues(storedFormikStateObject.values, true); + const valuesToRestore = cloneDeep(storedFormikStateObject.values); + // Restore fresh fields with their initial values instead of stored ones: + for (const path of alwaysFreshFields) { + set(valuesToRestore, path, get(initialValues, path)); + } + formik.setValues(valuesToRestore, true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/common/components/formikPersist/__tests__/FormikPersist.test.tsx b/src/common/components/formikPersist/__tests__/FormikPersist.test.tsx index 38cbdc2c..83bf4633 100644 --- a/src/common/components/formikPersist/__tests__/FormikPersist.test.tsx +++ b/src/common/components/formikPersist/__tests__/FormikPersist.test.tsx @@ -121,3 +121,56 @@ test('attempts to rehydrate on mount if session storage is true on props', async ); }); }); + +test('alwaysFreshFields resets listed fields to their initial values', async () => { + let injected: FormikProps<{ regularField: string; freshField: string }>; + + (localStorage.getItem as jest.Mock).mockReturnValueOnce( + JSON.stringify({ + ...defaultState, + values: { + regularField: 'Persisted value of regular field', + freshField: 'Persisted value of fresh field', + }, + }) + ); + + render( + + {(props: FormikProps<{ regularField: string; freshField: string }>) => { + injected = props; + + return ( +
+ +
+ ); + }} +
+ ); + + expect(localStorage.getItem).toHaveBeenCalled(); + + // regularField is not in alwaysFreshFields, so it should be restored from storage + expect(injected!.values.regularField).toEqual( + 'Persisted value of regular field' + ); + // freshField is in alwaysFreshFields, so it must be reset to its initial value: + expect(injected!.values.freshField).toEqual( + 'Initial value of fresh field in Persist' + ); +}); diff --git a/src/domain/enrolment/__tests__/utils.test.ts b/src/domain/enrolment/__tests__/utils.test.ts new file mode 100644 index 00000000..2769523c --- /dev/null +++ b/src/domain/enrolment/__tests__/utils.test.ts @@ -0,0 +1,27 @@ +import { IsPartOfCulturalRoute } from '../enrolmentForm/constants'; +import { validIsPartOfCulturalRouteToBoolean } from '../utils'; + +describe('validIsPartOfCulturalRouteToBoolean', () => { + test.each([ + IsPartOfCulturalRoute.YES, // Test that the enum version works + 'YES', // Test that the string version also works + ])('validIsPartOfCulturalRouteToBoolean(%p) returns true', (input) => { + expect(validIsPartOfCulturalRouteToBoolean(input)).toBe(true); + }); + + test.each([ + IsPartOfCulturalRoute.NO_OR_UNKNOWN, // Test that the enum version works + 'NO_OR_UNKNOWN', // Test that the string version also works + ])('validIsPartOfCulturalRouteToBoolean(%p) returns false', (input) => { + expect(validIsPartOfCulturalRouteToBoolean(input)).toBe(false); + }); + + test.each(['', ' ', 'yes', 'NO', 'unanswered', null, undefined, true, 0, 1])( + 'validIsPartOfCulturalRouteToBoolean(%p) throws on invalid value', + (input) => { + expect(() => validIsPartOfCulturalRouteToBoolean(input)).toThrow( + `Invalid isPartOfCulturalRoute value ${input}` + ); + } + ); +}); diff --git a/src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx b/src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx index a938c59f..bf9611be 100644 --- a/src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx +++ b/src/domain/enrolment/enrolmentForm/EnrolmentForm.tsx @@ -17,6 +17,7 @@ import { nameToLabelPath, } from './constants'; import styles from './enrolmentForm.module.scss'; +import IsPartOfCulturalRouteField from './IsPartOfCulturalRouteField'; import getValidationSchema from './ValidationSchema'; import ErrorMessage from '../../../common/components/form/ErrorMessage'; import CheckboxField from '../../../common/components/form/fields/CheckboxField'; @@ -103,6 +104,7 @@ const EnrolmentForm: React.FC = ({

{t('enrolment:enrolmentForm.studyGroup.titleNotifier')}

= ({ + + + +

{t('enrolment:enrolmentForm.studyGroup.titleGroup')}

diff --git a/src/domain/enrolment/enrolmentForm/IsPartOfCulturalRouteField.tsx b/src/domain/enrolment/enrolmentForm/IsPartOfCulturalRouteField.tsx new file mode 100644 index 00000000..c6741693 --- /dev/null +++ b/src/domain/enrolment/enrolmentForm/IsPartOfCulturalRouteField.tsx @@ -0,0 +1,62 @@ +import { Link } from '@city-of-helsinki/react-helsinki-headless-cms'; +import { useField } from 'formik'; +import { RadioButton, SelectionGroup } from 'hds-react'; +import { Trans, useTranslation } from 'next-i18next'; +import React from 'react'; + +import { IsPartOfCulturalRoute, nameToLabelPath } from './constants'; +import type { I18nNamespace } from '../../../types'; + +type Props = { + formikFieldName: string; +}; + +/** + * React component for "Is part of cultural route?" field in the enrolment form, + * with choices of "No / I don't know" and "Yes". + */ +const IsPartOfCulturalRouteField: React.FC = ({ formikFieldName }) => { + const { t } = useTranslation(); + const [field, meta] = useField(formikFieldName); + const errorText = meta.touched && meta.error ? t(meta.error) : undefined; + const readMoreUrl = t('enrolment:enrolmentForm.culturalRoute.readMoreUrl'); + + return ( + }} + /> + } + > + + + + ); +}; + +export default IsPartOfCulturalRouteField; diff --git a/src/domain/enrolment/enrolmentForm/ValidationSchema.tsx b/src/domain/enrolment/enrolmentForm/ValidationSchema.tsx index 7fbcfd69..c63443ac 100644 --- a/src/domain/enrolment/enrolmentForm/ValidationSchema.tsx +++ b/src/domain/enrolment/enrolmentForm/ValidationSchema.tsx @@ -1,5 +1,6 @@ import * as Yup from 'yup'; +import { IsPartOfCulturalRoute } from './constants'; import { VALIDATION_MESSAGE_KEYS } from '../../app/forms/constants'; type EnrolmentFormValidationSchemaProps = { @@ -14,6 +15,12 @@ export default function getValidationSchema({ isQueueEnrolment, }: EnrolmentFormValidationSchemaProps) { return Yup.object().shape({ + isPartOfCulturalRoute: Yup.string() + .oneOf( + [IsPartOfCulturalRoute.NO_OR_UNKNOWN, IsPartOfCulturalRoute.YES], + VALIDATION_MESSAGE_KEYS.STRING_REQUIRED + ) + .required(VALIDATION_MESSAGE_KEYS.STRING_REQUIRED), hasEmailNotification: Yup.bool().oneOf( [true], 'enrolment:enrolmentForm.validation.hasEmailNotification' diff --git a/src/domain/enrolment/enrolmentForm/__tests__/EnrolmentForm.test.tsx b/src/domain/enrolment/enrolmentForm/__tests__/EnrolmentForm.test.tsx index 5b0d460d..225eac71 100644 --- a/src/domain/enrolment/enrolmentForm/__tests__/EnrolmentForm.test.tsx +++ b/src/domain/enrolment/enrolmentForm/__tests__/EnrolmentForm.test.tsx @@ -23,7 +23,10 @@ import { within, sleep, } from '../../../../utils/testUtils'; -import { defaultEnrolmentInitialValues } from '../constants'; +import { + defaultEnrolmentInitialValues, + IsPartOfCulturalRoute, +} from '../constants'; import EnrolmentForm, { Props } from '../EnrolmentForm'; configure({ defaultHidden: true }); @@ -113,6 +116,14 @@ test('renders form and user can fill it and submit and form is saved to local st // // close dropdown + // Answer "Is part of cultural route?" question + const culturalRouteGroup = await screen.findByRole('group', { + name: /käynti sisältyy kulttuuripolkuun/i, + }); + await userEvent.click( + within(culturalRouteGroup).getByRole('radio', { name: /kyllä/i }) + ); + await userEvent.click(screen.getByRole('combobox', { name: /luokka-aste/i })); await userEvent.type(screen.getByLabelText(/lapsia/i), '10'); @@ -411,6 +422,7 @@ if (isFeatureEnabled('FORMIK_PERSIST')) { isSameResponsiblePerson: true, isSharingDataAccepted: false, isMandatoryAdditionalInformationRequired: false, + isPartOfCulturalRoute: IsPartOfCulturalRoute.NO_OR_UNKNOWN, language: '', studyGroup: { person: { @@ -504,6 +516,23 @@ if (isFeatureEnabled('FORMIK_PERSIST')) { expect( screen.getByRole('combobox', { name: /luokka-aste/i }) ).toHaveTextContent('1. luokka'); + + // Test that "Is part of cultural route?" question is not answered, + // because this field should not be restored from local storage: + const culturalRouteGroup = await screen.findByRole('group', { + name: /käynti sisältyy kulttuuripolkuun/i, + }); + const isNotPartOfCulturalRoute = within(culturalRouteGroup).getByRole( + 'radio', + { name: /ei \/ en tiedä/i } + ); + const isPartOfCulturalRoute = within(culturalRouteGroup).getByRole( + 'radio', + { name: /kyllä/i } + ); + expect(isNotPartOfCulturalRoute).not.toBeChecked(); + expect(isPartOfCulturalRoute).not.toBeChecked(); + const childrenCountInput = screen.getByRole('spinbutton', { name: /lapsia \*/i, }); diff --git a/src/domain/enrolment/enrolmentForm/__tests__/IsPartOfCulturalRouteField.test.tsx b/src/domain/enrolment/enrolmentForm/__tests__/IsPartOfCulturalRouteField.test.tsx new file mode 100644 index 00000000..65063cc8 --- /dev/null +++ b/src/domain/enrolment/enrolmentForm/__tests__/IsPartOfCulturalRouteField.test.tsx @@ -0,0 +1,124 @@ +import { Formik } from 'formik'; +import * as React from 'react'; + +import { + render, + screen, + userEvent, + within, + configure, + waitFor, +} from '../../../../utils/testUtils'; +import { IsPartOfCulturalRoute } from '../constants'; +import IsPartOfCulturalRouteField from '../IsPartOfCulturalRouteField'; +import getValidationSchema from '../ValidationSchema'; + +configure({ defaultHidden: true }); + +const renderComponent = (initialValue: IsPartOfCulturalRoute | '' = '') => + render( + + {({ submitForm }) => ( + <> + + + + )} + + ); + +const getGroup = () => + screen.findByRole('group', { name: /käynti sisältyy kulttuuripolkuun/i }); + +const getNoOrUnknownRadio = (group: HTMLElement) => + within(group).getByRole('radio', { name: /ei \/ en tiedä/i }); + +const getYesRadio = (group: HTMLElement) => + within(group).getByRole('radio', { name: /kyllä/i }); + +describe('IsPartOfCulturalRouteField', () => { + it('renders the selection group with both radio options', async () => { + renderComponent(); + const group = await getGroup(); + expect(getNoOrUnknownRadio(group)).toBeInTheDocument(); + expect(getYesRadio(group)).toBeInTheDocument(); + }); + + it('has no radio checked when initial value is empty', async () => { + renderComponent(''); + const group = await getGroup(); + expect(getNoOrUnknownRadio(group)).not.toBeChecked(); + expect(getYesRadio(group)).not.toBeChecked(); + }); + + it('checks "No / I don\'t know" when initial value is NO_OR_UNKNOWN', async () => { + renderComponent(IsPartOfCulturalRoute.NO_OR_UNKNOWN); + const group = await getGroup(); + expect(getNoOrUnknownRadio(group)).toBeChecked(); + expect(getYesRadio(group)).not.toBeChecked(); + }); + + it('checks "Yes" when initial value is YES', async () => { + renderComponent(IsPartOfCulturalRoute.YES); + const group = await getGroup(); + expect(getNoOrUnknownRadio(group)).not.toBeChecked(); + expect(getYesRadio(group)).toBeChecked(); + }); + + it('checks only "Yes" radio after clicking it', async () => { + renderComponent(''); + const group = await getGroup(); + await userEvent.click(getYesRadio(group)); + expect(getYesRadio(group)).toBeChecked(); + expect(getNoOrUnknownRadio(group)).not.toBeChecked(); + }); + + it('checks only "No / I don\'t know" radio after clicking it', async () => { + renderComponent(''); + const group = await getGroup(); + await userEvent.click(getNoOrUnknownRadio(group)); + expect(getNoOrUnknownRadio(group)).toBeChecked(); + expect(getYesRadio(group)).not.toBeChecked(); + }); + + it('renders a "read more" link with correct href', async () => { + renderComponent(); + const group = await getGroup(); + const link = within(group).getByRole('link', { + name: /lue lisää kulttuuripoluista/i, + }); + expect(link).toHaveAttribute( + 'href', + '/fi/cms-page/kulttuurikasvatus/kulttuuripolulla-kulttuuria-kaikille' + ); + }); + + it.each([IsPartOfCulturalRoute.NO_OR_UNKNOWN, IsPartOfCulturalRoute.YES])( + 'shows no error text when submitted with valid value %p', + async (validValue) => { + renderComponent(validValue); + + await userEvent.click(screen.getByRole('button', { name: /submit/i })); + + await waitFor(() => + expect( + screen.queryByText(/tämä kenttä on pakollinen/i) + ).not.toBeInTheDocument() + ); + } + ); + + it('shows error text when the field is submitted without a selection', async () => { + renderComponent(''); + + await userEvent.click(screen.getByRole('button', { name: /submit/i })); + + await screen.findByText(/tämä kenttä on pakollinen/i); + }); +}); diff --git a/src/domain/enrolment/enrolmentForm/__tests__/__snapshots__/EnrolmentForm.test.tsx.snap b/src/domain/enrolment/enrolmentForm/__tests__/__snapshots__/EnrolmentForm.test.tsx.snap index b5e128fd..9ed4b96e 100644 --- a/src/domain/enrolment/enrolmentForm/__tests__/__snapshots__/EnrolmentForm.test.tsx.snap +++ b/src/domain/enrolment/enrolmentForm/__tests__/__snapshots__/EnrolmentForm.test.tsx.snap @@ -10,6 +10,7 @@ exports[`renders form and user can fill it and submit and form is saved to local "hasEmailNotification": true, "hasSmsNotification": false, "isMandatoryAdditionalInformationRequired": false, + "isPartOfCulturalRoute": "", "isSameResponsiblePerson": true, "isSharingDataAccepted": false, "language": "", @@ -42,6 +43,7 @@ exports[`renders form and user can fill it and submit and form is saved to local "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": true, + "isPartOfCulturalRoute": true, "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": true, @@ -76,6 +78,7 @@ exports[`renders form and user can fill it and submit and form is saved to local "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": false, + "isPartOfCulturalRoute": "YES", "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": "FI", @@ -112,6 +115,7 @@ exports[`renders form and user can fill it and submit and form is saved to local "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": false, + "isPartOfCulturalRoute": "YES", "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": "FI", diff --git a/src/domain/enrolment/enrolmentForm/constants.ts b/src/domain/enrolment/enrolmentForm/constants.ts index 968b06dd..f0c32030 100644 --- a/src/domain/enrolment/enrolmentForm/constants.ts +++ b/src/domain/enrolment/enrolmentForm/constants.ts @@ -1,3 +1,8 @@ +export enum IsPartOfCulturalRoute { + NO_OR_UNKNOWN = 'NO_OR_UNKNOWN', + YES = 'YES', +} + export type EnrolmentFormFields = { hasEmailNotification: boolean; hasSmsNotification: boolean; @@ -5,6 +10,7 @@ export type EnrolmentFormFields = { isSharingDataAccepted: boolean; isMandatoryAdditionalInformationRequired: boolean; language: string; + isPartOfCulturalRoute: IsPartOfCulturalRoute | ''; person: { name: string; phoneNumber: string; @@ -34,6 +40,7 @@ export const defaultEnrolmentInitialValues: EnrolmentFormFields = { isSharingDataAccepted: false, isMandatoryAdditionalInformationRequired: false, language: '', + isPartOfCulturalRoute: '', person: { name: '', phoneNumber: '', @@ -85,4 +92,5 @@ export const nameToLabelPath: Record = { 'enrolment:enrolmentForm.studyGroup.person.labelPhoneNumber', 'person.emailAddress': 'enrolment:enrolmentForm.studyGroup.person.labelEmailAddress', + isPartOfCulturalRoute: 'enrolment:enrolmentForm.culturalRoute.labelIsPartOf', }; diff --git a/src/domain/enrolment/query.ts b/src/domain/enrolment/query.ts index 279e8f11..21bf737f 100644 --- a/src/domain/enrolment/query.ts +++ b/src/domain/enrolment/query.ts @@ -3,6 +3,7 @@ import { gql } from 'graphql-tag'; export const QUERY_ENROLMENT = gql` fragment enrolmentFields on EnrolmentNode { id + isPartOfCulturalRoute notificationType enrolmentTime status diff --git a/src/domain/enrolment/utils.ts b/src/domain/enrolment/utils.ts index 716ec139..b526f3a7 100644 --- a/src/domain/enrolment/utils.ts +++ b/src/domain/enrolment/utils.ts @@ -1,4 +1,7 @@ -import { EnrolmentFormFields } from './enrolmentForm/constants'; +import { + EnrolmentFormFields, + IsPartOfCulturalRoute, +} from './enrolmentForm/constants'; import { EnrolEventQueueMutationInput, EnrolOccurrenceMutationInput, @@ -6,6 +9,22 @@ import { NotificationType, } from '../../generated/graphql'; +/** + * Converts IsPartOfCulturalRoute.YES to true, + * IsPartOfCulturalRoute.NO_OR_UNKNOWN to false, + * and raises an error on everything else. + */ +export const validIsPartOfCulturalRouteToBoolean = ( + isPart: unknown +): boolean => { + if (isPart === IsPartOfCulturalRoute.YES) { + return true; + } else if (isPart === IsPartOfCulturalRoute.NO_OR_UNKNOWN) { + return false; + } + throw new Error(`Invalid isPartOfCulturalRoute value ${isPart}`); +}; + const getNotificationType = (values: EnrolmentFormFields): NotificationType => { return values.hasEmailNotification && values.studyGroup.person.phoneNumber && @@ -18,6 +37,9 @@ export const getEnrolmentPayloadBase = ( values: EnrolmentFormFields ): Omit => { return { + isPartOfCulturalRoute: validIsPartOfCulturalRouteToBoolean( + values.isPartOfCulturalRoute + ), notificationType: getNotificationType(values), person: values.isSameResponsiblePerson ? undefined diff --git a/src/domain/event/__tests__/__snapshots__/enrolmentFormIntegration.test.tsx.snap b/src/domain/event/__tests__/__snapshots__/enrolmentFormIntegration.test.tsx.snap index 72d64452..6ab00061 100644 --- a/src/domain/event/__tests__/__snapshots__/enrolmentFormIntegration.test.tsx.snap +++ b/src/domain/event/__tests__/__snapshots__/enrolmentFormIntegration.test.tsx.snap @@ -10,6 +10,7 @@ exports[`user can select multiple occurrences and enrol to them with enrolment f "hasEmailNotification": true, "hasSmsNotification": false, "isMandatoryAdditionalInformationRequired": false, + "isPartOfCulturalRoute": "", "isSameResponsiblePerson": true, "isSharingDataAccepted": false, "language": "FI", @@ -42,6 +43,7 @@ exports[`user can select multiple occurrences and enrol to them with enrolment f "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": true, + "isPartOfCulturalRoute": true, "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": true, @@ -76,6 +78,7 @@ exports[`user can select multiple occurrences and enrol to them with enrolment f "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": false, + "isPartOfCulturalRoute": "NO_OR_UNKNOWN", "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": "FI", @@ -116,6 +119,7 @@ exports[`user can select single occurrences and enrol to it with enrolment form "hasEmailNotification": true, "hasSmsNotification": false, "isMandatoryAdditionalInformationRequired": true, + "isPartOfCulturalRoute": "", "isSameResponsiblePerson": true, "isSharingDataAccepted": false, "language": "FI", @@ -148,6 +152,7 @@ exports[`user can select single occurrences and enrol to it with enrolment form "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": true, + "isPartOfCulturalRoute": true, "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": true, @@ -182,6 +187,7 @@ exports[`user can select single occurrences and enrol to it with enrolment form "hasEmailNotification": true, "hasSmsNotification": true, "isMandatoryAdditionalInformationRequired": true, + "isPartOfCulturalRoute": "NO_OR_UNKNOWN", "isSameResponsiblePerson": true, "isSharingDataAccepted": true, "language": "FI", diff --git a/src/domain/event/__tests__/enrolmentFormIntegration.test.tsx b/src/domain/event/__tests__/enrolmentFormIntegration.test.tsx index de67f598..82109958 100644 --- a/src/domain/event/__tests__/enrolmentFormIntegration.test.tsx +++ b/src/domain/event/__tests__/enrolmentFormIntegration.test.tsx @@ -221,6 +221,7 @@ test('user can select single occurrences and enrol to it with enrolment form', a variables: { input: { captchaKey: 'captcha-token', + isPartOfCulturalRoute: false, notificationType: 'EMAIL_SMS', occurrenceIds: ['123321'], person: undefined, @@ -308,6 +309,7 @@ test('user can select multiple occurrences and enrol to them with enrolment form variables: { input: { captchaKey: 'captcha-token', + isPartOfCulturalRoute: false, notificationType: 'EMAIL_SMS', occurrenceIds: ['123321', '321123'], person: undefined, @@ -416,6 +418,15 @@ async function fillForm({ ); // close dropdown await user.click(gradeButton); + + // Answer "Is part of cultural route?" question + const culturalRouteGroup = await screen.findByRole('group', { + name: /käynti sisältyy kulttuuripolkuun/i, + }); + await userEvent.click( + within(culturalRouteGroup).getByRole('radio', { name: /ei \/ en tiedä/i }) + ); + await user.type(await enrolmentForm.findByLabelText(/lapsia/i), '10'); await user.type(await enrolmentForm.findByLabelText(/aikuisia/i), '2'); diff --git a/src/domain/eventQueueEnrolment/query.ts b/src/domain/eventQueueEnrolment/query.ts index 0fd9da23..4369551f 100644 --- a/src/domain/eventQueueEnrolment/query.ts +++ b/src/domain/eventQueueEnrolment/query.ts @@ -3,6 +3,7 @@ import { gql } from 'graphql-tag'; export const QUERY_EVENT_QUEUE_ENROLMENT = gql` fragment eventQueueEnrolmentFields on EventQueueEnrolmentNode { id + isPartOfCulturalRoute notificationType enrolmentTime status diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index 235e8848..1743a29a 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -261,6 +261,8 @@ export type EnrolEventQueueMutationInput = { /** The user response token provided by the reCAPTCHA client-side integration */ captchaKey?: InputMaybe; clientMutationId?: InputMaybe; + /** Is the enrolment part of a cultural route? False = No / I don't know, True = Yes. */ + isPartOfCulturalRoute: Scalars['Boolean']['input']; notificationType?: InputMaybe; /** The event that a group would like to queue to */ pEventId: Scalars['ID']['input']; @@ -280,6 +282,8 @@ export type EnrolOccurrenceMutationInput = { /** The user response token provided by the reCAPTCHA client-side integration */ captchaKey?: InputMaybe; clientMutationId?: InputMaybe; + /** Is the enrolment part of a cultural route? False = No / I don't know, True = Yes. */ + isPartOfCulturalRoute: Scalars['Boolean']['input']; notificationType?: InputMaybe; /** Occurrence ids of event */ occurrenceIds: Array>; @@ -302,6 +306,8 @@ export type EnrolmentNode = Node & { enrolmentTime: Scalars['DateTime']['output']; /** The ID of the object */ id: Scalars['ID']['output']; + /** Is enrolment part of a cultural route? True means yes, False means no, I don't know or unanswered. */ + isPartOfCulturalRoute: Scalars['Boolean']['output']; notificationType?: Maybe; occurrence: OccurrenceNode; person?: Maybe; @@ -411,6 +417,8 @@ export type EventQueueEnrolmentNode = Node & { enrolmentTime: Scalars['DateTime']['output']; /** The ID of the object */ id: Scalars['ID']['output']; + /** Is enrolment part of a cultural route? True means yes, False means no, I don't know or unanswered. */ + isPartOfCulturalRoute: Scalars['Boolean']['output']; notificationType?: Maybe; pEvent: PalvelutarjotinEventNode; person?: Maybe; @@ -1092,7 +1100,7 @@ export enum OrganisationTypeEnum { export enum OrganisationsOrganisationTypeChoices { /** Provider */ Provider = 'PROVIDER', - /** Käyttäjä */ + /** User */ User = 'USER' } @@ -1894,6 +1902,7 @@ export type UnpublishEventMutation = { export type UpdateEnrolmentMutationInput = { clientMutationId?: InputMaybe; enrolmentId: Scalars['ID']['input']; + isPartOfCulturalRoute?: InputMaybe; notificationType?: InputMaybe; /** Leave blank if the contact person is the same with group contact person */ person?: InputMaybe; @@ -2140,9 +2149,9 @@ export type EnrolOccurrenceMutationVariables = Exact<{ }>; -export type EnrolOccurrenceMutation = { __typename?: 'Mutation', enrolOccurrence?: { __typename?: 'EnrolOccurrenceMutationPayload', enrolments?: Array<{ __typename?: 'EnrolmentNode', id: string, notificationType?: NotificationType | null, enrolmentTime: any, status?: EnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } } | null> | null } | null }; +export type EnrolOccurrenceMutation = { __typename?: 'Mutation', enrolOccurrence?: { __typename?: 'EnrolOccurrenceMutationPayload', enrolments?: Array<{ __typename?: 'EnrolmentNode', id: string, isPartOfCulturalRoute: boolean, notificationType?: NotificationType | null, enrolmentTime: any, status?: EnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } } | null> | null } | null }; -export type EnrolmentFieldsFragment = { __typename?: 'EnrolmentNode', id: string, notificationType?: NotificationType | null, enrolmentTime: any, status?: EnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } }; +export type EnrolmentFieldsFragment = { __typename?: 'EnrolmentNode', id: string, isPartOfCulturalRoute: boolean, notificationType?: NotificationType | null, enrolmentTime: any, status?: EnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } }; export type PEventFieldsFragment = { __typename?: 'PalvelutarjotinEventNode', autoAcceptance: boolean, id: string, enrolmentEndDays?: number | null, enrolmentStart?: any | null, externalEnrolmentUrl?: string | null, neededOccurrences: number, contactPhoneNumber: string, contactEmail: string, isQueueingAllowed: boolean, mandatoryAdditionalInformation: boolean, nextOccurrenceDatetime?: any | null, lastOccurrenceDatetime?: any | null, organisation?: { __typename?: 'OrganisationNode', id: string, name: string } | null, contactPerson?: { __typename?: 'PersonNode', id: string, name: string } | null, occurrences?: { __typename?: 'OccurrenceNodeConnection', edges: Array<{ __typename?: 'OccurrenceNodeEdge', node?: { __typename?: 'OccurrenceNode', id: string, amountOfSeats: number, seatsTaken: number, seatType: OccurrencesOccurrenceSeatTypeChoices, remainingSeats: number, minGroupSize?: number | null, maxGroupSize?: number | null, cancelled: boolean, startTime: any, endTime: any, placeId: string, pEvent?: { __typename?: 'PalvelutarjotinEventNode', id: string } | null, languages: { __typename?: 'LanguageNodeConnection', edges: Array<{ __typename?: 'LanguageNodeEdge', node?: { __typename?: 'LanguageNode', id: string, name: string } | null } | null> } } | null } | null> } | null, nextOccurrence?: { __typename?: 'OccurrenceNodeConnection', edges: Array<{ __typename?: 'OccurrenceNodeEdge', node?: { __typename?: 'OccurrenceNode', id: string, startTime: any, endTime: any } | null } | null> } | null }; @@ -2166,9 +2175,9 @@ export type EnrolEventQueueMutationVariables = Exact<{ }>; -export type EnrolEventQueueMutation = { __typename?: 'Mutation', enrolEventQueue?: { __typename?: 'EnrolEventQueueMutationPayload', eventQueueEnrolment?: { __typename?: 'EventQueueEnrolmentNode', id: string, notificationType?: NotificationType | null, enrolmentTime: any, status?: EventQueueEnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } } | null } | null }; +export type EnrolEventQueueMutation = { __typename?: 'Mutation', enrolEventQueue?: { __typename?: 'EnrolEventQueueMutationPayload', eventQueueEnrolment?: { __typename?: 'EventQueueEnrolmentNode', id: string, isPartOfCulturalRoute: boolean, notificationType?: NotificationType | null, enrolmentTime: any, status?: EventQueueEnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } } | null } | null }; -export type EventQueueEnrolmentFieldsFragment = { __typename?: 'EventQueueEnrolmentNode', id: string, notificationType?: NotificationType | null, enrolmentTime: any, status?: EventQueueEnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } }; +export type EventQueueEnrolmentFieldsFragment = { __typename?: 'EventQueueEnrolmentNode', id: string, isPartOfCulturalRoute: boolean, notificationType?: NotificationType | null, enrolmentTime: any, status?: EventQueueEnrolmentStatus | null, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null, studyGroup: { __typename?: 'StudyGroupNode', id: string, unitId?: string | null, unitName: string, groupSize: number, amountOfAdult: number, groupName: string, extraNeeds: string, unit?: { __typename?: 'ExternalPlace', name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | { __typename?: 'Place', internalId: string, id?: string | null, name?: { __typename?: 'LocalisedObject', en?: string | null, fi?: string | null, sv?: string | null } | null } | null, studyLevels: { __typename?: 'StudyLevelNodeConnection', edges: Array<{ __typename?: 'StudyLevelNodeEdge', node?: { __typename?: 'StudyLevelNode', id: string, label?: string | null, level: number, translations: Array<{ __typename?: 'StudyLevelTranslationType', languageCode: Language, label: string }> } | null } | null> }, person?: { __typename?: 'PersonNode', id: string, emailAddress: string, name: string, phoneNumber: string, language: Language } | null } }; export type MetaFieldsFragment = { __typename?: 'Meta', count?: number | null, next?: string | null, previous?: string | null }; @@ -2409,6 +2418,7 @@ ${PersonFieldsFragmentDoc}`; export const EnrolmentFieldsFragmentDoc = gql` fragment enrolmentFields on EnrolmentNode { id + isPartOfCulturalRoute notificationType enrolmentTime status @@ -2633,6 +2643,7 @@ ${VenueFieldsFragmentDoc}`; export const EventQueueEnrolmentFieldsFragmentDoc = gql` fragment eventQueueEnrolmentFields on EventQueueEnrolmentNode { id + isPartOfCulturalRoute notificationType enrolmentTime status diff --git a/src/playwright/mocks.ts b/src/playwright/mocks.ts index cdb5aab3..f108a1fb 100644 --- a/src/playwright/mocks.ts +++ b/src/playwright/mocks.ts @@ -412,6 +412,7 @@ export const MOCK_ENROL_OCCURRENCE_MUTATION_RESPONSE: EnrolOccurrenceMutation = enrolments: [ { id: 'RW5yb2xtZW50Tm9kZTo5ODI=', + isPartOfCulturalRoute: true, notificationType: NotificationType.Email, enrolmentTime: '2025-05-06T10:49:07.076077+00:00', status: EnrolmentStatus.Pending, diff --git a/src/playwright/pages/event.page.ts b/src/playwright/pages/event.page.ts index 1c6b7810..ae5aa6ec 100644 --- a/src/playwright/pages/event.page.ts +++ b/src/playwright/pages/event.page.ts @@ -48,11 +48,21 @@ const TRANS = { sv: /^Grupp \*/, en: /^Group name \*/, }, + isPartOfCulturalRoute: { + fi: /^Käynti sisältyy kulttuuripolkuun \*/, + sv: /^Besöket ingår i kulturstigen \*/, + en: /^The visit is part of the cultural route \*/, + }, name: { fi: /^Nimi \*/, sv: /^Namn \*/, en: /^Name \*/, }, + noOrUnknown: { + fi: 'Ei / en tiedä', + sv: 'Nej / jag vet inte', + en: "No / I don't know", + }, phoneNumber: { fi: 'Puhelinnumero *', sv: 'Telefonnummer *', @@ -98,6 +108,11 @@ const TRANS = { sv: 'Skicka anmälan', en: 'Submit enquiry', }, + yes: { + fi: 'Kyllä', + sv: 'Ja', + en: 'Yes', + }, } as const satisfies Record; type TranslationsOf = @@ -247,6 +262,23 @@ export class EventPage extends BasePage { await this.hasVisibleHeading(TRANS.reservationEnquirySent[lang]); } + /** + * Select a radio button answer to "Is part of cultural route?" question. + * @param lang Language + * @param value False to select "No / I don't know", true to select "Yes". + */ + async selectIsPartOfCulturalRoute(lang: Language, value: boolean) { + const isPartOfCulturalRouteGroup = this.enrolmentForm.getByRole('group', { + name: TRANS.isPartOfCulturalRoute[lang], + }); + const radioButtonName = value ? TRANS.yes[lang] : TRANS.noOrUnknown[lang]; + const radioButtonLabel = isPartOfCulturalRouteGroup.getByText( + radioButtonName, + { exact: true } + ); + await radioButtonLabel.click(); + } + /** * Wait for study group unit list to load with at least a single option. */ diff --git a/src/playwright/tests/eventEnrolling.spec.ts b/src/playwright/tests/eventEnrolling.spec.ts index 361a0527..1cbef66a 100644 --- a/src/playwright/tests/eventEnrolling.spec.ts +++ b/src/playwright/tests/eventEnrolling.spec.ts @@ -28,6 +28,7 @@ LANGUAGES.forEach((lang) => { await eventPage.selectDaycareKamppiForStudyGroupUnit(lang); await eventPage.fillStudyGroupName(lang, 'Testiryhmä'); await eventPage.selectPreschoolForStudyLevel(lang); + await eventPage.selectIsPartOfCulturalRoute(lang, true); await eventPage.selectNumberOfChildren(lang, '8'); await eventPage.selectNumberOfAdults(lang, '2'); await eventPage.fillAdditionalInfo(lang, 'Neurokirjon ryhmä'); diff --git a/src/utils/mockDataUtils.ts b/src/utils/mockDataUtils.ts index d8af54c4..3fe3a592 100644 --- a/src/utils/mockDataUtils.ts +++ b/src/utils/mockDataUtils.ts @@ -155,6 +155,7 @@ export const fakeEnrolments = ( export const fakeEnrolment = ( overrides?: Partial ): EnrolmentNode => ({ + isPartOfCulturalRoute: faker.datatype.boolean(), enrolmentTime: '2020-08-18T06:37:40.755109+00:00', updatedAt: '2020-08-18T06:37:40.755109+00:00', id: faker.string.uuid(),