import { describe, it, expect } from 'vitest'; import { parseDueDate, parseAllCandidates } from '@main/services/dueDateParser.js'; // 2026-04-26 is a Sunday (KST). Use that as "today" for fixtures. const TODAY = new Date('2026-04-26T00:00:00.000Z'); // KST midnight = UTC 15:00 prior day, but for parser logic we treat input Date as KST-aligned. The parser treats the passed Date as the "today reference" without further timezone math. describe('parseDueDate (Korean rule parser)', () => { it('returns null for empty / no-token text', () => { expect(parseDueDate('아무 일정도 없는 메모', TODAY).iso).toBeNull(); }); it('parses YYYY-MM-DD literal', () => { expect(parseDueDate('2026-05-15 마감', TODAY).iso).toBe('2026-05-15'); }); it('rejects invalid YYYY-MM-DD (silent coercion)', () => { // 2026-02-30 is invalid; parser should NOT match it as the literal rule const r = parseDueDate('2026-02-30 마감', TODAY); // Falls through to other rules — but no other token matches "2026-02-30 마감" expect(r.iso).toBeNull(); }); it('parses N월 N일 in current year if future', () => { expect(parseDueDate('5월 1일 휴가 신청', TODAY).iso).toBe('2026-05-01'); }); it('parses N월 N일 in next year if past', () => { expect(parseDueDate('3월 1일 발표', TODAY).iso).toBe('2027-03-01'); }); it('parses MM/DD', () => { expect(parseDueDate('5/3 데모', TODAY).iso).toBe('2026-05-03'); }); it('parses N일 뒤', () => { expect(parseDueDate('3일 뒤 데모', TODAY).iso).toBe('2026-04-29'); }); it('parses N일 후', () => { expect(parseDueDate('5일 후 미팅', TODAY).iso).toBe('2026-05-01'); }); it('parses N주 뒤', () => { expect(parseDueDate('2주 뒤 회의', TODAY).iso).toBe('2026-05-10'); }); it('parses N개월 뒤', () => { expect(parseDueDate('1개월 뒤 점검', TODAY).iso).toBe('2026-05-26'); }); it('parses 모레', () => { expect(parseDueDate('모레 발표', TODAY).iso).toBe('2026-04-28'); }); it('parses 내일', () => { expect(parseDueDate('내일 회의 준비', TODAY).iso).toBe('2026-04-27'); }); it('parses 글피', () => { expect(parseDueDate('글피까지 마무리', TODAY).iso).toBe('2026-04-29'); }); it('parses 오늘', () => { expect(parseDueDate('오늘 PR 리뷰', TODAY).iso).toBe('2026-04-26'); }); it('parses 다음 주 월요일', () => { expect(parseDueDate('다음 주 월요일까지 슬라이드', TODAY).iso).toBe('2026-05-04'); }); it('parses 다음 주 금요일', () => { expect(parseDueDate('다음 주 금요일에 점검', TODAY).iso).toBe('2026-05-08'); }); it('parses 이번 주 일요일 = today (Sunday)', () => { expect(parseDueDate('이번 주 일요일 약속', TODAY).iso).toBe('2026-04-26'); }); it('parses 이번 주 수요일', () => { // This week's Mon = 04-20, so this week's Wed = 04-22. But that's past — still resolve to 04-22 (parser does not skip past dates within current week — caller can decide to ignore). expect(parseDueDate('이번 주 수요일 회의', TODAY).iso).toBe('2026-04-22'); }); it('parses 다음 달 1일', () => { expect(parseDueDate('다음 달 1일', TODAY).iso).toBe('2026-05-01'); }); it('parses 다음 달 (alone) → first of next month', () => { expect(parseDueDate('다음 달 마감', TODAY).iso).toBe('2026-05-01'); }); it('returns medium confidence with iso null for 월말', () => { const r = parseDueDate('월말 마감', TODAY); expect(r.iso).toBeNull(); expect(r.confidence).toBe('medium'); expect(r.matchedToken).toBe('월말'); }); it('returns medium confidence with iso null for 주말', () => { const r = parseDueDate('주말까지 정리', TODAY); expect(r.iso).toBeNull(); expect(r.confidence).toBe('medium'); }); it('returns medium confidence for 퇴근 전', () => { const r = parseDueDate('퇴근 전 답장', TODAY); expect(r.iso).toBeNull(); expect(r.confidence).toBe('medium'); }); it('returns null for time-only (오후 3시)', () => { // Time tokens get medium confidence (defer to AI) since they imply same-day but not a date directly. const r = parseDueDate('오후 3시 미팅', TODAY); expect(r.iso).toBeNull(); expect(r.confidence).toBe('medium'); }); it('first match wins when multiple tokens present', () => { expect(parseDueDate('내일도 모레도 바쁨', TODAY).iso).toBe('2026-04-27'); }); it('matches 다음 주 alone → next Monday', () => { // If not followed by 요일, "다음 주" alone defaults to next Monday expect(parseDueDate('다음 주 발표', TODAY).iso).toBe('2026-05-04'); }); }); describe('parseAllCandidates', () => { it('returns empty array when no token', () => { expect(parseAllCandidates('아무 일정 없음', TODAY)).toEqual([]); }); it('returns 2 high-confidence candidates for "내일 모레"', () => { const r = parseAllCandidates('내일 모레', TODAY); expect(r.length).toBe(2); const isos = r.map((c) => c.iso); expect(isos).toContain('2026-04-27'); expect(isos).toContain('2026-04-28'); expect(r.every((c) => c.confidence === 'high')).toBe(true); }); it('returns candidates in text-position order', () => { const r = parseAllCandidates('내일 모레', TODAY); expect(r[0]!.matchedToken).toBe('내일'); expect(r[1]!.matchedToken).toBe('모레'); }); it('returns 1 candidate for single token "내일"', () => { const r = parseAllCandidates('내일 회의', TODAY); expect(r.length).toBe(1); expect(r[0]!.iso).toBe('2026-04-27'); }); it('returns 1 medium-confidence candidate for "월말 마감"', () => { const r = parseAllCandidates('월말 마감', TODAY); expect(r.length).toBe(1); expect(r[0]!.iso).toBeNull(); expect(r[0]!.confidence).toBe('medium'); expect(r[0]!.matchedToken).toBe('월말'); }); it('returns mix of high + medium candidates', () => { const r = parseAllCandidates('내일 월말 회의', TODAY); expect(r.length).toBe(2); expect(r[0]!.confidence).toBe('high'); expect(r[1]!.confidence).toBe('medium'); }); it('returns 2 candidates for "5월 1일 이후 다음 주 월요일까지"', () => { const r = parseAllCandidates('5월 1일 이후 다음 주 월요일까지', TODAY); expect(r.length).toBe(2); expect(r[0]!.iso).toBe('2026-05-01'); expect(r[1]!.iso).toBe('2026-05-04'); }); });