20260927215912
This commit is contained in:
1 parent
27e49e2e45
commit
f22e67fc85
294 files changed
+29247
-3576
No files matched your search
@@ -0,0 +1,349 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import DatePicker from '@/components/ui/date/date-picker.vue'
|
||||
|
||||
/** 用真 v-model 宿主,才能验证 emit 出的是字符串并回流到输入框 */
|
||||
function mountPicker(props = {}) {
|
||||
return mount(
|
||||
{
|
||||
components: { DatePicker },
|
||||
setup() {
|
||||
const value = ref(props.modelValue ?? '')
|
||||
return {
|
||||
value,
|
||||
format: props.format,
|
||||
valueFormat: props.valueFormat,
|
||||
disabled: Boolean(props.disabled),
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<DatePicker
|
||||
v-model="value"
|
||||
:format="format"
|
||||
:value-format="valueFormat"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
`,
|
||||
},
|
||||
{ attachTo: document.body },
|
||||
)
|
||||
}
|
||||
|
||||
function inputOf(wrapper) {
|
||||
return wrapper.find('.fms-date-input')
|
||||
}
|
||||
|
||||
/** emit 来自子组件,宿主只负责 v-model 回流 */
|
||||
function pickerOf(wrapper) {
|
||||
return wrapper.findComponent(DatePicker)
|
||||
}
|
||||
|
||||
function lastEmitted(wrapper) {
|
||||
return pickerOf(wrapper).emitted('update:modelValue')?.at(-1)?.[0]
|
||||
}
|
||||
|
||||
/** 把光标停到指定位置后按下方向键,模拟「微调当前段」 */
|
||||
async function stepAt(input, pos, key) {
|
||||
input.element.setSelectionRange(pos, pos)
|
||||
await input.trigger('keydown', { key })
|
||||
await nextTick()
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
describe('DatePicker 手动输入', () => {
|
||||
it('输入框不再 readonly,type 仍为 text', () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD' })
|
||||
const input = inputOf(wrapper)
|
||||
expect(input.attributes('readonly')).toBeUndefined()
|
||||
expect(input.attributes('type')).toBe('text')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('键入合法整串后失焦提交为该格式的字符串', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('2026-09-26')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('2026-09-26')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('斜杠、点号与中文年月日都能提交', async () => {
|
||||
for (const text of ['2026/09/26', '2026.09.26', '2026年09月26日']) {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue(text)
|
||||
await input.trigger('blur')
|
||||
expect(lastEmitted(wrapper), text).toBe('2026-09-26')
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
|
||||
it('Enter 提交当前文本并关闭面板', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('click')
|
||||
await nextTick()
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-open')
|
||||
|
||||
await input.setValue('2026-09-26')
|
||||
await input.trigger('keydown', { key: 'Enter' })
|
||||
await nextTick()
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('2026-09-26')
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-open')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('datetime:format 决定展示,valueFormat 决定提交格式(秒会被补齐)', async () => {
|
||||
const wrapper = mountPicker({
|
||||
format: 'YYYY-MM-DD HH:mm',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
})
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('2026-09-26 14:30')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('2026-09-26 14:30:00')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('非法输入失焦:不 emit,文本回退到已提交值,并标记错误态', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-01-01' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('2026-13-45')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(pickerOf(wrapper).emitted('update:modelValue')).toBeFalsy()
|
||||
expect(input.element.value).toBe('2026-01-01')
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-invalid')
|
||||
expect(input.attributes('aria-invalid')).toBe('true')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('错误态 1500ms 后自动清除', async () => {
|
||||
vi.useFakeTimers()
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-01-01' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('abc')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-invalid')
|
||||
vi.advanceTimersByTime(1500)
|
||||
await nextTick()
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-invalid')
|
||||
wrapper.unmount()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
it('文本与已提交值相同时失焦不重复 emit', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('2026-09-26')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(pickerOf(wrapper).emitted('update:modelValue')).toBeFalsy()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('清空文本失焦等同清除值', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('Esc 还原为已提交文本并关闭面板', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('click')
|
||||
await nextTick()
|
||||
await input.setValue('2027-01-01')
|
||||
await input.trigger('keydown', { key: 'Escape' })
|
||||
await nextTick()
|
||||
|
||||
expect(pickerOf(wrapper).emitted('update:modelValue')).toBeFalsy()
|
||||
expect(input.element.value).toBe('2026-09-26')
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-open')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('Space 作为普通字符输入,不打开面板', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD HH:mm', modelValue: '2026-09-26 14:30' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('2026-09-26 14:30')
|
||||
await input.trigger('keydown', { key: ' ', code: 'Space' })
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-open')
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('DatePicker 方向键微调当前段', () => {
|
||||
it('年份段 ArrowUp +1', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await stepAt(input, 2, 'ArrowUp')
|
||||
|
||||
expect(input.element.value).toBe('2027-09-26')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('月份段 ArrowDown -1', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await stepAt(input, 6, 'ArrowDown')
|
||||
|
||||
expect(input.element.value).toBe('2026-08-26')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('日段 ArrowUp 跨月进位', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-30' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await stepAt(input, 9, 'ArrowUp')
|
||||
|
||||
expect(input.element.value).toBe('2026-10-01')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('时分秒微调只改当前段且日期不变', async () => {
|
||||
const wrapper = mountPicker({
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
modelValue: '2026-09-26 23:10:00',
|
||||
})
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await stepAt(input, 12, 'ArrowUp') // 小时段回绕
|
||||
expect(input.element.value).toBe('2026-09-26 00:10:00')
|
||||
|
||||
await stepAt(input, 15, 'ArrowDown') // 分钟段
|
||||
expect(input.element.value).toBe('2026-09-26 00:09:00')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('光标不在任何段上时不拦截方向键、不改文本', async () => {
|
||||
// ddd 是不可步进的 token(编译为跳读片段),文本尾部的星期不产生任何段
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD ddd', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
const before = input.element.value
|
||||
expect(before).toMatch(/^2026-09-26 \S+$/)
|
||||
|
||||
await stepAt(input, before.length, 'ArrowUp')
|
||||
expect(input.element.value).toBe(before)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('微调结果失焦后提交为新值', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await stepAt(input, 2, 'ArrowUp')
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('2027-09-26')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('Alt+ArrowDown 打开面板并把焦点交给日历网格', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.trigger('focus')
|
||||
await input.trigger('keydown', { key: 'ArrowDown', altKey: true })
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-open')
|
||||
expect(document.body.querySelector('.fms-cal-cell:focus')).not.toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('DatePicker 与面板 / 清除按钮的协作', () => {
|
||||
it('点输入框打开面板且焦点留在输入框,可以继续打字', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD' })
|
||||
const input = inputOf(wrapper)
|
||||
input.element.focus()
|
||||
await input.trigger('click')
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-open')
|
||||
expect(document.activeElement).toBe(input.element)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('面板点选日期后输入框显示新值并清空错误态', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-01-01' })
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue('abc')
|
||||
await input.trigger('blur')
|
||||
expect(wrapper.find('.fms-date-picker').classes()).toContain('is-invalid')
|
||||
|
||||
await input.trigger('click')
|
||||
await nextTick()
|
||||
const cell = document.body.querySelector('.fms-cal-cell:not(.is-disabled)')
|
||||
cell.click()
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
|
||||
const committed = lastEmitted(wrapper)
|
||||
expect(committed).toMatch(/^\d{4}-\d{2}-\d{2}$/)
|
||||
expect(input.element.value).toBe(committed)
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-invalid')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('清除按钮清空值且不触发重复提交', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26' })
|
||||
await inputOf(wrapper).trigger('click')
|
||||
await nextTick()
|
||||
const clear = wrapper.find('.fms-date-clear')
|
||||
await clear.trigger('click')
|
||||
await nextTick()
|
||||
|
||||
expect(lastEmitted(wrapper)).toBe('')
|
||||
expect(document.body.querySelector('.popover-content')).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
||||
describe('DatePicker 禁用态与多格式', () => {
|
||||
it('禁用态下输入与方向键均不生效,也不进入错误态', async () => {
|
||||
const wrapper = mountPicker({ format: 'YYYY-MM-DD', modelValue: '2026-09-26', disabled: true })
|
||||
const input = inputOf(wrapper)
|
||||
expect(input.attributes('disabled')).toBeDefined()
|
||||
|
||||
await input.trigger('keydown', { key: 'ArrowUp' })
|
||||
await nextTick()
|
||||
expect(pickerOf(wrapper).emitted('update:modelValue')).toBeFalsy()
|
||||
expect(wrapper.find('.fms-date-picker').classes()).not.toContain('is-invalid')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('format 数组时任一写法都能提交', async () => {
|
||||
for (const text of ['2026-09-26', '2026年09月26日']) {
|
||||
const wrapper = mountPicker({
|
||||
format: ['YYYY年MM月DD日', 'YYYY-MM-DD'],
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
})
|
||||
const input = inputOf(wrapper)
|
||||
await input.setValue(text)
|
||||
await input.trigger('blur')
|
||||
|
||||
expect(lastEmitted(wrapper), text).toBe('2026-09-26')
|
||||
wrapper.unmount()
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user