Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenbal committed Feb 4, 2025
1 parent aec40e4 commit c1ef2f9
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 304 deletions.
16 changes: 11 additions & 5 deletions src/components/builder/values/referentielijsten/code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,26 @@ function isTabelOptions(
}

function transformItems(items: ReferentielijstenTabelOption[]) {
const intl = useIntl();
return items.map(item => {
const {code, naam, isGeldig} = item;
return {
value: code,
label: !isGeldig ? `${naam} (niet meer geldig)` : naam,
label: !isGeldig
? `${naam} ${intl.formatMessage({
description: 'Message to indicate that Referentielijsten tabel is expired',
defaultMessage: '(niet meer geldig)',
})}`
: naam,
};
});
}

interface ComponentWithReferentielijsten {
export interface ComponentWithReferentielijsten {
openForms?: {
dataSrc: 'referentielijsten';
service?: string;
code?: string;
service: string;
code: string;
};
}

Expand Down Expand Up @@ -63,7 +69,7 @@ export const ReferentielijstenTabelCode: React.FC = () => {

return (
<Select
name={'openForms.code'}
name="openForms.code"
label={
<FormattedMessage
description="Label for 'openForms.code' builder field"
Expand Down
46 changes: 15 additions & 31 deletions src/components/builder/values/referentielijsten/service.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {useField} from 'formik';
import {useFormikContext} from 'formik';
import {useContext} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import useAsync from 'react-use/esm/useAsync';

import Select, {Option} from '@/components/formio/select';
import Select from '@/components/formio/select';
import {BuilderContext} from '@/context';

import {ComponentWithReferentielijsten} from './code';

export interface ReferentielijstenServiceOption {
url: string;
slug: string;
Expand All @@ -14,12 +16,6 @@ export interface ReferentielijstenServiceOption {
apiType: string;
}

function isServiceOptions(
options: ReferentielijstenServiceOption[] | undefined
): options is ReferentielijstenServiceOption[] {
return options !== undefined;
}

/**
* Fetch the available Referentielijsten Services and display them in a Select
*
Expand All @@ -33,32 +29,19 @@ function isServiceOptions(
*/
const ReferentielijstenServiceSelect: React.FC = () => {
const intl = useIntl();
const {values, setFieldValue} = useFormikContext<ComponentWithReferentielijsten>();
const {getServices} = useContext(BuilderContext);
const {
value: options,
loading,
error,
} = useAsync(async () => await getServices('referentielijsten'), []);
if (error) {
throw error;
}
const _options = isServiceOptions(options) ? options : [];
const transformedOptions: Option[] = _options.map(({slug, label}) => ({
value: slug,
label: label,
}));

// react-select doesn't update the defaultValue if it is initially `null`,
// so instead we update the value of the Formik field to ensure the correct default
// is selected
const [field, , {setValue}] = useField('openForms.service');
if (transformedOptions && transformedOptions.length == 1 && !field.value) {
setValue(transformedOptions[0].value);
}
const {value: options = [], loading} = useAsync(async () => {
const options = await getServices('referentielijsten');
if (options.length === 1 && !values?.openForms?.service) {
setFieldValue('openForms.service', options[0].slug);
}
return options;
}, [getServices, setFieldValue]); // values is deliberately excluded from the dependency array

return (
<Select
name={'openForms.service'}
name="openForms.service"
label={
<FormattedMessage
description="Label for 'openForms.service' builder field"
Expand All @@ -70,7 +53,8 @@ const ReferentielijstenServiceSelect: React.FC = () => {
defaultMessage: `The identifier of the Referentielijsten service from which the options will be retrieved.`,
})}
isLoading={loading}
options={transformedOptions}
valueProperty="slug"
options={options}
required
/>
);
Expand Down
2 changes: 0 additions & 2 deletions src/components/builder/values/values-config.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import {Meta, StoryObj} from '@storybook/react';
import {expect, fireEvent, fn, userEvent, waitFor, within} from '@storybook/test';
import {Form, Formik} from 'formik';

import selectboxes from '@/registry/selectboxes';
import {BuilderContextDecorator} from '@/sb-decorators';
import {withFormik} from '@/sb-decorators';
import {DEFAULT_SERVICES} from '@/tests/sharedUtils';

import ValuesConfig from './values-config';

Expand Down
2 changes: 2 additions & 0 deletions src/registry/radio/edit-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const buildValuesSchema = (intl: IntlShape) =>
]),
// TODO: wire up infernologic type checking
itemsExpression: jsonSchema.optional(),
service: z.string().optional(),
code: z.string().optional(),
}),
});

Expand Down
11 changes: 6 additions & 5 deletions src/registry/radio/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {RadioComponentSchema} from '@open-formulieren/types';
import {Option} from '@open-formulieren/types/lib/formio/common';
import {JSONObject} from '@open-formulieren/types/lib/types';

