Skip to content

Commit

Permalink
Merge branch 'master' into update-form-types
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanbond committed May 30, 2024
2 parents 1b5b070 + b12e8f5 commit 66139bc
Show file tree
Hide file tree
Showing 32 changed files with 835 additions and 47 deletions.
10 changes: 10 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,16 @@ Formio.createForm(document.getElementById('formio'), 'https://examples.form.io/e
- FIO-8091: Fixed missing metadata for html5 select component with default value
- FIO-7445: fixed an issue with interpolated data in HTML
- FIO-7507: publish-dev-tag-to-npm
- FIO-8330: fixed saving draft if saveDraft and skipDraftRestore are true
- FIO-7595: fixed incorrect value for conditionally hidden Checkbox
- FIO-8342: fixed triggering saveDraft after submitting the form
- FIO-8240: fixed skipDraftRestore effect for the nested Forms
- FIO-8360 fixed submission state for nested form
- FIO-7195: Fixes an issue where Select, Radio and SelectBoxes components with URL DataSource show values instead of labels in modal preview
- FIO-8302: Fixed issue with wizard api key overriding window.property objects
- FIO-8326: Recaptcha now requires type of event to be selected
- FIO-8234: Fixes an issue where Select with Resource data source renders values instead of labels in the read only mode
- FIO-8366: API key is not unique translation

## 5.0.0-rc.37
### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"core-js": "^3.37.0",
"dialog-polyfill": "^0.5.6",
"dom-autoscroller": "^2.3.4",
"dompurify": "^3.1.0",
"dompurify": "^3.1.1",
"downloadjs": "^1.4.7",
"dragula": "^3.7.3",
"eventemitter3": "^5.0.1",
Expand Down
19 changes: 13 additions & 6 deletions src/Webform.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,19 @@ export default class Webform extends NestedDataComponent {
this.language = this.i18next.language;

// See if we need to restore the draft from a user.
if (this.options.saveDraft && !this.options.skipDraftRestore) {
if (this.options.saveDraft) {
this.formReady.then(()=> {
const user = Formio.getUser();
// Only restore a draft if the submission isn't explicitly set.
if (user && !this.submissionSet) {
this.restoreDraft(user._id);
if (!this.options.skipDraftRestore) {
const user = Formio.getUser();
// Only restore a draft if the submission isn't explicitly set.
if (user && !this.submissionSet) {
this.restoreDraft(user._id);
}
}
else {
// Enable drafts
this.draftEnabled = true;
this.savingDraft = false;
}
});
}
Expand Down Expand Up @@ -773,7 +780,7 @@ export default class Webform extends NestedDataComponent {
const draft = fastCloneDeep(this.submission);
draft.state = 'draft';

if (!this.savingDraft) {
if (!this.savingDraft && !this.submitting) {
this.emit('saveDraftBegin');
this.savingDraft = true;
this.formio.saveSubmission(draft).then((sub) => {
Expand Down
101 changes: 99 additions & 2 deletions src/Webform.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
formWithCollapsedPanel,
formWithCustomFormatDate,
tooltipActivateCheckbox,
formWithObjectValueSelect
formWithObjectValueSelect,
} from '../test/formtest';
import UpdateErrorClassesWidgets from '../test/forms/updateErrorClasses-widgets';
import nestedModalWizard from '../test/forms/nestedModalWizard';
Expand Down Expand Up @@ -78,7 +78,9 @@ import formWithDeeplyNestedConditionalComps from '../test/forms/formWithDeeplyNe
import formWithValidation from '../test/forms/formWithValidation';
import formWithNotAllowedTags from '../test/forms/formWithNotAllowedTags';
import formWithValidateWhenHidden from '../test/forms/formWithValidateWhenHidden';
import formWithSelectRadioUrlDataSource from '../test/forms/selectRadioUrlDataSource';
const SpySanitize = sinon.spy(FormioUtils, 'sanitize');

global.requestAnimationFrame = (cb) => cb();
global.cancelAnimationFrame = () => {};

Expand Down Expand Up @@ -1270,7 +1272,7 @@ describe('Webform tests', function() {
.catch((err) => done(err));
});

it('Should show validation errors and update validation errors list when opening and editing edit grid rows in draft modal mode after pushing submit btn', function(done) {
it('Should show validation errors and update validation errors list when opening and editing edit grid rows in draft modal mode after pushing submit btn',function(done) {
const formElement = document.createElement('div');
const formWithDraftModals = new Webform(formElement, { sanitize: true });

Expand Down Expand Up @@ -4601,6 +4603,7 @@ describe('Webform tests', function() {
const originalMakeRequest = Formio.makeRequest;
let saveDraftCalls = 0;
let restoreDraftCalls = 0;
let state = null;
const scenario = {
restoreDraftError: false,
saveDraftError: false,
Expand All @@ -4624,6 +4627,11 @@ describe('Webform tests', function() {
? Promise.reject('Save Draft Error')
: Promise.resolve(fastCloneDeep(data));
}
if (type === 'submission' && method === 'post') {
state = data.state;
saveDraftCalls = ++saveDraftCalls;
return Promise.resolve(fastCloneDeep(data));
}
if (type === 'form' && method === 'get') {
return Promise.resolve(fastCloneDeep({
_id: '65cdd69efb1b9683c216fa1d',
Expand Down Expand Up @@ -4707,6 +4715,7 @@ describe('Webform tests', function() {
afterEach(() => {
saveDraftCalls = 0;
restoreDraftCalls = 0;
state = null;
scenario.restoreDraftError = false;
scenario.saveDraftError = false;
});
Expand Down Expand Up @@ -4810,6 +4819,94 @@ describe('Webform tests', function() {
}, 200);
}).catch((err) => done(err));
});

it('Should save the draft after changing the data if skipDraftRestore is set as true', function(done) {
const formElement = document.createElement('div');
Formio.createForm(
formElement,
'http://localhost:3000/zarbzxibjafpcjb/testdrafterrors',
{
saveDraft: true,
skipDraftRestore: true
}
).then((form) => {
setTimeout(() => {
assert.equal(restoreDraftCalls, 0, 'Should not restore Draft');
assert.equal(saveDraftCalls, 0);
assert.equal(_.isUndefined(form.submission.state), true);
const tfInput = form.getComponent('textField').refs.input[0];
tfInput.value = 'test';
const inputEvent = new Event('input');
tfInput.dispatchEvent(inputEvent);
setTimeout(() => {
assert.equal(restoreDraftCalls, 0);
assert.equal(saveDraftCalls, 1, 'Should save Draft');
assert.equal(state, 'draft');
done();
}, 300);
},200);
}).catch((err) => done(err));
});
});

it('Should render labels for Select, Radio and selectBoxes components when Data Source is URL', (done) => {
const element = document.createElement('div');
const form = new Webform(element);
const originalMakeRequest = Formio.makeRequest;

Formio.makeRequest = function() {
return new Promise(resolve => {
const values = [
{ name : 'Alabama', abbreviation : 'AL' },
{ name : 'Alaska', abbreviation: 'AK' },
{ name: 'American Samoa', abbreviation: 'AS' }
];
resolve(values);
});
};

form.setForm(formWithSelectRadioUrlDataSource).then(() => {
const selectBoxes = form.getComponent('selectBoxes');
const select = form.getComponent('select');
const radio = form.getComponent('radio');

selectBoxes.componentModal.openModal();
select.componentModal.openModal();
radio.componentModal.openModal();

setTimeout(() => {
form.setSubmission({
data: {
selectBoxes: { AL: false, AK: true, AS: true },
select: 'AL',
radio: 'AL',
}
});

setTimeout(() => {
selectBoxes.componentModal.closeModal();
select.componentModal.closeModal();
radio.componentModal.closeModal();

setTimeout(() => {
const previewSelectBoxes = selectBoxes.element.querySelector('[ref="openModal"]');
const previewSelect = select.element.querySelector('[ref="openModal"]');
const previewRadio = radio.element.querySelector('[ref="openModal"]');

assert.equal(previewSelectBoxes.innerHTML, '\n <span>Alaska</span>, <span>American Samoa</span>\n', 'Should show labels as a selected value' +
' for SelectBoxes component');
assert.equal(previewRadio.innerHTML, '\n <span>Alabama</span>\n', 'Should show label as a selected value' +
' for Radio component');
assert.equal(previewSelect.innerHTML, '\n <span>Alabama</span>\n', 'Should show label as a selected value' +
' for Select component');

Formio.makeRequest = originalMakeRequest;
done();
}, 300);
}, 300);
}, 300);
})
.catch((err) => done(err));
});

for (const formTest of FormTests) {
Expand Down
6 changes: 1 addition & 5 deletions src/WebformBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1285,14 +1285,10 @@ export default class WebformBuilder extends Component {

this.webform.everyComponent((comp) => {
const path = comp.path;
const errors = comp.visibleErrors || [];
if (repeatablePaths.includes(path)) {
comp.setCustomValidity(`API Key is not unique: ${comp.key}`);
comp.setCustomValidity(this.t('apiKey', { key: comp.key }));
hasInvalidComponents = true;
}
else if (errors.length && errors[0].message?.startsWith('API Key is not unique')) {
comp.setCustomValidity('');
}
});

this.emit('builderFormValidityChange', hasInvalidComponents);
Expand Down
23 changes: 22 additions & 1 deletion src/WebformBuilder.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Harness from '../test/harness';
import WebformBuilder from './WebformBuilder';
import Builders from './builders';
import { Formio } from './Formio';
import { uniqueApiKeys, uniqueApiKeysLayout, uniqueApiKeysSameLevel, columnsForm, resourceKeyCamelCase } from '../test/formtest';
import { uniqueApiKeys, uniqueApiKeysLayout, uniqueApiKeysSameLevel, columnsForm, resourceKeyCamelCase, uniqueApiKeysTranslation } from '../test/formtest';
import sameApiKeysLayoutComps from '../test/forms/sameApiKeysLayoutComps';
import testApiKeysUniquifying from '../test/forms/testApiKeysUniquifying';
import formBasedOnWizard from '../test/forms/formBasedOnWizard';
Expand Down Expand Up @@ -32,6 +32,27 @@ describe('WebformBuilder tests', function() {
done();
}, 500);
});
it('Should show API Key is not unique: {{key}} error when api keys are the same', (done) => {
const builder = Harness.getBuilder();
builder.i18next.currentLanguage = { apiKey: 'translated api key error {{key}}' };
builder.webform.setForm(uniqueApiKeysTranslation).then(()=>{
builder.highlightInvalidComponents();
const component = builder.webform.getComponent(['textField']);
assert.equal(component.visibleErrors.length, 1);
done();
}).catch(done);
});

it('Should show translated api key error {{key}} when apiKey is overridden in i18next translations', (done) => {
const builder = Harness.getBuilder();
builder.i18next.currentLanguage = { apiKey: 'translated api key error {{key}}' };
builder.webform.setForm(uniqueApiKeysTranslation).then(() => {
builder.highlightInvalidComponents();
const component = builder.webform.getComponent(['textField']);
assert.equal(component.visibleErrors[0].message,'translated api key error textField');
done();
}).catch(done);
});

it('Should not show unique API error when components with same keys are inside and outside of the Data component', (done) => {
const builder = Harness.getBuilder();
Expand Down
9 changes: 4 additions & 5 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { processOne, processOneSync, validateProcessInfo } from '@formio/core/pr
import { Formio } from '../../../Formio';
import * as FormioUtils from '../../../utils/utils';
import {
fastCloneDeep, boolValue, getComponentPath, isInsideScopingComponent, currentTimezone
fastCloneDeep, boolValue, getComponentPath, isInsideScopingComponent, currentTimezone, getScriptPlugin
} from '../../../utils/utils';
import Element from '../../../Element';
import ComponentModal from '../componentModal/ComponentModal';
Expand Down Expand Up @@ -2133,8 +2133,7 @@ export default class Component extends Element {
/**
* Add a new input error to this element.
*
* @param message
* @param dirty
* @param {{level: string, message: string}[]} messages
*/
addMessages(messages) {
if (!messages) {
Expand Down Expand Up @@ -3750,7 +3749,7 @@ Component.requireLibrary = function(name, property, src, polling) {
}.bind(Component.externalLibraries[name]);
}
// See if the plugin already exists.
const plugin = _.get(window, property);
const plugin = getScriptPlugin(property);
if (plugin) {
Component.externalLibraries[name].resolve(plugin);
}
Expand Down Expand Up @@ -3795,7 +3794,7 @@ Component.requireLibrary = function(name, property, src, polling) {
// if no callback is provided, then check periodically for the script.
if (polling) {
setTimeout(function checkLibrary() {
const plugin = _.get(window, property);
const plugin = getScriptPlugin(property);
if (plugin) {
Component.externalLibraries[name].resolve(plugin);
}
Expand Down
13 changes: 2 additions & 11 deletions src/components/checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,8 @@ export default class CheckBoxComponent extends Field {
}

setValue(value, flags = {}) {
if (
this.setCheckedState(value) !== undefined ||
(!this.input && value !== undefined && (this.visible || this.conditionallyVisible() || !this.component.clearOnHide))
) {
const changed = this.updateValue(value, flags);
if (this.isHtmlRenderMode() && flags && flags.fromSubmission && changed) {
this.redraw();
}
return changed;
}
return false;
this.setCheckedState(value);
return super.setValue(value, flags);
}

getValueAsString(value) {
Expand Down
26 changes: 25 additions & 1 deletion src/components/checkbox/Checkbox.unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
customDefaultComponent,
comp2,
comp3,
comp4
comp4,
comp5
} from './fixtures';

describe('Checkbox Component', () => {
Expand Down Expand Up @@ -91,4 +92,27 @@ describe('Checkbox Component', () => {
}, 300);
}).catch((err) => done(err));
});

it('Should set the value for the checkbox if it set before the component from checbox`s condition', (done) => {
const form = _.cloneDeep(comp5);
const element = document.createElement('div');
const data = {
textField: 'test',
checkboxBefore: true,
checkboxAfter: true
};
Formio.createForm(element, form).then(form => {
form.setValue({ data }, { sanitize: true });
const checkboxBefore = form.getComponent('checkboxBefore');
const checkboxAfter = form.getComponent('checkboxAfter');
setTimeout(() => {
const inputBefore = Harness.testElements(checkboxBefore, 'input[type="checkbox"]', 1)[0];
assert.equal(inputBefore.checked, true);
const inputAfter = Harness.testElements(checkboxAfter, 'input[type="checkbox"]', 1)[0];
assert.equal(inputAfter.checked, true);
assert.deepEqual(form.data, data);
done();
}, 300);
}).catch((err) => done(err));
});
});
Loading

0 comments on commit 66139bc

Please sign in to comment.