diff --git a/frontend/src/components/TaskDueDisplay.test.ts b/frontend/src/components/TaskDueDisplay.test.ts index 8411ef4..c420cc7 100644 --- a/frontend/src/components/TaskDueDisplay.test.ts +++ b/frontend/src/components/TaskDueDisplay.test.ts @@ -24,26 +24,44 @@ describe('TaskDueDisplay', () => { expect(host.innerHTML).toBe('') }) - 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 () => { diff --git a/frontend/src/components/TaskDueDisplay.vue b/frontend/src/components/TaskDueDisplay.vue index fc06063..180be5b 100644 --- a/frontend/src/components/TaskDueDisplay.vue +++ b/frontend/src/components/TaskDueDisplay.vue @@ -13,15 +13,15 @@ const presentation = computed(() => getTaskDuePresentation(props))