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

FIO-9360: validate current page only on wizard change #5920

Merged
merged 4 commits into from
Nov 25, 2024
Merged
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
17 changes: 4 additions & 13 deletions src/Wizard.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,8 +824,9 @@ export default class Wizard extends Webform {
}

validateCurrentPage(flags = {}) {
const components = this.currentPage?.components.map((component) => component.component);
// Accessing the parent ensures the right instance (whether it's the parent Wizard or a nested Wizard) performs its validation
return this.currentPage?.parent.validateComponents(this.currentPage.component.components, this.currentPage.parent.data, flags);
return this.currentPage?.parent.validateComponents(components, this.currentPage.parent.data, flags);
}

emitPrevPage() {
Expand Down Expand Up @@ -1000,7 +1001,8 @@ export default class Wizard extends Webform {

onChange(flags, changed, modified, changes) {
super.onChange(flags, changed, modified, changes);
const errors = this.validate(this.localData, { dirty: false });
// The onChange loop doesn't need all components for wizards
const errors = this.submitted ? this.validate(this.localData, { dirty: true }) : this.validateCurrentPage();
if (this.alert) {
this.showErrors(errors, true, true);
}
Expand Down Expand Up @@ -1073,17 +1075,6 @@ export default class Wizard extends Webform {
return super.errors;
}

showErrors(errors, triggerEvent) {
if (this.hasExtraPages) {
this.subWizards.forEach((subWizard) => {
if(Array.isArray(subWizard.errors)) {
errors = [...errors, ...subWizard.errors]
}
})
};
return super.showErrors(errors, triggerEvent)
}

focusOnComponent(key) {
const component = this.getComponent(key);
if (component) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ export default class FormComponent extends Component {
options = options || {};
const silentCheck = options.silentCheck || false;

if (this.subForm && !this.isNestedWizard) {
if (this.subForm) {
return this.subForm.checkValidity(this.subFormData, dirty, null, silentCheck, errors);
}

Expand Down
152 changes: 145 additions & 7 deletions test/unit/Wizard.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import formsWithAllowOverride from '../forms/formsWithAllowOverrideComps';
import WizardWithCheckboxes from '../forms/wizardWithCheckboxes';
import WizardWithRequiredFields from '../forms/wizardWithRequiredFields';
import formWithNestedWizardAndRequiredFields from '../forms/formWithNestedWizardAndRequiredFields';
import { wait } from '../util';

// eslint-disable-next-line max-statements
describe('Wizard tests', () => {
Expand Down Expand Up @@ -390,7 +391,7 @@ describe('Wizard tests', () => {
.catch((err) => done(err));
}).timeout(6000);

it('Should trigger validation of nested wizard before going to the next page', function(done) {
it('Should trigger validation of nested wizard before going to the next page', function (done) {
const formElement = document.createElement('div');
const wizard = new Wizard(formElement);
const nestedWizard = _.cloneDeep(WizardWithRequiredFields);
Expand All @@ -415,7 +416,7 @@ describe('Wizard tests', () => {
wizard.setForm(parentWizard).then(() => {
const nestedFormComp = wizard.getComponent('formNested');

nestedFormComp.loadSubForm = ()=> {
nestedFormComp.loadSubForm = () => {
nestedFormComp.formObj = nestedWizard;
nestedFormComp.subFormLoading = false;
return new Promise((resolve) => resolve(nestedWizard));
Expand All @@ -439,15 +440,152 @@ describe('Wizard tests', () => {
assert.equal(errors.length, 2, 'Must err before next page');
errors.forEach((error) => {
assert.equal(error.ruleName, 'required');
assert.equal(error.message, 'Text Field is required' , 'Should set correct lebel in the error message');
assert.equal(error.message, 'Text Field is required', 'Should set correct lebel in the error message');
});
done();
}, 300)
}, 300)
}, 300)
})
.catch((err) => done(err));
})
.catch((err) => done(err));
});

it('Should only validate the current page components when the form has not been submitted', async function () {
const wizardDefinition = {
display: 'wizard',
components: [
{
title: 'Page 1',
collapsible: false,
key: 'page1',
type: 'panel',
label: 'Panel',
input: false,
tableView: false,
components: [
{
type: 'checkbox',
label: 'Trigger Change',
input: true,
key: 'triggerChange',
},
{
label: 'Text Field',
applyMaskOn: 'change',
tableView: true,
validateWhenHidden: false,
key: 'textField',
type: 'textfield',
input: true,
validate: {
required: true
}
},
],
},
{
title: 'Page 2',
collapsible: false,
key: 'page2',
type: 'panel',
label: 'Panel',
input: false,
tableView: false,
components: [
{
label: 'Text Field',
applyMaskOn: 'change',
tableView: true,
validateWhenHidden: false,
key: 'textField1',
type: 'textfield',
validate: {
required: true
},
input: true,
},
],
},
],
};

const form = await Formio.createForm(document.createElement('div'), wizardDefinition);
assert(form, 'Form should be created');
const checkbox = form.getComponent('triggerChange');
const clickEvent = new Event('click');
checkbox.refs.input[0].dispatchEvent(clickEvent);
await wait(200);
assert.equal(form.errors.length, 1, 'Should have one error from the first page');
});

it('Should validate the entire wizard\'s components when the form has been submitted', async function () {
const wizardDefinition = {
display: 'wizard',
components: [
{
title: 'Page 1',
collapsible: false,
key: 'page1',
type: 'panel',
label: 'Panel',
input: false,
tableView: false,
components: [
{
type: 'checkbox',
label: 'Trigger Change',
input: true,
key: 'triggerChange',
},
{
label: 'Text Field',
applyMaskOn: 'change',
tableView: true,
validateWhenHidden: false,
key: 'textField',
type: 'textfield',
input: true,
validate: {
required: true
}
},
],
},
{
title: 'Page 2',
collapsible: false,
key: 'page2',
type: 'panel',
label: 'Panel',
input: false,
tableView: false,
components: [
{
label: 'Text Field',
applyMaskOn: 'change',
tableView: true,
validateWhenHidden: false,
key: 'textField1',
type: 'textfield',
validate: {
required: true
},
input: true,
},
],
},
],
};

const form = await Formio.createForm(document.createElement('div'), wizardDefinition);
assert(form, 'Form should be created');
form.submitted = true;
form.setSubmission({ data: { triggerChange: true } });
await wait(200);
assert(form.alert, 'Form should have an error list');
assert.equal(form.alert.querySelectorAll("span[ref=errorRef]").length, 2, 'Should have two errors total');
});


it('Should have validation errors when parent form is valid but nested wizard is not', function(done) {
const formElement = document.createElement('div');
Expand Down Expand Up @@ -482,11 +620,11 @@ describe('Wizard tests', () => {
assert.equal(wizard.submission.data.textField, 'test');
const nestedWizardBreadcrumbBtn = _.get(wizard.refs, `${wizard.wizardKey}-link[4]`);
nestedWizardBreadcrumbBtn.dispatchEvent(clickEvent);

setTimeout(() => {
checkPage(4);
_.get(wizard.refs, `${wizard.wizardKey}-submit`).dispatchEvent(clickEvent);

setTimeout(() => {
assert.equal(wizard.errors.length, 2);
done();
Expand Down
Loading