Forráskód Böngészése

input: fix regression (#14572)

* input: fix regression

guard against: #14551
fix: #14501, #14521, #14564, #14332

* Input: update
Jiewei Qian 6 éve
szülő
commit
ed13de38c7
2 módosított fájl, 99 hozzáadás és 53 törlés
  1. 30 24
      packages/input/src/input.vue
  2. 69 29
      test/unit/specs/input.spec.js

+ 30 - 24
packages/input/src/input.vue

@@ -28,11 +28,9 @@
         :disabled="inputDisabled"
         :readonly="readonly"
         :autocomplete="autoComplete || autocomplete"
-        :value="nativeInputValue"
         ref="input"
-        @compositionstart="handleComposition"
-        @compositionupdate="handleComposition"
-        @compositionend="handleComposition"
+        @compositionstart="handleCompositionStart"
+        @compositionend="handleCompositionEnd"
         @input="handleInput"
         @focus="handleFocus"
         @blur="handleBlur"
@@ -82,10 +80,8 @@
       v-else
       :tabindex="tabindex"
       class="el-textarea__inner"
-      :value="nativeInputValue"
-      @compositionstart="handleComposition"
-      @compositionupdate="handleComposition"
-      @compositionend="handleComposition"
+      @compositionstart="handleCompositionStart"
+      @compositionend="handleCompositionEnd"
       @input="handleInput"
       ref="textarea"
       v-bind="$attrs"
@@ -130,7 +126,7 @@
         textareaCalcStyle: {},
         hovering: false,
         focused: false,
-        isOnComposition: false,
+        isComposing: false,
         passwordVisible: false
       };
     },
@@ -208,7 +204,7 @@
         return this.disabled || (this.elForm || {}).disabled;
       },
       nativeInputValue() {
-        return this.value === null || this.value === undefined ? '' : this.value;
+        return this.value === null || this.value === undefined ? '' : String(this.value);
       },
       showClear() {
         return this.clearable &&
@@ -231,6 +227,12 @@
         if (this.validateEvent) {
           this.dispatch('ElFormItem', 'el.form.change', [val]);
         }
+      },
+      // native input value is set explicitly
+      // do not use v-model / :value in template
+      // see: https://github.com/ElemeFE/element/issues/14521
+      nativeInputValue() {
+        this.setNativeInputValue();
       }
     },
 
@@ -277,21 +279,27 @@
 
         this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
       },
+      setNativeInputValue() {
+        const input = this.getInput();
+        if (!input) return;
+        if (input.value === this.nativeInputValue) return;
+        input.value = this.nativeInputValue;
+      },
       handleFocus(event) {
         this.focused = true;
         this.$emit('focus', event);
       },
