58 lines
2.7 KiB
TypeScript
58 lines
2.7 KiB
TypeScript
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { createApp, h, nextTick, ref } from 'vue'
|
|
import TaskDueDisplay from './TaskDueDisplay.vue'
|
|
|
|
const cleanups: Array<() => void> = []
|
|
const nowMs = new Date(2026, 8, 10, 12).getTime()
|
|
|
|
async function mountDue(props: { dueAt: string | null; dueHasTime: boolean; completed: boolean; nowMs: number }) {
|
|
const host = document.createElement('div')
|
|
document.body.append(host)
|
|
const values = ref(props)
|
|
const app = createApp({ setup: () => () => h(TaskDueDisplay, values.value) })
|
|
app.mount(host)
|
|
cleanups.push(() => { app.unmount(); host.remove() })
|
|
await nextTick()
|
|
return { host, values }
|
|
}
|
|
|
|
afterEach(() => cleanups.splice(0).forEach((cleanup) => cleanup()))
|
|
|
|
describe('TaskDueDisplay', () => {
|
|
it.each([null, '', '2026-02-30'])('renders no DOM for %s', async (dueAt) => {
|
|
const { host } = await mountDue({ dueAt, dueHasTime: false, completed: false, nowMs })
|
|
expect(host.innerHTML).toBe('<!--v-if-->')
|
|
})
|
|
|
|
it('renders semantic canonical time with absolute and relative copy', async () => {
|
|
const { host } = await mountDue({ dueAt: '2026-09-10', dueHasTime: false, completed: false, nowMs })
|
|
const root = host.querySelector('.task-due')!
|
|
const time = root.querySelector('time')!
|
|
expect(root.classList.contains('task-due--neutral')).toBe(true)
|
|
expect(time.getAttribute('datetime')).toBe('2026-09-10')
|
|
expect(time.textContent).toContain('9月10日')
|
|
expect(time.textContent).toContain('今天')
|
|
expect(time.getAttribute('title')).toBe(time.getAttribute('aria-label'))
|
|
expect(time.getAttribute('title')).toContain('2026年9月10日')
|
|
expect(root.querySelector('svg')?.getAttribute('aria-hidden')).toBe('true')
|
|
})
|
|
|
|
it('distinguishes incomplete overdue and completed neutral states', async () => {
|
|
const overdue = await mountDue({ dueAt: '2026-09-09', dueHasTime: false, completed: false, nowMs })
|
|
expect(overdue.host.querySelector('.task-due--overdue')).not.toBeNull()
|
|
const completed = await mountDue({ dueAt: '2026-09-09', dueHasTime: false, completed: true, nowMs })
|
|
expect(completed.host.querySelector('.task-due--completed')).not.toBeNull()
|
|
expect(completed.host.querySelector('.task-due--overdue')).toBeNull()
|
|
expect(completed.host.textContent).toContain('已完成(原定1天前)')
|
|
})
|
|
|
|
it('reacts when the shared nowMs prop advances', async () => {
|
|
const dueAt = new Date(nowMs + 60_000).toISOString()
|
|
const { host, values } = await mountDue({ dueAt, dueHasTime: true, completed: false, nowMs })
|
|
expect(host.textContent).toContain('还有1分钟')
|
|
values.value = { ...values.value, nowMs: nowMs + 60_001 }
|
|
await nextTick()
|
|
expect(host.textContent).toContain('刚过期')
|
|
})
|
|
})
|