Skip to content

Commit

Permalink
feat(input-component): correct invalid input value (#182)
Browse files Browse the repository at this point in the history
  • Loading branch information
smithoo authored May 23, 2024
1 parent 674ad83 commit e3ee38f
Show file tree
Hide file tree
Showing 17 changed files with 796 additions and 622 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export default defineComponent({
required,
rules,
} = toRefs(props);
const checkboxRefs: Ref<HTMLInputElement[]> = ref([]);
const { emit } = context;
Expand Down Expand Up @@ -145,7 +146,7 @@ export default defineComponent({
);
function requiredCheck() {
return required.value && inputValue.value.length === 0 ? 'required' : '';
return required.value && inputValue.value && inputValue.value.length === 0 ? 'required' : '';
}
const allRules = computed(() => [...rules.value, requiredCheck]);
Expand All @@ -159,13 +160,21 @@ export default defineComponent({
inputValue.value = [];
}
},
onChange: () => {
if (!inputValue.value) {
inputValue.value = [];
}
},
onClear: () => {
inputValue.value = [];
},
},
});
function isChecked(option: any) {
if (!inputValue.value) {
return false;
}
return inputValue.value.some((v: any) => utils.object.isEqual(v, getOptionValue(option)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,22 @@ describe('vs-checkbox-set', () => {
const checked = wrapper.findAll('input').filter((e) => e.element.checked);
expect(checked).toHaveLength(1);
expect(checked[0].element.value).toBe('A');
expect(wrapper.vm.inputValue).toEqual(['A']);
});

it('modelValue๊ฐ€ null์ธ ๊ฒฝ์šฐ ๋นˆ ๋ฐฐ์—ด๋กœ ๋ณด์ •๋œ๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckboxSet, {
props: {
// @ts-expect-error: for null test
modelValue: null,
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
},
});

// then
expect(wrapper.vm.inputValue).toEqual([]);
});

it('modelValue๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
Expand All @@ -120,6 +136,24 @@ describe('vs-checkbox-set', () => {
expect(updateModelValueEvent?.[0]).toEqual([['A', 'B']]);
});

it('modelValue์— null์„ ํ• ๋‹นํ•˜๋ฉด ๋นˆ ๋ฐฐ์—ด๋กœ ๋ณด์ •๋œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckboxSet, {
props: {
modelValue: ['A'],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
},
});

// when
// @ts-expect-error: for null test
await wrapper.setProps({ modelValue: null });

// then
expect(wrapper.vm.inputValue).toEqual([]);
});

it('modelValue๋ฅผ ๋ฐ”๊ฟ”์„œ checkbox ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckboxSet, {
Expand Down
9 changes: 7 additions & 2 deletions packages/vlossom/src/components/vs-checkbox/VsCheckbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,24 @@ export default defineComponent({
callbacks: {
onMounted: () => {
if (checked.value) {
inputValue.value = getUpdatedValue(true, inputValue.value);
inputValue.value = getUpdatedValue(true);
} else {
inputValue.value = getInitialValue();
}
},
onChange: () => {
if (inputValue.value === undefined || inputValue.value === null) {
inputValue.value = getClearedValue();
}
},
onClear: () => {
inputValue.value = getClearedValue();
},
},
});
async function onToggle(c: boolean) {
const toValue = getUpdatedValue(c, inputValue.value);
const toValue = getUpdatedValue(c);
const beforeChangeFn = beforeChange.value;
if (beforeChangeFn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,30 @@ describe('vs-checkbox', () => {
expect(wrapper.find('input').element.checked).toBe(false);
expect(wrapper.props('modelValue')).toBe(false);
});

it('modelValue๋ฅผ null๋กœ ํ• ๋‹นํ•˜๋ฉด falseValue๋กœ ๋ณด์ •ํ•ด์ค€๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
modelValue: true,
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'hello',
falseValue: 'world',
},
});

// when
await wrapper.setProps({ modelValue: null });

// then
expect(wrapper.find('input').element.checked).toBe(false);
expect(wrapper.vm.isChecked).toBe(false);
expect(wrapper.vm.inputValue).toBe('world');
});
});

