Skip to content

Commit

Permalink
feat(vs-select, vs-checkbox-set): add max/min props and rules (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
yeriiiii authored Jun 20, 2024
1 parent 799699e commit 9eac351
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 40 deletions.
19 changes: 15 additions & 4 deletions packages/vlossom/src/components/vs-checkbox-set/VsCheckboxSet.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { utils } from '@/utils';
import VsInputWrapper from '@/components/vs-input-wrapper/VsInputWrapper.vue';
import VsWrapper from '@/components/vs-wrapper/VsWrapper.vue';
import { VsCheckboxNode } from '@/nodes';
import { useVsCheckboxSetRules } from './vs-checkbox-set-rules';
import type { VsCheckboxSetStyleSet } from './types';
import type { VsCheckboxStyleSet } from '@/components/vs-checkbox/types';
Expand All @@ -88,6 +89,16 @@ export default defineComponent({
type: Function as PropType<(from: any, to: any, option: any) => Promise<boolean> | null>,
default: null,
},
max: {
type: [Number, String],
default: Number.MAX_SAFE_INTEGER,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'max', value),
},
min: {
type: [Number, String],
default: 0,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'min', value),
},
vertical: { type: Boolean, default: false },
// v-model
modelValue: {
Expand All @@ -113,6 +124,8 @@ export default defineComponent({
required,
rules,
state,
max,
min,
} = toRefs(props);
const checkboxRefs: Ref<HTMLInputElement[]> = ref([]);
Expand Down Expand Up @@ -140,11 +153,9 @@ export default defineComponent({
ref(true),
);
function requiredCheck() {
return required.value && inputValue.value && inputValue.value.length === 0 ? 'required' : '';
}
const { requiredCheck, maxCheck, minCheck } = useVsCheckboxSetRules(required, max, min);
const allRules = computed(() => [...rules.value, requiredCheck]);
const allRules = computed(() => [...rules.value, requiredCheck, maxCheck, minCheck]);
const { computedMessages, computedState, computedDisabled, computedReadonly, shake, validate, clear, id } =
useInput(context, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,47 @@ describe('vs-checkbox-set', () => {
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('required');
});

it('최대로 선택 가능한 아이템 수를 max props를 통해 제한하고 체크할 수 있다', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckboxSet, {
props: {
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
max: 1,
},
});

// when
await nextTick();
await wrapper.find('input[value="A"]').trigger('click');
await wrapper.find('input[value="B"]').trigger('click');

// then
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('max number of items: 1');
});

it('최소로 선택 가능한 아이템 수를 max props를 통해 제한하고 체크할 수 있다', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsCheckboxSet, {
props: {
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
min: 2,
},
});

// when
await nextTick();
await wrapper.find('input[value="A"]').trigger('click');

// then
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('min number of items: 2');
});
});

describe('validate', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Ref } from 'vue';

