Files
cinny/src/app/utils/datetimeInput.test.ts
T
jaredandClaude Opus 4.8 7d67cdeef0 feat(reminders): view and cancel a message's existing reminders
There was no way to see or cancel a reminder once set (removeReminder was only
called by the fire-and-forget monitor), and addReminder didn't dedupe, so a
message could silently accumulate duplicate reminders. The Remind Me dialog now
lists the reminders already set on that message (soonest first) each with a
cancel button.

- New shared, tested formatFriendlyDateTime(ts, now?) in utils/datetimeInput.ts
  (Today/Tomorrow/date + time).
- Per-row cancel busy-guard; inline "Could not cancel" on failure.

Also applies two nits from the custom-time review: focus the date input when the
custom picker is revealed, and clear the error when editing date/time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:51:11 -04:00

71 lines
2.7 KiB
TypeScript

import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
toLocalDate,
toLocalTime,
parseLocalDateTime,
formatFriendlyDateTime,
} from './datetimeInput';
// All helpers work in the LOCAL timezone. We construct Dates with the local
// component constructor so the assertions hold regardless of where tests run.
test('toLocalDate: zero-pads month and day', () => {
assert.equal(toLocalDate(new Date(2026, 0, 5, 9, 3)), '2026-01-05');
assert.equal(toLocalDate(new Date(2026, 11, 25, 0, 0)), '2026-12-25');
});
test('toLocalTime: zero-pads hours and minutes (24h)', () => {
assert.equal(toLocalTime(new Date(2026, 0, 5, 9, 3)), '09:03');
assert.equal(toLocalTime(new Date(2026, 0, 5, 23, 59)), '23:59');
assert.equal(toLocalTime(new Date(2026, 0, 5, 0, 0)), '00:00');
});
test('toLocalDate/toLocalTime round-trip through parseLocalDateTime', () => {
const d = new Date(2026, 5, 15, 14, 30, 0, 0);
const parsed = parseLocalDateTime(toLocalDate(d), toLocalTime(d));
assert.ok(parsed);
assert.equal(parsed.getTime(), d.getTime());
});
test('parseLocalDateTime: returns null for missing parts', () => {
assert.equal(parseLocalDateTime('', '09:00'), null);
assert.equal(parseLocalDateTime('2026-01-05', ''), null);
assert.equal(parseLocalDateTime('', ''), null);
});
test('parseLocalDateTime: returns null for an invalid combination', () => {
assert.equal(parseLocalDateTime('not-a-date', '09:00'), null);
assert.equal(parseLocalDateTime('2026-13-40', '09:00'), null);
});
test('parseLocalDateTime: valid input yields the local wall-clock time', () => {
const parsed = parseLocalDateTime('2026-01-05', '09:03');
assert.ok(parsed);
assert.equal(parsed.getFullYear(), 2026);
assert.equal(parsed.getMonth(), 0);
assert.equal(parsed.getDate(), 5);
assert.equal(parsed.getHours(), 9);
assert.equal(parsed.getMinutes(), 3);
});
test('formatFriendlyDateTime: uses Today/Tomorrow/date prefixes', () => {
const now = new Date(2026, 0, 5, 10, 0).getTime();
const laterToday = new Date(2026, 0, 5, 15, 30).getTime();
const tomorrow = new Date(2026, 0, 6, 9, 0).getTime();
const nextWeek = new Date(2026, 0, 12, 9, 0).getTime();
assert.ok(formatFriendlyDateTime(laterToday, now).startsWith('Today at '));
assert.ok(formatFriendlyDateTime(tomorrow, now).startsWith('Tomorrow at '));
const other = formatFriendlyDateTime(nextWeek, now);
assert.ok(!other.startsWith('Today'));
assert.ok(!other.startsWith('Tomorrow'));
assert.ok(other.includes(' at '));
});
test('formatFriendlyDateTime: Tomorrow rolls over month/year boundaries', () => {
const nye = new Date(2026, 11, 31, 23, 0).getTime();
const jan1 = new Date(2027, 0, 1, 9, 0).getTime();
assert.ok(formatFriendlyDateTime(jan1, nye).startsWith('Tomorrow at '));
});