Files
dodo/frontend/src/components/TaskDueDisplay.test.ts
T
bboysoul 0345fbe73c
ci / gitleaks (push) Successful in 8s
ci / docker (push) Successful in 3m36s
feat: show task due distance
2026-09-10 16:14:09 +08:00

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('刚过期')
})
})