From 4641c3896a0334d516564b4a98bded93efc8aae4 Mon Sep 17 00:00:00 2001 From: David Khourshid Date: Tue, 25 Jul 2017 22:29:03 -0400 Subject: [PATCH] Ensuring that debounced update function is canceled upon reset + unit tests. Fixes #884 --- src/components/control-component.js | 7 ++++ src/reducers/form-actions-reducer.js | 2 +- src/utils/debounce.js | 5 +++ test/control-component-spec.js | 60 ++++++++++++++++++++++++++++ test/field-actions-spec.js | 6 +-- 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/components/control-component.js b/src/components/control-component.js index 0bbae5412..3b40dc7ba 100644 --- a/src/components/control-component.js +++ b/src/components/control-component.js @@ -400,6 +400,13 @@ function createControlClass(s = defaultStrategy) { return; } case 'validate': + case 'reset': + if (intent.type === 'reset') { + this.setViewValue(modelValue); + if (this.handleUpdate.cancel) { + this.handleUpdate.cancel(); + } + } if (containsEvent(validateOn, 'change')) { this.validate({ clearIntents: intent }); } diff --git a/src/reducers/form-actions-reducer.js b/src/reducers/form-actions-reducer.js index 0df23693c..218be50c2 100644 --- a/src/reducers/form-actions-reducer.js +++ b/src/reducers/form-actions-reducer.js @@ -22,7 +22,7 @@ import getFormValue from '../utils/get-form-value'; const resetFieldState = (field, customInitialFieldState) => { if (!isPlainObject(field)) return field; - const intents = [{ type: 'validate' }]; + const intents = [{ type: 'reset' }]; let resetValue = getMeta(field, 'initialValue'); const loadedValue = getMeta(field, 'loadedValue'); diff --git a/src/utils/debounce.js b/src/utils/debounce.js index e424dbfe0..5f624e926 100644 --- a/src/utils/debounce.js +++ b/src/utils/debounce.js @@ -18,5 +18,10 @@ export default function debounce(func, delay) { if (laterFunc) laterFunc(); }; + debouncedFunc.cancel = () => { + clearTimeout(timeout); + laterFunc = undefined; + }; + return debouncedFunc; } diff --git a/test/control-component-spec.js b/test/control-component-spec.js index acc8ceef1..d0c6aafdb 100644 --- a/test/control-component-spec.js +++ b/test/control-component-spec.js @@ -2144,6 +2144,66 @@ Object.keys(testContexts).forEach((testKey) => { assert.equal(get(store.getState().test, 'foo'), 'debounced'); assert.equal(input.value, 'debounced'); }); + + it('should cancel debounced changes when control is reset', (done) => { + const initialState = getInitialState({ foo: 'bar' }); + const store = testCreateStore({ + test: modelReducer('test', initialState), + testForm: formReducer('test', initialState), + }); + + const control = testRender( + , store); + + const input = TestUtils.findRenderedDOMComponentWithTag(control, 'input'); + input.value = 'debounced'; + + TestUtils.Simulate.change(input); + + store.dispatch(actions.reset('test')); + + setTimeout(() => { + assert.equal(get(store.getState().test, 'foo'), 'bar'); + assert.equal(input.value, 'bar'); + done(); + }, 20); + }); + + it('should cancel debounced changes when control is reset then unmounted', (done) => { + const initialState = getInitialState({ foo: 'bar' }); + const store = testCreateStore({ + test: modelReducer('test', initialState), + testForm: formReducer('test', initialState), + }); + + const container = document.createElement('div'); + + const control = ReactDOM.render( + + + , + container); + + const input = TestUtils.findRenderedDOMComponentWithTag(control, 'input'); + input.value = 'debounced'; + + TestUtils.Simulate.change(input); + + store.dispatch(actions.reset('test')); + ReactDOM.unmountComponentAtNode(container); + + setTimeout(() => { + assert.equal(get(store.getState().test, 'foo'), 'bar'); + assert.equal(input.value, 'bar'); + done(); + }, 20); + }); }); describe('persist prop', () => { diff --git a/test/field-actions-spec.js b/test/field-actions-spec.js index f2ecf5e89..cd32036dc 100644 --- a/test/field-actions-spec.js +++ b/test/field-actions-spec.js @@ -180,10 +180,10 @@ Object.keys(testContexts).forEach((testKey) => { const resetState = reducer(undefined, actions.reset('test')); - assert.include(resetState.$form.intents, { type: 'validate' }); + assert.include(resetState.$form.intents, { type: 'reset' }); - assert.include(resetState.button.$form.intents, { type: 'validate' }, - 'should intend to revalidate subfields'); + assert.include(resetState.button.$form.intents, { type: 'reset' }, + 'should intend to revalidate subfields (handled with reset)'); }); });