diff --git a/src/components/select/Select.js b/src/components/select/Select.js index 068b2dd29e..b7f4bbe5ba 100644 --- a/src/components/select/Select.js +++ b/src/components/select/Select.js @@ -905,8 +905,8 @@ export default class SelectComponent extends ListComponent { removeItemButton: this.component.disabled ? false : _.get(this.component, 'removeItemButton', true), itemSelectText: '', classNames: { - containerOuter: 'choices form-group formio-choices', - containerInner: this.transform('class', 'form-control ui fluid selection dropdown') + containerOuter: ['choices', 'form-group', 'formio-choices'], + containerInner: this.transform('class', 'form-control ui fluid selection dropdown').split(' '), }, addItemText: false, allowHTML: true, @@ -1097,14 +1097,6 @@ export default class SelectComponent extends ListComponent { }); } - if (this.choices && choicesOptions.placeholderValue && this.choices._isSelectOneElement) { - this.addPlaceholderItem(choicesOptions.placeholderValue); - - this.addEventListener(input, 'removeItem', () => { - this.addPlaceholderItem(choicesOptions.placeholderValue); - }); - } - // Add value options. this.addValueOptions(); this.setChoicesValue(this.dataValue); @@ -1208,21 +1200,6 @@ export default class SelectComponent extends ListComponent { } } - addPlaceholderItem(placeholderValue) { - const items = this.choices._store.activeItems; - if (!items.length) { - this.choices._addItem({ - value: '', - label: placeholderValue, - choiceId: 0, - groupId: -1, - customProperties: null, - placeholder: true, - keyCode: null - }); - } - } - /* eslint-enable max-statements */ update() { if (this.component.dataSrc === 'custom') { diff --git a/src/utils/ChoicesWrapper.js b/src/utils/ChoicesWrapper.js index 9ae4d9829c..6cfc8b7ece 100644 --- a/src/utils/ChoicesWrapper.js +++ b/src/utils/ChoicesWrapper.js @@ -1,48 +1,8 @@ -import Choices from '@formio/choices.js'; - -/** - * TODO: REMOVE THIS ONCE THE PULL REQUEST HAS BEEN RESOLVED. - * - * https://github.com/jshjohnson/Choices/pull/788 - * - * This is intentionally not part of the extended class, since other components use Choices and need this fix as well. - * @type {Choices._generatePlaceholderValue} - * @private - */ -Choices.prototype._generatePlaceholderValue = function() { - if (this._isSelectElement && this.passedElement.placeholderOption) { - const { placeholderOption } = this.passedElement; - return placeholderOption ? placeholderOption.text : false; - } - const { placeholder, placeholderValue } = this.config; - const { - element: { dataset }, - } = this.passedElement; - - if (placeholder) { - if (placeholderValue) { - return placeholderValue; - } - - if (dataset.placeholder) { - return dataset.placeholder; - } - } +import Choices, { KeyCodeMap } from '@formio/choices.js'; - return false; -}; - -export const KEY_CODES = { - BACK_KEY: 46, - DELETE_KEY: 8, +const ExtendedKeyCodeMap = { + ...KeyCodeMap, TAB_KEY: 9, - ENTER_KEY: 13, - A_KEY: 65, - ESC_KEY: 27, - UP_KEY: 38, - DOWN_KEY: 40, - PAGE_UP_KEY: 33, - PAGE_DOWN_KEY: 34, }; class ChoicesWrapper extends Choices { @@ -74,29 +34,13 @@ class ChoicesWrapper extends Choices { this._wasTap = true; } - _handleButtonAction(activeItems, element) { - if (!this._isSelectOneElement) { - return super._handleButtonAction(activeItems, element); - } - - if ( - !activeItems || - !element || - !this.config.removeItems || - !this.config.removeItemButton - ) { - return; - } - - super._handleButtonAction(activeItems, element); - } - - _onEnterKey(args) { + _onEnterKey(...args) { + const [event] = args; // Prevent dropdown form opening when removeItemButton was pressed using 'Enter' on keyboard - if (args.event.target.className === 'choices__button') { + if (event.target.className === 'choices__button') { this.shouldOpenDropDown = false; } - super._onEnterKey(args); + super._onEnterKey(...args); } _onDirectionKey(...args) { @@ -116,21 +60,22 @@ class ChoicesWrapper extends Choices { }, 250); } - _onTabKey({ activeItems, hasActiveDropdown }) { - if (hasActiveDropdown) { - this._selectHighlightedChoice(activeItems); + _onTabKey() { + if (this.dropdown.isActive) { + this._selectHighlightedChoice(); } } _selectHighlightedChoice() { - const highlightedChoice = this.dropdown.getChild( + const highlightedChoice = this.dropdown.element.querySelector( `.${this.config.classNames.highlightedState}`, ); if (highlightedChoice) { const id = highlightedChoice.dataset.id; - const choice = id && this._store.getChoiceById(id); + const choice = id && this._store.getChoiceById(Number(id)); this._addItem({ + id: choice.id, value: choice.value, label: choice.label, choiceId: choice.id, @@ -141,84 +86,18 @@ class ChoicesWrapper extends Choices { }); this._triggerChange(choice.value); } - - event.preventDefault(); } _onKeyDown(event) { - if (!this._isSelectOneElement) { - return super._onKeyDown(event); - } - - const { target, keyCode, ctrlKey, metaKey } = event; - - if ( - target !== this.input.element && - !this.containerOuter.element.contains(target) - ) { - return; - } - - const activeItems = this._store.activeItems; - const hasFocusedInput = this.input.isFocussed; - const hasActiveDropdown = this.dropdown.isActive; - const hasItems = this.itemList.hasChildren; - const keyString = String.fromCharCode(keyCode); - - const { - BACK_KEY, - DELETE_KEY, - TAB_KEY, - ENTER_KEY, - A_KEY, - ESC_KEY, - UP_KEY, - DOWN_KEY, - PAGE_UP_KEY, - PAGE_DOWN_KEY, - } = KEY_CODES; - const hasCtrlDownKeyPressed = ctrlKey || metaKey; - - // If a user is typing and the dropdown is not active - if (!hasActiveDropdown && !this._isTextElement && /[a-zA-Z0-9-_ ]/.test(keyString)) { - const currentValue = this.input.element.value; - this.input.element.value = currentValue ? `${currentValue}${keyString}` : keyString; - this.showDropdown(); - } - - // Map keys to key actions - const keyDownActions = { - [A_KEY]: this._onAKey, - [TAB_KEY]: this._onTabKey, - [ENTER_KEY]: this._onEnterKey, - [ESC_KEY]: this._onEscapeKey, - [UP_KEY]: this._onDirectionKey, - [PAGE_UP_KEY]: this._onDirectionKey, - [DOWN_KEY]: this._onDirectionKey, - [PAGE_DOWN_KEY]: this._onDirectionKey, - [DELETE_KEY]: this._onDeleteKey, - [BACK_KEY]: this._onDeleteKey, - }; - - // If keycode has a function, run it - if (keyDownActions[keyCode]) { - keyDownActions[keyCode]({ - event, - target, - keyCode, - metaKey, - activeItems, - hasFocusedInput, - hasActiveDropdown, - hasItems, - hasCtrlDownKeyPressed, - }); - } + const keyCode = event.keyCode; + return this._isSelectOneElement && keyCode === ExtendedKeyCodeMap.TAB_KEY + ? this._onTabKey() + : super._onKeyDown(event); } - onSelectValue({ event, activeItems, hasActiveDropdown }) { + onSelectValue(event, hasActiveDropdown) { if (hasActiveDropdown) { - this._selectHighlightedChoice(activeItems); + this._selectHighlightedChoice(); } else if (this._isSelectOneElement) { this.showDropdown(); @@ -227,12 +106,14 @@ class ChoicesWrapper extends Choices { } showDropdown(...args) { - if (!this.shouldOpenDropDown) { - this.shouldOpenDropDown = true; - return; - } + setTimeout(() => { + if (!this.shouldOpenDropDown) { + this.shouldOpenDropDown = true; + return; + } - super.showDropdown(...args); + super.showDropdown(...args); + }, 0); } hideDropdown(...args) { @@ -242,13 +123,6 @@ class ChoicesWrapper extends Choices { super.hideDropdown(...args); } - - _onBlur(...args) { - if (this._isScrollingOnIe) { - return; - } - super._onBlur(...args); - } } export default ChoicesWrapper; diff --git a/test/unit/Radio.unit.js b/test/unit/Radio.unit.js index a4fd032280..e37800eddd 100644 --- a/test/unit/Radio.unit.js +++ b/test/unit/Radio.unit.js @@ -343,9 +343,7 @@ describe('Radio Component', () => { }, 350); }).catch(done); }); -}); -describe('Radio Component', () => { it('should have red asterisk left hand side to the options labels if component is required and label is hidden', () => { return Harness.testCreate(RadioComponent, comp7).then(component => { const options = component.element.querySelectorAll('.form-check-label'); diff --git a/test/unit/Select.unit.js b/test/unit/Select.unit.js index 40b7d4eb18..2266cdf732 100644 --- a/test/unit/Select.unit.js +++ b/test/unit/Select.unit.js @@ -41,834 +41,703 @@ import { comp28 } from './fixtures/select'; +global.requestAnimationFrame = cb => cb(); +window.matchMedia = window.matchMedia || function() { + return { + matches : false, + addListener : function() {}, + removeListener: function() {} + }; +}; +window.scrollTo = () => {}; + +const timeout = (delay) => new Promise((resolve) => setTimeout(resolve, delay)); +const mockDebounce = (timeout, cb) => { + const originalDebounce = _.debounce; + + _.debounce = (fn, wait, opts) => { + cb?.(wait); + return originalDebounce(fn, timeout, opts); + } + + return () => { + _.debounce = originalDebounce; + } +}; +const mockMakeRequest = (func) => { + const originalMakeRequest = Formio.makeRequest; + Formio.makeRequest = func; + + return () => { + Formio.makeRequest = originalMakeRequest; + } +} + + // eslint-disable-next-line max-statements describe('Select Component', () => { - it('should not stringify select option value', function(done) { - Harness.testCreate(SelectComponent, comp6).then((component) => { - component.setValue({ value:'a', label:'A' }); - setTimeout(()=> { - assert.equal(component.choices._currentState.items[0].value.value, 'a'); - assert.equal(typeof component.choices._currentState.items[0].value , 'object'); - assert.equal(component.dataValue.value, 'a'); - assert.equal(typeof component.dataValue , 'object'); - done(); - }, 200); - }); + it('Should not stringify select option value', async () => { + const component = await Harness.testCreate(SelectComponent, comp6); + await component.itemsLoaded; + component.setValue({ value:'a', label:'A' }); + + await component.itemsLoaded; + assert.equal(typeof component.choices._store.items[0].value , 'object'); + assert.equal(component.choices._store.items[0].value.value, 'a'); + assert.equal(component.dataValue.value, 'a'); + assert.equal(typeof component.dataValue , 'object'); }); - it('should return string value for different value types', function(done) { - Harness.testCreate(SelectComponent, comp4).then((component) => { - const stringValue = component.asString(true); - const stringValue1 = component.asString(11); - const stringValue2 = component.asString('test'); - const stringValue3 = component.asString(12); - const stringValue4 = component.asString([1, 2, 3]); - assert.equal(stringValue, 'true'); - assert.equal(stringValue1, '11'); - assert.equal(stringValue2, 'test'); - assert.equal(stringValue3, '1.2'); - assert.equal(stringValue4, '1,2,3'); - done(); - }); + it('Should return string value for different value types', async () => { + const component = await Harness.testCreate(SelectComponent, comp4); + const stringValue = component.asString(true); + const stringValue1 = component.asString(11); + const stringValue2 = component.asString('test'); + const stringValue3 = component.asString(12); + const stringValue4 = component.asString([1, 2, 3]); + assert.equal(stringValue, 'true'); + assert.equal(stringValue1, '11'); + assert.equal(stringValue2, 'test'); + assert.equal(stringValue3, '1.2'); + assert.equal(stringValue4, '1,2,3'); }); - it('should return string value if dataSrc set as custom', function(done) { - Harness.testCreate(SelectComponent, comp27).then((component) => { - const stringValue = component.asString('California'); - assert.equal(stringValue, 'California'); - done(); - }); - }) + it('Should return string value if dataSrc set as custom', async () => { + const component = await Harness.testCreate(SelectComponent, comp27); + const stringValue = component.asString('California'); + assert.equal(stringValue, 'California'); + }); - it('Should return plain text when csv option is provided', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - assert.equal(component.getView('red', { csv:true }), 'Red'); - }); + it('Should return plain text when csv option is provided', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + assert.equal(component.getView('red', { csv: true }), 'Red'); }); - it('should correctly determine storage type when dataType is auto', function(done) { - Harness.testCreate(SelectComponent, comp4).then((component) => { - const value = component.normalizeSingleValue('true'); - const value1 = component.normalizeSingleValue('11'); - const value2 = component.normalizeSingleValue('test'); - const value3 = component.normalizeSingleValue('11test11test'); - const value4 = component.normalizeSingleValue('test11'); - const value5 = component.normalizeSingleValue('0'); - const value6 = component.normalizeSingleValue(''); - assert.equal(typeof value, 'boolean'); - assert.equal(typeof value1, 'number'); - assert.equal(typeof value2, 'string'); - assert.equal(typeof value3, 'string'); - assert.equal(typeof value4, 'string'); - assert.equal(typeof value5, 'number'); - assert.equal(typeof value6, 'string'); - done(); - }); + it('Should correctly determine storage type when dataType is auto', async () => { + const component = await Harness.testCreate(SelectComponent, comp4); + const value = component.normalizeSingleValue('true'); + const value1 = component.normalizeSingleValue('11'); + const value2 = component.normalizeSingleValue('test'); + const value3 = component.normalizeSingleValue('11test11test'); + const value4 = component.normalizeSingleValue('test11'); + const value5 = component.normalizeSingleValue('0'); + const value6 = component.normalizeSingleValue(''); + assert.equal(typeof value, 'boolean'); + assert.equal(typeof value1, 'number'); + assert.equal(typeof value2, 'string'); + assert.equal(typeof value3, 'string'); + assert.equal(typeof value4, 'string'); + assert.equal(typeof value5, 'number'); + assert.equal(typeof value6, 'string'); }); - it('should not stringify default empty values', function(done) { - Harness.testCreate(SelectComponent, comp4).then((component) => { - const value = component.normalizeSingleValue({}); - const value1 = component.normalizeSingleValue([]); - assert.equal(typeof value, 'object'); - assert.equal(typeof value1, 'object'); - done(); - }); + it('Should not stringify default empty values', async () => { + const component = await Harness.testCreate(SelectComponent, comp4); + const value = component.normalizeSingleValue({}); + const value1 = component.normalizeSingleValue([]); + assert.deepEqual(value, {}); + assert.deepEqual(value1, []); }); - it('should not change value letter case', function(done) { - Harness.testCreate(SelectComponent, comp4).then((component) => { - const value = component.normalizeSingleValue('data.textArea'); - const value1 = component.normalizeSingleValue('ECMAScript'); - const value2 = component.normalizeSingleValue('JS'); - assert.equal(value, 'data.textArea'); - assert.equal(value1, 'ECMAScript'); - assert.equal(value2, 'JS'); - done(); - }); + it('Should not change value letter case', async () => { + const component = await Harness.testCreate(SelectComponent, comp4); + const value = component.normalizeSingleValue('data.textArea'); + const value1 = component.normalizeSingleValue('ECMAScript'); + const value2 = component.normalizeSingleValue('JS'); + assert.equal(value, 'data.textArea'); + assert.equal(value1, 'ECMAScript'); + assert.equal(value2, 'JS'); }); - it('should define boolean value', function(done) { - Harness.testCreate(SelectComponent, comp4).then((component) => { - const value = component.normalizeSingleValue('TRUE'); - const value1 = component.normalizeSingleValue('False'); - const value2 = component.normalizeSingleValue('true'); - assert.equal(value, true); - assert.equal(value1, false); - assert.equal(value2, true); - done(); - }); + it('Should define boolean value', async () => { + const component = await Harness.testCreate(SelectComponent, comp4); + const value = component.normalizeSingleValue('TRUE'); + const value1 = component.normalizeSingleValue('False'); + const value2 = component.normalizeSingleValue('true'); + assert.equal(value, true); + assert.equal(value1, false); + assert.equal(value2, true); }); - it('1/2 should not display empty choice options if property value is not defined', function(done) { - Harness.testCreate(SelectComponent, comp5).then((component) => { - component.setItems([{ - 'label': '111', - 'value': '111' - }, { - 'label': '222', - 'value': '222' - }, { - 'label': '333', - 'value': '333' - }], false); - assert.equal(component.selectOptions.length, 0); - done(); - }); + it('Should not display empty choice options if property value is not defined', async () => { + const component = await Harness.testCreate(SelectComponent, comp5); + component.setItems([{ + 'label': '111', + 'value': '111' + }, { + 'label': '222', + 'value': '222' + }, { + 'label': '333', + 'value': '333' + }], false); + assert.equal(component.selectOptions.length, 0); }); - it('2/2 should display choice option if property value is set', function(done) { + it('Should display choice option if property value is set', async () => { comp5.template = '{{ item.label }}'; - Harness.testCreate(SelectComponent, comp5).then((component) => { - component.setItems([{ - 'label': '111', - 'value': '111' - }, { - 'label': '222', - 'value': '222' - }, { - 'label': '333', - 'value': '333' - }], false); - assert.equal(component.selectOptions.length, 3); - done(); - }); + const component = await Harness.testCreate(SelectComponent, comp5); + component.setItems([{ + 'label': '111', + 'value': '111' + }, { + 'label': '222', + 'value': '222' + }, { + 'label': '333', + 'value': '333' + }], false); + assert.equal(component.selectOptions.length, 3); }); - it('should have only unique dropdown options', function(done) { + it('Should have only unique dropdown options', async () => { comp5.template = '{{ item.label }}'; comp5.uniqueOptions = true; - Harness.testCreate(SelectComponent, comp5).then((component) => { - component.setItems([{ - 'label': 'Label 1', - 'value': 'value1' - }, { - 'label': 'Label 2', - 'value': 'value2' - }, { - 'label': 'Label 3', - 'value': 'value3' - }, { - 'label': 'Label 4', - 'value': 'value3' - }], false); - - assert.equal(component.selectOptions.length, 3); - done(); - }); + const component = await Harness.testCreate(SelectComponent, comp5); + component.setItems([{ + 'label': 'Label 1', + 'value': 'value1' + }, { + 'label': 'Label 2', + 'value': 'value2' + }, { + 'label': 'Label 3', + 'value': 'value3' + }, { + 'label': 'Label 4', + 'value': 'value3' + }], false); + assert.equal(component.selectOptions.length, 3); }); - it('should format unlisted values', function(done) { + it('Should format unlisted values', async () => { comp5.template = '{{ item.label }}'; - Harness.testCreate(SelectComponent, comp5).then((component) => { - const formattedValue1 = component.getView('Unlisted value'); - const formattedValue2 = component.getView(0); - - assert.equal(formattedValue1, 'Unlisted value'); - assert.equal(formattedValue2, '0'); - done(); - }); - }); - - it('should set multiple selected values not repeating them', function(done) { - Harness.testCreate(SelectComponent, multiSelect).then((component) => { - component.setItems(multiSelectOptions, false); - component.setChoicesValue(['Cheers']); - component.setChoicesValue(['Cheers', 'Cyberdyne Systems'], 1); - component.setChoicesValue(['Cheers', 'Cyberdyne Systems', 'Massive Dynamic'], 2); - const choices = component.element.querySelector('.choices__list--multiple').children; - assert.equal(choices.length, 3); - done(); - }); + const component = await Harness.testCreate(SelectComponent, comp5); + assert.equal(component.getView('Unlisted value'), 'Unlisted value'); + assert.equal(component.getView(0), '0'); }); - it('should not show selected values in dropdown when searching', function(done) { - Harness.testCreate(SelectComponent, multiSelect).then((component) => { - component.setItems(multiSelectOptions, false); - component.setChoicesValue(['Cheers']); - component.setChoicesValue(['Cheers', 'Cyberdyne Systems'], 1); - component.setItems([], true); - const itemsInDropdown = component.element.querySelectorAll('.choices__item--choice'); - const choices = component.element.querySelector('.choices__list--multiple').children; - assert.equal(choices.length, 2); - assert.equal(itemsInDropdown.length, 1); - done(); - }); + it('Should set multiple selected values not repeating them', async () => { + const component = await Harness.testCreate(SelectComponent, multiSelect); + component.setItems(multiSelectOptions, false); + component.setChoicesValue(['Cheers']); + component.setChoicesValue(['Cheers', 'Cyberdyne Systems'], 1); + component.setChoicesValue(['Cheers', 'Cyberdyne Systems', 'Massive Dynamic'], 2); + const choices = component.element.querySelector('.choices__list--multiple').children; + assert.equal(choices.length, 3); }); - it('Should build a Select component', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - Harness.testElements(component, 'select', 1); - }); + it('Should not show selected values in dropdown when searching', async () => { + const component = await Harness.testCreate(SelectComponent, multiSelect); + component.setItems(multiSelectOptions, false); + component.setChoicesValue(['Cheers']); + component.setChoicesValue(['Cheers', 'Cyberdyne Systems'], 1); + component.setItems([], true); + const itemsInDropdown = component.element.querySelectorAll('.choices__item--choice'); + const choices = component.element.querySelector('.choices__list--multiple').children; + assert.equal(choices.length, 2); + assert.equal(itemsInDropdown.length, 1); }); - it('Should preserve the tabindex', () => { - return Harness.testCreate(SelectComponent, comp2).then((component) => { - const element = component.element.getElementsByClassName('choices__list choices__list--single')[0]; - Harness.testElementAttribute(element, 'tabindex', '10'); - }); + it('Should build a Select component', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + Harness.testElements(component, 'select', 1); }); - it('Should default to 0 when tabindex is not specified', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - const element = component.element.getElementsByClassName('choices__list choices__list--single')[0]; - Harness.testElementAttribute(element, 'tabindex', '0'); - }); + it('Should preserve the tabindex', async () => { + const component = await Harness.testCreate(SelectComponent, comp2); + Harness.testElementAttribute(component.focusableElement, 'tabindex', '10'); }); - it('Should allow to override threshold option of fuzzy search', () => { - try { - const c1 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.2 }); - const c2 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.4 }); - const c3 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.8 }); - const comps = [ - Harness.testCreate(SelectComponent, c1), - Harness.testCreate(SelectComponent, c2), - Harness.testCreate(SelectComponent, c3), - ]; - - return Promise - .all(comps) - .then(([a, b, c]) => { - expect(a.choices.config.fuseOptions.threshold).to.equal(0.2); - expect(b.choices.config.fuseOptions.threshold).to.equal(0.4); - expect(c.choices.config.fuseOptions.threshold).to.equal(0.8); - }); - } - catch (error) { - return Promise.reject(error); - } + it('Should default to 0 when tabindex is not specified', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + Harness.testElementAttribute(component.focusableElement, 'tabindex', '0'); }); - it('should set component value', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - assert.deepEqual(component.dataValue, ''); - component.setValue('red'); - assert.equal(component.dataValue, 'red'); - }); + it('Should allow to override threshold option of fuzzy search', async () => { + const c1 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.2 }); + const c2 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.4 }); + const c3 = Object.assign(cloneDeep(comp1), { selectThreshold: 0.8 }); + + const [a, b, c] = await Promise.all([ + Harness.testCreate(SelectComponent, c1), + Harness.testCreate(SelectComponent, c2), + Harness.testCreate(SelectComponent, c3), + ]); + expect(a.choices.config.fuseOptions.threshold).to.equal(0.2); + expect(b.choices.config.fuseOptions.threshold).to.equal(0.4); + expect(c.choices.config.fuseOptions.threshold).to.equal(0.8); }); - it('should remove selected item', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - assert.deepEqual(component.dataValue, ''); - component.setValue('red'); - assert.equal(component.dataValue, 'red'); - - const element = component.element.getElementsByClassName('choices__button')[0]; - component.choices._handleButtonAction(component.choices._store.activeItems, element); + it('Should set component value', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + await component.itemsLoaded; + assert.equal(component.dataValue, ''); + component.setValue('red'); - assert.equal(component.dataValue, ''); - }); + await component.itemsLoaded; + assert.equal(component.dataValue, 'red'); + assert.equal(component.choices._store.items[0]?.value, 'red'); }); - it('should open dropdown after item has been removed', () => { - global.requestAnimationFrame = cb => cb(); - window.matchMedia = window.matchMedia || function() { - return { - matches : false, - addListener : function() {}, - removeListener: function() {} - }; - }; - - return Harness.testCreate(SelectComponent, comp1).then((component) => { - component.setValue('red'); - - const element = component.element.getElementsByClassName('choices__button')[0]; - component.choices._handleButtonAction(component.choices._store.activeItems, element); - - component.choices.showDropdown(true); - - assert.equal(component.choices.dropdown.isActive, true); - }); + it('Should remove selected item', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + await component.itemsLoaded; + assert.deepEqual(component.dataValue, ''); + component.setValue('red'); + + await component.itemsLoaded; + assert.equal(component.dataValue, 'red'); + const element = component.element.querySelector('.choices__button'); + component.choices._handleButtonAction(element); + assert.equal(component.dataValue, ''); }); - it('should keep dropdown closed after item has been removed by keypress', () => { - return Harness.testCreate(SelectComponent, comp1).then((component) => { - component.setValue('red'); - - const element = component.element.querySelector('.choices__button'); - const ke = new KeyboardEvent('keydown', { - bubbles: true, cancelable: true, keyCode: 13 - }); - - element.dispatchEvent(ke); - - assert.equal(component.dataValue, ''); - assert.equal(component.choices.dropdown.isActive, false); + it('Should keep dropdown closed after item has been removed by keypress', async () => { + const component = await Harness.testCreate(SelectComponent, comp1); + await component.itemsLoaded; + component.setValue('red'); + + await component.itemsLoaded; + assert.equal(component.dataValue, 'red'); + assert.equal(component.choices._store.items.length, 1); + const element = component.element.querySelector('.choices__button'); + const event = new KeyboardEvent('keydown', { + bubbles: true, cancelable: true, keyCode: 13 }); - }); + element.dispatchEvent(event); - it('Should render and set values in selects with different widget types', (done) => { - const form = _.cloneDeep(comp7); - const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const selectHTML = form.getComponent('selectHtml'); - const selectChoices = form.getComponent('selectChoices'); - assert.equal(!!selectHTML.choices, false); - assert.equal(!!selectChoices.choices, true); - - setTimeout(() => { - assert.equal(selectChoices.element.querySelectorAll('.choices__item--choice').length, 3); - const value = 'b'; - selectHTML.setValue(value); - selectChoices.setValue(value); - - setTimeout(() => { - assert.equal(selectHTML.dataValue, value); - assert.equal(selectChoices.dataValue, value); - assert.equal(selectHTML.getValue(), value); - assert.equal(selectChoices.getValue(), value); - - done(); - }, 200); - }, 200); - }).catch(done); + await timeout(0); + assert.equal(component.choices._store.items.length, 0); + assert.equal(component.dataValue, ''); + assert.equal(component.choices.dropdown.isActive, false); }); - it('Should clear select value when "clear value on refresh options" and "refresh options on" is enable and number component is changed ', (done) => { - const form = _.cloneDeep(comp8); + it('Should render and set values in selects with different widget types', async () => { const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const numberComp = form.getComponent('number'); - const value = 'b'; - select.setValue(value); - - setTimeout(() => { - assert.equal(select.dataValue, value); - assert.equal(select.getValue(), value); - const numberInput = numberComp.refs.input[0]; - const numberValue = 5; - const inputEvent = new Event('input'); - numberInput.value = numberValue; - numberInput.dispatchEvent(inputEvent); - - setTimeout(() => { - assert.equal(numberComp.dataValue, numberValue); - assert.equal(numberComp.getValue(), numberValue); - assert.equal(select.dataValue, ''); - assert.equal(select.getValue(), ''); - - done(); - }, 400); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp7)); + const selectHtml = form.getComponent('selectHtml'); + const selectChoices = form.getComponent('selectChoices'); + assert.equal(!!selectHtml.choices, false); + assert.equal(!!selectChoices.choices, true); + + await Promise.all([selectHtml.itemsLoaded, selectChoices.itemsLoaded]); + await timeout(0); + assert.equal(selectHtml.selectOptions.length, 3); + assert.equal(selectChoices.selectOptions.length, 3); + assert.equal(selectChoices.choices._store.choices.length, 3); + assert.equal(selectChoices.choices._store.items.length, 0); + assert.equal(selectChoices.element.querySelectorAll('.choices__item--choice').length, 3); + const value = 'b'; + selectHtml.setValue(value); + selectChoices.setValue(value); + + await Promise.all([selectHtml.itemsLoaded, selectChoices.itemsLoaded]); + assert.equal(selectHtml.dataValue, value); + assert.equal(selectChoices.dataValue, value); + assert.equal(selectChoices.choices._store.items.length, 1); + assert.equal(selectHtml.getValue(), value); + assert.equal(selectChoices.getValue(), value); }); - it('Should update select items when "refresh options on" is enable and number component is changed', (done) => { - const form = _.cloneDeep(comp9); - const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; - Formio.makeRequest = function(formio, type, url) { - return new Promise(resolve => { - let values =[{ name: 'Ivan' }, { name: 'Mike' }]; - - if (url.endsWith('5')) { - values = [{ name: 'Kate' }, { name: 'Ann' }, { name: 'Lana' }]; - } - resolve(values); - }); - }; + it('Should clear select value when "clear value on refresh options" and "refresh options on" is enable and number component is changed', async () => { + const restoreDebounce = mockDebounce(0); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const numberComp = form.getComponent('number'); - setTimeout(() => { - assert.equal(select.selectOptions.length, 2); - assert.deepEqual(select.selectOptions[0].value, { name: 'Ivan' }); - - const numberValue = 5; - const inputEvent = new Event('input'); - const numberInput = numberComp.refs.input[0]; - - numberInput.value = numberValue; - numberInput.dispatchEvent(inputEvent); - - setTimeout(() => { - assert.equal(numberComp.dataValue, numberValue); - assert.equal(numberComp.getValue(), numberValue); - assert.equal(select.selectOptions.length, 3); - assert.deepEqual(select.selectOptions[0].value, { name: 'Kate' }); - - Formio.makeRequest = originalMakeRequest; - done(); - }, 500); - }, 200); - }).catch(done); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp8)); + const select = form.getComponent('select'); + const numberComp = form.getComponent('number'); + + await select.itemsLoaded; + const value = 'b'; + select.setValue(value); + + // timeout(0) need to complete triggerChange + await Promise.all([select.itemsLoaded, timeout(0)]); + assert.equal(select.dataValue, value); + assert.equal(select.getValue(), value); + + const numberInput = numberComp.refs.input[0]; + const numberValue = 5; + const inputEvent = new Event('input'); + numberInput.value = numberValue; + numberInput.dispatchEvent(inputEvent); + + await timeout(50); + assert.equal(numberComp.dataValue, numberValue); + assert.equal(numberComp.getValue(), numberValue); + assert.equal(select.dataValue, ''); + assert.equal(select.getValue(), ''); + assert.equal(select.choices._store.items.length, 0); + + restoreDebounce(); }); - it('Should update select items when "refresh options on blur" is enable and number component is changed', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].refreshOn = null; - form.components[1].refreshOnBlur = 'number'; + it('Should update select items when "refresh options on" is enable and number component is changed', async () => { + const restoreDebounce = mockDebounce(0); + const restoreMakeRequest = mockMakeRequest((formio, type, url) => new Promise((resolve) => { + let values = [{ name: 'Ivan' }, { name: 'Mike' }]; + if (url.endsWith('5')) { + values = [{ name: 'Kate' }, { name: 'Ann' }, { name: 'Lana' }]; + } + resolve(values); + })); const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; - Formio.makeRequest = function(formio, type, url) { - return new Promise(resolve => { - let values =[{ name: 'Ivan' }, { name: 'Mike' }]; - - if (url.endsWith('5')) { - values = [{ name: 'Kate' }, { name: 'Ann' }, { name: 'Lana' }]; - } - resolve(values); - }); - }; - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const numberComp = form.getComponent('number'); - setTimeout(() => { - assert.equal(select.selectOptions.length, 2); - assert.deepEqual(select.selectOptions[0].value, { name: 'Ivan' }); - - const numberValue = 5; - const inputEvent = new Event('input'); - const focusEvent = new Event('focus'); - const blurEvent = new Event('blur'); - const numberInput = numberComp.refs.input[0]; - numberInput.dispatchEvent(focusEvent); - numberInput.value = numberValue; - numberInput.dispatchEvent(inputEvent); - numberInput.dispatchEvent(blurEvent); - - setTimeout(() => { - assert.equal(numberComp.dataValue, numberValue); - assert.equal(numberComp.getValue(), numberValue); - assert.equal(select.selectOptions.length, 3); - assert.deepEqual(select.selectOptions[0].value, { name: 'Kate' }); - - Formio.makeRequest = originalMakeRequest; - done(); - }, 500); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp9)); + const select = form.getComponent('select'); + const numberComp = form.getComponent('number'); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 2); + assert.deepEqual(select.selectOptions[0].value, { name: 'Ivan' }); + + const numberValue = 5; + const inputEvent = new Event('input'); + const numberInput = numberComp.refs.input[0]; + numberInput.value = numberValue; + numberInput.dispatchEvent(inputEvent); + + await timeout(50); + assert.equal(numberComp.dataValue, numberValue); + assert.equal(numberComp.getValue(), numberValue); + assert.equal(select.selectOptions.length, 3); + assert.deepEqual(select.selectOptions[0].value, { name: 'Kate' }); + + restoreDebounce(); + restoreMakeRequest(); }); - it('Should be able to search if static search is enable', (done) => { - const form = _.cloneDeep(comp10); - const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - const focusEvent = new Event('focus'); - searchField.dispatchEvent(focusEvent); + it('Should update select items when "refresh options on blur" is enable and number component is changed', async () => { + const restoreDebounce = mockDebounce(0); + const restoreMakeRequest = mockMakeRequest((formio, type, url) => new Promise((resolve) => { + let values =[{ name: 'Ivan' }, { name: 'Mike' }]; + if (url.endsWith('5')) { + values = [{ name: 'Kate' }, { name: 'Ann' }, { name: 'Lana' }]; + } + resolve(values); + })); - setTimeout(() => { - assert.equal(select.choices.dropdown.isActive, true); - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 5); + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].refreshOn = null; + formSchema.components[1].refreshOnBlur = 'number'; - const keyupEvent = new Event('keyup'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - searchField.value = 'par'; - searchField.dispatchEvent(keyupEvent); - - setTimeout(() => { - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 1); - - done(); - }, 400); - }, 200); - }).catch(done); + const element = document.createElement('div'); + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); + const numberComp = form.getComponent('number'); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 2); + assert.deepEqual(select.selectOptions[0].value, { name: 'Ivan' }); + + const numberValue = 5; + const inputEvent = new Event('input'); + const focusEvent = new Event('focus'); + const blurEvent = new Event('blur'); + const numberInput = numberComp.refs.input[0]; + numberInput.dispatchEvent(focusEvent); + numberInput.value = numberValue; + numberInput.dispatchEvent(inputEvent); + numberInput.dispatchEvent(blurEvent); + + await timeout(50); + assert.equal(numberComp.dataValue, numberValue); + assert.equal(numberComp.getValue(), numberValue); + assert.equal(select.selectOptions.length, 3); + assert.deepEqual(select.selectOptions[0].value, { name: 'Kate' }); + + restoreDebounce(); + restoreMakeRequest(); }); - it('Should not be able to search if static search is disable', (done) => { - const form = _.cloneDeep(comp10); - form.components[0].searchEnabled = false; + it('Should be able to search if static search is enable', async () => { const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - assert.equal(searchField, null); - - done(); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp10)); + const select = form.getComponent('select'); + + await select.itemsLoaded; + const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); + const focusEvent = new Event('focus'); + searchField.dispatchEvent(focusEvent); + + await timeout(0); + await select.itemsLoaded; + assert.equal(select.choices.dropdown.isActive, true); + let items = select.choices.choiceList.element.children; + assert.equal(items.length, 5); + + const event = new Event('input'); + searchField.value = 'par'; + searchField.dispatchEvent(event); + items = select.choices.choiceList.element.children; + assert.equal(items.length, 1); }); - it('Should save correct value if value property and item template property are different', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].refreshOn = null; - form.components[1].valueProperty = 'age'; - form.components[1].lazyLoad = true; - + it('Should not be able to search if static search is disable', async () => { + const formSchema = _.cloneDeep(comp10); + formSchema.components[0].searchEnabled = false; const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; - - Formio.makeRequest = function() { - return new Promise(resolve => { - const values =[{ name: 'Ivan', age: 35 }, { name: 'Mike', age: 41 }]; - resolve(values); - }); - }; - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - assert.equal(select.selectOptions.length, 0); - select.choices.showDropdown(); - - setTimeout(() => { - assert.equal(select.selectOptions.length, 2); - assert.deepEqual(select.selectOptions[0].value, 35); - assert.deepEqual(select.selectOptions[0].label, 'Ivan'); - - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 2); - assert.equal(items[0].textContent.trim(), 'Ivan'); - - select.setValue(41); - - setTimeout(() => { - assert.equal(select.getValue(), 41); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Mike'); - - Formio.makeRequest = originalMakeRequest; - - done(); - }, 400); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); + const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); + assert.equal(searchField, null); }); - it('Should set custom header when sending request in select url', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].refreshOn = null; - form.components[1].lazyLoad = true; - form.components[1].data.headers = [{ key:'testHeader', value:'test' }]; + it('Should save correct value if value property and item template property are different', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([{ name: 'Ivan', age: 35 }, { name: 'Mike', age: 41 }]); + })); + + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].refreshOn = null; + formSchema.components[1].valueProperty = 'age'; + formSchema.components[1].lazyLoad = true; const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 0); + select.choices.showDropdown(); + + // trigger change + await timeout(0); + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 2); + assert.deepEqual(select.selectOptions[0].value, 35); + assert.deepEqual(select.selectOptions[0].label, 'Ivan'); + + const items = select.choices.choiceList.element.children; + assert.equal(items.length, 2); + assert.equal(items[0].textContent.trim(), 'Ivan'); + select.setValue(41); + + await select.itemsLoaded; + assert.equal(select.getValue(), 41); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Mike'); + + restoreMakeRequest(); + }); - Formio.makeRequest = function(formio, type, url, method, data, opts) { + it('Should set custom header when sending request in select url', async () => { + const restoreMakeRequest = mockMakeRequest((formio, type, url, method, data, opts) => { assert.equal(opts.header.get('testHeader'), 'test'); - return new Promise(resolve => { + return new Promise((resolve) => { const values = [{ name: 'Ivan', age: 35 }, { name: 'Mike', age: 41 }]; resolve(values); }); - }; - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - assert.equal(select.selectOptions.length, 0); - select.choices.showDropdown(); - - setTimeout(() => { - Formio.makeRequest = originalMakeRequest; - done(); - }, 200); - }).catch(done); - }); + }); - it('Should set value in select url with lazy load option', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].refreshOn = null; - form.components[1].lazyLoad = true; + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].refreshOn = null; + formSchema.components[1].lazyLoad = true; + formSchema.components[1].data.headers = [{ key:'testHeader', value:'test' }]; const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); - Formio.makeRequest = function() { - return new Promise(resolve => { - const values = [{ name: 'Ivan' }, { name: 'Mike' }]; - resolve(values); - }); - }; + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 0); + select.choices.showDropdown(); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setValue({ name: 'Ivan' }); - setTimeout(() => { - assert.deepEqual(select.getValue(), { name: 'Ivan' }); - assert.deepEqual(select.dataValue, { name: 'Ivan' }); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); + // trigger change + await timeout(0); + await select.itemsLoaded; + restoreMakeRequest(); + }); - Formio.makeRequest = originalMakeRequest; + it('Should set value in select url with lazy load option', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + const values = [{ name: 'Ivan' }, { name: 'Mike' }]; + resolve(values); + })); - done(); - }, 200); - }).catch(done); - }); + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].refreshOn = null; + formSchema.components[1].lazyLoad = true; - it('Should set value in select url with lazy load option when value property is defined', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].refreshOn = null; - form.components[1].lazyLoad = true; - form.components[1].valueProperty = 'name'; const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); + select.setValue({ name: 'Ivan' }); - Formio.makeRequest = function() { - return new Promise(resolve => { - const values = [{ name: 'Ivan' }, { name: 'Mike' }]; - resolve(values); - }); - }; + await select.itemsLoaded; + assert.deepEqual(select.getValue(), { name: 'Ivan' }); + assert.deepEqual(select.dataValue, { name: 'Ivan' }); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setValue('Ivan'); - setTimeout(() => { - assert.equal(select.getValue(), 'Ivan'); - assert.equal(select.dataValue, 'Ivan'); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); + restoreMakeRequest(); + }); - Formio.makeRequest = originalMakeRequest; + it('Should set value in select url with lazy load option when value property is defined', async () => { + const restoreDebounce = mockDebounce(0); + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + const values = [{ name: 'Ivan' }, { name: 'Mike' }]; + resolve(values); + })); - done(); - }, 200); - }).catch(done); - }); + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].refreshOn = null; + formSchema.components[1].lazyLoad = true; + formSchema.components[1].valueProperty = 'name'; - it('Should be able to search if static search is enable', (done) => { - const form = _.cloneDeep(comp10); const element = document.createElement('div'); + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); + await select.itemsLoaded; + select.setValue('Ivan'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - const focusEvent = new Event('focus'); - searchField.dispatchEvent(focusEvent); + await timeout(0); + await select.itemsLoaded; + assert.equal(select.getValue(), 'Ivan'); + assert.equal(select.dataValue, 'Ivan'); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); - setTimeout(() => { - assert.equal(select.choices.dropdown.isActive, true); - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 5); - - const keyupEvent = new Event('keyup'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - searchField.value = 'par'; - searchField.dispatchEvent(keyupEvent); + restoreDebounce(); + restoreMakeRequest(); + }); - setTimeout(() => { - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 1); + it('Server side search is debounced with the correct timeout', async () => { + let searchHasBeenDebounced = false; + const restoreDebounce = mockDebounce(0, (wait) => { + if (wait === 700) { + searchHasBeenDebounced = true; + } + }); - done(); - }, 400); - }, 200); - }).catch(done); - }); + const formSchema = _.cloneDeep(comp9); + formSchema.components[1].searchDebounce = 0.7; + formSchema.components[1].searchField = 'name'; - it('Server side search is debounced with the correct timeout', (done) => { - const form = _.cloneDeep(comp9); - form.components[1].lazyLoad = false; - form.components[1].searchDebounce = 0.7; - form.components[1].disableLimit = false; - form.components[1].searchField = 'name'; const element = document.createElement('div'); + await Formio.createForm(element, formSchema); - const originalMakeRequest = Formio.makeRequest; - Formio.makeRequest = function() { - return new Promise(resolve => { - resolve([]); - }); - }; - - var searchHasBeenDebounced = false; - var originalDebounce = _.debounce; - _.debounce = (fn, timeout, opts) => { - searchHasBeenDebounced = true; - return originalDebounce(fn, 0, opts); - }; + assert.equal(searchHasBeenDebounced, true); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - const focusEvent = new Event('focus'); - searchField.dispatchEvent(focusEvent); - - setTimeout(() => { - const keyupEvent = new Event('keyup'); - searchField.value = 'the_name'; - searchField.dispatchEvent(keyupEvent); - - setTimeout(() => { - _.debounce = originalDebounce; - Formio.makeRequest = originalMakeRequest; - - assert.equal(searchHasBeenDebounced, true); - done(); - }, 500); - }, 300); - }).catch(done); + restoreDebounce(); }); - it('Should provide "Allow only available values" validation', (done) => { - const form = _.cloneDeep(comp10); - form.components[0].validate.onlyAvailableItems = true; - const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const value = 'Dallas'; - select.setValue(value); - - setTimeout(() => { - assert.equal(select.getValue(), value); - assert.equal(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(form.errors.length, 1); - assert.equal(select.errors[0].message, 'Select is an invalid value.'); - document.innerHTML = ''; - done(); - }, 400); - }, 200); - }).catch(done); - }); + it('Should provide "Allow only available values" validation', async () => { + const formSchema = _.cloneDeep(comp10); + formSchema.components[0].validate.onlyAvailableItems = true; - it('Should render and set value in select json', (done) => { - const formObj = _.cloneDeep(comp11); const element = document.createElement('div'); + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); - Formio.createForm(element, formObj).then(form => { - const select = form.getComponent('select'); - assert.equal(select.choices.containerInner.element.children[1].children[0].dataset.value, ''); - select.choices.showDropdown(); + await select.itemsLoaded; + const value = 'Dallas'; + select.setValue(value); - setTimeout(() => { - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 4); + await select.itemsLoaded; + assert.equal(select.getValue(), value); + assert.equal(select.dataValue, value); - const value = { value: 'a', label:'A' }; - select.setValue(value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); - setTimeout(() => { - assert.deepEqual(select.getValue(), value); - assert.deepEqual(select.dataValue, value); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'A'); - - done(); - }, 400); - }, 200); - }).catch(done); + await new Promise((resolve, reject) => { + form.on('submit', () => reject('Shouldn\'t submit the form with a validation error.')); + form.on('submitError', () => resolve()); + }); + assert.equal(form.errors.length, 1); + assert.equal(select.errors[0].message, 'Select is an invalid value.'); }); - it('Should load and set items in select resource and set value', (done) => { - const form = _.cloneDeep(comp12); + it('Should render and set value in select json', async () => { + const formObj = _.cloneDeep(comp11); const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; + const form = await Formio.createForm(element, formObj); + const select = form.getComponent('select'); + + await select.itemsLoaded; + assert.equal(select.choices.containerInner.element.children[1].children[0].dataset.value, ''); + select.choices.showDropdown(); + + // trigger change + await timeout(0); + await select.itemsLoaded; + const items = select.choices.choiceList.element.children; + assert.equal(items.length, 4); + const value = { value: 'a', label:'A' }; + select.setValue(value); + + await select.itemsLoaded; + assert.deepEqual(select.getValue(), value); + assert.deepEqual(select.dataValue, value); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'A'); + }); - Formio.makeRequest = function(formio, type, url) { - return new Promise(resolve => { - let values = [{ data: { name: 'Ivan' } }, { data: { name: 'Mike' } }]; + it('Should load and set items in select resource and set value', async () => { + const restoreDebounce = mockDebounce(0); + const restoreMakeRequest = mockMakeRequest((formio, type, url) => new Promise((resolve) => { + let values = [{ data: { name: 'Ivan' } }, { data: { name: 'Mike' } }]; - if (url.endsWith('Ivan')) { - assert.equal(url.endsWith('/form/60114dd32cab36ad94ac4f94/submission?limit=100&skip=0&data.name__regex=Ivan'), true); - values = [{ data: { name: 'Ivan' } }]; - } - else { - assert.equal(url.endsWith('/form/60114dd32cab36ad94ac4f94/submission?limit=100&skip=0'), true); - } + if (url.endsWith('Ivan')) { + assert.equal(url.endsWith('/form/60114dd32cab36ad94ac4f94/submission?limit=100&skip=0&data.name__regex=Ivan'), true); + values = [{ data: { name: 'Ivan' } }]; + } + else { + assert.equal(url.endsWith('/form/60114dd32cab36ad94ac4f94/submission?limit=100&skip=0'), true); + } - resolve(values); - }); - }; + resolve(values); + })); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 1); - select.setValue('Ivan'); - - setTimeout(() => { - assert.equal(select.getValue(), 'Ivan'); - assert.equal(select.dataValue, 'Ivan'); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); - select.choices.showDropdown(); - - setTimeout(() => { - const items = select.choices.choiceList.element.children; - - assert.equal(items.length, 2); - assert.equal(items[0].textContent, 'Ivan'); - - Formio.makeRequest = originalMakeRequest; - done(); - }, 400); - }, 200); - }).catch(done); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp12)); + const select = form.getComponent('select'); + + let items = select.choices.choiceList.element.children; + assert.equal(items.length, 1); + select.setValue('Ivan'); + + await timeout(0); + await select.itemsLoaded; + assert.equal(select.getValue(), 'Ivan'); + assert.equal(select.dataValue, 'Ivan'); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'Ivan'); + select.choices.showDropdown(); + + await timeout(0); + await select.itemsLoaded; + items = select.choices.choiceList.element.children; + assert.equal(items.length, 2); + assert.equal(items[0].textContent, 'Ivan'); + + restoreDebounce(); + restoreMakeRequest(); }); - it('Should not have "limit" and "skip" query params when "Disable limit" option checked', (done) => { - const form = _.cloneDeep(comp9); - const element = document.createElement('div'); - const originalMakeRequest = Formio.makeRequest; - Formio.makeRequest = (_, __, url) => { + it('Should not have "limit" and "skip" query params when "Disable limit" option checked', async () => { + const restoreMakeRequest = mockMakeRequest((_, __, url) => { assert.equal(url, 'https://test.com/'); return Promise.resolve({}); - }; + }); + + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp9)); + const select = form.getComponent('select'); - Formio.createForm(element, form).then(() => { - setTimeout(() => { - Formio.makeRequest = originalMakeRequest; - done(); - }, 200); - }).catch(done); + await select.itemsLoaded; + restoreMakeRequest(); }); - it('The empty option in html5 shouldn\'t have the [Object Object] value', () => { - return Harness.testCreate(SelectComponent, comp13).then((component) => { - const emptyOption = component.element.querySelectorAll('option')[0]; - assert.notEqual(emptyOption.value, '[object Object]'); - assert.equal(emptyOption.value, ''); - }); + it('The empty option in html5 Shouldn\'t have the [Object Object] value', async () => { + const component = await Harness.testCreate(SelectComponent, comp13); + await component.itemsLoaded; + const emptyOption = component.element.querySelectorAll('option')[0]; + assert.notEqual(emptyOption.value, '[object Object]'); + assert.equal(emptyOption.value, ''); }); - it('Should not have default values in schema', (done) => { - const form = _.cloneDeep(comp14); + it('Should not have default values in schema', async () => { const element = document.createElement('div'); const requiredSchema = { @@ -879,398 +748,401 @@ describe('Select Component', () => { input: true }; - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - assert.deepEqual(requiredSchema, select.schema); - done(); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp14)); + const select = form.getComponent('select'); + assert.deepEqual(requiredSchema, select.schema); }); - it('Should show async custom values and be able to set submission', (done) => { - const formObj = _.cloneDeep(comp16); + it('Should show async custom values and be able to set submission', async () => { const element = document.createElement('div'); - - Formio.createForm(element, formObj).then(form => { - const select = form.getComponent('select'); - select.choices.showDropdown(); - - setTimeout(() => { - const items = select.choices.choiceList.element.children; - assert.equal(items.length, 3); - const value = 'bb'; - form.submission = { data: { select: value } }; - - setTimeout(() => { - assert.deepEqual(select.getValue(), value); - assert.deepEqual(select.dataValue, value); - assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'B'); - - done(); - }, 400); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp16)); + const select = form.getComponent('select'); + select.choices.showDropdown(); + + await timeout(0); + await select.itemsLoaded; + assert.equal(select.dataValue, ''); + const items = select.choices.choiceList.element.children; + assert.equal(items.length, 3); + const value = 'bb'; + form.submission = { data: { select: value } }; + + await form.submissionReady; + assert.deepEqual(select.getValue(), value); + assert.deepEqual(select.dataValue, value); + assert.equal(select.choices.containerInner.element.children[1].children[0].children[0].textContent, 'B'); }); - it('Should provide metadata.selectData for Select component pointed to a resource where value property is set to a field', (done) => { - const form = _.cloneDeep(comp17); + it('Should provide metadata.selectData for Select component pointed to a resource where value property is set to a field', async () => { const testItems = [ - { textField: 'John' }, - { textField: 'Mary' }, - { textField: 'Sally' } + { data: { textField: 'John' }}, + { data: { textField: 'Mary' }}, + { data: { textField: 'Sally' }}, ]; + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve(testItems); + })); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp17)); + const select = form.getComponent('select'); + + const value = 'John'; + select.setValue(value); + + await select.itemsLoaded; + assert.equal(select.dataValue, value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + assert.equal(_.isEqual(form.submission.metadata.selectData.select.data, testItems[0].data), true); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setItems(testItems.map(item => ({ data: item }))); - const value = 'John'; - select.setValue(value); - - setTimeout(() => { - assert.equal(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(_.isEqual(form.submission.metadata.selectData.select.data, testItems[0]), true); - done(); - }, 200); - }, 200); - }).catch(done); + restoreMakeRequest(); }); - it('Should provide correct metadata.selectData for multiple Select', (done) => { - const form = _.cloneDeep(comp20); + it('Should provide correct metadata.selectData for multiple Select', async () => { const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const values = ['apple', 'orange']; - select.setValue(values); - - setTimeout(()=> { - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - const metadata = form.submission.metadata.selectData.select; - assert.equal(_.keys(metadata).length, 2); - values.forEach((value) => { - assert.equal(_.find(select.component.data.values, { value }).label, metadata[value].label); - }); - done(); - }, 200); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp20)); + const select = form.getComponent('select'); + const values = ['apple', 'orange']; + select.setValue(values); + + await select.itemsLoaded; + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + const metadata = form.submission.metadata.selectData.select; + assert.equal(_.keys(metadata).length, 2); + values.forEach((value) => { + assert.equal(_.find(select.component.data.values, { value }).label, metadata[value].label); + }); }); - it('Should provide correct metadata.selectData for HTML5 Select', (done) => { + it('Should provide correct metadata.selectData for HTML5 Select', async () => { + const restoreDebounce = mockDebounce(0); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp21)); + const select = form.getComponent('animals'); + const checkbox = form.getComponent('checkbox'); + const value = 'dog'; + select.setValue(value); + + await select.itemsLoaded; + checkbox.setValue(true); + + await timeout(50); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + const metadata = form.submission.metadata.selectData.animals2; + assert.equal(metadata.label, 'Dog'); - Formio.createForm(element, comp21).then(form => { - const select = form.getComponent('animals'); - const checkbox = form.getComponent('checkbox'); - const value = 'dog'; - select.setValue(value); - - setTimeout(()=> { - checkbox.setValue(true); - setTimeout(() => { - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - const metadata = form.submission.metadata.selectData.animals2; - assert.equal(metadata.label, 'Dog'); - done(); - }, 200); - }, 300); - }, 200); - }).catch(done); + restoreDebounce(); }); - it('Should provide correct metadata.selectData for HTML5 Select with default value', (done) => { - const form = _.cloneDeep(comp22); + it('Should provide correct metadata.selectData for HTML5 Select with default value', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([]); + })); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp22)); + const select = form.getComponent('select'); + await select.itemsLoaded; + + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + + const metadata = form.submission.metadata.selectData.select; + assert.equal(metadata.label, 'Label 1'); - Formio.createForm(element, form).then(form => { - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(()=> { - const metadata = form.submission.metadata.selectData.select; - assert.equal(metadata.label, 'Label 1'); - done(); - }, 200); - }).catch(done); + restoreMakeRequest(); }); - it('Should provide correct metadata.selectData for ChoicesJS Select with default value', (done) => { - const form = _.cloneDeep(comp22); - form.components[0].widget='choicesjs'; + it('Should provide correct metadata.selectData for ChoicesJS Select with default value', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([]); + })); + + const formSchema = _.cloneDeep(comp22); + formSchema.components[0].widget='choicesjs'; const element = document.createElement('div'); + const form = await Formio.createForm(element, formSchema); + + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); - Formio.createForm(element, form).then(form => { - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(()=> { - const metadata = form.submission.metadata.selectData.select; - assert.equal(metadata.label, 'Label 1'); - done(); - }, 200); - }).catch(done); + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + + const metadata = form.submission.metadata.selectData.select; + assert.equal(metadata.label, 'Label 1'); + + restoreMakeRequest(); }); - it('Should set correct label from metadata for ChoicesJS Select with default value', (done) => { - const form = _.cloneDeep(comp22); - form.components[0].widget='choicesjs'; + it('Should set correct label from metadata for ChoicesJS Select with default value', async () => { + const formSchema = _.cloneDeep(comp22); + formSchema.components[0].widget='choicesjs'; const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - form.submission = { - data: { - select: 'value2', - }, - metadata: { - selectData: { - select: { - label: 'Label 2', - }, + const form = await Formio.createForm(element, formSchema); + const select = form.getComponent('select'); + form.submission = { + data: { + select: 'value2', + }, + metadata: { + selectData: { + select: { + label: 'Label 2', }, }, - }; + }, + }; - setTimeout(()=> { - assert.equal(select.templateData['value2'].label, 'Label 2'); - done(); - }, 200); - }).catch(done); + await form.submissionReady; + await select.itemsLoaded; + assert.equal(select.templateData['value2'].label, 'Label 2'); }); - it('Should provide correct metadata.selectData for multiple Select with default value', (done) => { - const form = _.cloneDeep(comp23); + it('Should provide correct metadata.selectData for multiple Select with default value', async () => { const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp23)); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); - Formio.createForm(element, form).then(form => { - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(()=> { - const metadata = form.submission.metadata.selectData.select; - assert.deepEqual(metadata, { - value1: { - label: 'Label 1', - }, - value3: { - label: 'Label 3', - }, - }); - done(); - }, 200); - }).catch(done); + const metadata = form.submission.metadata.selectData.select; + assert.deepEqual(metadata, { + value1: { + label: 'Label 1', + }, + value3: { + label: 'Label 3', + }, + }); }); - it('Should set correct label from metadata for multiple Select with default value', (done) => { - const form = _.cloneDeep(comp23); + it('Should set correct label from metadata for multiple Select with default value', async () => { const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - form.submission = { - data: { - select: ['value1', 'value2'], - }, - metadata: { - selectData: { - select: { - value1: { - label: 'Label 1', - }, - value2: { - label: 'Label 2', - }, + const form = await Formio.createForm(element, _.cloneDeep(comp23)); + const select = form.getComponent('select'); + form.submission = { + data: { + select: ['value1', 'value2'], + }, + metadata: { + selectData: { + select: { + value1: { + label: 'Label 1', + }, + value2: { + label: 'Label 2', }, }, }, - }; - - setTimeout(()=> { - assert.equal(select.templateData['value1'].label, 'Label 1'); - assert.equal(select.templateData['value2'].label, 'Label 2'); - done(); - }, 200); - }).catch(done); - }); - - it('OnBlur validation should work properly with Select component', function(done) { - this.timeout(0); - const element = document.createElement('div'); + }, + }; - Formio.createForm(element, comp19).then(form => { - const select = form.components[0]; - select.setValue('banana'); - select.choices.input.element.focus(); - select.pristine = false; - - setTimeout(() => { - assert(!select.visibleErrors.length, 'Select should be valid while changing'); - select.choices.input.element.dispatchEvent(new Event('blur')); - - setTimeout(() => { - assert(select.visibleErrors.length, 'Should set error after Select component was blurred'); - done(); - }, 300); - }, 300); - }).catch(done); + await form.submissionReady; + await select.itemsLoaded; + assert.equal(select.templateData['value1'].label, 'Label 1'); + assert.equal(select.templateData['value2'].label, 'Label 2'); }); - it('Should escape special characters in regex search field', done => { - const form = _.cloneDeep(comp17); + it('OnBlur validation Should work properly with Select component', async () => { + const restoreDebounce = mockDebounce(0); + const element = document.createElement('div'); - Formio.setProjectUrl('https://formio.form.io'); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); - const focusEvent = new Event('focus'); - searchField.dispatchEvent(focusEvent); + const form = await Formio.createForm(element, comp19); + const select = form.components[0]; + select.setValue('banana'); - setTimeout(() => { - const keyupEvent = new Event('keyup'); - searchField.value = '^$.*+?()[]{}|'; - searchField.dispatchEvent(keyupEvent); + await select.itemsLoaded; + select.choices.input.element.focus(); + select.pristine = false; - const spy = sinon.spy(Formio, 'makeRequest'); + await timeout(50); + assert(!select.visibleErrors.length, 'Select Should be valid while changing'); + select.choices.input.element.dispatchEvent(new Event('blur')); - setTimeout(() => { - assert.equal(spy.callCount, 1); + await timeout(50); + assert(select.visibleErrors.length, 'Should set error after Select component was blurred'); - const urlArg = spy.args[0][2]; + restoreDebounce(); + }); - assert.ok(urlArg && typeof urlArg === 'string' && urlArg.startsWith('http'), 'A URL should be passed as the third argument to "Formio.makeRequest()"'); + it('Should escape special characters in regex search field', async () => { + const restoreDebounce = mockDebounce(0); + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([]); + })) - assert.ok(urlArg.includes('__regex=%5C%5E%5C%24%5C.%5C*%5C%2B%5C%3F%5C(%5C)%5C%5B%5C%5D%5C%7B%5C%7D%5C%7C'), 'The URL should contain escaped and encoded search value regex'); - done(); - }, 500); - }, 200); - }).catch(done); + const element = document.createElement('div'); + Formio.setProjectUrl('https://formio.form.io'); + const form = await Formio.createForm(element, _.cloneDeep(comp17)); + const select = form.getComponent('select'); + const searchField = select.element.querySelector('.choices__input.choices__input--cloned'); + const focusEvent = new Event('focus'); + searchField.dispatchEvent(focusEvent); + + await timeout(0); + await select.itemsLoaded; + const keyupEvent = new Event('input'); + searchField.value = '^$.*+?()[]{}|'; + searchField.dispatchEvent(keyupEvent); + const spy = sinon.spy(Formio, 'makeRequest'); + + await timeout(0); + await select.itemsLoaded; + assert.equal(spy.callCount, 1); + const urlArg = spy.args[0][2]; + assert.ok(urlArg && typeof urlArg === 'string' && urlArg.startsWith('http'), 'A URL Should be passed as the third argument to "Formio.makeRequest()"'); + assert.ok(urlArg.includes('__regex=%5C%5E%5C%24%5C.%5C*%5C%2B%5C%3F%5C(%5C)%5C%5B%5C%5D%5C%7B%5C%7D%5C%7C'), 'The URL Should contain escaped and encoded search value regex'); + + restoreDebounce(); + restoreMakeRequest(); }); - it('Should perfom simple conditional logic for number data type', (done) => { - const form = _.cloneDeep(comp26); - const element = document.createElement('div'); + it('Should perform simple conditional logic for number data type', async () => { + const restoreDebounce = mockDebounce(); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const textfield = form.getComponent('textField'); - select.setValue('1'); - - setTimeout(() => { - assert.equal(select.dataValue, 1); - assert.equal(textfield.visible, true); - select.setValue('2'); - - setTimeout(() => { - assert.equal(select.dataValue, 2); - assert.equal(textfield.visible, true); - select.setValue('10'); - - setTimeout(() => { - assert.equal(select.dataValue, 10); - assert.equal(textfield.visible, false); - select.setValue('1d'); - - setTimeout(() => { - assert.equal(select.dataValue, '1d'); - assert.equal(textfield.visible, false); - done(); - }, 300); - }, 300); - }, 300); - }, 300); - }).catch(done); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp26)); + const select = form.getComponent('select'); + const textfield = form.getComponent('textField'); + select.setValue('1'); + + await timeout(50); + assert.equal(select.dataValue, 1); + assert.equal(textfield.visible, true); + select.setValue('2'); + + await timeout(50); + assert.equal(select.dataValue, 2); + assert.equal(textfield.visible, true); + select.setValue('10'); + + await timeout(50); + assert.equal(select.dataValue, 10); + assert.equal(textfield.visible, false); + select.setValue('1d'); + + await timeout(50); + assert.equal(select.dataValue, '1d'); + assert.equal(textfield.visible, false); + + restoreDebounce(); }); - it('Should open edit grid modal when clicking on validation link when editing a submission', (done) => { - Formio.createForm(document.createElement('div'), comp25, {}).then((form) => { - form.submission = { - "data": { - "editGrid": [ - { - "notselect": "", - "textField": "" - } - ], - "draft": true, - "submit": false - }, - "state": "draft", - }; - const buttonComponent = form.getComponent('submit'); - buttonComponent.refs.button.click(); - setTimeout(() => { - form.refs.errorRef[0].click(); - setTimeout(() => { - assert(document.querySelector('body').classList.contains('modal-open'), 'modal should be open'); - done(); - }, 200); - }, 200); + it('Should open edit grid modal when clicking on validation link when editing a submission', async () => { + const form = await Formio.createForm(document.createElement('div'), comp25, {}); + form.submission = { + "data": { + "editGrid": [ + { + "notselect": "", + "textField": "" + } + ], + "draft": true, + "submit": false + }, + "state": "draft", + }; + const buttonComponent = form.getComponent('submit'); + buttonComponent.refs.button.click(); + + await new Promise((resolve, reject) => { + form.on('submit', () => reject('Shouldn\'t submit the form with a validation error.')); + form.on('submitError', () => resolve()); }); + + form.refs.errorRef[0].click(); + assert(document.querySelector('body').classList.contains('modal-open'), 'modal Should be open'); }); + it('Should render label for multiple Select when Data Source is Resource in read only mode', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([]); + })); - it('Should render label for multiple Select when Data Source is Resource in read only mode', (done) => { const element = document.createElement('div'); - const form = cloneDeep(comp24); - set(form, 'components[0].multiple', true); - Formio.createForm(element, form, { readOnly: true }).then((form) => { - form.setSubmission({ - metadata: { - selectData: { - select: { - 1: { - data: { - textField1: 'One' - } - }, - olivia: { - data: { - textField1: 'Olivia Miller' - } + const formSchema = cloneDeep(comp24); + set(formSchema, 'components[0].multiple', true); + const form = await Formio.createForm(element, formSchema, { readOnly: true }); + const select = form.getComponent('select'); + form.setSubmission({ + metadata: { + selectData: { + select: { + 1: { + data: { + textField1: 'One' } }, + olivia: { + data: { + textField1: 'Olivia Miller' + } + } }, }, - data: { - select: [1, 'olivia'], - submit: true, - }, - state: 'submitted', - }); + }, + data: { + select: [1, 'olivia'], + submit: true, + }, + state: 'submitted', + }); + + await form.submissionReady; + await select.itemsLoaded; + await timeout(0); + const selectedItems = select.element.querySelectorAll('[aria-selected="true"] span'); + assert.equal(selectedItems[0].innerHTML, 'One', 'Should show correct label for numeric values'); + assert.equal(selectedItems[1].innerHTML, 'Olivia Miller', 'Should show correct label for string values'); - setTimeout(() => { - const select = form.getComponent('select'); - const selectedItems = select.element.querySelectorAll('[aria-selected="true"] span'); - assert.equal(selectedItems[0].innerHTML, 'One', 'Should show correct label for numeric values'); - assert.equal(selectedItems[1].innerHTML, 'Olivia Miller', 'Should show correct label for string values');; - done(); - }, 700); - }) - .catch((err) => done(err)); + restoreMakeRequest(); }); + // it('should reset input value when called with empty value', () => { // const comp = Object.assign({}, comp1); // delete comp.placeholder; @@ -1286,309 +1158,261 @@ describe('Select Component', () => { // assert.equal(component.refs.input[0].value, ''); // }); // }); -}); -describe('Select Component', () => { - it('Select Component should work correctly with the values in the form of an array', (done) => { - const form = _.cloneDeep(comp18); + it('Select Component Should work correctly with the values in the form of an array', async () => { const testItems = [ { textField: ['one','two'] }, { textField: ['three','four'] }, { textField: ['five','six'] }, ]; const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp18)); + const select = form.getComponent('select'); + select.setItems(testItems.map(item => ({ data: item }))); + const value = ['three','four']; + select.setValue(value); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 3); + assert.deepEqual(select.getValue(), value); + assert.deepEqual(select.dataValue, value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setItems(testItems.map(item => ({ data: item }))); - const value = ['three','four']; - select.setValue(value); - assert.equal(select.selectOptions.length, 3); - setTimeout(() => { - assert.deepEqual(select.getValue(), value); - assert.deepEqual(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(select.dataValue, value); - done(); - }, 200); - }, 200); - }).catch(done); + assert.equal(select.dataValue, value); }); }); describe('Select Component with Entire Object Value Property', () => { - it('Should provide correct value', (done) => { - const form = _.cloneDeep(comp15); + it('Should provide correct value', async () => { + const restoreMakeRequest = mockMakeRequest(() => new Promise((resolve) => { + resolve([]); + })); + const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp15)); + const select = form.getComponent('select'); + const value = { 'textField':'rgd','submit':true,'number':11 }; + select.setValue(value); + + await select.itemsLoaded; + assert.equal(select.getValue(), value); + assert.equal(select.dataValue, value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + assert.equal(select.dataValue, value); - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const value = { 'textField':'rgd','submit':true,'number':11 }; - select.setValue(value); - - setTimeout(() => { - assert.equal(select.getValue(), value); - assert.equal(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(select.dataValue, value); - done(); - }, 200); - }, 200); - }).catch(done); + restoreMakeRequest(); }); - it('Should provide correct items for Resource DataSrc Type and Entire Object Value Property', (done) => { - const form = _.cloneDeep(comp15); + it('Should provide correct items for Resource DataSrc Type and Entire Object Value Property', async () => { const testItems = [ { textField: 'Jone', number: 1 }, { textField: 'Mary', number: 2 }, { textField: 'Sally', number: 3 } ]; const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setItems(testItems.map(item => ({ data: item }))); - const value = { textField: 'Jone', number: 1 }; - select.setValue(value); - assert.equal(select.selectOptions.length, 3); - - setTimeout(() => { - assert.equal(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(typeof select.dataValue, 'object'); - done(); - }, 200); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp15)); + const select = form.getComponent('select'); + select.setItems(testItems.map(item => ({ data: item }))); + const value = { textField: 'Jone', number: 1 }; + select.setValue(value); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 3); + assert.equal(select.dataValue, value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + assert.equal(typeof select.dataValue, 'object'); }); - it('Should provide correct html value for Resource DataSrc Type and Entire Object Value Property', (done) => { - const form = _.cloneDeep(comp15); + it('Should provide correct html value for Resource DataSrc Type and Entire Object Value Property', async () => { const testItems = [ { textField: 'Jone', number: 1 }, { textField: 'Mary', number: 2 }, { textField: 'Sally', number: 3 } ]; const element = document.createElement('div'); - - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - select.setItems(testItems.map(item => ({ data: item }))); - const selectContainer = element.querySelector('[ref="selectContainer"]'); - assert.notEqual(selectContainer, null); - const options = selectContainer.childNodes; - assert.equal(options.length, 4); - options.forEach((option) => { - assert.notEqual(option.value, '[object Object]'); - }); - const value = { textField: 'Jone', number: 1 }; - select.setValue(value); - assert.equal(select.selectOptions.length, 3); - - setTimeout(() => { - assert.equal(select.dataValue, value); - const submit = form.getComponent('submit'); - const clickEvent = new Event('click'); - const submitBtn = submit.refs.button; - submitBtn.dispatchEvent(clickEvent); - - setTimeout(() => { - assert.equal(typeof select.dataValue, 'object'); - done(); - }, 200); - }, 200); - }).catch(done); + const form = await Formio.createForm(element, _.cloneDeep(comp15)); + const select = form.getComponent('select'); + select.setItems(testItems.map(item => ({ data: item }))); + + await select.itemsLoaded; + const selectContainer = element.querySelector('[ref="selectContainer"]'); + assert.notEqual(selectContainer, null); + const options = selectContainer.childNodes; + assert.equal(options.length, 4); + options.forEach((option) => { + assert.notEqual(option.value, '[object Object]'); + }); + const value = { textField: 'Jone', number: 1 }; + select.setValue(value); + + await select.itemsLoaded; + assert.equal(select.selectOptions.length, 3); + assert.equal(select.dataValue, value); + const submit = form.getComponent('submit'); + const clickEvent = new Event('click'); + const submitBtn = submit.refs.button; + submitBtn.dispatchEvent(clickEvent); + + await new Promise((resolve, reject) => { + form.on('submit', () => resolve()); + form.on('submitError', () => reject('Should submit the form.')); + }); + assert.equal(typeof select.dataValue, 'object'); }); - it('Should set submission value for Resource DataSrc Type and Entire Object Value Property', (done) => { - const form = _.cloneDeep(comp15); + // TODO: figure out why this test is working only with huge timeout + it('Should set submission value for Resource DataSrc Type and Entire Object Value Property', async () => { const element = document.createElement('div'); + const form = await Formio.createForm(element, _.cloneDeep(comp15)); + const select = form.getComponent('select'); + const value = { textField: 'Jone', number: 1 }; + form.submission = { + data: { + select: value + } + }; - Formio.createForm(element, form).then(form => { - const select = form.getComponent('select'); - const value = { textField: 'Jone', nubmer: 1 }; - form.submission = { - data: { - select: value - } - }; - - setTimeout(() => { - assert.equal(typeof select.dataValue, 'object'); - const selectContainer = element.querySelector('[ref="selectContainer"]'); - assert.notEqual(selectContainer, null); - assert.notEqual(selectContainer.value, ''); - const options = selectContainer.childNodes; - assert.equal(options.length, 2); - done(); - }, 1000); - }).catch(done); + await timeout(1000); + assert.equal(typeof select.dataValue, 'object'); + const selectContainer = element.querySelector('[ref="selectContainer"]'); + assert.notEqual(selectContainer, null); + assert.notEqual(selectContainer.value, ''); + const options = selectContainer.childNodes; + assert.equal(options.length, 2); }); - it('Should get string representation of value for Resource DataSrc Type and Entire Object Value Property', (done) => { - Harness.testCreate(SelectComponent, comp15.components[0]).then((component) => { - const entireObject = { - a: '1', - b: '2', - }; - const formattedValue = component.getView(entireObject); - assert.equal(formattedValue, JSON.stringify(entireObject)); - done(); - }); + it('Should get string representation of value for Resource DataSrc Type and Entire Object Value Property', async () => { + const component = await Harness.testCreate(SelectComponent, comp15.components[0]); + const entireObject = { + a: '1', + b: '2', + }; + const formattedValue = component.getView(entireObject); + assert.equal(formattedValue, JSON.stringify(entireObject)); }); - it('Should render label for Select components when Data Source is Resource in read only mode', (done) => { + it('Should render label for Select components when Data Source is Resource in read only mode', async () => { const element = document.createElement('div'); - Formio.createForm(element, comp24, { readOnly: true }).then((form) => { - const select = form.getComponent('select'); - form.setSubmission({ - metadata: { - selectData: { - select: { - data: { - textField1: 'A', - }, + const form =await Formio.createForm(element, comp24, { readOnly: true }); + const select = form.getComponent('select'); + form.setSubmission({ + metadata: { + selectData: { + select: { + data: { + textField1: 'A', }, }, - timezone: 'Europe/Kiev', - offset: 180, - origin: 'http://localhost:3001', - referrer: '', - browserName: 'Netscape', - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', - pathName: '/', - onLine: true, - headers: { - host: 'qvecgdgwpwujbpi.localhost:3000', - connection: 'keep-alive', - 'content-length': '457', - 'sec-ch-ua': '"Google Chrome";v="125", "Chromium";v="125", "Not.A/Brand";v="24"', - accept: 'application/json', - 'content-type': 'application/json', - 'sec-ch-ua-mobile': '?0', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', - 'sec-ch-ua-platform': '"Windows"', - origin: 'http://localhost:3001', - 'sec-fetch-site': 'cross-site', - 'sec-fetch-mode': 'cors', - 'sec-fetch-dest': 'empty', - referer: 'http://localhost:3001/', - 'accept-encoding': 'gzip, deflate, br, zstd', - 'accept-language': 'en-US,en;q=0.9,ru-RU;q=0.8,ru;q=0.7', - }, }, - data: { - select: 1, - select1: { - textField1: 'A', - textField2: '1', - submit: true, - }, + }, + data: { + select: 1, + select1: { + textField1: 'A', + textField2: '1', submit: true, }, - state: 'submitted', - }); - - setTimeout(() => { - const previewSelect = select.element.querySelector('[aria-selected="true"] span'); - - assert.equal(previewSelect.innerHTML, 'A', 'Should show label as a selected value' + - ' for Select component'); + submit: true, + }, + state: 'submitted', + }); - done(); - }, 300); - }) - .catch((err) => done(err)); + await form.submissionReady; + await select.itemsLoaded; + const previewSelect = select.element.querySelector('[aria-selected="true"] span'); + assert.equal(previewSelect.innerHTML, 'A', 'Should show label as a selected value' + + ' for Select component'); }); - it('Should render label for Select components when Data Source is Resource for modal preview', (done) => { + it('Should render label for Select components when Data Source is Resource for modal preview', async () => { const element = document.createElement('div'); const comp = { ...comp24, modalEdit: true }; - Formio.createForm(element, comp).then((form) => { - const select = form.getComponent('select'); - form.setSubmission({ - metadata: { - selectData: { - select: { - data: { - textField1: 'A', - }, + const form = await Formio.createForm(element, comp); + const select = form.getComponent('select'); + form.setSubmission({ + metadata: { + selectData: { + select: { + data: { + textField1: 'A', }, }, }, - data: { - select: 1, - select1: { - textField1: 'A', - textField2: '1', - submit: true, - }, + }, + data: { + select: 1, + select1: { + textField1: 'A', + textField2: '1', submit: true, }, - state: 'submitted', - }); - - setTimeout(() => { - const previewSelect = select.element.querySelector('[aria-selected="true"] span'); - - assert.equal(previewSelect.innerHTML, 'A', 'Should show label as a selected value' + - ' for Select component'); + submit: true, + }, + state: 'submitted', + }); - done(); - }, 300); - }) - .catch((err) => done(err)); + await form.submissionReady; + await select.itemsLoaded; + await timeout(0); + const previewSelect = select.element.querySelector('[aria-selected="true"] span'); + assert.equal(previewSelect.innerHTML, 'A', 'Should show label as a selected value' + + ' for Select component'); }); - it('Should render label for Select Resource type in readOnly mode', (done) => { + it('Should render label for Select Resource type in readOnly mode', async () => { const element = document.createElement('div'); - Formio.createForm(element, comp28, { readOnly: true }).then((form) => { - const select = form.getComponent('selectResource'); - form.setSubmission({ - form: '672483c1d9abe46bcd70bca4', - metadata: { - selectData: { - selectResource: { - data: { - textField: 'test1', - }, + const form = await Formio.createForm(element, comp28, { readOnly: true }); + const select = form.getComponent('selectResource'); + form.setSubmission({ + form: '672483c1d9abe46bcd70bca4', + metadata: { + selectData: { + selectResource: { + data: { + textField: 'test1', }, }, }, - data: { - selectResource: 'test1', - submit: true, - }, - _id: '6724d15cd9abe46bcd7115d1', - project: '67211a9aa929e4e6ebc2bf77', - state: 'submitted', - created: '2024-11-01T13:02:20.349Z', - modified: '2024-11-01T13:02:20.349Z', - }); + }, + data: { + selectResource: 'test1', + submit: true, + }, + _id: '6724d15cd9abe46bcd7115d1', + project: '67211a9aa929e4e6ebc2bf77', + state: 'submitted', + created: '2024-11-01T13:02:20.349Z', + modified: '2024-11-01T13:02:20.349Z', + }); - setTimeout(() => { - const previewSelect = select.element.querySelector('[aria-selected="true"] span'); - assert.equal(previewSelect.innerHTML, 'test1'); + await form.submissionReady; + await select.itemsLoaded; - done(); - }, 400); - }) - .catch((err) => done(err)); + const previewSelect = select.element.querySelector('[aria-selected="true"] span'); + assert.equal(previewSelect.innerHTML, 'test1'); }); }); diff --git a/test/unit/Webform.unit.js b/test/unit/Webform.unit.js index 7d51605d1f..f467730de5 100644 --- a/test/unit/Webform.unit.js +++ b/test/unit/Webform.unit.js @@ -975,7 +975,7 @@ describe('Webform tests', function() { form.setForm(translationTestForm).then(() => { setTimeout(() => { const selectComp = form.getComponent('select'); - const options = selectComp.element.querySelector('[role="listbox"]').children; + const options = selectComp.choices.choiceList.element.children; const option1 = options[0].textContent.trim(); const option2 = options[1].textContent.trim(); const label = selectComp.element.querySelector('label').textContent.trim(); @@ -2842,18 +2842,18 @@ describe('Webform tests', function() { const field = form.components[0]; const field2 = form.components[1]; const fieldInput = field.refs.input[0]; - + Harness.setInputValue(field, 'data[textField]', '12'); - + setTimeout(() => { assert.equal(field.errors.length, 0, 'Should be valid while changing'); const blurEvent = new Event('blur'); fieldInput.dispatchEvent(blurEvent); - + setTimeout(() => { assert.equal(field.errors.length, 1, 'Should set error after component was blurred'); Harness.setInputValue(field2, 'data[textField1]', 'ab'); - + setTimeout(() => { assert.equal(field.errors.length, 1, 'Should keep error when editing another component'); done();