2 changed files with 146 additions and 0 deletions
@ -0,0 +1,42 @@ |
|||
<script setup lang="ts"> |
|||
const props = withDefaults(defineProps<{ |
|||
modelValue: string |
|||
label?: string |
|||
placeholder?: string |
|||
disabled?: boolean |
|||
}>(), { |
|||
label: '密码', |
|||
placeholder: '请输入密码', |
|||
disabled: false, |
|||
}) |
|||
|
|||
const emit = defineEmits<{ |
|||
'update:modelValue': [value: string] |
|||
}>() |
|||
|
|||
const showPassword = ref(false) |
|||
</script> |
|||
|
|||
<template> |
|||
<UFormField :label="label" required> |
|||
<div class="relative"> |
|||
<UInput |
|||
:type="showPassword ? 'text' : 'password'" |
|||
:model-value="modelValue" |
|||
:placeholder="placeholder" |
|||
:disabled="disabled" |
|||
class="w-full" |
|||
@update:model-value="emit('update:modelValue', $event)" |
|||
/> |
|||
<UButton |
|||
variant="link" |
|||
color="neutral" |
|||
size="sm" |
|||
:icon="showPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'" |
|||
:aria-label="showPassword ? '隐藏密码' : '显示密码'" |
|||
class="absolute right-1 top-1/2 -translate-y-1/2" |
|||
@click="showPassword = !showPassword" |
|||
/> |
|||
</div> |
|||
</UFormField> |
|||
</template> |
|||
@ -0,0 +1,104 @@ |
|||
import { describe, it, expect } from 'vitest' |
|||
import { mount } from '@vue/test-utils' |
|||
import PasswordInput from '../PasswordInput.vue' |
|||
|
|||
describe('PasswordInput', () => { |
|||
it('renders with type=password by default', () => { |
|||
const wrapper = mount(PasswordInput, { |
|||
props: { modelValue: '' }, |
|||
global: { |
|||
stubs: { |
|||
UInput: { |
|||
template: '<input :type="$attrs.type" :disabled="$attrs.disabled" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />', |
|||
props: ['type', 'disabled', 'modelValue', 'placeholder'], |
|||
}, |
|||
UButton: { |
|||
template: '<button :disabled="$attrs.disabled" @click="$emit(\'click\')"><slot /></button>', |
|||
props: ['disabled'], |
|||
emits: ['click'], |
|||
}, |
|||
UFormField: { |
|||
template: '<div><slot /></div>', |
|||
props: ['label'], |
|||
}, |
|||
}, |
|||
}, |
|||
}) |
|||
const input = wrapper.find('input') |
|||
expect(input.attributes('type')).toBe('password') |
|||
}) |
|||
|
|||
it('toggles to type=text when button clicked', async () => { |
|||
const wrapper = mount(PasswordInput, { |
|||
props: { modelValue: '' }, |
|||
global: { |
|||
stubs: { |
|||
UInput: { |
|||
template: '<input :type="$attrs.type" :disabled="$attrs.disabled" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />', |
|||
props: ['type', 'disabled', 'modelValue', 'placeholder'], |
|||
}, |
|||
UButton: { |
|||
template: '<button :disabled="$attrs.disabled" @click="$emit(\'click\')"><slot /></button>', |
|||
props: ['disabled'], |
|||
emits: ['click'], |
|||
}, |
|||
UFormField: { |
|||
template: '<div><slot /></div>', |
|||
props: ['label'], |
|||
}, |
|||
}, |
|||
}, |
|||
}) |
|||
const btn = wrapper.find('button') |
|||
await btn.trigger('click') |
|||
expect(wrapper.find('input').attributes('type')).toBe('text') |
|||
}) |
|||
|
|||
it('renders label when provided', () => { |
|||
const wrapper = mount(PasswordInput, { |
|||
props: { modelValue: '', label: 'Test Label' }, |
|||
global: { |
|||
stubs: { |
|||
UInput: { |
|||
template: '<input :type="$attrs.type" :disabled="$attrs.disabled" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />', |
|||
props: ['type', 'disabled', 'modelValue', 'placeholder'], |
|||
}, |
|||
UButton: { |
|||
template: '<button :disabled="$attrs.disabled" @click="$emit(\'click\')"><slot /></button>', |
|||
props: ['disabled'], |
|||
emits: ['click'], |
|||
}, |
|||
UFormField: { |
|||
template: '<div><slot /></div>', |
|||
props: ['label'], |
|||
}, |
|||
}, |
|||
}, |
|||
}) |
|||
expect(wrapper.text()).toContain('Test Label') |
|||
}) |
|||
|
|||
it('disables input when disabled prop is true', () => { |
|||
const wrapper = mount(PasswordInput, { |
|||
props: { modelValue: '', disabled: true }, |
|||
global: { |
|||
stubs: { |
|||
UInput: { |
|||
template: '<input :type="$attrs.type" :disabled="$attrs.disabled" :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />', |
|||
props: ['type', 'disabled', 'modelValue', 'placeholder'], |
|||
}, |
|||
UButton: { |
|||
template: '<button :disabled="$attrs.disabled" @click="$emit(\'click\')"><slot /></button>', |
|||
props: ['disabled'], |
|||
emits: ['click'], |
|||
}, |
|||
UFormField: { |
|||
template: '<div><slot /></div>', |
|||
props: ['label'], |
|||
}, |
|||
}, |
|||
}, |
|||
}) |
|||
expect(wrapper.find('input').element.disabled).toBe(true) |
|||
}) |
|||
}) |
|||
Loading…
Reference in new issue