import {ComponentWithReferentielijsten} from '@/components/builder/values/referentielijsten/code';

// A type guard is needed because TS cannot figure out it's a discriminated union
// when the discriminator is nested.
// See https://github.com/microsoft/TypeScript/issues/18758
Expand All @@ -16,10 +18,10 @@ export const checkIsManualOptions = (
// See https://github.com/microsoft/TypeScript/issues/18758
export const checkIsReferentielijstenOptions = (
component: RadioComponentSchema
): component is RadioComponentSchema & {
data: {values: Option[] | undefined};
openForms: {code: string; service: string};
} => {
): component is RadioComponentSchema &
ComponentWithReferentielijsten & {
openForms: {code: string; service: string};
} => {
return component.openForms.dataSrc === 'referentielijsten';
};

Expand All @@ -29,7 +31,6 @@ export const checkIsReferentielijstenOptions = (
export const checkIsVariableOptions = (
component: RadioComponentSchema
): component is RadioComponentSchema & {
data: {values: Option[] | undefined};
openForms: {itemsExpression: string | JSONObject};
} => {
return component.openForms.dataSrc === 'variable';
Expand Down
151 changes: 71 additions & 80 deletions src/registry/radio/radio-referentielijsten.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export default {
dataSrc: 'manual',
translations: {},
},
data: {values: [{value: '', label: ''}]},
values: [{value: '', label: ''}],
defaultValue: '',
},
Expand All @@ -49,30 +48,28 @@ export const StoreValuesInComponent: Story = {
play: async ({canvasElement, step, args}) => {
const canvas = within(canvasElement);

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');
const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');
const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
code: 'tabel2',
dataSrc: 'referentielijsten',
service: 'referentielijsten',
translations: {},
},
type: 'radio',
})
);
});
expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
code: 'tabel2',
dataSrc: 'referentielijsten',
service: 'referentielijsten',
translations: {},
},
type: 'radio',
})
);
},
};

Expand All @@ -81,35 +78,33 @@ export const SwitchToVariableResetOptions: Story = {
play: async ({canvasElement, step, args}) => {
const canvas = within(canvasElement);

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');
const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');
const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(canvas, dataSourceInput, 'From variable');
await rsSelect(canvas, dataSourceInput, 'From variable');

const itemsExpressionInput = canvas.getByTestId('jsonEdit');
await userEvent.clear(itemsExpressionInput);
await userEvent.type(itemsExpressionInput, '"foo"');
const itemsExpressionInput = canvas.getByTestId('jsonEdit');
await userEvent.clear(itemsExpressionInput);
await userEvent.type(itemsExpressionInput, '"foo"');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
dataSrc: 'variable',
itemsExpression: 'foo',
translations: {},
},
type: 'radio',
})
);
});
expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
dataSrc: 'variable',
itemsExpression: 'foo',
translations: {},
},
type: 'radio',
})
);
},
};

Expand All @@ -118,42 +113,40 @@ export const SwitchToManualResetOptions: Story = {
play: async ({canvasElement, step, args}) => {
const canvas = within(canvasElement);

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');
const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');
const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(canvas, dataSourceInput, 'Manually fill in');
await rsSelect(canvas, dataSourceInput, 'Manually fill in');

const labelInput = canvas.getByTestId('input-values[0].label');
await userEvent.type(labelInput, 'Foo');
const labelInput = canvas.getByTestId('input-values[0].label');
await userEvent.type(labelInput, 'Foo');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));
await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
dataSrc: 'manual',
translations: {},
},
type: 'radio',
values: [
{
label: 'Foo',
value: 'foo',
openForms: {
translations: {},
},
expect(args.onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
openForms: {
dataSrc: 'manual',
translations: {},
},
type: 'radio',
values: [
{
label: 'Foo',
value: 'foo',
openForms: {
translations: {},
},
],
})
);
});
},
],
})
);
},
};

Expand All @@ -176,15 +169,13 @@ export const AutoSelectIfOnlyOneReferentielijstenService: Story = {
play: async ({canvasElement, step, args}) => {
const canvas = within(canvasElement);

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
const serviceInput = canvas.getByLabelText('Referentielijsten service');

await waitFor(async () => {
expect(serviceInput.parentElement?.parentElement).toHaveTextContent('Referentielijsten');
});
await waitFor(() => {
expect(serviceInput.parentElement?.parentElement).toHaveTextContent('Referentielijsten');
});
},
};
2 changes: 2 additions & 0 deletions src/registry/select/edit-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const buildValuesSchema = (intl: IntlShape) =>
]),
// TODO: wire up infernologic type checking
itemsExpression: jsonSchema.optional(),
service: z.string().optional(),
code: z.string().optional(),
}),
});

Expand Down
Loading

0 comments on commit c1ef2f9

Please sign in to comment.