import assert from "node:assert/strict"; import test from "node:test"; import type { SchedulingRequest } from "../src/api/scheduling.ts"; import { groupSchedulingRequests, schedulingSortPhase, type SchedulingActor } from "../src/features/scheduling/schedulingViewModel.ts"; const now = new Date("2026-07-20T10:00:00Z"); const actor: SchedulingActor = { accountId: "account-1", membershipId: "membership-1", email: "person@example.test" }; function request( id: string, options: { organizer?: string; participantStatus?: string; status?: SchedulingRequest["status"]; start?: string; end?: string; } = {} ): SchedulingRequest { return { id, tenant_id: "tenant-1", title: id, timezone: "UTC", status: options.status ?? "collecting", organizer_user_id: options.organizer ?? "organizer-elsewhere", allow_external_participants: true, allow_participant_updates: true, result_visibility: "after_close", calendar_integration_enabled: false, calendar_freebusy_enabled: false, calendar_hold_enabled: false, create_calendar_event_on_decision: false, created_at: "2026-07-01T00:00:00Z", updated_at: "2026-07-01T00:00:00Z", metadata: {}, slots: [{ id: `slot-${id}`, label: id, start_at: options.start ?? "2026-07-21T09:00:00Z", end_at: options.end ?? "2026-07-21T10:00:00Z", timezone: "UTC", position: 0, freebusy_conflicts: [], metadata: {} }], participants: options.participantStatus ? [{ id: `participant-${id}`, respondent_id: "membership-1", email: "person@example.test", participant_type: "internal", required: true, status: options.participantStatus, metadata: {} }] : [] }; } test("groups owned, invited, and administrator-only requests without hiding any", () => { const groups = groupSchedulingRequests([ request("managed"), request("mine", { organizer: "account-1" }), request("invited", { participantStatus: "invited" }) ], actor, now); assert.deepEqual(groups.owned.map((item) => item.id), ["mine"]); assert.deepEqual(groups.invited.map((item) => item.id), ["invited"]); assert.deepEqual(groups.other.map((item) => item.id), ["managed"]); }); test("keeps an organizer's active request in the open phase even if they also responded", () => { const owned = request("mine-and-invited", { organizer: "account-1", participantStatus: "responded" }); assert.equal(schedulingSortPhase(owned, actor, now), "unanswered"); }); test("orders unanswered by nearest slot before answered, closed, determined, and past", () => { const groups = groupSchedulingRequests([ request("past", { participantStatus: "invited", start: "2026-07-18T09:00:00Z", end: "2026-07-18T10:00:00Z" }), request("determined", { participantStatus: "invited", status: "decided" }), request("closed", { participantStatus: "invited", status: "closed" }), request("answered", { participantStatus: "responded" }), request("later", { participantStatus: "invited", start: "2026-07-23T09:00:00Z" }), request("nearer", { participantStatus: "invited", start: "2026-07-21T09:00:00Z" }) ], actor, now); assert.deepEqual(groups.invited.map((item) => item.id), [ "nearer", "later", "answered", "closed", "determined", "past" ]); assert.equal(schedulingSortPhase(groups.invited[0], actor, now), "unanswered"); assert.equal(schedulingSortPhase(groups.invited.at(-1)!, actor, now), "past"); });