fix: simplify date-only due labels
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m29s

This commit is contained in:
2026-09-10 17:22:06 +08:00
parent 1366f302ec
commit ab8c0a5138
6 changed files with 76 additions and 47 deletions
+35 -17
View File
@@ -24,26 +24,44 @@ describe('TaskDueDisplay', () => {
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')).toBeNull()
it('renders date-only visual text without an absolute date or explanatory prefixes', async () => {
const cases = [
{ dueAt: '2026-09-12', text: '2天', state: 'neutral', explanation: '还有2天' },
{ dueAt: '2026-09-08', text: '2天', state: 'overdue', explanation: '已过期2天' },
{ dueAt: '2026-09-10', text: '今天', state: 'neutral', explanation: '今天' },
] as const
for (const item of cases) {
const { host } = await mountDue({ dueAt: item.dueAt, dueHasTime: false, completed: false, nowMs })
const root = host.querySelector('.task-due')!
const time = root.querySelector('time')!
expect(root.classList.contains(`task-due--${item.state}`)).toBe(true)
expect(time.textContent?.trim()).toBe(item.text)
expect(time.textContent).not.toMatch(/2026|9月|还有|已过期/)
expect(time.getAttribute('datetime')).toBe(item.dueAt)
expect(time.getAttribute('title')).toBe(time.getAttribute('aria-label'))
expect(time.getAttribute('title')).toContain(`2026年9月${Number(item.dueAt.slice(8))}`)
expect(time.getAttribute('title')).toContain(item.explanation)
}
})
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()
it('keeps completed date-only deadlines neutral and visually minimal', async () => {
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天前)')
const root = completed.host.querySelector('.task-due')!
const time = root.querySelector('time')!
expect(root.classList.contains('task-due--completed')).toBe(true)
expect(root.classList.contains('task-due--overdue')).toBe(false)
expect(time.textContent?.trim()).toBe('已完成')
expect(time.textContent).not.toMatch(/2026|9月|已过期|还有/)
expect(time.getAttribute('title')).toContain('截止日期:2026年9月9日')
expect(time.getAttribute('title')).toContain('已完成(原定1天前)')
})
it('keeps timed task absolute and relative visual copy unchanged', async () => {
const dueAt = new Date(nowMs + 2 * 60 * 60_000).toISOString()
const { host } = await mountDue({ dueAt, dueHasTime: true, completed: false, nowMs })
const time = host.querySelector('time')!
expect(time.querySelector('.task-due__absolute')?.textContent).toMatch(/^9月10日 \d{2}:\d{2}$/)
expect(time.querySelector('.task-due__relative')?.textContent).toBe('还有2小时')
})
it('reacts when the shared nowMs prop advances', async () => {
+3 -3
View File
@@ -13,15 +13,15 @@ const presentation = computed(() => getTaskDuePresentation(props))
</script>
<template>
<span v-if="presentation" class="task-due" :class="`task-due--${presentation.state}`">
<span v-if="presentation" class="task-due" :class="[`task-due--${presentation.state}`, { 'task-due--timed': dueHasTime }]">
<time
class="task-due__text"
:datetime="presentation.datetime"
:title="presentation.fullText"
:aria-label="presentation.fullText"
>
<span class="task-due__absolute">{{ presentation.absoluteText }}</span>
<span class="task-due__separator" aria-hidden="true"> · </span>
<span v-if="dueHasTime" class="task-due__absolute">{{ presentation.absoluteText }}</span>
<span v-if="dueHasTime" class="task-due__separator" aria-hidden="true"> · </span>
<span class="task-due__relative">{{ presentation.relativeText }}</span>
</time>
</span>