From 8a4b9837f2dc8af1fa6f148d2663c556f493bb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8B=92=E5=B8=83=E6=9C=97-=E8=A9=B9=E5=A7=86=E6=96=AF?= <2986253039@qq.com> Date: Mon, 28 Sep 2026 15:13:58 +0800 Subject: [PATCH] fix(projects): pin the schedule bar spec clock so clamping stays calendar-independent The schedule window starts at the Monday of the current week (ProjectBoard.tsx:95), so the hard-coded fixtures in this spec leave the window as the calendar moves: 2026-09-28 for the partially invalid date task, 2026-10-05 for the inverted task, 2027-01-04 for the window-spanning task. Pin the clock inside the spec with the pattern already used in approval-queue.test.tsx and turn-progress-interaction.test.tsx, and restore real timers in a finally block so a failing assertion cannot leak fake time into the file. Closes #525 --- .../renderer/test/project-board.test.tsx | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/apps/desktop/renderer/test/project-board.test.tsx b/apps/desktop/renderer/test/project-board.test.tsx index fb329909..bfd9feff 100644 --- a/apps/desktop/renderer/test/project-board.test.tsx +++ b/apps/desktop/renderer/test/project-board.test.tsx @@ -559,58 +559,67 @@ describe("ProjectBoard", () => { }); it("clamps schedule bar calculations for inverted dates, window-edge spans, and partially invalid dates", () => { - setup( - detail([ - { - ...task, - taskId: "inverted-task", - title: "Inverted dates task", - startDate: "2026-09-28", - dueDate: "2026-09-22", - }, - { - ...task, - taskId: "span-task", - title: "Span task across window", - startDate: "2025-01-01", - dueDate: "2027-01-01", - }, - { - ...task, - taskId: "partial-date-task", - title: "Partially invalid date task", - startDate: "malformed-date", - dueDate: "2026-09-22", - }, - ]), - ); - - fireEvent.click(screen.getByRole("button", { name: "排期" })); + // The schedule window starts at the Monday of the current week + // (ProjectBoard.tsx:95), so these fixed fixtures only stay inside it for a + // finite calendar span. Pin the clock to keep the clamping math deterministic. + vi.useFakeTimers({ toFake: ["Date"] }); + vi.setSystemTime(new Date("2026-09-24T12:00:00.000Z")); + try { + setup( + detail([ + { + ...task, + taskId: "inverted-task", + title: "Inverted dates task", + startDate: "2026-09-28", + dueDate: "2026-09-22", + }, + { + ...task, + taskId: "span-task", + title: "Span task across window", + startDate: "2025-01-01", + dueDate: "2027-01-01", + }, + { + ...task, + taskId: "partial-date-task", + title: "Partially invalid date task", + startDate: "malformed-date", + dueDate: "2026-09-22", + }, + ]), + ); - // Inverted task bar is clamped chronologically without negative width - const invBar = screen.getByRole("button", { name: /^Inverted dates task: / }); - expect(invBar).toBeInTheDocument(); - const invLeft = parseFloat(invBar.style.left.match(/([\d.]+)%/)![1]); - const invWidth = parseFloat(invBar.style.width.match(/([\d.]+)%/)![1]); - expect(invLeft).toBeGreaterThanOrEqual(0); - expect(invWidth).toBeGreaterThan(0); - - // Span task covering beyond the window boundaries is clamped to [0%, 100%] - const spanBar = screen.getByRole("button", { name: /^Span task across window: / }); - expect(spanBar).toBeInTheDocument(); - expect(spanBar.style.left).toBe("0%"); - expect(spanBar.style.width).toBe("100%"); - - // Partially invalid date task uses the valid date safely without NaN - const partialBar = screen.getByRole("button", { name: /^Partially invalid date task: / }); - expect(partialBar).toBeInTheDocument(); - const partialLeft = parseFloat(partialBar.style.left.match(/([\d.]+)%/)![1]); - const partialWidth = parseFloat(partialBar.style.width.match(/([\d.]+)%/)![1]); - expect(Number.isFinite(partialLeft)).toBe(true); - expect(Number.isFinite(partialWidth)).toBe(true); - expect(partialLeft).toBeGreaterThanOrEqual(0); - expect(partialWidth).toBeGreaterThan(0); - expect(screen.getByText("截止:2026-09-22 · 开始未定")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "排期" })); + + // Inverted task bar is clamped chronologically without negative width + const invBar = screen.getByRole("button", { name: /^Inverted dates task: / }); + expect(invBar).toBeInTheDocument(); + const invLeft = parseFloat(invBar.style.left.match(/([\d.]+)%/)![1]); + const invWidth = parseFloat(invBar.style.width.match(/([\d.]+)%/)![1]); + expect(invLeft).toBeGreaterThanOrEqual(0); + expect(invWidth).toBeGreaterThan(0); + + // Span task covering beyond the window boundaries is clamped to [0%, 100%] + const spanBar = screen.getByRole("button", { name: /^Span task across window: / }); + expect(spanBar).toBeInTheDocument(); + expect(spanBar.style.left).toBe("0%"); + expect(spanBar.style.width).toBe("100%"); + + // Partially invalid date task uses the valid date safely without NaN + const partialBar = screen.getByRole("button", { name: /^Partially invalid date task: / }); + expect(partialBar).toBeInTheDocument(); + const partialLeft = parseFloat(partialBar.style.left.match(/([\d.]+)%/)![1]); + const partialWidth = parseFloat(partialBar.style.width.match(/([\d.]+)%/)![1]); + expect(Number.isFinite(partialLeft)).toBe(true); + expect(Number.isFinite(partialWidth)).toBe(true); + expect(partialLeft).toBeGreaterThanOrEqual(0); + expect(partialWidth).toBeGreaterThan(0); + expect(screen.getByText("截止:2026-09-22 · 开始未定")).toBeInTheDocument(); + } finally { + vi.useRealTimers(); + } }); it("surfaces project.deleteTaskFail error message when deletion fails", async () => {