describe('true / false value', () => {
it('true-value, false-value๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
it('trueValue, falseValue๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand All @@ -94,7 +114,7 @@ describe('vs-checkbox', () => {
expect(wrapper.vm.isChecked).toBe(true);
});

it('checkbox๋ฅผ true๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด modelValue๋ฅผ true-value ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค', async () => {
it('checkbox๋ฅผ true๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด modelValue๋ฅผ trueValue ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand All @@ -114,7 +134,7 @@ describe('vs-checkbox', () => {
expect(updateModelValueEvent?.[0][0]).toEqual('A');
});

it('checkbox๋ฅผ false๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด modelValue๋ฅผ false-value ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค', async () => {
it('checkbox๋ฅผ false๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด modelValue๋ฅผ falseValue ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand All @@ -135,7 +155,7 @@ describe('vs-checkbox', () => {
expect(wrapper.find('input').element.checked).toBe(false);
});

it('object ํƒ€์ž… true-value, false-value๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค', () => {
it('object ํƒ€์ž… trueValue, falseValue๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand All @@ -152,82 +172,79 @@ describe('vs-checkbox', () => {
});
});

describe('v-model ( array )', () => {
describe('multiple ์ด true ์ธ ๊ฒฝ์šฐ', () => {
it('modelValue ์›์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ trueValue์™€ ์ผ์น˜ํ•˜๋ฉด checkbox ๊ฐ’์€ true์ด๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: ['A'],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});
describe('v-model (multiple true)', () => {
it('modelValue ์›์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ trueValue์™€ ์ผ์น˜ํ•˜๋ฉด checkbox ๊ฐ’์€ true์ด๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: ['A'],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});

// then
expect(wrapper.find('input').element.checked).toBe(true);
expect(wrapper.vm.isChecked).toBe(true);
// then
expect(wrapper.find('input').element.checked).toBe(true);
expect(wrapper.vm.isChecked).toBe(true);
});

it('input์„ clickํ•ด์„œ true๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด trueValue๊ฐ€ modelValue๋ฐฐ์—ด์— ํฌํ•จ๋œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});

it('์ธํ’‹ ๊ฐ’์„ true๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด true-value๊ฐ€ modelValue๋ฐฐ์—ด์— ํฌํ•จ๋œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});
// when
await wrapper.find('input').trigger('click');

// when
await wrapper.find('input').trigger('click');
// then
const updateModelValueEvent = wrapper.emitted('update:modelValue');
expect(updateModelValueEvent).toHaveLength(1);
expect(updateModelValueEvent?.[0][0]).toEqual(['A']);
expect(wrapper.find('input').element.checked).toBe(true);
});

// then
const updateModelValueEvent = wrapper.emitted('update:modelValue');
expect(updateModelValueEvent).toHaveLength(1);
expect(updateModelValueEvent?.[0][0]).toEqual(['A']);
expect(wrapper.find('input').element.checked).toBe(true);
it('modelValue์˜ ์ดˆ๊นƒ๊ฐ’์ด null์ด๋ฉด ๋นˆ ๋ฐฐ์—ด๋กœ ๋ณด์ •ํ•ด์ค€๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: null,
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});
});

describe('multiple ์ด false ์ธ ๊ฒฝ์šฐ', () => {
it('modelValue ์›์†Œ ์ค‘์— trueValue์™€ ์ผ์น˜ํ•˜๋Š” ๊ฒƒ์ด ์žˆ๋”๋ผ๋„ checkbox ๊ฐ’์€ false์ด๋‹ค', () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: false,
modelValue: ['A'],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});
// then
expect(wrapper.find('input').element.checked).toBe(false);
expect(wrapper.vm.isChecked).toBe(false);
expect(wrapper.vm.inputValue).toEqual([]);
});

// then
expect(wrapper.find('input').element.checked).toBe(false);
it('modelValue์— null์„ ํ• ๋‹นํ•˜๋ฉด ๋นˆ ๋ฐฐ์—ด๋กœ ๋ณด์ •ํ•ด์ค€๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: true,
modelValue: ['A'],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});

it('์ธํ’‹ ๊ฐ’์„ true๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ฉด modelValue๊ฐ€ true-value๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
multiple: false,
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
trueValue: 'A',
},
});

// when
await wrapper.find('input').trigger('click');
// when
await wrapper.setProps({ modelValue: null });

// then
const updateModelValueEvent = wrapper.emitted('update:modelValue');
expect(updateModelValueEvent).toHaveLength(1);
expect(updateModelValueEvent?.[0][0]).toEqual('A');
expect(wrapper.find('input').element.checked).toBe(true);
});
// then
expect(wrapper.find('input').element.checked).toBe(false);
expect(wrapper.vm.isChecked).toBe(false);
expect(wrapper.vm.inputValue).toEqual([]);
});

it('array ํƒ€์ž… modelValue๋ฅผ ๋ฐ”๊ฟ”์„œ checkbox ๊ฐ’์„ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
Expand Down Expand Up @@ -343,8 +360,8 @@ describe('vs-checkbox', () => {
});

describe('clear', () => {
describe('multiple ์ด true์ด๊ณ  v-model์ด array ํƒ€์ž…์ธ ๊ฒฝ์šฐ', () => {
it('clear ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด true-value๊ฐ€ ์ œ์™ธ๋œ ๋ฐฐ์—ด๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค', async () => {
describe('multiple ์ด true์ธ ๊ฒฝ์šฐ', () => {
it('clear ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋นˆ ๋ฐฐ์—ด๋กœ ์—…๋ฐ์ดํŠธ๋œ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand All @@ -361,12 +378,12 @@ describe('vs-checkbox', () => {

// then
expect(wrapper.vm.isChecked).toBe(false);
expect(wrapper.props('modelValue')).toEqual(['B']);
expect(wrapper.props('modelValue')).toEqual([]);
});
});

describe('multiple ์ด false ์ด๊ฑฐ๋‚˜ v-model์ด array ํƒ€์ž…์ด ์•„๋‹Œ ๊ฒฝ์šฐ', () => {
it('clear ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด false-value ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
it('clear ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด falseValue ๊ฐ’์œผ๋กœ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckbox, {
props: {
Expand Down
24 changes: 17 additions & 7 deletions packages/vlossom/src/components/vs-file-input/VsFileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,27 @@ export default defineComponent({
}
}
function correctEmptyValue() {
const isArrayInputValue = Array.isArray(inputValue.value);
if (multiple.value) {
if (fileInputRef.value) {
fileInputRef.value.value = '';
}
inputValue.value = isArrayInputValue ? inputValue.value : [];
} else {
if (fileInputRef.value) {
fileInputRef.value.value = '';
}
inputValue.value = isArrayInputValue ? null : inputValue.value;
}
}
const { computedMessages, shake, validate, clear, id } = useInput(inputValue, modelValue, context, label, {
messages,
rules: allRules,
callbacks: {
onMounted: () => {
if (multiple.value && !Array.isArray(inputValue.value)) {
inputValue.value = [];
} else if (!multiple.value && Array.isArray(inputValue.value)) {
inputValue.value = null;
}
},
onMounted: correctEmptyValue,
onChange: correctEmptyValue,
onClear,
},
});
Expand Down
Loading

0 comments on commit e3ee38f

Please sign in to comment.