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 authored and sergei-maertens committed Oct 24, 2024
1 parent 0a12116 commit 5d34ffa
Show file tree
Hide file tree
Showing 11 changed files with 1,625 additions and 5 deletions.
75 changes: 75 additions & 0 deletions src/formio/components/Checkbox.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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).toHaveClass('is-invalid');
expect(checkbox).toHaveAttribute('aria-describedby');
expect(checkbox).toHaveAttribute('aria-invalid', 'true');
expect(form.isValid()).toBeFalsy();

await user.click(checkbox);

// All checkboxes are again marked as valid and without aria-describedby and aria-invalid
expect(checkbox).not.toHaveClass('is-invalid');
expect(checkbox).not.toHaveAttribute('aria-describedby');
expect(checkbox).not.toHaveAttribute('aria-invalid');
expect(form.isValid()).toBeTruthy();
});
});
77 changes: 77 additions & 0 deletions src/formio/components/Currency.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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).toHaveClass('is-invalid');
expect(input).toHaveAttribute('aria-describedby');
expect(input).toHaveAttribute('aria-invalid', 'true');
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).not.toHaveClass('is-invalid');
expect(input).not.toHaveAttribute('aria-describedby');
expect(input).not.toHaveAttribute('aria-invalid');
expect(form.isValid()).toBeTruthy();
});
});
229 changes: 229 additions & 0 deletions src/formio/components/DateField.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
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 {getComponentNode} from 'formio/components/jest-util';
import OpenFormsModule from 'formio/module';
import {sleep} from 'utils';

// 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).toHaveClass('is-invalid');
expect(input).toHaveAttribute('aria-describedby');
expect(input).toHaveAttribute('aria-invalid', 'true');
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).not.toHaveClass('is-invalid');
expect(input).not.toHaveAttribute('aria-describedby');
expect(input).not.toHaveAttribute('aria-invalid', 'true');
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).toHaveClass('is-invalid');
expect(input).not.toHaveAttribute('aria-describedby');
expect(input).not.toHaveAttribute('aria-invalid');
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).not.toHaveClass('is-invalid');
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 = getComponentNode(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).toHaveClass('has-error');
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]).not.toHaveClass('is-invalid');
expect(inputs[1]).not.toHaveClass('is-invalid');
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]).toHaveClass('is-invalid');
expect(inputs[1]).not.toHaveClass('is-invalid');
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]).not.toHaveClass('is-invalid');
expect(inputs[1]).not.toHaveClass('is-invalid');
expect(form.isValid()).toBeTruthy();
});
}, 10000);
});
Loading

0 comments on commit 5d34ffa

Please sign in to comment.