From 91fa52b7b6ebf88a579866ea9a4a1f90745e2633 Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Fri, 25 Jul 2025 13:32:46 +0200 Subject: [PATCH] add test for ValidatedInput --- .../components/Inputs/ValidatedInput.spec.js | 92 +++++++++++++++++++ .../src/components/Inputs/ValidatedInput.vue | 6 +- 2 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/Inputs/ValidatedInput.spec.js diff --git a/frontend/src/components/Inputs/ValidatedInput.spec.js b/frontend/src/components/Inputs/ValidatedInput.spec.js new file mode 100644 index 000000000..1e82dac74 --- /dev/null +++ b/frontend/src/components/Inputs/ValidatedInput.spec.js @@ -0,0 +1,92 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { mount } from '@vue/test-utils' +import ValidatedInput from '@/components/Inputs/ValidatedInput.vue' +import * as yup from 'yup' +import { BFormInvalidFeedback, BFormInput, BFormTextarea, BFormGroup } from 'bootstrap-vue-next' +import LabeledInput from '@/components/Inputs/LabeledInput.vue' + +vi.mock('vue-i18n', () => ({ + useI18n: () => ({ + t: (key) => key, + n: (n) => String(n), + }), +})) + +describe('ValidatedInput', () => { + let wrapper + const createWrapper = (props = {}) => + mount(ValidatedInput, { + props: { + label: 'Test Label', + modelValue: '', + name: 'testInput', + rules: yup.string().required().min(3).default(''), + ...props, + }, + global: { + mocks: { + $t: (key) => key, + $i18n: { + locale: 'en', + }, + $n: (n) => String(n), + }, + components: { + BFormInvalidFeedback, + BFormInput, + BFormTextarea, + BFormGroup, + LabeledInput, + }, + }, + }) + + beforeEach(() => { + wrapper = createWrapper() + }) + + it('renders the label and input', () => { + expect(wrapper.text()).toContain('Test Label') + const input = wrapper.find('input') + expect(input.exists()).toBe(true) + }) + + it('starts with neutral validation state', () => { + const input = wrapper.find('input') + expect(input.classes()).not.toContain('is-valid') + expect(input.classes()).not.toContain('is-invalid') + }) + + it('shows green border when value is valid before blur', async () => { + await wrapper.setProps({ modelValue: 'validInput' }) + await wrapper.vm.$nextTick() + const input = wrapper.find('input') + expect(input.classes()).toContain('is-valid') + expect(input.classes()).not.toContain('is-invalid') + }) + + it('does not show red border before blur even if invalid', async () => { + await wrapper.setProps({ modelValue: 'a' }) + const input = wrapper.find('input') + expect(input.classes()).not.toContain('is-invalid') + }) + + it('shows red border and error message after blur when input is invalid', async () => { + await wrapper.setProps({ modelValue: 'a' }) + const input = wrapper.find('input') + await input.trigger('blur') + await wrapper.vm.$nextTick() + expect(input.classes()).toContain('is-invalid') + expect(wrapper.text()).toContain('this must be at least 3 characters') + }) + + it('emits update:modelValue on input', async () => { + const input = wrapper.find('input') + await input.setValue('hello') + await wrapper.vm.$nextTick() + expect(wrapper.emitted()['update:modelValue']).toBeTruthy() + const [value, name] = wrapper.emitted()['update:modelValue'][0] + expect(value).toBe('hello') + expect(name).toBe('testInput') + }) +}) diff --git a/frontend/src/components/Inputs/ValidatedInput.vue b/frontend/src/components/Inputs/ValidatedInput.vue index 8f4601124..bbd998635 100644 --- a/frontend/src/components/Inputs/ValidatedInput.vue +++ b/frontend/src/components/Inputs/ValidatedInput.vue @@ -48,7 +48,7 @@ const model = ref(props.modelValue !== 0 ? props.modelValue : '') // prevent showing errors on form init const afterFirstInput = ref(false) -const valid = computed(() => props.rules.isValidSync(props.modelValue)) +const valid = computed(() => props.rules.isValidSync(model.value)) // smartValidState controls the visual validation feedback for the input field. // The goal is to avoid showing red (invalid) borders too early, creating a smoother UX: // @@ -67,11 +67,11 @@ const smartValidState = computed(() => { return valid.value ? true : null }) const errorMessage = computed(() => { - if (props.modelValue === undefined || props.modelValue === '' || props.modelValue === null) { + if (model.value === undefined || model.value === '' || model.value === null) { return undefined } try { - props.rules.validateSync(props.modelValue) + props.rules.validateSync(model.value) return undefined } catch (e) { return translateYupErrorString(e.message, t)