Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OF#5006] Update addressNL component to manually fill in city and streetname #775

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/components/FormStep/FormStep.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,36 @@ export const SummaryProgressNotVisible = {
expect(canvas.queryByText(/Stap 1 van 1/)).toBeNull();
},
};

export const AddressNLManuallyTriggeredValidation = {
render,
args: {
formioConfiguration: {
display: 'form',
components: [
{
key: 'addressnl',
type: 'addressNL',
label: 'Address NL',
validate: {
required: false,
},
},
],
},
form: buildForm(),
submission: buildSubmission(),
},
play: async ({canvasElement}) => {
const canvas = within(canvasElement);

const postcodeInput = await canvas.findByLabelText('Postcode');
const submitButton = await canvas.findByRole('button', {name: 'Next'});

await userEvent.type(postcodeInput, '1017 CJ');

// wait for the check logic api call
await sleep(1800);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understood it correctly, the button is disabled for all this time, so I have to wait for it and then click on it.

await userEvent.click(submitButton);
},
};
119 changes: 84 additions & 35 deletions src/formio/components/AddressNL.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import {Formik, useFormikContext} from 'formik';
import debounce from 'lodash/debounce';
import {useContext, useEffect} from 'react';
import {createRef, useContext, useEffect, useState} from 'react';
import {createRoot} from 'react-dom/client';
import {Formio} from 'react-formio';
import {FormattedMessage, IntlProvider, defineMessages, useIntl} from 'react-intl';
Expand All @@ -26,6 +26,8 @@
// the edit grid renderRow otherwise wraps the result of getValueAsString in a
// readonly input...
this.component.template = 'hack';
// needed for manually triggering the formik validate method
this.formikInnerRef = createRef();
}

static schema(...extend) {
Expand Down Expand Up @@ -56,11 +58,37 @@
};
}

checkComponentValidity(data, dirty, row, options = {}) {
async checkComponentValidity(data, dirty, row, options = {}) {
let updatedOptions = {...options};
if (this.component.validate.plugins && this.component.validate.plugins.length) {
updatedOptions.async = true;
}

if (!dirty) {
return super.checkComponentValidity(data, dirty, row, updatedOptions);
}

// Trigger again formik validation in order to show the generic error along with the
// nested fields errors and prevent the form from being submitted.
// Tried to go deeper for this in formio but this will be properly handled in the new
// form renderer.
if (this.formikInnerRef.current) {
const errors = await this.formikInnerRef.current.validateForm();

Check warning on line 76 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L76

Added line #L76 was not covered by tests

if (Object.keys(errors).length > 0) {
this.setComponentValidity(

Check warning on line 79 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L79

Added line #L79 was not covered by tests
[
{
message: this.t('There are errors concerning the nested fields.'),
level: 'error',
},
],
true,
false
);
return false;

Check warning on line 89 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L89

Added line #L89 was not covered by tests
}
}
return super.checkComponentValidity(data, dirty, row, updatedOptions);
}

Expand All @@ -77,6 +105,7 @@
city: '',
streetName: '',
secretStreetCity: '',
autoPopulated: false,
};
}

Expand Down Expand Up @@ -169,6 +198,7 @@
deriveAddress={this.component.deriveAddress}
layout={this.component.layout}
setFormioValues={this.onFormikChange.bind(this)}
formikInnerRef={this.formikInnerRef}
/>
</ConfigContext.Provider>
</IntlProvider>
Expand Down Expand Up @@ -204,9 +234,25 @@
description: 'Label for addressNL houseNumber input',
defaultMessage: 'House number',
},
houseLetter: {
description: 'Label for addressNL houseLetter input',
defaultMessage: 'House letter',
},
houseNumberAddition: {
description: 'Label for addressNL houseNumberAddition input',
defaultMessage: 'House number addition',
},
streetName: {
description: 'Label for addressNL streetName input',
defaultMessage: 'Street name',
},
city: {
description: 'Label for addressNL city input',
defaultMessage: 'City',
},
});

const addressNLSchema = (required, intl, {postcode = {}, city = {}}) => {
const addressNLSchema = (required, deriveAddress, intl, {postcode = {}, city = {}}) => {
// Optionally use a user-supplied pattern/regex for more fine grained pattern
// validation, and if a custom error message was supplied, use it.
const postcodeRegex = postcode?.pattern
Expand All @@ -221,6 +267,7 @@
});
let postcodeSchema = z.string().regex(postcodeRegex, {message: postcodeErrorMessage});

let streetNameSchema = z.string();
const {pattern: cityPattern = '', errorMessage: cityErrorMessage = ''} = city;
let citySchema = z.string();
if (cityPattern) {
Expand All @@ -237,15 +284,22 @@
defaultMessage: 'House number must be a number with up to five digits (e.g. 456).',
}),
});

if (!required) {
postcodeSchema = postcodeSchema.optional();
houseNumberSchema = houseNumberSchema.optional();
streetNameSchema = streetNameSchema.optional();
citySchema = citySchema.optional();

Check warning on line 292 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L291-L292

Added lines #L291 - L292 were not covered by tests
} else if (!deriveAddress) {
streetNameSchema = streetNameSchema.optional();
citySchema = citySchema.optional();
}

