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 8c7e6babafd7858a164e15423ebe6a16adadd42e..e4d634e7b83cb757e6d02d04a37eddbdfeb5b977 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 22049ea807baa44cc5ca1916d2b072e7e749c7ab..f7212e236442f8459df48e5f8ed1d7e1e5910b1d 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 0000000000000000000000000000000000000000..a23d2700aa6568c72ac0341a51a8befab57a6922 --- /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 25c1f55f71664ebc32c8b1d23a0afa40bf0a614d..35c8081819cda66d13cd5cd0f0d3499267da5f4b 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" />