From eae10499b8dc691a39fe9a6b8861939120126902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marker=20dao=20=C2=AE?= Date: Mon, 23 Dec 2024 20:06:50 +0100 Subject: [PATCH] DropDownList: do not load current value on reset method call (T1247576) (#28575) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: marker dao ® Co-authored-by: ksercs --- .../ui/drop_down_editor/m_drop_down_list.ts | 22 ++- .../js/__internal/ui/m_drop_down_box.ts | 9 +- .../devextreme/js/__internal/ui/m_lookup.ts | 4 +- .../js/__internal/ui/m_select_box.ts | 4 +- .../devextreme/js/__internal/ui/m_tag_box.ts | 4 +- .../ui/text_box/m_text_editor.base.ts | 18 ++- .../dateRangeBox.tests.js | 8 +- .../dropDownList.tests.js | 125 +++++++++++++++++- .../selectBox.tests.js | 11 -- .../textEditorParts/common.tests.js | 65 +++++++++ .../textbox.tests.js | 4 +- 11 files changed, 239 insertions(+), 35 deletions(-) diff --git a/packages/devextreme/js/__internal/ui/drop_down_editor/m_drop_down_list.ts b/packages/devextreme/js/__internal/ui/drop_down_editor/m_drop_down_list.ts index ec47661310bc..43650c517b18 100644 --- a/packages/devextreme/js/__internal/ui/drop_down_editor/m_drop_down_list.ts +++ b/packages/devextreme/js/__internal/ui/drop_down_editor/m_drop_down_list.ts @@ -265,12 +265,20 @@ const DropDownList = DropDownEditor.inherit({ return DROPDOWNLIST_POPUP_WRAPPER_CLASS; }, - _renderInputValue() { - const value = this._getCurrentValue(); + _renderInputValue({ value, renderOnly }: { value?: unknown; renderOnly?: boolean } = {}) { + const currentValue = value ?? this._getCurrentValue(); this._rejectValueLoading(); - return this._loadInputValue(value, this._setSelectedItem.bind(this)) - .always(this.callBase.bind(this, value)); + if (renderOnly) { + return this.callBase(currentValue); + } + + return this + ._loadInputValue( + currentValue, + (...args) => { this._setSelectedItem(...args); }, + ) + .always(this.callBase.bind(this, currentValue)); }, _loadInputValue(value, callback) { @@ -301,6 +309,10 @@ const DropDownList = DropDownEditor.inherit({ return selectedItem; }, + _resetInputText(): void { + this._renderInputValue({ renderOnly: true }); + }, + _loadItem(value, cache) { const selectedItem = this._getItemFromPlain(value, cache); @@ -812,7 +824,9 @@ const DropDownList = DropDownEditor.inherit({ if (this._list) { delete this._list; } + delete this._isLastMinSearchLengthExceeded; + this.callBase(); }, diff --git a/packages/devextreme/js/__internal/ui/m_drop_down_box.ts b/packages/devextreme/js/__internal/ui/m_drop_down_box.ts index cf6dbb96de22..bea731c797f6 100644 --- a/packages/devextreme/js/__internal/ui/m_drop_down_box.ts +++ b/packages/devextreme/js/__internal/ui/m_drop_down_box.ts @@ -101,12 +101,12 @@ const DropDownBox = (DropDownEditor as any).inherit({ return sortedValues.map((x) => x.itemDisplayValue); }, - _renderInputValue() { + _renderInputValue({ renderOnly }: { renderOnly?: boolean } = {}) { this._rejectValueLoading(); const values = []; if (!this._dataSource) { - this.callBase(values); + this.callBase({ renderOnly, value: values }); return Deferred().resolve(); } @@ -138,7 +138,10 @@ const DropDownBox = (DropDownEditor as any).inherit({ .always(() => { const orderedValues = this._sortValuesByKeysOrder(keys, values); this.option('displayValue', orderedValues); - callBase(values.length && orderedValues); + callBase({ + renderOnly, + value: values.length && orderedValues, + }); }); }, diff --git a/packages/devextreme/js/__internal/ui/m_lookup.ts b/packages/devextreme/js/__internal/ui/m_lookup.ts index 7079b955b6a2..66d75c8d67d1 100644 --- a/packages/devextreme/js/__internal/ui/m_lookup.ts +++ b/packages/devextreme/js/__internal/ui/m_lookup.ts @@ -945,8 +945,8 @@ const Lookup = DropDownList.inherit({ return this.option('searchEnabled') && this._searchBox ? this._searchBox.option('value') : ''; }, - _renderInputValue() { - return this.callBase().always(() => { + _renderInputValue(...args) { + return this.callBase(...args).always(() => { this._refreshSelected(); }); }, diff --git a/packages/devextreme/js/__internal/ui/m_select_box.ts b/packages/devextreme/js/__internal/ui/m_select_box.ts index 5775e1abca5b..184d1eeb5461 100644 --- a/packages/devextreme/js/__internal/ui/m_select_box.ts +++ b/packages/devextreme/js/__internal/ui/m_select_box.ts @@ -287,8 +287,8 @@ const SelectBox = (DropDownList as any).inherit({ return Deferred().resolve(); }, - _renderInputValue() { - return this.callBase().always(() => { + _renderInputValue(...args) { + return this.callBase(...args).always(() => { this._renderInputValueAsync(); }); }, diff --git a/packages/devextreme/js/__internal/ui/m_tag_box.ts b/packages/devextreme/js/__internal/ui/m_tag_box.ts index 7080c1eeb6d9..33e5bbc6a719 100644 --- a/packages/devextreme/js/__internal/ui/m_tag_box.ts +++ b/packages/devextreme/js/__internal/ui/m_tag_box.ts @@ -601,10 +601,10 @@ const TagBox = (SelectBox as any).inherit({ this.callBase(e); }, - _renderInputValue() { + _renderInputValue(...args) { this.option('displayValue', this._searchValue()); - return this.callBase(); + return this.callBase(...args); }, _restoreInputText(saveEditingValue) { diff --git a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.base.ts b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.base.ts index e1536da9341b..2b1fdd063dab 100644 --- a/packages/devextreme/js/__internal/ui/text_box/m_text_editor.base.ts +++ b/packages/devextreme/js/__internal/ui/text_box/m_text_editor.base.ts @@ -916,10 +916,22 @@ const TextEditorBase = Editor.inherit({ } }, + _resetInputText(): void { + this._options.silent('text', this._initialValue); + this._renderValue(); + }, + + _isValueEqualToInitial(): boolean { + const { value } = this.option(); + const initialValue = this._initialValue; + + return value === initialValue; + }, + _resetToInitialValue() { - if (this.option('value') === this._initialValue) { - this._options.silent('text', this._initialValue); - this._renderValue(); + const shouldResetInputText = this._isValueEqualToInitial(); + if (shouldResetInputText) { + this._resetInputText(); } else { this.callBase(); } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js index 8fc84a85aa3d..fd2191d25e75 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dateRangeBox.tests.js @@ -2615,7 +2615,7 @@ QUnit.module('Validation', { assert.strictEqual(this.instance.option('isValid'), true); }); - QUnit.test('reset should clear input value when editor`s value hasn`t been changed', function(assert) { + QUnit.test('reset should clear input value if editor\'s value has not been changed', function(assert) { const initialValue = [null, null]; this.reinit({ value: initialValue }); @@ -2625,12 +2625,12 @@ QUnit.module('Validation', { const keyboard = keyboardMock($startDateBoxInput); keyboard.type('123').press('enter'); - assert.strictEqual($startDateBoxInput.val(), '123'); - assert.deepEqual(this.instance.option('value'), initialValue); + assert.strictEqual($startDateBoxInput.val(), '123', 'input value is correct'); + assert.deepEqual(this.instance.option('value'), initialValue, 'value option is equal to initial'); this.instance.reset(); - assert.strictEqual($startDateBoxInput.val(), ''); + assert.strictEqual($startDateBoxInput.val(), '', 'input value is reset'); }); QUnit.test('dateRangeBox should not be re-validated after readOnly option change', function(assert) { diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownList.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownList.tests.js index 428b5b860e99..8ef5a1d1cb05 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownList.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/dropDownList.tests.js @@ -1534,6 +1534,128 @@ QUnit.module('dataSource integration', moduleConfig, function() { }); }); +QUnit.module('reset method', moduleConfig, () => { + [true, false].forEach(acceptCustomValue => { + QUnit.test(`byKey should not be called if value is equal to initial, acceptCustomValue is ${acceptCustomValue} (T1247576)`, function(assert) { + const byKeyHandler = sinon.spy(key => key); + const items = ['initial']; + + const dataSource = new DataSource({ + store: new CustomStore({ + load: () => items, + byKey: byKeyHandler, + }), + }); + + const instance = $('#dropDownList').dxDropDownList({ + acceptCustomValue, + searchEnabled: false, + dataSource, + value: items[0], + }).dxDropDownList('instance'); + + assert.strictEqual(byKeyHandler.callCount, 1, 'byKey is called once after init'); + + instance.reset(); + + assert.strictEqual(byKeyHandler.callCount, 1, 'byKey is still called once'); + }); + }); + + ['acceptCustomValue', 'searchEnabled'].forEach(editingOption => { + QUnit.test(`reset should restore the input text and text option to the initial value even if the value is NOT changed, ${editingOption}=true`, function(assert) { + assert.expect(12); + + const byKeyHandler = sinon.spy(key => key); + const items = ['initial']; + const additionalText = 'NEW'; + + const dataSource = new DataSource({ + store: new CustomStore({ + load: () => items, + byKey: byKeyHandler, + }), + }); + + const $element = $('#dropDownList').dxDropDownList({ + acceptCustomValue: false, + searchEnabled: false, + [editingOption]: true, + dataSource, + valueChangeEvent: 'change', + value: items[0], + }); + + const instance = $element.dxDropDownList('instance'); + const $input = $element.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); + + const assertState = (expectedText, messageComment) => { + assert.strictEqual($input.val(), expectedText, `input text is "${expectedText}" ${messageComment}`); + assert.strictEqual(instance.option('text'), expectedText, `text option is "${expectedText}" ${messageComment}`); + assert.strictEqual(instance.option('value'), items[0], `value option is "${items[0]}" ${messageComment}`); + assert.strictEqual(byKeyHandler.callCount, 1, 'no additional byKey for initial item is presented'); + }; + + assertState(items[0], 'initially'); + + keyboard.type(additionalText); + + assertState(`${additionalText}${items[0]}`, 'after typing'); + + instance.reset(); + + assertState(items[0], 'after reset'); + }); + }); + + QUnit.test('reset should restore the input value, value and text options to the initial value if the value is changed, acceptCustomValue=true', function(assert) { + assert.expect(12); + + const byKeyHandler = sinon.spy(key => key); + const items = ['initial']; + const additionalText = 'NEW'; + let expectedByKeyCallCount = 0; + + const dataSource = new DataSource({ + store: new CustomStore({ + load: () => items, + byKey: byKeyHandler, + }), + }); + + const $element = $('#dropDownList').dxDropDownList({ + acceptCustomValue: true, + valueChangeEvent: 'change', + dataSource, + value: items[0], + }); + + const instance = $element.dxDropDownList('instance'); + const $input = $element.find(`.${TEXTEDITOR_INPUT_CLASS}`); + const keyboard = keyboardMock($input); + + const assertState = (expectedText, messageComment) => { + expectedByKeyCallCount++; + assert.strictEqual($input.val(), expectedText, `input text is "${expectedText}" ${messageComment}`); + assert.strictEqual(instance.option('text'), expectedText, `text option is "${expectedText}" ${messageComment}`); + assert.strictEqual(instance.option('value'), expectedText, `value option is "${expectedText}" ${messageComment}`); + assert.strictEqual(byKeyHandler.callCount, expectedByKeyCallCount, 'byKey call is okay if loading value is not the current value'); + }; + + assertState(items[0], 'initially'); + + keyboard.type(additionalText); + keyboard.change(); + + assertState(`${additionalText}${items[0]}`, 'after typing'); + + instance.reset(); + + assertState(items[0], 'after reset'); + }); +}); + QUnit.module('action options', moduleConfig, () => { QUnit.test('onItemClick action', function(assert) { assert.expect(3); @@ -1806,8 +1928,7 @@ QUnit.module('dropdownlist with groups', { }); }); -QUnit.module( - 'data source from url', +QUnit.module('data source from url', { afterEach: function() { ajaxMock.clear(); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js index 86d44e53f586..9df9248a9852 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/selectBox.tests.js @@ -104,7 +104,6 @@ const moduleSetup = { }; QUnit.module('hidden input', moduleSetup, () => { - QUnit.test('the hidden input should get correct value on widget value change', function(assert) { const $element = $('#selectBox').dxSelectBox({ items: [1, 2, 3], @@ -157,7 +156,6 @@ QUnit.module('hidden input', moduleSetup, () => { }); QUnit.module('functionality', moduleSetup, () => { - QUnit.test('value can be set to "null"', function(assert) { const $element = $('#selectBox').dxSelectBox({ items: ['first', 'second', 'third'], @@ -1579,7 +1577,6 @@ QUnit.module('widget options', moduleSetup, () => { }); QUnit.module('clearButton', moduleSetup, () => { - QUnit.test('"clear" button click should not open selectbox', function(assert) { const $element = $('#selectBox').dxSelectBox({ items: [1, 2, 3], @@ -1757,7 +1754,6 @@ QUnit.module('clearButton', moduleSetup, () => { }); QUnit.module('showSelectionControls', moduleSetup, () => { - QUnit.test('showSelectionControls is true', function(assert) { $('#selectBox').dxSelectBox({ items: [1], @@ -1788,7 +1784,6 @@ QUnit.module('showSelectionControls', moduleSetup, () => { }); QUnit.module('editing', moduleSetup, () => { - QUnit.test('readOnly option with searchEnabled', function(assert) { const $selectBox = $('#selectBox').dxSelectBox({ items: ['item1', 'item2', 'text3'], @@ -4195,7 +4190,6 @@ QUnit.module('search substitution', { }); }); - QUnit.module('Scrolling', { beforeEach: function() { fx.off = true; @@ -4393,7 +4387,6 @@ QUnit.module('Async tests', {}, () => { }); QUnit.module('regressions', moduleSetup, () => { - QUnit.test('dataSource null reference error', function(assert) { assert.expect(0); @@ -4720,7 +4713,6 @@ QUnit.module('regressions', moduleSetup, () => { }); QUnit.module('hide on blur', moduleSetup, () => { - QUnit.testInActiveWindow('selectbox does not hide self after input blur', function(assert) { const $selectBox = $('#selectBoxWithoutScroll').dxSelectBox({ dataSource: [100, 200, 300] @@ -5474,7 +5466,6 @@ QUnit.module('keyboard navigation', moduleSetup, () => { }); QUnit.module('keyboard navigation "TAB" button', moduleSetup, () => { - QUnit.test('T309987 - item should not be changed on the "tab" press', function(assert) { const items = ['first', 'second']; const value = items[1]; @@ -5954,7 +5945,6 @@ QUnit.module('acceptCustomValue mode', moduleSetup, () => { }); }); - QUnit.module('focus policy', { beforeEach: function() { this.clock = sinon.useFakeTimers(); @@ -6523,7 +6513,6 @@ QUnit.module('displayExpr', moduleSetup, () => { }); }); - QUnit.module('The "customItemCreateEvent" option warning', { beforeEach: function() { this.$selectBox = $('#selectBox'); diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textEditorParts/common.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textEditorParts/common.tests.js index 1edd977a39d9..eb4d77c0f73f 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textEditorParts/common.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textEditorParts/common.tests.js @@ -109,6 +109,71 @@ QUnit.module('general', {}, () => { assert.equal($textEditor.find('*').length, $contentElements.length); }); + QUnit.test('reset should clear input value, value and text options, if value was not changed', function(assert) { + assert.expect(9); + + const $element = $('#texteditor').dxTextEditor(); + const instance = $element.dxTextEditor('instance'); + const $input = $element.find(`.${INPUT_CLASS}`); + const keyboard = keyboardMock($input); + + const initialState = { input: '', value: '', text: '' }; + const afterTypingState = { input: 'test', value: '', text: 'test' }; + + const getter = { + input: () => $input.val(), + value: () => instance.option('value'), + text: () => instance.option('text'), + }; + + const check = (expected, message) => { + Object.keys(expected).forEach(prop => { + assert.strictEqual(getter[prop](), expected[prop], `${prop} is '${expected[prop]}' ${message}`); + }); + }; + + check(initialState, 'before typing'); + + keyboard.type('test'); + check(afterTypingState, 'after typing'); + + instance.reset(); + check(initialState, 'after reset'); + }); + + + QUnit.test('reset should clear input value, value and text options, if value was changed', function(assert) { + assert.expect(9); + + const $element = $('#texteditor').dxTextEditor(); + const instance = $element.dxTextEditor('instance'); + const $input = $element.find(`.${INPUT_CLASS}`); + const keyboard = keyboardMock($input); + + const essences = [ + { name: 'input value', get: () => $input.val() }, + { name: 'value option', get: () => instance.option('value') }, + { name: 'text option', get: () => instance.option('text') }, + ]; + + const check = (expected, message) => { + essences.forEach(essence => { + assert.strictEqual(essence.get(), expected, `${essence.name} ${message}`); + }); + }; + + check('', 'is empty before typing'); + + keyboard.type('test'); + $input.trigger('change'); + + check('test', 'is present after typing'); + + instance.reset(); + + check('', 'is absent after reset'); + }); + QUnit.test('value === 0 should be rendered on init', function(assert) { const $element = $('#texteditor').dxTextEditor({ value: 0 diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textbox.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textbox.tests.js index c189dec3e772..d73fce105378 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textbox.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets.editors/textbox.tests.js @@ -75,7 +75,7 @@ QUnit.module('common', {}, () => { const instance = $('#textbox').dxTextBox('instance'); inFocus = element.find('.dx-texteditor-input').is(':focus'); - assert.ok(!inFocus, 'at start input has not focused'); + assert.ok(!inFocus, 'at start input has not focused'); instance.focus(); assert.ok(inFocus, 'when call \'focus\' method, then focus on input'); @@ -98,7 +98,7 @@ QUnit.module('common', {}, () => { assert.strictEqual(editor.option('isValid'), true, 'editor state is valid after reset'); }); - QUnit.test('reset should clear input value', function(assert) { + QUnit.test('reset should clear input value if value was not changed', function(assert) { const instance = $('#textbox').dxTextBox().dxTextBox('instance'); const $input = $(`.${INPUT_CLASS}`); const keyboard = keyboardMock($input);