return z
.object({
postcode: postcodeSchema,
city: citySchema.optional(),
streetName: streetNameSchema,
city: citySchema,
houseNumber: houseNumberSchema,
houseLetter: z
.string()
Expand Down Expand Up @@ -297,7 +351,14 @@
});
};

const AddressNLForm = ({initialValues, required, deriveAddress, layout, setFormioValues}) => {
const AddressNLForm = ({
initialValues,
required,
deriveAddress,
layout,
setFormioValues,
formikInnerRef,
}) => {
const intl = useIntl();

const {
Expand Down Expand Up @@ -340,14 +401,16 @@

return (
<Formik
innerRef={formikInnerRef}
initialValues={initialValues}
initialTouched={{
postcode: true,
houseNumber: true,
city: true,
streetName: true,
}}
validationSchema={toFormikValidationSchema(
addressNLSchema(required, intl, {
addressNLSchema(required, deriveAddress, intl, {
postcode: {
pattern: postcodePattern,
errorMessage: postcodeError,
Expand All @@ -373,6 +436,7 @@
const FormikAddress = ({required, setFormioValues, deriveAddress, layout}) => {
const {values, isValid, setFieldValue} = useFormikContext();
const {baseUrl} = useContext(ConfigContext);
const [isAddressAutoFilled, setAddressAutoFilled] = useState(true);
const useColumns = layout === 'doubleColumn';

useEffect(() => {
Expand All @@ -399,6 +463,12 @@
setFieldValue('city', data['city']);
setFieldValue('streetName', data['streetName']);
setFieldValue('secretStreetCity', data['secretStreetCity']);

// mark the auto-filled fields as populated and disabled when they have been both
// retrieved from the API and they do have a value
const dataRetrieved = !!(data['city'] && data['streetName']);
setAddressAutoFilled(dataRetrieved);
setFieldValue('autoPopulated', dataRetrieved);

Check warning on line 471 in src/formio/components/AddressNL.jsx

View check run for this annotation

Codecov / codecov/patch

src/formio/components/AddressNL.jsx#L470-L471

Added lines #L470 - L471 were not covered by tests
};

return (
Expand All @@ -411,45 +481,24 @@
>
<PostCodeField required={required} autoFillAddress={autofillAddress} />
<HouseNumberField required={required} autoFillAddress={autofillAddress} />
<TextField
name="houseLetter"
label={
<FormattedMessage
description="Label for addressNL houseLetter input"
defaultMessage="House letter"
/>
}
/>
<TextField name="houseLetter" label={<FormattedMessage {...FIELD_LABELS.houseLetter} />} />
<TextField
name="houseNumberAddition"
label={
<FormattedMessage
description="Label for addressNL houseNumberAddition input"
defaultMessage="House number addition"
/>
}
label={<FormattedMessage {...FIELD_LABELS.houseNumberAddition} />}
/>
{deriveAddress && (
<>
<TextField
name="streetName"
label={
<FormattedMessage
description="Label for addressNL streetName read only result"
defaultMessage="Street name"
/>
}
disabled
label={<FormattedMessage {...FIELD_LABELS.streetName} />}
disabled={isAddressAutoFilled}
isRequired={required}
/>
<TextField
name="city"
label={
<FormattedMessage
description="Label for addressNL city read only result"
defaultMessage="City"
/>
}
disabled
label={<FormattedMessage {...FIELD_LABELS.city} />}
disabled={isAddressAutoFilled}
isRequired={required}
/>
</>
)}
Expand Down
55 changes: 55 additions & 0 deletions src/formio/components/AddressNL.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {screen} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import {renderForm} from 'jstests/formio/utils';

const addressNLForm = {
type: 'form',
components: [
{
key: 'addressnl',
type: 'addressNL',
label: 'Address NL',
validate: {
required: true,
},
},
],
};

describe('The addressNL component', () => {
afterEach(() => {
document.body.innerHTML = '';
});
test('Postcode provided and missing housenumber', async () => {
const user = userEvent.setup({delay: 50});
await renderForm(addressNLForm, {
evalContext: {
requiredFieldsWithAsterisk: true,
},
});
const postcode = screen.getByLabelText('Postcode');
const houseNumber = screen.getByLabelText('House number');

await user.type(postcode, '1017 CJ');

expect(houseNumber).toHaveClass('utrecht-textbox--invalid');
expect(houseNumber).toHaveAttribute('aria-describedby');
expect(houseNumber).toHaveAttribute('aria-invalid');
});
test('Postcode missing and housenumber provided', async () => {
const user = userEvent.setup({delay: 50});
await renderForm(addressNLForm, {
evalContext: {
requiredFieldsWithAsterisk: true,
},
});
const postcode = screen.getByLabelText('Postcode');
const houseNumber = screen.getByLabelText('House number');

await user.type(houseNumber, '22');

expect(postcode).toHaveClass('utrecht-textbox--invalid');
expect(postcode).toHaveAttribute('aria-describedby');
expect(postcode).toHaveAttribute('aria-invalid');
});
});
Loading
Loading