diff --git a/src/components/FormStepSummary/ComponentValueDisplay.js b/src/components/FormStepSummary/ComponentValueDisplay.js index e91228a03..3ced00778 100644 --- a/src/components/FormStepSummary/ComponentValueDisplay.js +++ b/src/components/FormStepSummary/ComponentValueDisplay.js @@ -209,6 +209,16 @@ const CoSignDisplay = ({component, value}) => { return ; }; +const AddressNLDisplay = ({component, value}) => { + if (!value || Object.values(value).every(v => v === '')) { + return ; + } + + return `${value.postcode} ${value.houseNumber}${value.houseLetter || ''} ${ + value.houseNumberAddition || '' + }`; +}; + const ComponentValueDisplay = ({value, component}) => { const {multiple = false, type} = component; @@ -259,6 +269,7 @@ const TYPE_TO_COMPONENT = { map: MapDisplay, password: PasswordDisplay, coSign: CoSignDisplay, + addressNL: AddressNLDisplay, }; export default ComponentValueDisplay; diff --git a/src/components/Summary/Summary.stories.js b/src/components/Summary/Summary.stories.js index e66ebbe05..16e73824b 100644 --- a/src/components/Summary/Summary.stories.js +++ b/src/components/Summary/Summary.stories.js @@ -281,3 +281,92 @@ export const Loading = { isLoading: true, }, }; + +export const AddressNLSummary = { + render, + args: { + summaryData: [ + { + slug: 'address-nl', + name: 'Address NL', + data: [ + { + name: 'Address NL', + value: {postcode: '1234 AB', houseNumber: '1'}, + component: { + key: 'addressNL', + type: 'addressNL', + label: 'Address NL', + hidden: false, + }, + }, + ], + }, + ], + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole('definition')).toHaveTextContent('1234 AB 1'); + }, +}; + +export const AddressNLSummaryFull = { + render, + args: { + summaryData: [ + { + slug: 'address-nl', + name: 'Address NL', + data: [ + { + name: 'Address NL', + value: { + postcode: '1234 AB', + houseNumber: '1', + houseLetter: 'A', + houseNumberAddition: 'Add', + }, + component: { + key: 'addressNL', + type: 'addressNL', + label: 'Address NL', + hidden: false, + }, + }, + ], + }, + ], + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole('definition')).toHaveTextContent('1234 AB 1A Add'); + }, +}; + +export const AddressNLSummaryEmpty = { + render, + args: { + summaryData: [ + { + slug: 'address-nl', + name: 'Address NL', + data: [ + { + name: 'Address NL', + value: {}, + component: { + key: 'addressNL', + type: 'addressNL', + label: 'Address NL', + hidden: false, + }, + }, + ], + }, + ], + }, + play: async ({canvasElement}) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole('definition')).toHaveTextContent(''); + }, +}; diff --git a/src/formio/components/AddressNL.js b/src/formio/components/AddressNL.js new file mode 100644 index 000000000..f9df85b93 --- /dev/null +++ b/src/formio/components/AddressNL.js @@ -0,0 +1,313 @@ +/** + * The addressNL component. + */ +import {Formik, useFormikContext} from 'formik'; +import debounce from 'lodash/debounce'; +import React, {useEffect} from 'react'; +import {createRoot} from 'react-dom/client'; +import {Formio} from 'react-formio'; +import {FormattedMessage, IntlProvider, createIntl} from 'react-intl'; +import {z} from 'zod'; +import {toFormikValidationSchema} from 'zod-formik-adapter'; + +import {ConfigContext} from 'Context'; +import {TextField} from 'components/forms'; +import enableValidationPlugins from 'formio/validators/plugins'; + +const Field = Formio.Components.components.field; + +export default class AddressNL extends Field { + constructor(component, options, data) { + super(component, options, data); + enableValidationPlugins(this); + } + + static schema(...extend) { + return Field.schema( + { + type: 'addressNL', + label: 'Address NL', + input: true, + key: 'addressNL', + defaultValue: {}, + validateOn: 'blur', + openForms: { + checkIsEmptyBeforePluginValidate: true, + }, + }, + ...extend + ); + } + + static get builderInfo() { + return { + title: 'Address NL', + icon: 'home', + weight: 500, + schema: AddressNL.schema(), + }; + } + + checkComponentValidity(data, dirty, row, options = {}) { + let updatedOptions = {...options}; + if (this.component.validate.plugins && this.component.validate.plugins.length) { + updatedOptions.async = true; + } + return super.checkComponentValidity(data, dirty, row, updatedOptions); + } + + get defaultSchema() { + return AddressNL.schema(); + } + + get emptyValue() { + return { + postcode: '', + houseNumber: '', + houseLetter: '', + houseNumberAddition: '', + }; + } + + validateMultiple() { + return false; + } + + render() { + return super.render( + `
+ ${this.renderTemplate('addressNL')} +
` + ); + } + + /** + * Defer to React to actually render things. + */ + attach(element) { + this.loadRefs(element, { + addressNLContainer: 'single', + }); + return super.attach(element).then(() => { + this.reactRoot = createRoot(this.refs.addressNLContainer); + this.renderReact(); + }); + } + + destroy() { + const container = this.refs.addressNLContainer; + container && this.reactRoot.unmount(); + super.destroy(); + } + + onFormikChange(value, isValid) { + this.updateValue(value, {modified: true}); + + // we can shortcuts-skip validation if the Formik form isn't valid. + if (!isValid) return; + + // `enableValidationPlugins` forces the component to be validateOn = 'blur', which + // surpresses the validators due to onChange events. + // Since this is a composite event, we need to fire the blur event ourselves and + // schedule the validation to run. + // Code inspired on Formio.js' `src/components/_classes/input/Input.js`, in + // particular the `addFocusBlurEvents` method. + // + if (this.component.validateOn === 'blur') { + if (this._debouncedBlur) { + this._debouncedBlur.cancel(); + } + + this._debouncedBlur = debounce(() => { + this.root.triggerChange( + {fromBlur: true}, + { + instance: this, + component: this.component, + value: this.dataValue, + flags: {fromBlur: true}, + } + ); + }, 50); + + this._debouncedBlur(); + } + } + + renderReact() { + const required = this.component?.validate?.required || false; + const intl = createIntl(this.options.intl); + const initialValues = {...this.emptyValue, ...this.dataValue}; + + this.reactRoot.render( + + + + + + + + ); + } + + setValue(value, flags = {}) { + const changed = super.setValue(value, flags); + // re-render if the value is set, which may be because of existing submission data + changed && this.renderReact(); + return changed; + } +} + +const addressNLSchema = (required, intl) => { + let postcodeSchema = z.string().regex(/^[1-9][0-9]{3} ?(?!sa|sd|ss|SA|SD|SS)[a-zA-Z]{2}$/); + let houseNumberSchema = z.string().regex(/^\d{1,5}$/); + if (!required) { + postcodeSchema = postcodeSchema.optional(); + houseNumberSchema = houseNumberSchema.optional(); + } + + return z + .object({ + postcode: postcodeSchema, + houseNumber: houseNumberSchema, + houseLetter: z + .string() + .regex(/^[a-zA-Z]$/) + .optional(), + houseNumberAddition: z + .string() + .regex(/^([a-zA-Z0-9]){1,4}$/) + .optional(), + }) + .superRefine((val, ctx) => { + if (!required) { + if (val.postcode && !val.houseNumber) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: intl.formatMessage({ + descripion: + 'ZOD error message when AddressNL postcode is provided but not houseNumber', + defaultMessage: 'You must provide a house number.', + }), + path: ['houseNumber'], + }); + } + + if (!val.postcode && val.houseNumber) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: intl.formatMessage({ + descripion: + 'ZOD error message when AddressNL houseNumber is provided but not postcode', + defaultMessage: 'You must provide a postcode.', + }), + path: ['postcode'], + }); + } + } + }); +}; + +const FormikAddress = ({required, setFormioValues}) => { + const {values, isValid} = useFormikContext(); + + useEffect(() => { + // *always* synchronize the state up, since: + // + // - we allow invalid values of a field to be saved in the backend when suspending + // the form + // - the field values don't change, but validation change states -> this can lead + // to missed backend-validation-plugin calls otherwise + setFormioValues(values, isValid); + }); + + return ( +
+
+
+ +
+
+ + } + placeholder="123" + isRequired={required} + /> +
+
+
+
+ + } + /> +
+
+ + } + /> +
+
+
+ ); +}; + +const PostCodeField = ({required}) => { + const {getFieldProps, getFieldHelpers} = useFormikContext(); + const {value, onBlur: onBlurFormik} = getFieldProps('postcode'); + const {setValue} = getFieldHelpers('postcode'); + + const onBlur = event => { + onBlurFormik(event); + // format the postcode with a space in between + const firstGroup = value.substring(0, 4); + const secondGroup = value.substring(4); + if (secondGroup && !secondGroup.startsWith(' ')) { + setValue(`${firstGroup} ${secondGroup}`); + } + }; + + return ( + + } + placeholder="1234 AB" + isRequired={required} + onBlur={onBlur} + /> + ); +}; diff --git a/src/formio/components/AddressNL.mocks.js b/src/formio/components/AddressNL.mocks.js new file mode 100644 index 000000000..68972200b --- /dev/null +++ b/src/formio/components/AddressNL.mocks.js @@ -0,0 +1,25 @@ +import {rest} from 'msw'; + +import {BASE_URL} from 'api-mocks'; + +export const mockBRKZaakgerechtigdeValidPost = rest.post( + `${BASE_URL}validation/plugins/brk-Zaakgerechtigde`, + (req, res, ctx) => { + const body = { + isValid: true, + messages: [], + }; + return res(ctx.json(body)); + } +); + +export const mockBRKZaakgerechtigdeInvalidPost = rest.post( + `${BASE_URL}validation/plugins/brk-Zaakgerechtigde`, + (req, res, ctx) => { + const body = { + isValid: false, + messages: ['User is not a zaakgerechtigde for property.'], + }; + return res(ctx.json(body)); + } +); diff --git a/src/formio/components/AddressNL.stories.js b/src/formio/components/AddressNL.stories.js new file mode 100644 index 000000000..cd5ce2107 --- /dev/null +++ b/src/formio/components/AddressNL.stories.js @@ -0,0 +1,184 @@ +import {expect} from '@storybook/jest'; +import {userEvent, waitFor, within} from '@storybook/testing-library'; + +import {withUtrechtDocument} from 'story-utils/decorators'; +import {ConfigDecorator} from 'story-utils/decorators'; + +import { + mockBRKZaakgerechtigdeInvalidPost, + mockBRKZaakgerechtigdeValidPost, +} from './AddressNL.mocks'; +import {SingleFormioComponent} from './story-util'; + +export default { + title: 'Form.io components / Custom / Address NL', + decorators: [withUtrechtDocument, ConfigDecorator], + args: { + type: 'addressNL', + key: 'addressNL', + label: 'Address NL', + extraComponentProperties: { + validate: { + required: true, + }, + }, + evalContext: {}, + }, + argTypes: { + key: {type: {required: true}}, + label: {type: {required: true}}, + type: {table: {disable: true}}, + }, +}; + +export const Pristine = { + render: SingleFormioComponent, +}; + +export const ClientSideValidation = { + render: SingleFormioComponent, + play: async ({canvasElement, step}) => { + const canvas = within(canvasElement); + + const postcodeInput = await canvas.findByLabelText('Postcode'); + const houseNumberInput = await canvas.findByLabelText('Huis nummer'); + const houseLetter = await canvas.findByLabelText('Huis letter'); + const houseNumberAddition = await canvas.findByLabelText('Huis nummer toevoeging'); + + await step('Fill only postcode - client side validation error', async () => { + userEvent.type(postcodeInput, '1234AB'); + expect(await canvas.findByText('Required')).toBeVisible(); + }); + + await step('Fill house number field', async () => { + userEvent.type(houseNumberInput, '1'); + + // ensure remaining fields are touched to reveal potential validation errors + userEvent.click(houseLetter); + houseLetter.blur(); + userEvent.click(houseNumberAddition); + houseNumberAddition.blur(); + + await waitFor(() => { + expect(houseNumberAddition).not.toHaveFocus(); + expect(canvas.queryByText('Required')).not.toBeInTheDocument(); + }); + }); + + await step('Clear postcode field, keep house number field', async () => { + userEvent.clear(postcodeInput); + expect(await canvas.findByText('Required')).toBeVisible(); + }); + }, +}; + +export const NotRequired = { + args: { + extraComponentProperties: { + validate: { + required: false, + }, + }, + }, + render: SingleFormioComponent, + play: async ({canvasElement, step}) => { + const canvas = within(canvasElement); + + const postcodeInput = await canvas.findByLabelText('Postcode'); + const houseNumberInput = await canvas.findByLabelText('Huis nummer'); + + await step('Enter only postcode, without house number', async () => { + userEvent.type(postcodeInput, '1234AB'); + expect(await canvas.findByText('You must provide a house number.')).toBeVisible(); + }); + + await step('Enter only house number, without postcode', async () => { + userEvent.clear(postcodeInput); + userEvent.type(houseNumberInput, '1'); + expect(await canvas.findByText('You must provide a postcode.')).toBeVisible(); + }); + }, +}; + +// const EXPECTED_VALIDATION_ERROR = 'User is not a zaakgerechtigde for property.'; + +export const WithPassingBRKValidation = { + render: SingleFormioComponent, + parameters: { + msw: { + handlers: [mockBRKZaakgerechtigdeValidPost], + }, + }, + args: { + type: 'addressNL', + key: 'addressNL', + label: 'Address NL', + extraComponentProperties: { + validate: { + required: false, + plugins: ['brk-Zaakgerechtigde'], + }, + }, + }, + play: async ({canvasElement, args, step}) => { + const canvas = within(canvasElement); + + const postcodeInput = await canvas.findByLabelText('Postcode'); + userEvent.type(postcodeInput, '1234AB'); + + const houseNumberInput = await canvas.findByLabelText('Huis nummer'); + userEvent.type(houseNumberInput, '1'); + + // this assertion is not worth much due to the async nature of the validators... + // expect(canvas.queryByText(EXPECTED_VALIDATION_ERROR)).not.toBeInTheDocument(); + }, +}; + +export const WithFailedBRKValidation = { + render: SingleFormioComponent, + parameters: { + msw: { + handlers: [mockBRKZaakgerechtigdeInvalidPost], + }, + }, + args: { + type: 'addressNL', + key: 'addressNL', + label: 'Address NL', + extraComponentProperties: { + validate: { + required: false, + plugins: ['brk-Zaakgerechtigde'], + }, + }, + }, + // We've spent considerable time trying to get this interaction test to work, but + // there seem to be race conditions all over the place with Formio, Storybook 7.0 (and + // testing-library 13 which is sync) and the hacky way the plugin validators work. + // We give up :( + // + // play: async ({canvasElement, args, step}) => { + // const canvas = within(canvasElement); + + // const postcodeInput = await canvas.findByLabelText('Postcode'); + // userEvent.type(postcodeInput, '1234AB', {delay: 50}); + // await waitFor(() => { + // expect(postcodeInput).toHaveDisplayValue('1234AB'); + // }); + + // const houseNumberInput = await canvas.findByLabelText('Huis nummer'); + // userEvent.type(houseNumberInput, '1'); + // await waitFor(() => { + // expect(houseNumberInput).toHaveDisplayValue('1'); + // }); + + // // blur so that error gets shown? + // houseNumberInput.blur(); + // await waitFor(() => { + // expect(houseNumberInput).not.toHaveFocus(); + // }); + // await waitFor(() => { + // expect(canvas.getByText(EXPECTED_VALIDATION_ERROR)).toBeVisible(); + // }); + // }, +}; diff --git a/src/formio/components/story-util.js b/src/formio/components/story-util.js index 0eea680ab..67435199e 100644 --- a/src/formio/components/story-util.js +++ b/src/formio/components/story-util.js @@ -27,6 +27,9 @@ const RenderFormioForm = ({configuration, submissionData = {}, evalContext = {}} }, // custom options intl, + ofContext: { + submissionUuid: '426c8d33-6dcb-4578-8208-f17071a4aebe', + }, }} /> ); diff --git a/src/formio/module.js b/src/formio/module.js index 47d1d5080..bb6e5b101 100644 --- a/src/formio/module.js +++ b/src/formio/module.js @@ -1,3 +1,4 @@ +import AddressNL from './components/AddressNL'; import BsnField from './components/BsnField'; import Button from './components/Button'; import Checkbox from './components/Checkbox'; @@ -46,6 +47,7 @@ const FormIOModule = { postcode: PostcodeField, phoneNumber: PhoneNumberField, bsn: BsnField, + addressNL: AddressNL, file: FileField, map: Map, password: PasswordField, diff --git a/src/formio/templates/addressNL.ejs b/src/formio/templates/addressNL.ejs new file mode 100644 index 000000000..5c806cc5f --- /dev/null +++ b/src/formio/templates/addressNL.ejs @@ -0,0 +1 @@ +
diff --git a/src/formio/templates/library.js b/src/formio/templates/library.js index 9a2411c17..040f5ae54 100644 --- a/src/formio/templates/library.js +++ b/src/formio/templates/library.js @@ -1,3 +1,4 @@ +import {default as AddressNLTemplate} from './addressNL.ejs'; import {default as ButtonTemplate} from './button.ejs'; import {default as CheckboxTemplate} from './checkbox.ejs'; import {default as ColumnsTemplate} from './columns.ejs'; @@ -35,6 +36,7 @@ const OFLibrary = { multiValueTable: {form: MultiValueTableTemplate}, editgrid: {form: EditGridTemplate}, editgridrow: {form: EditGridRowTemplate}, + addressNL: {form: AddressNLTemplate}, }; export default OFLibrary; diff --git a/src/formio/validators/plugins.js b/src/formio/validators/plugins.js index 625bf7c17..019d79ddb 100644 --- a/src/formio/validators/plugins.js +++ b/src/formio/validators/plugins.js @@ -1,16 +1,22 @@ +import {isEmpty} from 'lodash'; + import {post} from '../../api'; export const pluginsAPIValidator = { key: `validate.backendApi`, check(component, setting, value) { - if (!value) return true; + console.log('validator check method', value); + const checkIsEmpty = component.component?.openForms?.checkIsEmptyBeforePluginValidate || false; + const shortCutBecauseEmpty = checkIsEmpty && isEmpty(value); + if (!value || shortCutBecauseEmpty) return true; const plugins = component.component.validate.plugins; const {baseUrl} = component.currentForm?.options || component.options; + const {submissionUuid} = component.currentForm?.options.ofContext; const promises = plugins.map(plugin => { const url = `${baseUrl}validation/plugins/${plugin}`; - return post(url, {value}).then(response => { + return post(url, {value, submissionUuid}).then(response => { const valid = response.data.isValid; return valid ? true : response.data.messages.join('
'); }); diff --git a/src/i18n/compiled/en.json b/src/i18n/compiled/en.json index d8b1e582e..4202b5686 100644 --- a/src/i18n/compiled/en.json +++ b/src/i18n/compiled/en.json @@ -761,6 +761,12 @@ "value": "isApplicable" } ], + "LsgvKh": [ + { + "type": 0, + "value": "Postcode" + } + ], "LwpSC/": [ { "type": 0, @@ -805,6 +811,12 @@ "value": "Payment is required for this product" } ], + "PCv4sQ": [ + { + "type": 0, + "value": "House number" + } + ], "PjYrw0": [ { "type": 0, @@ -1175,6 +1187,18 @@ "value": "Your payment is received and processed." } ], + "cBsrax": [ + { + "type": 0, + "value": "House letter" + } + ], + "cHn60V": [ + { + "type": 0, + "value": "You must provide a house number." + } + ], "cKFCTI": [ { "type": 0, @@ -1463,6 +1487,12 @@ "value": "Product" } ], + "lVNV/d": [ + { + "type": 0, + "value": "House number addition" + } + ], "lY+Mza": [ { "type": 0, @@ -1589,6 +1619,12 @@ "value": "Use ⌘ + scroll to zoom the map" } ], + "p+11YF": [ + { + "type": 0, + "value": "You must provide a postcode." + } + ], "pguTkQ": [ { "type": 0, diff --git a/src/i18n/compiled/nl.json b/src/i18n/compiled/nl.json index 029afb22f..213aa08d2 100644 --- a/src/i18n/compiled/nl.json +++ b/src/i18n/compiled/nl.json @@ -761,6 +761,12 @@ "value": "isApplicable" } ], + "LsgvKh": [ + { + "type": 0, + "value": "Postcode" + } + ], "LwpSC/": [ { "type": 0, @@ -805,6 +811,12 @@ "value": "Voor dit product is betaling vereist" } ], + "PCv4sQ": [ + { + "type": 0, + "value": "Huis nummer" + } + ], "PjYrw0": [ { "type": 0, @@ -1179,6 +1191,18 @@ "value": "Uw betaling is ontvangen en verwerkt." } ], + "cBsrax": [ + { + "type": 0, + "value": "Huis letter" + } + ], + "cHn60V": [ + { + "type": 0, + "value": "You must provide a house number." + } + ], "cKFCTI": [ { "type": 0, @@ -1467,6 +1491,12 @@ "value": "Product" } ], + "lVNV/d": [ + { + "type": 0, + "value": "Huis nummer toevoeging" + } + ], "lY+Mza": [ { "type": 0, @@ -1593,6 +1623,12 @@ "value": "Gebruik ⌘ + scroll om te zoomen in de kaart" } ], + "p+11YF": [ + { + "type": 0, + "value": "You must provide a postcode." + } + ], "pguTkQ": [ { "type": 0, diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index c307b8b61..19bdcc119 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -344,6 +344,11 @@ "description": "Step label in progress indicator", "originalDefault": "{isApplicable, select, false {{label} (n/a)} other {{label}} }" }, + "LsgvKh": { + "defaultMessage": "Postcode", + "description": "Label for addressNL postcode input", + "originalDefault": "Postcode" + }, "LwpSC/": { "defaultMessage": "Total", "description": "Label for the total price to pay", @@ -374,6 +379,11 @@ "description": "Payment required info text", "originalDefault": "Payment is required for this product" }, + "PCv4sQ": { + "defaultMessage": "House number", + "description": "Label for addressNL houseNumber input", + "originalDefault": "House number" + }, "PjYrw0": { "defaultMessage": "Invalid input.", "description": "ZOD 'too_small' error message, generic", @@ -544,6 +554,15 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "cBsrax": { + "defaultMessage": "House letter", + "description": "Label for addressNL houseLetter input", + "originalDefault": "House letter" + }, + "cHn60V": { + "defaultMessage": "You must provide a house number.", + "originalDefault": "You must provide a house number." + }, "cKFCTI": { "defaultMessage": "Add another", "description": "Edit grid add button, default label text", @@ -694,6 +713,11 @@ "description": "Appoinments: product select label", "originalDefault": "Product" }, + "lVNV/d": { + "defaultMessage": "House number addition", + "description": "Label for addressNL houseNumberAddition input", + "originalDefault": "House number addition" + }, "lY+Mza": { "defaultMessage": "Product", "description": "Appointments products step page title", @@ -749,6 +773,10 @@ "description": "Gesturehandeling mac scroll message.", "originalDefault": "Use ⌘ + scroll to zoom the map" }, + "p+11YF": { + "defaultMessage": "You must provide a postcode.", + "originalDefault": "You must provide a postcode." + }, "pguTkQ": { "defaultMessage": "Intersection results could not be merged", "description": "ZOD 'invalid_intersection_types' error message", diff --git a/src/i18n/messages/nl.json b/src/i18n/messages/nl.json index 9a750d8cd..520b9afda 100644 --- a/src/i18n/messages/nl.json +++ b/src/i18n/messages/nl.json @@ -347,6 +347,12 @@ "description": "Step label in progress indicator", "originalDefault": "{isApplicable, select, false {{label} (n/a)} other {{label}} }" }, + "LsgvKh": { + "defaultMessage": "Postcode", + "description": "Label for addressNL postcode input", + "isTranslated": true, + "originalDefault": "Postcode" + }, "LwpSC/": { "defaultMessage": "Totaal", "description": "Label for the total price to pay", @@ -378,6 +384,11 @@ "description": "Payment required info text", "originalDefault": "Payment is required for this product" }, + "PCv4sQ": { + "defaultMessage": "Huis nummer", + "description": "Label for addressNL houseNumber input", + "originalDefault": "House number" + }, "PjYrw0": { "defaultMessage": "Ongeldige invoer.", "description": "ZOD 'too_small' error message, generic", @@ -550,6 +561,15 @@ "description": "payment registered status", "originalDefault": "Your payment is received and processed." }, + "cBsrax": { + "defaultMessage": "Huis letter", + "description": "Label for addressNL houseLetter input", + "originalDefault": "House letter" + }, + "cHn60V": { + "defaultMessage": "You must provide a house number.", + "originalDefault": "You must provide a house number." + }, "cKFCTI": { "defaultMessage": "Nog één toevoegen", "description": "Edit grid add button, default label text", @@ -703,6 +723,11 @@ "isTranslated": true, "originalDefault": "Product" }, + "lVNV/d": { + "defaultMessage": "Huis nummer toevoeging", + "description": "Label for addressNL houseNumberAddition input", + "originalDefault": "House number addition" + }, "lY+Mza": { "defaultMessage": "Product", "description": "Appointments products step page title", @@ -759,6 +784,10 @@ "description": "Gesturehandeling mac scroll message.", "originalDefault": "Use ⌘ + scroll to zoom the map" }, + "p+11YF": { + "defaultMessage": "You must provide a postcode.", + "originalDefault": "You must provide a postcode." + }, "pguTkQ": { "defaultMessage": "Intersectie-resultaten kunnen niet samengevoegd worden.", "description": "ZOD 'invalid_intersection_types' error message", diff --git a/src/jstests/formio/validators/pluginapivalidator.spec.js b/src/jstests/formio/validators/pluginapivalidator.spec.js index 8e18bfae2..d02a9d9f3 100644 --- a/src/jstests/formio/validators/pluginapivalidator.spec.js +++ b/src/jstests/formio/validators/pluginapivalidator.spec.js @@ -19,8 +19,13 @@ describe('The OpenForms plugins validation', () => { const component = { component: phoneNumberComponent, - options: { - baseUrl: BASE_URL, + currentForm: { + options: { + baseUrl: BASE_URL, + ofContext: { + submissionUuid: 'dummy', + }, + }, }, }; @@ -38,11 +43,15 @@ describe('The OpenForms plugins validation', () => { const component = { component: phoneNumberComponent, - options: { - baseUrl: BASE_URL, + currentForm: { + options: { + baseUrl: BASE_URL, + ofContext: { + submissionUuid: 'dummy', + }, + }, }, }; - for (const sample of validSamples) { const result = await pluginsAPIValidator.check(component, undefined, sample); expect(result).toBe(true); @@ -57,8 +66,13 @@ describe('The OpenForms plugins validation', () => { const component = { component: phoneNumberComponent, - options: { - baseUrl: BASE_URL, + currentForm: { + options: { + baseUrl: BASE_URL, + ofContext: { + submissionUuid: 'dummy', + }, + }, }, }; diff --git a/src/sdk.js b/src/sdk.js index f020ccd6f..7821c8e06 100644 --- a/src/sdk.js +++ b/src/sdk.js @@ -16,6 +16,7 @@ import {AddFetchAuth} from 'formio/plugins'; import {CSPNonce} from 'headers'; import {I18NErrorBoundary, I18NManager} from 'i18n'; import initialiseSentry from 'sentry'; +import {DEBUG} from 'utils'; import {getVersion} from 'utils'; import OpenFormsModule from './formio/module'; @@ -190,6 +191,7 @@ class OpenForm { displayComponents: this.displayComponents, // XXX: deprecate and refactor usage to use useFormContext? requiredFieldsWithAsterisk: this.formObject.requiredFieldsWithAsterisk, + debug: DEBUG, }} >