-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #726 from open-formulieren/feature/4546-soft-requi…
…red-validation Implement soft required validation
- Loading branch information
Showing
11 changed files
with
358 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import FormioUtils from 'formiojs/utils'; | ||
import {Formio} from 'react-formio'; | ||
|
||
const FormioContentField = Formio.Components.components.content; | ||
|
||
class SoftRequiredErrors extends FormioContentField { | ||
static schema(...extend) { | ||
return FormioContentField.schema( | ||
{ | ||
type: 'softRequiredErrors', | ||
key: 'softRequiredErrors', | ||
label: '', // not displayed anyway | ||
}, | ||
...extend | ||
); | ||
} | ||
|
||
constructor(component, options, data) { | ||
component.refreshOnChange = true; | ||
super(component, options, data); | ||
} | ||
|
||
get content() { | ||
// figure out which components use soft required validation | ||
const softRequiredComponents = []; | ||
FormioUtils.eachComponent(this.root.components, component => { | ||
if (component.component.openForms?.softRequired) { | ||
softRequiredComponents.push(component); | ||
} | ||
}); | ||
|
||
const missingFieldLabels = []; | ||
// check which components have an empty value | ||
for (const component of softRequiredComponents) { | ||
const isEmpty = component.isEmpty(); | ||
if (isEmpty) missingFieldLabels.push(component.label); | ||
} | ||
|
||
if (!missingFieldLabels.length) return ''; | ||
|
||
const missingFieldsMarkup = this.renderTemplate('missingFields', {labels: missingFieldLabels}); | ||
const content = this.interpolate(this.component.html, { | ||
missingFields: missingFieldsMarkup, | ||
}); | ||
|
||
return ` | ||
<div | ||
id="${this.id}-content" | ||
class="utrecht-alert utrecht-alert--warning openforms-soft-required-errors" | ||
role="status" | ||
> | ||
<div class="utrecht-alert__icon"> | ||
<i class="fa fas fa-exclamation-triangle"></i> | ||
</div> | ||
<div class="utrecht-alert__message"> | ||
${content} | ||
</div> | ||
</div> | ||
`; | ||
} | ||
} | ||
|
||
export default SoftRequiredErrors; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import {expect, userEvent, waitFor, within} from '@storybook/test'; | ||
|
||
import {withUtrechtDocument} from 'story-utils/decorators'; | ||
import {sleep} from 'utils'; | ||
|
||
import {UPLOAD_URL, mockFileUploadDelete, mockFileUploadPost} from './FileField.mocks'; | ||
import {MultipleFormioComponents} from './story-util'; | ||
|
||
export default { | ||
title: 'Form.io components / Custom / SoftRequiredErrors', | ||
decorators: [withUtrechtDocument], | ||
render: MultipleFormioComponents, | ||
args: { | ||
components: [ | ||
{ | ||
type: 'file', | ||
key: 'file', | ||
storage: 'url', | ||
url: UPLOAD_URL, | ||
label: 'Soft required file', | ||
multiple: false, | ||
openForms: {softRequired: true}, | ||
}, | ||
{ | ||
type: 'textfield', | ||
key: 'textfield', | ||
label: 'Soft required text', | ||
openForms: {softRequired: true}, | ||
}, | ||
{ | ||
type: 'softRequiredErrors', | ||
html: ` | ||
<p>Not all required fields are filled out. That can get expensive!</p> | ||
{{ missingFields }} | ||
<p>Are you sure you want to continue?</p> | ||
`, | ||
}, | ||
], | ||
}, | ||
|
||
argTypes: { | ||
components: {table: {disable: true}}, | ||
evalContext: {table: {disable: true}}, | ||
}, | ||
parameters: { | ||
controls: {sort: 'requiredFirst'}, | ||
msw: { | ||
handlers: [mockFileUploadPost, mockFileUploadDelete], | ||
}, | ||
}, | ||
}; | ||
|
||
export const EmptyFields = { | ||
play: async ({canvasElement}) => { | ||
const canvas = within(canvasElement); | ||
|
||
await canvas.findByText('Not all required fields are filled out. That can get expensive!'); | ||
const list = await canvas.findByRole('list', {name: 'Empty fields'}); | ||
const listItems = within(list).getAllByRole('listitem'); | ||
expect(listItems).toHaveLength(2); | ||
const content = listItems.map(item => item.textContent); | ||
expect(content).toEqual(['Soft required file', 'Soft required text']); | ||
}, | ||
}; | ||
|
||
export const FillField = { | ||
args: { | ||
components: [ | ||
{ | ||
type: 'textfield', | ||
key: 'textfield', | ||
label: 'Soft required text', | ||
openForms: {softRequired: true}, | ||
}, | ||
{ | ||
type: 'softRequiredErrors', | ||
html: ` | ||
<p>Not all required fields are filled out. That can get expensive!</p> | ||
{{ missingFields }} | ||
<p>Are you sure you want to continue?</p> | ||
`, | ||
}, | ||
], | ||
}, | ||
play: async ({canvasElement, step}) => { | ||
const canvas = within(canvasElement); | ||
// formio... :thisisfine: | ||
await sleep(100); | ||
|
||
const ERROR_TEXT = 'Not all required fields are filled out. That can get expensive!'; | ||
|
||
await step('Initial state', async () => { | ||
expect(await canvas.findByText(ERROR_TEXT)).toBeVisible(); | ||
const list = await canvas.findByRole('list', {name: 'Empty fields'}); | ||
const listItems = within(list).getAllByRole('listitem'); | ||
expect(listItems).toHaveLength(1); | ||
}); | ||
|
||
await step('Fill out field and remove error', async () => { | ||
const input = canvas.getByLabelText('Soft required text'); | ||
await userEvent.type(input, 'Not empty'); | ||
await waitFor(() => { | ||
expect(canvas.queryByText(ERROR_TEXT)).toBeNull(); | ||
}); | ||
}); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<span class="sr-only" id="{{ctx.id}}-missing-fields-header"> | ||
{{ ctx.t('Empty fields') }} | ||
</span> | ||
<ul class="utrecht-unordered-list openforms-soft-required-errors__missing-fields" aria-labelledby="{{ctx.id}}-missing-fields-header"> | ||
{% for (const label of ctx.labels) { %} | ||
<li class="utrecht-unordered-list__item">{{ label }}</li> | ||
{% } %} | ||
</ul> |
Oops, something went wrong.