input: fix regression (#14572)

* input: fix regression

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

* Input: update
pull/14766/head
Jiewei Qian 2019-03-19 18:54:55 +11:00 committed by hetech
parent f0ae2ae759
commit ed13de38c7
2 changed files with 99 additions and 53 deletions

View File

@ -28,11 +28,9 @@
:disabled="inputDisabled" :disabled="inputDisabled"
:readonly="readonly" :readonly="readonly"
:autocomplete="autoComplete || autocomplete" :autocomplete="autoComplete || autocomplete"
:value="nativeInputValue"
ref="input" ref="input"
@compositionstart="handleComposition" @compositionstart="handleCompositionStart"
@compositionupdate="handleComposition" @compositionend="handleCompositionEnd"
@compositionend="handleComposition"
@input="handleInput" @input="handleInput"
@focus="handleFocus" @focus="handleFocus"
@blur="handleBlur" @blur="handleBlur"
@ -82,10 +80,8 @@
v-else v-else
:tabindex="tabindex" :tabindex="tabindex"
class="el-textarea__inner" class="el-textarea__inner"
:value="nativeInputValue" @compositionstart="handleCompositionStart"
@compositionstart="handleComposition" @compositionend="handleCompositionEnd"
@compositionupdate="handleComposition"
@compositionend="handleComposition"
@input="handleInput" @input="handleInput"
ref="textarea" ref="textarea"
v-bind="$attrs" v-bind="$attrs"
@ -130,7 +126,7 @@
textareaCalcStyle: {}, textareaCalcStyle: {},
hovering: false, hovering: false,
focused: false, focused: false,
isOnComposition: false, isComposing: false,
passwordVisible: false passwordVisible: false
}; };
}, },
@ -208,7 +204,7 @@
return this.disabled || (this.elForm || {}).disabled; return this.disabled || (this.elForm || {}).disabled;
}, },
nativeInputValue() { nativeInputValue() {
return this.value === null || this.value === undefined ? '' : this.value; return this.value === null || this.value === undefined ? '' : String(this.value);
}, },
showClear() { showClear() {
return this.clearable && return this.clearable &&
@ -231,6 +227,12 @@
if (this.validateEvent) { if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [val]); 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); 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) { handleFocus(event) {
this.focused = true; this.focused = true;
this.$emit('focus', event); this.$emit('focus', event);
}, },
handleComposition(event) { handleCompositionStart() {
if (event.type === 'compositionstart') { this.isComposing = true;
this.isOnComposition = true; },
} handleCompositionEnd(event) {
if (event.type === 'compositionend') { this.isComposing = false;
this.isOnComposition = false; this.handleInput(event);
this.handleInput(event);
}
}, },
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 // hack for https://github.com/ElemeFE/element/issues/8548
// should remove the following line when we don't support IE // should remove the following line when we don't support IE
@ -299,12 +307,9 @@
this.$emit('input', event.target.value); 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 // see: https://github.com/ElemeFE/element/issues/12850
this.$nextTick(() => { this.$nextTick(this.setNativeInputValue);
let input = this.getInput();
input.value = this.value;
});
}, },
handleChange(event) { handleChange(event) {
this.$emit('change', event.target.value); this.$emit('change', event.target.value);
@ -355,6 +360,7 @@
}, },
mounted() { mounted() {
this.setNativeInputValue();
this.resizeTextarea(); this.resizeTextarea();
this.updateIconOffset(); this.updateIconOffset();
}, },

View File

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