Move an event by the days the hand moved it, not to the date dropped on

Dragging an event across the month grid wrote the date of the cell it
landed on into the event's stored start. Those are the same date only
while the event's time zone is the reader's.

An event kept in Asia/Tokyo at 15:00 is drawn to a reader in Phoenix at
23:00 the previous evening. Dropped on the 11th, it was written as the
11th in Tokyo -- which is the 10th on screen. It went where its own
calendar said rather than where the pointer did, one day short, every
time.

Moving by the difference between the two local days instead moves it
exactly as far as the hand did, and adding whole days to a stored wall
clock leaves the time of day alone without touching the zone -- so the
frame the rest of this path is careful about is still not crossed.

Found by giving the mock an event in a zone that is not the machine's.
Every other fixture used the machine's own, which cannot tell a correct
conversion from no conversion at all: the case that works is the one the
fixtures were all testing.
This commit is contained in:
2026-09-02 01:07:07 -07:00
parent 13a6bcd66b
commit 7b3069e41b
5 changed files with 69 additions and 11 deletions
+29 -4
View File
@@ -9,7 +9,8 @@ import {
resizedBy,
snap,
movePatch,
moveToDayPatch,
moveByDaysPatch,
dayDelta,
resizePatch,
SNAP_MINUTES,
} from "@/lib/eventDrag";
@@ -142,8 +143,31 @@ describe("the patch a drag sends, computed in the event's own frame", () => {
expect(movePatch("2026-09-04T14:00:00", 30).duration).toBeUndefined();
});
it("keeps the time of day when moving to another date", () => {
expect(moveToDayPatch("2026-09-04T14:30:00", new Date(2026, 8, 10))).toEqual({ start: "2026-09-10T14:30:00" });
it("keeps the time of day when moving by whole days", () => {
expect(moveByDaysPatch("2026-09-04T14:30:00", 6)).toEqual({ start: "2026-09-10T14:30:00" });
expect(moveByDaysPatch("2026-09-04T14:30:00", -3)).toEqual({ start: "2026-09-01T14:30:00" });
});
it("moves by the delta the hand made, not to the date that was dropped on", () => {
/*
* The month grid's cells are local days; the stored date is in the event's
* own zone. Writing the dropped-on date put a Tokyo event dropped on the
* 11th onto the 10th, because 15:00 in Tokyo is the previous evening in
* Phoenix — it went where its own calendar said, not where the pointer did.
*/
const storedTokyo = "2026-09-04T15:00:00"; // shown to a Phoenix reader on the 3rd
const shownOn = new Date(2026, 8, 3);
const droppedOn = new Date(2026, 8, 11);
const patch = moveByDaysPatch(storedTokyo, dayDelta(shownOn, droppedOn));
// Eight days later in its own frame, so eight days later on screen too.
expect(patch).toEqual({ start: "2026-09-12T15:00:00" });
});
it("counts whole local days, ignoring the time on either side", () => {
expect(dayDelta(new Date(2026, 8, 3, 23, 30), new Date(2026, 8, 4, 0, 30))).toBe(1);
expect(dayDelta(new Date(2026, 8, 4), new Date(2026, 8, 4))).toBe(0);
expect(dayDelta(new Date(2026, 8, 11), new Date(2026, 8, 3))).toBe(-8);
expect(dayDelta(new Date(2026, 8, 30), new Date(2026, 9, 2))).toBe(2);
});
it("never sends a start for a resize, so the zone question does not arise", () => {
@@ -158,7 +182,8 @@ describe("the patch a drag sends, computed in the event's own frame", () => {
it("says nothing at all about a start it cannot read", () => {
expect(movePatch("not a date", 30)).toEqual({});
expect(moveToDayPatch("", new Date(2026, 8, 10))).toEqual({});
expect(moveByDaysPatch("", 3)).toEqual({});
expect(moveByDaysPatch("2026-09-04T14:00:00", Number.NaN)).toEqual({});
});
});