From cf699aa263b45acfb42c538b5b8bbe4167024550 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 17 Aug 2020 13:50:34 +0100 Subject: [PATCH] feat(textarea): Add debounce/lazy support This is very similar work to that found in https://gitlab.com/gitlab-org/gitlab-ui/-/issues/631, but applied to `GlFormTextarea` rather than `GlFormInput`. --- .../form_input/form_input.documentation.js | 22 ++++ .../form_textarea.documentation.js | 22 ++++ .../form/form_textarea/form_textarea.spec.js | 105 ++++++++++++++++++ .../base/form/form_textarea/form_textarea.vue | 23 +++- 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/components/base/form/form_textarea/form_textarea.spec.js diff --git a/src/components/base/form/form_input/form_input.documentation.js b/src/components/base/form/form_input/form_input.documentation.js index 8c7e6babaf..e4d634e7b8 100644 --- a/src/components/base/form/form_input/form_input.documentation.js +++ b/src/components/base/form/form_input/form_input.documentation.js @@ -12,4 +12,26 @@ export default { enum: 'formInputSizes', }, }, + events: [ + { + event: 'input', + args: [ + { + arg: 'value', + description: '(String)', + }, + ], + description: 'Emitted to update the v-model', + }, + { + event: 'update', + args: [ + { + arg: 'value', + description: '(String)', + }, + ], + description: `Triggered by user interaction. Emitted after any formatting (not including 'trim' or 'number' props). Useful for getting the currently entered value when the 'debounce' or 'lazy' props are set.`, + }, + ], }; diff --git a/src/components/base/form/form_textarea/form_textarea.documentation.js b/src/components/base/form/form_textarea/form_textarea.documentation.js index 22049ea807..f7212e2364 100644 --- a/src/components/base/form/form_textarea/form_textarea.documentation.js +++ b/src/components/base/form/form_textarea/form_textarea.documentation.js @@ -5,4 +5,26 @@ export default { description, examples, bootstrapComponent: 'b-form-textarea', + events: [ + { + event: 'input', + args: [ + { + arg: 'value', + description: '(String)', + }, + ], + description: 'Emitted to update the v-model', + }, + { + event: 'update', + args: [ + { + arg: 'value', + description: '(String)', + }, + ], + description: `Triggered by user interaction. Emitted after any formatting (not including 'trim' or 'number' props). Useful for getting the currently entered value when the 'debounce' or 'lazy' props are set.`, + }, + ], }; diff --git a/src/components/base/form/form_textarea/form_textarea.spec.js b/src/components/base/form/form_textarea/form_textarea.spec.js new file mode 100644 index 0000000000..a23d2700aa --- /dev/null +++ b/src/components/base/form/form_textarea/form_textarea.spec.js @@ -0,0 +1,105 @@ +import { mount } from '@vue/test-utils'; +import GlFormTextarea from './form_textarea.vue'; + +const modelEvent = GlFormTextarea.model.event; +const newValue = 'foo'; + +describe('GlFormTextArea', () => { + let wrapper; + + const createComponent = (propsData = {}) => { + wrapper = mount(GlFormTextarea, { + propsData, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('v-model', () => { + describe('value binding', () => { + beforeEach(() => { + createComponent({ value: 'initial' }); + }); + + it(`sets the textarea's value`, () => { + expect(wrapper.element.value).toBe('initial'); + }); + + describe('when the value prop changes', () => { + beforeEach(() => { + wrapper.setProps({ value: newValue }); + return wrapper.vm.$nextTick(); + }); + + it(`updates the textarea's value`, () => { + expect(wrapper.element.value).toBe(newValue); + }); + }); + }); + + describe('event emission', () => { + beforeEach(() => { + createComponent(); + + wrapper.setValue(newValue); + }); + + it('synchronously emits update event', () => { + expect(wrapper.emitted('update')).toEqual([[newValue]]); + }); + + it(`synchronously emits ${modelEvent} event`, () => { + expect(wrapper.emitted(modelEvent)).toEqual([[newValue]]); + }); + }); + }); + + describe('debounce', () => { + describe.each([10, 100, 1000])('given a debounce of %dms', debounce => { + beforeEach(() => { + jest.useFakeTimers(); + + createComponent({ debounce }); + + wrapper.setValue(newValue); + }); + + it('synchronously emits an update event', () => { + expect(wrapper.emitted('update')).toEqual([[newValue]]); + }); + + it(`emits a ${modelEvent} event after the debounce delay`, () => { + // Just before debounce completes + jest.advanceTimersByTime(debounce - 1); + expect(wrapper.emitted(modelEvent)).toBe(undefined); + + // Exactly when debounce completes + jest.advanceTimersByTime(1); + expect(wrapper.emitted(modelEvent)).toEqual([[newValue]]); + }); + }); + }); + + describe('lazy', () => { + beforeEach(() => { + createComponent({ lazy: true }); + + wrapper.setValue(newValue); + }); + + it('synchronously emits an update event', () => { + expect(wrapper.emitted('update')).toEqual([[newValue]]); + }); + + it.each(['change', 'blur'])('updates model after %s event', event => { + expect(wrapper.emitted(modelEvent)).toBe(undefined); + + wrapper.trigger(event); + + expect(wrapper.emitted(modelEvent)).toEqual([[newValue]]); + }); + }); +}); diff --git a/src/components/base/form/form_textarea/form_textarea.vue b/src/components/base/form/form_textarea/form_textarea.vue index 25c1f55f71..35c8081819 100644 --- a/src/components/base/form/form_textarea/form_textarea.vue +++ b/src/components/base/form/form_textarea/form_textarea.vue @@ -1,11 +1,17 @@ @@ -29,6 +50,6 @@ export default { :no-resize="noResize" v-bind="$attrs" :value="value" - v-on="$listeners" + v-on="listeners" /> -- GitLab