Skip to content

Commit

Permalink
✅ [open-formulieren/open-forms#4716] Added jest test for accessible f…
Browse files Browse the repository at this point in the history
…orm field validation behaviour
  • Loading branch information
robinmolen committed Oct 17, 2024
1 parent ff0f724 commit 34ad85a
Show file tree
Hide file tree
Showing 11 changed files with 1,635 additions and 5 deletions.
76 changes: 76 additions & 0 deletions src/formio/components/Checkbox.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {screen} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import _ from 'lodash';
import {Formio} from 'react-formio';

import OpenFormsModule from 'formio/module';

// Use our custom components
Formio.use(OpenFormsModule);

const selectboxesForm = {
type: 'form',
components: [
{
key: 'checkbox',
type: 'checkbox',
label: 'Checkbox',
validate: {
required: true,
},
},
],
};

const renderForm = async () => {
let formJSON = _.cloneDeep(selectboxesForm);
const container = document.createElement('div');
document.body.appendChild(container);
const form = await Formio.createForm(container, formJSON);
return {form, container};
};

describe('The checkbox component', () => {
afterEach(() => {
document.body.innerHTML = '';
});

test('Checkbox component required and checked', async () => {
const user = userEvent.setup({delay: 50});
const {form} = await renderForm();

const checkbox = screen.getByLabelText('Checkbox');

expect(checkbox).toBeVisible();

await user.click(checkbox);

expect(form.isValid()).toBeTruthy();
});

test('Checkbox component required without being checked', async () => {
const user = userEvent.setup({delay: 50});
const {form} = await renderForm();

const checkbox = screen.getByLabelText('Checkbox');

// Check and uncheck the checkbox to trigger the validation
await user.click(checkbox);
await user.click(checkbox);

// All selectboxes are marked as invalid and have aria-describedby and aria-invalid
expect(checkbox.className.includes('is-invalid')).toBeTruthy();
expect(checkbox.hasAttribute('aria-describedby')).toBeTruthy();
expect(checkbox.hasAttribute('aria-invalid')).toBeTruthy();
expect(checkbox.getAttribute('aria-invalid')).toBeTruthy();
expect(form.isValid()).toBeFalsy();

await user.click(checkbox);

// All checkboxes are again marked as valid and without aria-describedby and aria-invalid
expect(checkbox.className.includes('is-invalid')).toBeFalsy();
expect(checkbox.hasAttribute('aria-describedby')).toBeFalsy();
expect(checkbox.hasAttribute('aria-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});
});
78 changes: 78 additions & 0 deletions src/formio/components/Currency.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {screen} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import _ from 'lodash';
import {Formio} from 'react-formio';

import OpenFormsModule from 'formio/module';

// Use our custom components
Formio.use(OpenFormsModule);

const currencyForm = {
type: 'form',
components: [
{
key: 'currency',
type: 'currency',
label: 'Currency',
validate: {
required: true,
},
},
],
};

const renderForm = async () => {
let formJSON = _.cloneDeep(currencyForm);
const container = document.createElement('div');
document.body.appendChild(container);
const form = await Formio.createForm(container, formJSON);
return {form, container};
};

describe('The currency component', () => {
afterEach(() => {
document.body.innerHTML = '';
});

test('Single currency component with valid input', async () => {
const user = userEvent.setup({delay: 50});
const {form} = await renderForm();

const input = screen.getByLabelText('Currency');

expect(input).toBeVisible();

await user.type(input, '6');

expect(form.isValid()).toBeTruthy();
});

test('Single currency component with invalid input', async () => {
const user = userEvent.setup({delay: 50});
const {form} = await renderForm();

const input = screen.getByLabelText('Currency');

// Trigger validation
await user.type(input, '6');
await user.clear(input);
await user.tab({shift: true});

// Input is invalid and should have aria-describedby and aria-invalid
expect(input.className.includes('is-invalid')).toBeTruthy();
expect(input.hasAttribute('aria-describedby')).toBeTruthy();
expect(input.hasAttribute('aria-invalid')).toBeTruthy();
expect(input.getAttribute('aria-invalid')).toBeTruthy();
expect(form.isValid()).toBeFalsy();

await user.type(input, '6');
await user.tab({shift: true});

// Input is again valid and without aria-describedby and aria-invalid
expect(input.className.includes('is-invalid')).toBeFalsy();
expect(input.hasAttribute('aria-describedby')).toBeFalsy();
expect(input.hasAttribute('aria-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});
});
230 changes: 230 additions & 0 deletions src/formio/components/DateField.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import {waitFor} from '@storybook/test';
import {screen} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import 'flatpickr';
import _ from 'lodash';
import {Formio} from 'react-formio';
import {sleep} from 'src/utils';

import {getComponent} from 'formio/components/jest-util';
import OpenFormsModule from 'formio/module';

// Use our custom components
Formio.use(OpenFormsModule);

const dateForm = {
type: 'form',
components: [
{
key: 'date',
type: 'date',
label: 'Date',
format: 'dd-MM-yyyy',
validate: {
required: true,
},
},
],
};

const multipleDateForm = {
type: 'form',
components: [
{
key: 'date-multiple',
type: 'date',
label: 'Multiple date',
format: 'dd-MM-yyyy',
multiple: true,
validate: {
required: true,
},
},
],
};

const renderForm = async formConfig => {
let formJSON = _.cloneDeep(formConfig);
const container = document.createElement('div');
document.body.appendChild(container);
const form = await Formio.createForm(container, formJSON);
return {form, container};
};

const waitForFlatpickr = async node => {
let calendarNode;
for (let i = 0; i < 20; i++) {
calendarNode = node.querySelector('.flatpickr-calendar');
if (calendarNode !== null) return;
await sleep(100);
}
};

describe('The date component', () => {
afterEach(() => {
document.body.innerHTML = '';
});

test('Single date component with valid input', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(dateForm);
await waitForFlatpickr(container);

const input = screen.getByRole('textbox');

expect(input).toBeVisible();

await user.type(input, '16-08-2024');
await user.tab({shift: true});

expect(form.isValid()).toBeTruthy();
}, 10000);

test('Single date component with invalid input', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(dateForm);
await waitForFlatpickr(container);

const input = screen.getByRole('textbox');
expect(input).toBeVisible();

// Trigger validation
await user.type(input, '16-08-2024');
await user.tab({shift: true});
await user.clear(input);
await user.tab({shift: true});

// Input is invalid and should have aria-describedby and aria-invalid
expect(input.className.includes('is-invalid')).toBeTruthy();
expect(input.hasAttribute('aria-describedby')).toBeTruthy();
expect(input.hasAttribute('aria-invalid')).toBeTruthy();
expect(input.getAttribute('aria-invalid')).toBeTruthy();
expect(form.isValid()).toBeFalsy();

await user.type(input, '16-08-2024');
await user.tab({shift: true});

// Input is again valid and without aria-describedby and aria-invalid
await waitFor(async () => {
expect(input.className.includes('is-invalid')).toBeFalsy();
expect(input.hasAttribute('aria-describedby')).toBeFalsy();
expect(input.hasAttribute('aria-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});
}, 10000);
});

describe('The multiple date component', () => {
afterEach(() => {
document.body.innerHTML = '';
});

test('Multiple date component with valid input', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(multipleDateForm);
await waitForFlatpickr(container);

const input = screen.getByRole('textbox');

expect(input).toBeVisible();

await user.type(input, '16-08-2024');
await user.tab({shift: true});

expect(form.isValid()).toBeTruthy();
}, 10000);

test('Multiple date component with invalid input', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(multipleDateForm);
await waitForFlatpickr(container);

const input = screen.getByRole('textbox');

// Trigger validation
await user.type(input, '16-08-2024');
await user.tab({shift: true});
await user.clear(input);
await user.tab({shift: true});

// The field is invalid, and shouldn't have the aria-describedby or aria-invalid tags
expect(input.className.includes('is-invalid')).toBeTruthy();
expect(input.hasAttribute('aria-describedby')).toBeFalsy();
expect(input.hasAttribute('aria-invalid')).toBeFalsy();
expect(form.isValid()).toBeFalsy();

await user.type(input, '16-08-2024');
await user.tab({shift: true});

// The field is again valid

await waitFor(async () => {
expect(input.className.includes('is-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});
}, 10000);

test('Multiple date without inputs', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(multipleDateForm);
await waitForFlatpickr(container);

const input = screen.getByRole('textbox');
const component = getComponent(input);

// Trigger validation
await user.type(input, '16-08-2024');
await user.tab({shift: true});
await user.clear(input);
await user.tab({shift: true});

// Remove input
const [removeButton] = screen.getAllByRole('button');
await user.click(removeButton);

expect(component.className.includes('has-error')).toBeTruthy();
expect(form.isValid()).toBeFalsy();
}, 10000);

test('Multiple date with one valid and one invalid input', async () => {
const user = userEvent.setup({delay: 50});
const {form, container} = await renderForm(multipleDateForm);
await waitForFlatpickr(container);

await user.click(screen.getByRole('button', {name: 'Add Another'}));

const inputs = screen.getAllByRole('textbox');
expect(inputs).toHaveLength(2);

await user.type(inputs[0], '08-08-2024 14:45');
await user.type(inputs[1], '08-08-2024 14:45');
await user.click(container);

// The Both inputs are valid
await waitFor(async () => {
expect(inputs[0].className.includes('is-invalid')).toBeFalsy();
expect(inputs[1].className.includes('is-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});

await user.clear(inputs[0]);
await user.click(container);

// Only the invalid input is marked as invalid
await waitFor(async () => {
expect(inputs[0].className.includes('is-invalid')).toBeTruthy();
expect(inputs[1].className.includes('is-invalid')).toBeFalsy();
expect(form.isValid()).toBeFalsy();
});

await user.type(inputs[0], '16-08-2024');
await user.tab({shift: true});

// Both inputs are again valid
await waitFor(async () => {
expect(inputs[0].className.includes('is-invalid')).toBeFalsy();
expect(inputs[1].className.includes('is-invalid')).toBeFalsy();
expect(form.isValid()).toBeTruthy();
});
}, 10000);
});
Loading

0 comments on commit 34ad85a

Please sign in to comment.