export function useVsCheckboxSetRules(required: Ref<boolean>, max: Ref<number | string>, min: Ref<number | string>) {
function requiredCheck(v: any[]) {
return required.value && v && v.length === 0 ? 'required' : '';
}

function maxCheck(v: any[]) {
const limit = Number(max.value);
return v && v.length > limit ? 'max number of items: ' + max.value : '';
}

function minCheck(v: any[]) {
const limit = Number(min.value);
return v && v.length < limit ? 'min number of items: ' + min.value : '';
}

return {
requiredCheck,
maxCheck,
minCheck,
};
}
13 changes: 11 additions & 2 deletions packages/vlossom/src/components/vs-input/VsInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import { VsIcon } from '@/icons';
import { InputType } from './types';
import type { InputValueType, VsInputStyleSet } from './types';
import { utils } from '@/utils';
const name = VsComponent.VsInput;
export default defineComponent({
Expand All @@ -96,8 +97,16 @@ export default defineComponent({
styleSet: { type: [String, Object] as PropType<string | VsInputStyleSet> },
autocomplete: { type: Boolean, default: false },
dense: { type: Boolean, default: false },
max: { type: [Number, String], default: Number.MAX_SAFE_INTEGER },
min: { type: [Number, String], default: Number.MIN_SAFE_INTEGER },
max: {
type: [Number, String],
default: Number.MAX_SAFE_INTEGER,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'max', value),
},
min: {
type: [Number, String],
default: Number.MIN_SAFE_INTEGER,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'min', value),
},
type: { type: String as PropType<InputType>, default: InputType.Text },
// v-model
modelValue: {
Expand Down
10 changes: 0 additions & 10 deletions packages/vlossom/src/components/vs-input/vs-input-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ export function useVsInputRules(

function maxCheck(v: InputValueType) {
const limit = Number(max.value);

if (isNaN(limit) || limit > Number.MAX_SAFE_INTEGER) {
return '';
}

if (type.value === InputType.Number && typeof v === 'number' && v > limit) {
return 'max value: ' + max.value;
}
Expand All @@ -35,11 +30,6 @@ export function useVsInputRules(

function minCheck(v: InputValueType) {
const limit = Number(min.value);

if (isNaN(limit) || limit < Number.MIN_SAFE_INTEGER) {
return '';
}

if (type.value === InputType.Number && typeof v === 'number' && v < limit) {
return 'min value: ' + min.value;
}
Expand Down
39 changes: 27 additions & 12 deletions packages/vlossom/src/components/vs-select/VsSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ import {
} from '@/composables';
import { useAutocomplete, useFocusControl, useInfiniteScroll, useSelectOption, useToggleOptions } from './composables';
import { VsComponent, type ColorScheme } from '@/declaration';
import { useVsSelectRules } from './vs-select-rules';
import { VsSelectStyleSet } from './types';
import { VsIcon } from '@/icons';
import { utils } from '@/utils';
Expand Down Expand Up @@ -271,6 +272,28 @@ export default defineComponent({
return isValid;
},
},
max: {
type: [Number, String],
default: Number.MAX_SAFE_INTEGER,
validator: (value: number | string, props) => {
if (!props.multiple && value) {
utils.log.propError(name, 'max', 'max can only be used with multiple prop');
return false;
}
return utils.props.checkValidNumber(name, 'max', value);
},
},
min: {
type: [Number, String],
default: 0,
validator: (value: number | string, props) => {
if (!props.multiple && value) {
utils.log.propError(name, 'min', 'min can only be used with multiple prop');
return false;
}
return utils.props.checkValidNumber(name, 'min', value);
},
},
multiple: { type: Boolean, default: false },
selectAll: {
type: Boolean,
Expand Down Expand Up @@ -308,6 +331,8 @@ export default defineComponent({
rules,
selectAll,
state,
max,
min,
} = toRefs(props);
const { emit } = context;
Expand Down Expand Up @@ -352,19 +377,9 @@ export default defineComponent({
options.value.map((option) => ({ id: utils.string.createID(), value: option })),
);
function requiredCheck() {
if (!required.value) {
return '';
}
if (multiple.value) {
return inputValue.value && inputValue.value.length > 0 ? '' : 'required';
} else {
return inputValue.value ? '' : 'required';
}
}
const { requiredCheck, maxCheck, minCheck } = useVsSelectRules(required, max, min, multiple);
const allRules = computed(() => [...rules.value, requiredCheck]);
const allRules = computed(() => [...rules.value, requiredCheck, maxCheck, minCheck]);
function onClear() {
if (multiple.value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,59 @@ describe('vs-select', () => {
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('required');
});

it('multiple이 true일 때, 최대로 선택 가능한 아이템 수를 max props를 통해 제한하고 체크할 수 있다', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsSelect, {
props: {
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
max: 1,
multiple: true,
},
global: {
stubs: {
teleport: true,
},
},
});

// when
await wrapper.find('.vs-select').trigger('click');
await wrapper.findAll('.vs-option')[1].trigger('click');
await wrapper.findAll('.vs-option')[2].trigger('click');

// then
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('max number of items: 1');
});

it('multiple이 true일 때, 최소로 선택 가능한 아이템 수를 min props를 통해 제한하고 체크할 수 있다', async () => {
// given
const wrapper: ReturnType<typeof mountComponent> = mount(VsSelect, {
props: {
modelValue: [],
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
options: ['A', 'B', 'C'],
min: 2,
multiple: true,
},
global: {
stubs: {
teleport: true,
},
},
});

// when
await wrapper.find('.vs-select').trigger('click');
await wrapper.findAll('.vs-option')[1].trigger('click');

// then
expect(wrapper.vm.computedMessages).toHaveLength(1);
expect(wrapper.html()).toContain('min number of items: 2');
});
});

describe('validate', () => {
Expand Down
44 changes: 44 additions & 0 deletions packages/vlossom/src/components/vs-select/vs-select-rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Ref } from 'vue';

export function useVsSelectRules(
required: Ref<boolean>,
max: Ref<number | string>,
min: Ref<number | string>,
multiple: Ref<boolean>,
) {
function requiredCheck(v: any) {
if (!required.value) {
return '';
}

if (multiple.value) {
return v && v.length > 0 ? '' : 'required';
} else {
return v ? '' : 'required';
}
}

function maxCheck(v: any) {
const limit = Number(max.value);
if (multiple.value) {
return v && v.length > limit ? 'max number of items: ' + max.value : '';
}

return '';
}

function minCheck(v: any) {
const limit = Number(min.value);
if (multiple.value) {
return v && v.length < limit ? 'min number of items: ' + min.value : '';
}

return '';
}

return {
requiredCheck,
maxCheck,
minCheck,
};
}
13 changes: 11 additions & 2 deletions packages/vlossom/src/components/vs-textarea/VsTextarea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import { VsComponent, StringModifiers, type ColorScheme } from '@/declaration';
import { useVsTextareaRules } from './vs-textarea-rules';
import VsInputWrapper from '@/components/vs-input-wrapper/VsInputWrapper.vue';
import VsWrapper from '@/components/vs-wrapper/VsWrapper.vue';
import { utils } from '@/utils';
import type { InputValueType, VsTextareaStyleSet } from './types';
Expand All @@ -68,8 +69,16 @@ export default defineComponent({
colorScheme: { type: String as PropType<ColorScheme> },
styleSet: { type: [String, Object] as PropType<string | VsTextareaStyleSet> },
autocomplete: { type: Boolean, default: false },
max: { type: [Number, String], default: Number.MAX_SAFE_INTEGER },
min: { type: [Number, String], default: Number.MIN_SAFE_INTEGER },
max: {
type: [Number, String],
default: Number.MAX_SAFE_INTEGER,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'max', value),
},
min: {
type: [Number, String],
default: 0,
validator: (value: number | string) => utils.props.checkValidNumber(name, 'min', value),
},
// v-model
modelValue: { type: String, default: '' },
modelModifiers: {
Expand Down
10 changes: 0 additions & 10 deletions packages/vlossom/src/components/vs-textarea/vs-textarea-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ export function useVsTextareaRules(required: Ref<boolean>, max: Ref<number | str

function maxCheck(v: InputValueType) {
const limit = Number(max.value);

if (isNaN(limit) || limit > Number.MAX_SAFE_INTEGER) {
return '';
}

if (typeof v === 'string' && v.length > limit) {
return 'max length: ' + max.value;
}
Expand All @@ -26,11 +21,6 @@ export function useVsTextareaRules(required: Ref<boolean>, max: Ref<number | str

function minCheck(v: InputValueType) {
const limit = Number(min.value);

if (isNaN(limit) || limit < Number.MIN_SAFE_INTEGER) {
return '';
}

if (typeof v === 'string' && v.length < limit) {
return 'min length: ' + min.value;
}
Expand Down
Loading

0 comments on commit 9eac351

Please sign in to comment.