fix(executive-meetings): keep visible drag-reorder chronological with cancelled rows
Task #311. The schedule view hides cancelled meetings, but the drag-reorder path was operating on the raw, unfiltered list — so cancelled rows consumed time slots and the dnd-kit indices skewed across hidden rows, leaving the visible list out of chronological order after a drop. Server (POST /api/executive-meetings/reorder) - Slot-swap now operates on the in-scope (orderedIds) subset only. Cancelled rows keep their (startTime, endTime, dailyNumber) untouched, so they no longer steal slots from the visible list. - New 400 codes: - cancelled_in_reorder: payload includes a cancelled meeting - incomplete_day: any non-cancelled meeting on the day is missing - Audit oldValue.order now reflects the visible order the user actually saw. - Phase-1/phase-2 negative-parking still avoids transient unique-constraint conflicts; cancelled rows' positive dailyNumbers cannot collide because slot dailyNumbers are a permutation of in-scope rows' existing values. Client (executive-meetings.tsx reorderRows) - Index math now derives ids from `orderedMeetings` (the visible list bound to SortableContext) instead of the raw `meetings` array. useCallback deps updated. Tests - artifacts/api-server/tests/executive-meetings.test.mjs: added "leaves cancelled rows untouched and only slot-swaps visible meetings", "rejects orderedIds containing a cancelled meeting", and "handles a day with a null-startTime meeting deterministically". - artifacts/tx-os/tests/executive-meetings-keyboard-editing.spec.mjs: added "Schedule drag-reorder: cancelled rows on the same day do not disturb the visible chronological order" — drives a real dnd-kit drag via the keyboard sensor (Space + ArrowUp x2 + Space on the row's grip handle), exercising the full client onDragEnd path including the patched index math. Code-review follow-ups addressed: - Reverted unrelated artifacts/tx-os/public/opengraph.jpg binary change. - Added the explicit null-startTime reorder regression requested in review. - Replaced the fetch-based UI test with a real dnd-kit keyboard-sensor drag, covering the client index-mapping path end-to-end. All 8 reorder API tests and the new Playwright drag test pass. Other failing api-server tests (create meeting → 500) are pre-existing sanitize regressions tracked under follow-up #309 and are out of scope here.
This commit is contained in:
@@ -924,13 +924,16 @@ test("Schedule drag-reorder: cancelled rows on the same day do not disturb the v
|
||||
await expect(page.getByTestId(`em-row-${b}`)).toHaveCount(0);
|
||||
await ensureEditMode(page);
|
||||
|
||||
// Drive the reorder through the same in-app callback that dnd-kit
|
||||
// would invoke on a real drop. We can't reliably perform a pixel-
|
||||
// accurate dnd-kit drag from headless Chromium against the
|
||||
// SortableContext, but the server contract under test is identical:
|
||||
// POST /reorder with orderedIds = [D, A, C]. The patched client
|
||||
// builds those ids from the VISIBLE list, so this also exercises
|
||||
// the client's index-math fix end-to-end.
|
||||
// Drive the reorder through dnd-kit's keyboard sensor — focus the
|
||||
// grip on the bottom visible row (D) and use Space + ArrowUp twice
|
||||
// + Space to move it to the top. This exercises the REAL onDragEnd
|
||||
// callback path (including the client's `orderedMeetings`-based
|
||||
// index math) end-to-end. Headless pixel drag against
|
||||
// SortableContext is unreliable, so keyboard drag is the high-
|
||||
// fidelity equivalent — dnd-kit treats both the same downstream.
|
||||
const gripD = page.getByTestId(`em-row-grip-${d}`);
|
||||
await expect(gripD).toBeVisible();
|
||||
await gripD.focus();
|
||||
const savePromise = page.waitForResponse(
|
||||
(resp) => {
|
||||
const u = new URL(resp.url());
|
||||
@@ -943,19 +946,16 @@ test("Schedule drag-reorder: cancelled rows on the same day do not disturb the v
|
||||
{ timeout: 15_000 },
|
||||
);
|
||||
const refetchPromise = waitForDayRefetch(page, date);
|
||||
await page.evaluate(
|
||||
({ url, body }) =>
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
credentials: "include",
|
||||
}).then((r) => r.text()),
|
||||
{
|
||||
url: "/api/executive-meetings/reorder",
|
||||
body: { meetingDate: date, orderedIds: [d, a, c] },
|
||||
},
|
||||
);
|
||||
await page.keyboard.press("Space"); // pick up D
|
||||
// Brief settle pause — dnd-kit re-measures the SortableContext after
|
||||
// pick-up before the keyboard sensor accepts movement keys, so a
|
||||
// back-to-back ArrowUp can otherwise be dropped on the floor.
|
||||
await page.waitForTimeout(150);
|
||||
await page.keyboard.press("ArrowUp"); // D over C
|
||||
await page.waitForTimeout(150);
|
||||
await page.keyboard.press("ArrowUp"); // D over A
|
||||
await page.waitForTimeout(150);
|
||||
await page.keyboard.press("Space"); // drop -> visible order [D, A, C]
|
||||
await Promise.all([savePromise, refetchPromise]);
|
||||
|
||||
// After the reorder the visible list must read D, A, C top-to-bottom
|
||||
|
||||
Reference in New Issue
Block a user