diff --git a/.changeset/sharp-doors-behave.md b/.changeset/sharp-doors-behave.md new file mode 100644 index 0000000..4bdbdac --- /dev/null +++ b/.changeset/sharp-doors-behave.md @@ -0,0 +1,6 @@ +--- +"@fessional/razor-mobile": patch +"@fessional/razor-common": patch +--- + +✨ ionicValidateInput support default v-model value #122 diff --git a/layers/common/package.json b/layers/common/package.json index 868f319..2e88be8 100644 --- a/layers/common/package.json +++ b/layers/common/package.json @@ -1,6 +1,6 @@ { "name": "@fessional/razor-common", - "version": "0.3.5", + "version": "0.3.6", "description": "common layer for mobile and desktop", "type": "module", "main": "./nuxt.config.ts", diff --git a/layers/mobile/package.json b/layers/mobile/package.json index 1d7b9f5..ce5c01b 100644 --- a/layers/mobile/package.json +++ b/layers/mobile/package.json @@ -1,6 +1,6 @@ { "name": "@fessional/razor-mobile", - "version": "0.3.5", + "version": "0.3.6", "description": "mobile layer with ionic and capacitor", "type": "module", "main": "./nuxt.config.ts", diff --git a/layers/mobile/tests/ionic-validator.test.ts b/layers/mobile/tests/ionic-validator.test.ts index 3d5951b..3aaac9f 100644 --- a/layers/mobile/tests/ionic-validator.test.ts +++ b/layers/mobile/tests/ionic-validator.test.ts @@ -3,78 +3,104 @@ import { describe, it, expect, vi } from 'vitest'; import { ref } from 'vue'; import { ionicValidateInput } from '../utils/ionic-validator'; -// Mock Ionic IonInput component -const mockClassList = { - add: vi.fn(), - remove: vi.fn(), -}; - -const mockInputRef = ref({ - $el: { - classList: mockClassList, - }, -}); - -describe('validateIonicInput', () => { - // Test case: valid input using regex pattern - it('should add ion-valid class for valid input', () => { - const validateFn = ionicValidateInput(mockInputRef, /^[1-9][0-9]?$/); - - // Simulate valid input - const inputEvent = { target: { value: '10' } } as unknown as Event; - const result = validateFn(inputEvent); - - expect(result).toBe(true); +describe('ionicValidateInput', () => { + it('validates input correctly with a regex', () => { + const mockClassList = { + add: vi.fn(), + remove: vi.fn(), + }; + + const inputRef = ref({ $el: { classList: mockClassList } }); + const checkFun = /^[1-9][0-9]?$/; + const modelRef = ref(''); + + const validator = ionicValidateInput(inputRef, checkFun, modelRef); + + // Test valid input + const event = { + target: { value: '25' }, + type: 'input', + } as unknown as Event; + + expect(validator(event)).toBe(true); expect(mockClassList.add).toHaveBeenCalledWith('ion-valid'); expect(mockClassList.remove).toHaveBeenCalledWith('ion-invalid'); - }); - // Test case: invalid input using regex pattern - it('should add ion-invalid class for invalid input', () => { - const validateFn = ionicValidateInput(mockInputRef, /^[1-9][0-9]?$/); + // Reset mocks + mockClassList.add.mockClear(); + mockClassList.remove.mockClear(); - // Simulate invalid input - const inputEvent = { target: { value: '100' } } as unknown as Event; - const result = validateFn(inputEvent); + // Test invalid input + const invalidEvent = { + target: { value: '-5' }, + type: 'input', + } as unknown as Event; - expect(result).toBe(false); + expect(validator(invalidEvent)).toBe(false); expect(mockClassList.add).toHaveBeenCalledWith('ion-invalid'); expect(mockClassList.remove).toHaveBeenCalledWith('ion-valid'); }); - // Test case: input blur event - it('should add ion-touched class on blur', () => { - const validateFn = ionicValidateInput(mockInputRef, /^[1-9][0-9]?$/); + it('adds ion-touched class on blur', () => { + const mockClassList = { + add: vi.fn(), + remove: vi.fn(), + }; - // Simulate blur event - const blurEvent = new Event('blur'); - const result = validateFn(blurEvent); + const inputRef = ref({ $el: { classList: mockClassList } }); + const checkFun = /^[1-9][0-9]?$/; - expect(result).toBeNull(); + const validator = ionicValidateInput(inputRef, checkFun); + + const blurEvent = { type: 'blur' } as unknown as Event; + + expect(validator(blurEvent)).toBe(null); expect(mockClassList.add).toHaveBeenCalledWith('ion-touched'); }); - // Test case: direct string input - it('should add ion-valid class for valid string input', () => { - const validateFn = ionicValidateInput(mockInputRef, /^[1-9][0-9]?$/); + it('validates input with a custom function', () => { + const mockClassList = { + add: vi.fn(), + remove: vi.fn(), + }; - // Simulate valid string input - const result = validateFn('5'); + const inputRef = ref({ $el: { classList: mockClassList } }); + const checkFun = (value: string) => value === 'valid'; + const modelRef = ref(''); - expect(result).toBe(true); + const validator = ionicValidateInput(inputRef, checkFun, modelRef); + + // Test valid input + expect(validator('valid')).toBe(true); expect(mockClassList.add).toHaveBeenCalledWith('ion-valid'); expect(mockClassList.remove).toHaveBeenCalledWith('ion-invalid'); - }); - // Test case: invalid string input - it('should add ion-invalid class for invalid string input', () => { - const validateFn = ionicValidateInput(mockInputRef, /^[1-9][0-9]?$/); + // Reset mocks + mockClassList.add.mockClear(); + mockClassList.remove.mockClear(); - // Simulate invalid string input - const result = validateFn('100'); - - expect(result).toBe(false); + // Test invalid input + expect(validator('invalid')).toBe(false); expect(mockClassList.add).toHaveBeenCalledWith('ion-invalid'); expect(mockClassList.remove).toHaveBeenCalledWith('ion-valid'); }); + + it('uses modelRef value when event is null', () => { + const mockClassList = { + add: vi.fn(), + remove: vi.fn(), + }; + + const inputRef = ref({ $el: { classList: mockClassList } }); + const checkFun = /^[1-9][0-9]?$/; + const modelRef = ref('42'); + + const validator = ionicValidateInput(inputRef, checkFun, modelRef); + + expect(validator()).toBe(true); + expect(validator(null)).toBe(true); + expect(validator(undefined)).toBe(true); + expect(mockClassList.add).toHaveBeenCalledWith('ion-valid'); + expect(mockClassList.remove).toHaveBeenCalledWith('ion-invalid'); + }); }); diff --git a/layers/mobile/utils/ionic-validator.ts b/layers/mobile/utils/ionic-validator.ts index 8f8908a..740afe2 100644 --- a/layers/mobile/utils/ionic-validator.ts +++ b/layers/mobile/utils/ionic-validator.ts @@ -12,7 +12,7 @@ * * * ``` * @@ -24,18 +24,30 @@ export function ionicValidateInput( // eslint-disable-next-line @typescript-eslint/no-explicit-any inputRef: Ref, checkFun: RegExp | ((value: string, event?: Event) => boolean), -): (ev: Event | string) => boolean | null { - return (ev: Event | string) => { + modelRef?: Ref, +): (ev?: Event | string | null) => boolean | null { + return (ev?: Event | string | null) => { const classList = inputRef.value.$el.classList; - const isValue = typeof ev === 'string'; - if (isValue || /blur/i.test(ev.type)) { - classList.add('ion-touched'); - if (!isValue) return null; + let value: string; + let event: Event | undefined; + + if (ev == null) { + value = modelRef?.value ?? ''; + } + else if (typeof ev === 'string') { + value = ev; + } + else { + if (/blur/i.test(ev.type)) { + classList.add('ion-touched'); + return null; + } + event = ev; + value = (ev.target as HTMLInputElement).value; } - const value = isValue ? ev : (ev.target as HTMLInputElement).value; - const valid = typeof checkFun === 'function' ? checkFun(value, isValue ? undefined : ev) : checkFun.test(value); + const valid = typeof checkFun === 'function' ? checkFun(value, event) : checkFun.test(value); if (valid) { classList.add('ion-valid'); diff --git a/package.json b/package.json index 0fdc55e..b6fe323 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fessional/razor", - "version": "0.3.5", + "version": "0.3.6", "description": "Use front-end tech (Nuxt/Ts) to build multi-platform from one codebase, suitable for small team and app to write app once, apply almost anywhere.", "type": "module", "files": [