From e462b1cbf94fa30a0fbe9ec42b992ea0bb6dee41 Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Tue, 14 Jan 2025 16:55:52 +0100 Subject: [PATCH] :construction: [#5016] Referentielijsten dataSrc for options --- .storybook/decorators.tsx | 7 ++ .../ComponentConfiguration.stories.tsx | 4 + src/components/ComponentConfiguration.tsx | 2 + .../builder/referentielijsten/index.ts | 1 + .../referentielijsten-services.spec.tsx | 56 ++++++++++++++ .../referentielijsten-services.stories.tsx | 27 +++++++ .../referentielijsten-services.tsx | 75 +++++++++++++++++++ .../builder/values/referentielijsten/code.tsx | 34 +++++++++ .../builder/values/referentielijsten/index.ts | 9 +++ .../values/referentielijsten/service.tsx | 18 +++++ .../builder/values/values-config.stories.tsx | 20 +++++ .../builder/values/values-config.tsx | 25 +++++++ src/components/builder/values/values-src.tsx | 6 +- src/context.ts | 3 + src/registry/select/edit-validation.ts | 6 +- src/registry/select/helpers.ts | 25 +++++++ src/registry/select/preview.tsx | 61 ++++++++++++--- src/tests/sharedUtils.tsx | 18 +++++ 18 files changed, 384 insertions(+), 13 deletions(-) create mode 100644 src/components/builder/referentielijsten/index.ts create mode 100644 src/components/builder/referentielijsten/referentielijsten-services.spec.tsx create mode 100644 src/components/builder/referentielijsten/referentielijsten-services.stories.tsx create mode 100644 src/components/builder/referentielijsten/referentielijsten-services.tsx create mode 100644 src/components/builder/values/referentielijsten/code.tsx create mode 100644 src/components/builder/values/referentielijsten/index.ts create mode 100644 src/components/builder/values/referentielijsten/service.tsx diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx index 2ad69c94..6960f92f 100644 --- a/.storybook/decorators.tsx +++ b/.storybook/decorators.tsx @@ -14,6 +14,7 @@ import { DEFAULT_PREFILL_ATTRIBUTES, DEFAULT_PREFILL_PLUGINS, DEFAULT_REGISTRATION_ATTRIBUTES, + DEFAULT_SERVICES, DEFAULT_VALIDATOR_PLUGINS, sleep, } from '@/tests/sharedUtils'; @@ -61,6 +62,7 @@ export const BuilderContextDecorator: Decorator = (Story, context) => { context.parameters.builder?.defaultValidatorPlugins || DEFAULT_VALIDATOR_PLUGINS; const defaultRegistrationAttributes = context.parameters.builder?.defaultRegistrationAttributes || DEFAULT_REGISTRATION_ATTRIBUTES; + const defaultServices = context.parameters.builder?.defaultServices || DEFAULT_SERVICES; const defaultPrefillPlugins = context.parameters.builder?.defaultPrefillPlugins || DEFAULT_PREFILL_PLUGINS; const defaultPrefillAttributes = @@ -86,6 +88,11 @@ export const BuilderContextDecorator: Decorator = (Story, context) => { await sleep(context.parameters?.builder?.registrationAttributesDelay || 0); return context?.args?.registrationAttributes || defaultRegistrationAttributes; }, + getServices: async () => { + await sleep(context.parameters?.builder?.servicesDelay || 0); + console.log('foo'); + return context?.args?.services || defaultServices; + }, getPrefillPlugins: async () => { await sleep(context.parameters?.builder?.prefillPluginsDelay || 0); return context?.args?.prefillPlugins || defaultPrefillPlugins; diff --git a/src/components/ComponentConfiguration.stories.tsx b/src/components/ComponentConfiguration.stories.tsx index 2810edb1..50d169d6 100644 --- a/src/components/ComponentConfiguration.stories.tsx +++ b/src/components/ComponentConfiguration.stories.tsx @@ -20,6 +20,7 @@ import {AnyComponentSchema} from '@/types'; import ComponentConfiguration from './ComponentConfiguration'; import {BuilderInfo} from './ComponentEditForm'; import {PrefillAttributeOption, PrefillPluginOption} from './builder/prefill'; +import {ReferentielijstenServiceOption} from './builder/referentielijsten/referentielijsten-services'; import {RegistrationAttributeOption} from './builder/registration/registration-attribute'; import {ValidatorOption} from './builder/validate/validator-select'; @@ -75,6 +76,7 @@ interface TemplateArgs { validatorPlugins: ValidatorOption[]; registrationAttributes: RegistrationAttributeOption[]; prefillPlugins: PrefillPluginOption[]; + services: ReferentielijstenServiceOption[]; prefillAttributes: Record; fileTypes: Array<{value: string; label: string}>; isNew: boolean; @@ -91,6 +93,7 @@ const Template: StoryFn = ({ registrationAttributes, prefillPlugins, prefillAttributes, + services, supportedLanguageCodes, fileTypes, isNew, @@ -107,6 +110,7 @@ const Template: StoryFn = ({ getFormComponents={() => otherComponents} getValidatorPlugins={async () => validatorPlugins} getRegistrationAttributes={async () => registrationAttributes} + getServices={async () => services} getPrefillPlugins={async () => prefillPlugins} getPrefillAttributes={async (plugin: string) => prefillAttributes[plugin]} getFileTypes={async () => fileTypes} diff --git a/src/components/ComponentConfiguration.tsx b/src/components/ComponentConfiguration.tsx index 7fec9d2a..67fe9cb1 100644 --- a/src/components/ComponentConfiguration.tsx +++ b/src/components/ComponentConfiguration.tsx @@ -38,6 +38,7 @@ const ComponentConfiguration: React.FC = ({ getFormComponents, getValidatorPlugins, getRegistrationAttributes, + getServices, getPrefillPlugins, getPrefillAttributes, getFileTypes, @@ -66,6 +67,7 @@ const ComponentConfiguration: React.FC = ({ getFormComponents, getValidatorPlugins, getRegistrationAttributes, + getServices, getPrefillPlugins, getPrefillAttributes, getFileTypes, diff --git a/src/components/builder/referentielijsten/index.ts b/src/components/builder/referentielijsten/index.ts new file mode 100644 index 00000000..86de2ab1 --- /dev/null +++ b/src/components/builder/referentielijsten/index.ts @@ -0,0 +1 @@ +export {default as ReferentielijstenServiceSelect} from './referentielijsten-services'; diff --git a/src/components/builder/referentielijsten/referentielijsten-services.spec.tsx b/src/components/builder/referentielijsten/referentielijsten-services.spec.tsx new file mode 100644 index 00000000..ae0051b3 --- /dev/null +++ b/src/components/builder/referentielijsten/referentielijsten-services.spec.tsx @@ -0,0 +1,56 @@ +import userEvent from '@testing-library/user-event'; +import {Formik} from 'formik'; + +import {act, contextRender, screen} from '@/tests/test-utils'; + +import RegistrationAttributeSelect, { + RegistrationAttributeOption, +} from './referentielijsten-services'; + +const REGISTRATION_ATTRIBUTES: RegistrationAttributeOption[] = [ + {id: 'bsn', label: 'BSN'}, + {id: 'firstName', label: 'First name'}, + {id: 'dob', label: 'Date of Birth'}, +]; + +beforeAll(() => { + jest.useFakeTimers(); +}); + +afterAll(() => { + jest.useRealTimers(); +}); + +test('Available registration attributes are retrieved via context', async () => { + const user = userEvent.setup({advanceTimers: jest.advanceTimersByTime}); + + contextRender( + + + , + { + enableContext: true, + locale: 'en', + builderOptions: { + registrationAttributes: REGISTRATION_ATTRIBUTES, + registrationAttributesDelay: 100, + }, + } + ); + + // open the dropdown + const input = await screen.findByLabelText('Registration attribute'); + await act(async () => { + input.focus(); + await user.keyboard('[ArrowDown]'); + }); + + // options are loaded async, while doing network IO the loading state is displayed + expect(await screen.findByText('Loading...')).toBeVisible(); + + // once resolved, the options are visible and the loading state is no longer + expect(await screen.findByText('BSN')).toBeVisible(); + expect(await screen.findByText('First name')).toBeVisible(); + expect(await screen.findByText('Date of Birth')).toBeVisible(); + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); +}); diff --git a/src/components/builder/referentielijsten/referentielijsten-services.stories.tsx b/src/components/builder/referentielijsten/referentielijsten-services.stories.tsx new file mode 100644 index 00000000..5d268d6a --- /dev/null +++ b/src/components/builder/referentielijsten/referentielijsten-services.stories.tsx @@ -0,0 +1,27 @@ +import {Meta, StoryObj} from '@storybook/react'; + +import {withFormik} from '@/sb-decorators'; +import {DEFAULT_SERVICES} from '@/tests/sharedUtils'; + +import ReferentielijstenServiceSelect, { + ReferentielijstenServiceOption, +} from './referentielijsten-services'; + +export default { + title: 'Formio/Builder/Referentielijsten/ReferentielijstenServiceSelect', + component: ReferentielijstenServiceSelect, + decorators: [withFormik], + parameters: { + controls: {hideNoControlsWarning: true}, + modal: {noModal: true}, + builder: {enableContext: true, registrationAttributesDelay: 100}, + formik: {initialValues: {registration: {attribute: ''}}}, + }, + args: { + services: DEFAULT_SERVICES, + }, +} as Meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/components/builder/referentielijsten/referentielijsten-services.tsx b/src/components/builder/referentielijsten/referentielijsten-services.tsx new file mode 100644 index 00000000..780902fb --- /dev/null +++ b/src/components/builder/referentielijsten/referentielijsten-services.tsx @@ -0,0 +1,75 @@ +import {useFormikContext} from 'formik'; +import {useContext} from 'react'; +import {FormattedMessage, useIntl} from 'react-intl'; +import useAsync from 'react-use/esm/useAsync'; + +import Select from '@/components/formio/select'; +import {BuilderContext} from '@/context'; + +// TODO transform this to id and label? +export interface ReferentielijstenServiceOption { + url: string; + slug: string; + label: string; + apiRoot: string; + apiType: string; +} + +function isServiceOptions( + options: ReferentielijstenServiceOption[] | undefined +): options is ReferentielijstenServiceOption[] { + return options !== undefined; +} + +interface ReferentielijstenServiceSelectProps { + name: string; +} + +/** + * Fetch the available Referentielijsten Services and display them in a Select + * + * This requires an async function `getServices` to be provided to the + * BuilderContext which is responsible for retrieving the list of available plugins. + * + * If a fetch error occurs, it is thrown during rendering - you should provide your + * own error boundary to catch this. + */ +const ReferentielijstenServiceSelect: React.FC = ({name}) => { + const intl = useIntl(); + const {getServices} = useContext(BuilderContext); + const {setFieldValue} = useFormikContext(); + + const { + value: options, + loading, + error, + } = useAsync(async () => await getServices('referentielijsten'), []); + if (error) { + throw error; + } + const _options = isServiceOptions(options) ? options : []; + + return ( +