-      handleComposition(event) {
-        if (event.type === 'compositionstart') {
-          this.isOnComposition = true;
-        }
-        if (event.type === 'compositionend') {
-          this.isOnComposition = false;
-          this.handleInput(event);
-        }
+      handleCompositionStart() {
+        this.isComposing = true;
+      },
+      handleCompositionEnd(event) {
+        this.isComposing = false;
+        this.handleInput(event);
       },
       handleInput(event) {
-        if (this.isOnComposition) return;
+        // should not emit input during composition
+        // see: https://github.com/ElemeFE/element/issues/10516
+        if (this.isComposing) return;
 
         // hack for https://github.com/ElemeFE/element/issues/8548
         // should remove the following line when we don't support IE
@@ -299,12 +307,9 @@
 
         this.$emit('input', event.target.value);
 
-        // set input's value, in case parent refuses the change
+        // ensure native input value is controlled
         // see: https://github.com/ElemeFE/element/issues/12850
-        this.$nextTick(() => {
-          let input = this.getInput();
-          input.value = this.value;
-        });
+        this.$nextTick(this.setNativeInputValue);
       },
       handleChange(event) {
         this.$emit('change', event.target.value);
@@ -355,6 +360,7 @@
     },
 
     mounted() {
+      this.setNativeInputValue();
       this.resizeTextarea();
       this.updateIconOffset();
     },

+ 69 - 29
test/unit/specs/input.spec.js

@@ -1,4 +1,4 @@
-import { createVue, destroyVM, wait, waitImmediate } from '../util';
+import { createVue, destroyVM, triggerEvent, wait, waitImmediate } from '../util';
 
 describe('Input', () => {
   let vm;
@@ -6,7 +6,7 @@ describe('Input', () => {
     destroyVM(vm);
   });
 
-  it('create', () => {
+  it('create', async() => {
     vm = createVue({
       template: `
         <el-input
@@ -14,11 +14,12 @@ describe('Input', () => {
           :maxlength="5"
           placeholder="请输入内容"
           @focus="handleFocus"
-          value="input">
+          :value="input">
         </el-input>
       `,
       data() {
         return {
+          input: 'input',
           inputFocus: false
         };
       },
@@ -35,6 +36,18 @@ describe('Input', () => {
     expect(inputElm.value).to.equal('input');
     expect(inputElm.getAttribute('minlength')).to.equal('3');
     expect(inputElm.getAttribute('maxlength')).to.equal('5');
+
+    vm.input = 'text';
+    await waitImmediate();
+    expect(inputElm.value).to.equal('text');
+  });
+
+  it('default to empty', () => {
+    vm = createVue({
+      template: '<el-input/>'
+    }, true);
+    let inputElm = vm.$el.querySelector('input');
+    expect(inputElm.value).to.equal('');
   });
 
   it('disabled', () => {
@@ -236,7 +249,7 @@ describe('Input', () => {
   });
 
   describe('Input Events', () => {
-    it('event:focus & blur', done => {
+    it('event:focus & blur', async() => {
       vm = createVue({
         template: `
           <el-input
@@ -255,13 +268,11 @@ describe('Input', () => {
       vm.$el.querySelector('input').focus();
       vm.$el.querySelector('input').blur();
 
-      vm.$nextTick(_ => {
-        expect(spyFocus.calledOnce).to.be.true;
-        expect(spyBlur.calledOnce).to.be.true;
-        done();
-      });
+      await waitImmediate();
+      expect(spyFocus.calledOnce).to.be.true;
+      expect(spyBlur.calledOnce).to.be.true;
     });
-    it('event:change', done => {
+    it('event:change', async() => {
       // NOTE: should be same as native's change behavior
       vm = createVue({
         template: `
@@ -290,13 +301,11 @@ describe('Input', () => {
       // simplified test, component should emit change when native does
       simulateEvent('1', 'input');
       simulateEvent('2', 'change');
-      vm.$nextTick(_ => {
-        expect(spy.calledWith('2')).to.be.true;
-        expect(spy.calledOnce).to.be.true;
-        done();
-      });
+      await waitImmediate();
+      expect(spy.calledWith('2')).to.be.true;
+      expect(spy.calledOnce).to.be.true;
     });
-    it('event:clear', done => {
+    it('event:clear', async() => {
       vm = createVue({
         template: `
           <el-input
@@ -319,18 +328,51 @@ describe('Input', () => {
       // focus to show clear button
       inputElm.focus();
       vm.$refs.input.$on('clear', spyClear);
-      vm.$nextTick(_ => {
-        vm.$el.querySelector('.el-input__clear').click();
-        vm.$nextTick(_ => {
-          expect(spyClear.calledOnce).to.be.true;
-          done();
-        });
-      });
+      await waitImmediate();
+      vm.$el.querySelector('.el-input__clear').click();
+      await waitImmediate();
+      expect(spyClear.calledOnce).to.be.true;
+    });
+    it('event:input', async() => {
+      vm = createVue({
+        template: `
+          <el-input
+            ref="input"
+            placeholder="请输入内容"
+            clearable
+            :value="input">
+          </el-input>
+        `,
+        data() {
+          return {
+            input: 'a'
+          };
+        }
+      }, true);
+      const spy = sinon.spy();
+      vm.$refs.input.$on('input', spy);
+      const nativeInput = vm.$refs.input.$el.querySelector('input');
+      nativeInput.value = '1';
+      triggerEvent(nativeInput, 'compositionstart');
+      triggerEvent(nativeInput, 'input');
+      await waitImmediate();
+      nativeInput.value = '2';
+      triggerEvent(nativeInput, 'compositionupdate');
+      triggerEvent(nativeInput, 'input');
+      await waitImmediate();
+      triggerEvent(nativeInput, 'compositionend');
+      await waitImmediate();
+      // input event does not fire during composition
+      expect(spy.calledOnce).to.be.true;
+      // native input value is controlled
+      expect(vm.input).to.equal('a');
+      expect(nativeInput.value).to.equal('a');
+
     });
   });
 
   describe('Input Methods', () => {
-    it('method:select', done => {
+    it('method:select', async() => {
       const testContent = 'test';
 
       vm = createVue({
@@ -347,11 +389,9 @@ describe('Input', () => {
 
       vm.$refs.inputComp.select();
 
-      vm.$nextTick(_ => {
-        expect(vm.$refs.inputComp.$refs.input.selectionStart).to.equal(0);
-        expect(vm.$refs.inputComp.$refs.input.selectionEnd).to.equal(testContent.length);
-        done();
-      });
+      await waitImmediate();
+      expect(vm.$refs.inputComp.$refs.input.selectionStart).to.equal(0);
+      expect(vm.$refs.inputComp.$refs.input.selectionEnd).to.equal(testContent.length);
     });
   });
 });