From 556ba475ef33d3947a55838fd32e03703ead24b9 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Fri, 15 May 2026 16:40:54 +0800 Subject: [PATCH] feat: add CaptchaField component with v-model and loading/error states Co-Authored-By: Claude Opus 4.7 --- app/components/register/CaptchaField.vue | 46 ++++++++++++++++ .../register/__tests__/CaptchaField.test.ts | 62 ++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 app/components/register/CaptchaField.vue create mode 100644 app/components/register/__tests__/CaptchaField.test.ts diff --git a/app/components/register/CaptchaField.vue b/app/components/register/CaptchaField.vue new file mode 100644 index 0000000..39e6696 --- /dev/null +++ b/app/components/register/CaptchaField.vue @@ -0,0 +1,46 @@ + + + diff --git a/app/components/register/__tests__/CaptchaField.test.ts b/app/components/register/__tests__/CaptchaField.test.ts new file mode 100644 index 0000000..6f471ae --- /dev/null +++ b/app/components/register/__tests__/CaptchaField.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest' +import { mount } from '@vue/test-utils' +import CaptchaField from '../CaptchaField.vue' + +const stubs = { + UInput: { + template: '', + props: ['type', 'disabled', 'modelValue', 'placeholder'], + }, + UButton: { + template: '', + props: ['disabled', 'icon', 'variant', 'color', 'square'], + emits: ['click'], + }, + UFormField: { + template: '
{{ label }}
', + props: ['label', 'required'], + }, +} + +const defaultProps = { svg: '', loading: false, modelValue: '' } + +function mountComponent(overrides = {}) { + return mount(CaptchaField, { + props: { ...defaultProps, ...overrides }, + global: { stubs }, + }) +} + +describe('CaptchaField', () => { + it('renders SVG via v-html', () => { + const svg = '' + const wrapper = mountComponent({ svg }) + expect(wrapper.html()).toContain(' { + const wrapper = mountComponent() + const btn = wrapper.find('button') + await btn.trigger('click') + expect(wrapper.emitted('refresh')).toBeTruthy() + }) + + it('disables refresh button when loading', () => { + const wrapper = mountComponent({ loading: true }) + const btn = wrapper.find('button') + expect(btn.attributes('disabled')).toBeDefined() + }) + + it('renders error state when svg is empty', () => { + const wrapper = mountComponent({ svg: '' }) + expect(wrapper.text()).toContain('加载失败') + }) + + it('emits update:modelValue when input changes', async () => { + const wrapper = mountComponent() + const input = wrapper.find('input') + await input.setValue('abc') + expect(wrapper.emitted('update:modelValue')).toBeTruthy() + expect(wrapper.emitted('update:modelValue')![0]).toEqual(['abc']) + }) +})