Skip to content

Commit

Permalink
⚡ [#76] Lazy load appointment routes/components
Browse files Browse the repository at this point in the history
Most forms won't need this code, so we can lazy load it instead.
  • Loading branch information
sergei-maertens committed Jan 24, 2025
1 parent 71a688b commit 0cd6fcf
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('Create appointment session expiration', () => {
});

// select a product
const dropdowns = screen.getAllByRole('combobox');
const dropdowns = await screen.findAllByRole('combobox');

Check failure on line 94 in src/components/appointments/CreateAppointment/CreateAppointment.spec.jsx

View workflow job for this annotation

GitHub Actions / Run Javascript tests

src/components/appointments/CreateAppointment/CreateAppointment.spec.jsx > Create appointment session expiration > resets the session storage/local state

TestingLibraryElementError: Unable to find role="combobox" Ignored nodes: comments, script, style <body> <div /> </body> Ignored nodes: comments, script, style <body> <div /> </body> ❯ waitForWrapper node_modules/@testing-library/dom/dist/wait-for.js:163:27 ❯ findAllByRole node_modules/@testing-library/dom/dist/query-helpers.js:86:33 ❯ src/components/appointments/CreateAppointment/CreateAppointment.spec.jsx:94:36
expect(dropdowns).toHaveLength(1);
await user.click(dropdowns[0]);
await user.keyboard('[ArrowDown]');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {FormContext} from 'Context';
import {buildForm} from 'api-mocks';
import {mockSubmissionPost, mockSubmissionProcessingStatusGet} from 'api-mocks/submissions';
import {loadCalendarLocale} from 'components/forms/DateField/DatePickerCalendar';
import {createAppointmentRoutes} from 'routes/appointments';
import routes from 'routes';
import {ConfigDecorator, LayoutDecorator} from 'story-utils/decorators';

import {
Expand Down Expand Up @@ -46,17 +46,7 @@ export default {
};

const Wrapper = ({form}) => {
const routes = [
{
path: '/appointments/*',
element: <CreateAppointment />,
children: createAppointmentRoutes,
},
];
const router = createMemoryRouter(routes, {
initialEntries: ['/appointments/'],
initialIndex: 0,
});
const router = createMemoryRouter(routes, {initialEntries: ['/afspraak-maken/']});
return (
<FormContext.Provider value={form}>
<RouterProvider router={router} />
Expand Down
17 changes: 17 additions & 0 deletions src/components/appointments/CreateAppointment/LandingPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {Navigate, useSearchParams} from 'react-router-dom';

import {APPOINTMENT_STEP_PATHS} from './steps';

// TODO: replace with loader that redirects at the route level
export const LandingPage = () => {
const [params] = useSearchParams();
return (
<Navigate
replace
to={{
pathname: APPOINTMENT_STEP_PATHS[0],
search: `?${params}`,
}}
/>
);
};
20 changes: 0 additions & 20 deletions src/components/appointments/CreateAppointment/steps.jsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
import {defineMessage} from 'react-intl';
import {Navigate, useSearchParams} from 'react-router-dom';

import {ChooseProductStep, ContactDetailsStep, LocationAndTimeStep} from '../steps';

export const APPOINTMENT_STEPS = [
{
path: 'producten',
element: <ChooseProductStep navigateTo="../kalender" />,
name: defineMessage({
description: "Appointments navbar title for 'products' step",
defaultMessage: 'Product',
}),
},
{
path: 'kalender',
element: <LocationAndTimeStep navigateTo="../contactgegevens" />,
name: defineMessage({
description: "Appointments navbar title for 'location and time' step",
defaultMessage: 'Location and time',
}),
},
{
path: 'contactgegevens',
element: <ContactDetailsStep navigateTo="../overzicht" />,
name: defineMessage({
description: "Appointments navbar title for 'contact details' step",
defaultMessage: 'Contact details',
Expand All @@ -31,17 +25,3 @@ export const APPOINTMENT_STEPS = [
];

export const APPOINTMENT_STEP_PATHS = APPOINTMENT_STEPS.map(s => s.path);

// TODO: replace with loader that redirects at the route level
export const LandingPage = () => {
const [params] = useSearchParams();
return (
<Navigate
replace
to={{
pathname: APPOINTMENT_STEP_PATHS[0],
search: `?${params}`,
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ describe('Cancelling an appointment', () => {
it('renders the correct page for a cancel route', async () => {
render(<Wrapper />);

const textbox = screen.getByRole('textbox', {name: 'Your email address'});
const textbox = await screen.findByRole('textbox', {name: 'Your email address'});
expect(textbox).toBeVisible();
expect(textbox.name).toBe('email');
expect(screen.getByRole('button')).toBeVisible();
Expand Down
22 changes: 21 additions & 1 deletion src/components/appointments/index.jsx
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
export {default as CreateAppointment} from './CreateAppointment';
/**
* This module acts as the (lazy loaded) entry point for the appointments chunk.
*/
import CreateAppointment from './CreateAppointment';
import Confirmation from './CreateAppointment/Confirmation';
import {LandingPage} from './CreateAppointment/LandingPage';
import Summary from './CreateAppointment/Summary';
import {CancelAppointment, CancelAppointmentSuccess} from './cancel';
import {ChooseProductStep, ContactDetailsStep, LocationAndTimeStep} from './steps';

export {
CreateAppointment,
Confirmation,
LandingPage,
Summary,
CancelAppointment,
CancelAppointmentSuccess,
ChooseProductStep,
ContactDetailsStep,
LocationAndTimeStep,
};
17 changes: 6 additions & 11 deletions src/routes/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ import {Cosign} from 'components/CoSign';
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import Form from 'components/Form';
import SessionExpired from 'components/Sessions/SessionExpired';
import {CreateAppointment} from 'components/appointments';

import {createAppointmentRoutes, manageAppointmentRoutes} from './appointments';
import appointmentRoutes from './appointments';
import cosignRoutes from './cosign';
import formRoutes from './form';

Expand All @@ -23,15 +22,6 @@ const routes = [
path: '*',
element: <App />,
children: [
{
path: 'afspraak-annuleren/*',
children: manageAppointmentRoutes,
},
{
path: 'afspraak-maken/*',
element: <CreateAppointment />,
children: createAppointmentRoutes,
},
{
path: 'cosign/*',
element: <Cosign />,
Expand All @@ -45,6 +35,11 @@ const routes = [
</ErrorBoundary>
),
},
// appointments splat
{
path: '*',
children: appointmentRoutes,
},
// All the rest goes to the formio-based form flow
{
path: '*',
Expand Down
78 changes: 63 additions & 15 deletions src/routes/appointments.jsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,89 @@
import ErrorBoundary from 'components/Errors/ErrorBoundary';
import Confirmation from 'components/appointments/CreateAppointment/Confirmation';
import Summary from 'components/appointments/CreateAppointment/Summary';
import {APPOINTMENT_STEPS, LandingPage} from 'components/appointments/CreateAppointment/steps';
import {CancelAppointment, CancelAppointmentSuccess} from 'components/appointments/cancel';

/**
* Route subtree for appointment forms.
*/
const createAppointmentRoutes = [
{
path: '',
element: <LandingPage />,
lazy: async () => {
const {LandingPage} = await import('components/appointments');
return {element: <LandingPage />};
},
},
{
path: 'producten',
lazy: async () => {
const {ChooseProductStep} = await import('components/appointments');
return {element: <ChooseProductStep navigateTo="../kalender" />};
},
},
{
path: 'kalender',
lazy: async () => {
const {LocationAndTimeStep} = await import('components/appointments');
return {element: <LocationAndTimeStep navigateTo="../contactgegevens" />};
},
},
{
path: 'contactgegevens',
lazy: async () => {
const {ContactDetailsStep} = await import('components/appointments');
return {element: <ContactDetailsStep navigateTo="../overzicht" />};
},
},
...APPOINTMENT_STEPS.map(({path, element}) => ({path, element})),
{
path: 'overzicht',
element: <Summary />,
lazy: async () => {
const {Summary} = await import('components/appointments');
return {element: <Summary />};
},
},
{
path: 'bevestiging',
element: <Confirmation />,
lazy: async () => {
const {Confirmation} = await import('components/appointments');
return {element: <Confirmation />};
},
},
];

const manageAppointmentRoutes = [
{
path: '',
element: (
<ErrorBoundary>
<CancelAppointment />
</ErrorBoundary>
),
lazy: async () => {
const {CancelAppointment} = await import('components/appointments');
return {

Check warning on line 56 in src/routes/appointments.jsx

View check run for this annotation

Codecov / codecov/patch

src/routes/appointments.jsx#L54-L56

Added lines #L54 - L56 were not covered by tests
element: (
<ErrorBoundary>
<CancelAppointment />
</ErrorBoundary>
),
};
},
},
{
path: 'succes',
element: <CancelAppointmentSuccess />,
lazy: async () => {
const {CancelAppointmentSuccess} = await import('components/appointments');
return {element: <CancelAppointmentSuccess />};

Check warning on line 69 in src/routes/appointments.jsx

View check run for this annotation

Codecov / codecov/patch

src/routes/appointments.jsx#L67-L69

Added lines #L67 - L69 were not covered by tests
},
},
];

const appointmentRoutes = [
{
path: 'afspraak-annuleren/*',
children: manageAppointmentRoutes,
},
{
path: 'afspraak-maken/*',
children: createAppointmentRoutes,
lazy: async () => {
const {CreateAppointment} = await import('components/appointments');
return {element: <CreateAppointment />};
},
},
];

export {createAppointmentRoutes, manageAppointmentRoutes};
export default appointmentRoutes;

0 comments on commit 0cd6fcf

Please sign in to comment.