457 lines
14 KiB
TypeScript
457 lines
14 KiB
TypeScript
|
|
import { describe, expect, test } from "vitest";
|
|||
|
|
import {
|
|||
|
|
findSlots,
|
|||
|
|
inWorkingHours,
|
|||
|
|
therapistAvailable,
|
|||
|
|
type FindSlotsOptions,
|
|||
|
|
type RoomState,
|
|||
|
|
type ServiceLite,
|
|||
|
|
type TherapistState,
|
|||
|
|
type WorkingHoursEntry,
|
|||
|
|
} from "@/lib/availability";
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// Fixture helpers
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
const TZ = "America/New_York";
|
|||
|
|
const D = (iso: string) => new Date(iso);
|
|||
|
|
|
|||
|
|
// Tue 2026-05-05 in the New York practice timezone:
|
|||
|
|
// 10:00 EDT == 14:00 UTC (EDT = UTC-4)
|
|||
|
|
// We'll use this date a lot in fixtures.
|
|||
|
|
const TUE_LOCAL_10AM = D("2026-05-05T14:00:00Z"); // 2026-05-05 10:00 EDT
|
|||
|
|
const TUE_LOCAL_11AM = D("2026-05-05T15:00:00Z");
|
|||
|
|
const TUE_LOCAL_12PM = D("2026-05-05T16:00:00Z");
|
|||
|
|
const TUE_LOCAL_5PM = D("2026-05-05T21:00:00Z");
|
|||
|
|
const TUE_LOCAL_7PM = D("2026-05-05T23:00:00Z");
|
|||
|
|
const TUE_LOCAL_9AM = D("2026-05-05T13:00:00Z"); // before opening
|
|||
|
|
|
|||
|
|
// Spring-forward 2026 in US Eastern: Mar 8 2026, local 02:00 EST → 03:00 EDT.
|
|||
|
|
const SPRING_FORWARD_SUN_LOCAL_10AM = D("2026-03-08T14:00:00Z"); // 10:00 EDT after the jump
|
|||
|
|
|
|||
|
|
const SERVICE_60MIN: ServiceLite = {
|
|||
|
|
id: "svc-60",
|
|||
|
|
durationMin: 60,
|
|||
|
|
bufferAfterMin: 15,
|
|||
|
|
requiredTherapistTags: [],
|
|||
|
|
requiredRoomTags: [],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const SERVICE_90MIN_PRENATAL: ServiceLite = {
|
|||
|
|
id: "svc-90-prenatal",
|
|||
|
|
durationMin: 90,
|
|||
|
|
bufferAfterMin: 20,
|
|||
|
|
requiredTherapistTags: ["prenatal-cert"],
|
|||
|
|
requiredRoomTags: ["prenatal-table"],
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const FULL_WEEK_10_TO_19: WorkingHoursEntry[] = [0, 1, 2, 3, 4, 5, 6].map((d) => ({
|
|||
|
|
weekday: d,
|
|||
|
|
startMin: 10 * 60,
|
|||
|
|
endMin: 19 * 60,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
const TUE_THRU_SAT_10_TO_19: WorkingHoursEntry[] = [2, 3, 4, 5, 6].map((d) => ({
|
|||
|
|
weekday: d,
|
|||
|
|
startMin: 10 * 60,
|
|||
|
|
endMin: 19 * 60,
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
function baseTherapist(overrides: Partial<TherapistState> = {}): TherapistState {
|
|||
|
|
return {
|
|||
|
|
id: "t1",
|
|||
|
|
active: true,
|
|||
|
|
tags: new Set(["swedish"]),
|
|||
|
|
serviceIds: new Set([SERVICE_60MIN.id]),
|
|||
|
|
workingHours: TUE_THRU_SAT_10_TO_19,
|
|||
|
|
overrides: [],
|
|||
|
|
bookings: [],
|
|||
|
|
...overrides,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function baseRoom(overrides: Partial<RoomState> = {}): RoomState {
|
|||
|
|
return {
|
|||
|
|
id: "r1",
|
|||
|
|
active: true,
|
|||
|
|
tags: new Set<string>(),
|
|||
|
|
blocks: [],
|
|||
|
|
bookings: [],
|
|||
|
|
...overrides,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function baseOpts(overrides: Partial<FindSlotsOptions> = {}): FindSlotsOptions {
|
|||
|
|
return {
|
|||
|
|
service: SERVICE_60MIN,
|
|||
|
|
from: TUE_LOCAL_10AM,
|
|||
|
|
to: TUE_LOCAL_12PM,
|
|||
|
|
therapists: [baseTherapist()],
|
|||
|
|
rooms: [baseRoom()],
|
|||
|
|
practiceTz: TZ,
|
|||
|
|
slotGranularityMin: 15,
|
|||
|
|
...overrides,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// inWorkingHours — direct tests
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
describe("inWorkingHours", () => {
|
|||
|
|
test("slot inside Tuesday working hours is accepted", () => {
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(TUE_THRU_SAT_10_TO_19, TUE_LOCAL_10AM, TUE_LOCAL_11AM, TZ),
|
|||
|
|
).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("slot before opening is rejected", () => {
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(TUE_THRU_SAT_10_TO_19, TUE_LOCAL_9AM, TUE_LOCAL_10AM, TZ),
|
|||
|
|
).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("slot ending exactly at closing is accepted (endMin is inclusive of equality)", () => {
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(TUE_THRU_SAT_10_TO_19, TUE_LOCAL_5PM, TUE_LOCAL_7PM, TZ),
|
|||
|
|
).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("Monday slot rejected when Mon is not a working day", () => {
|
|||
|
|
// 2026-05-04 is a Monday
|
|||
|
|
const monLocal10am = D("2026-05-04T14:00:00Z");
|
|||
|
|
const monLocal11am = D("2026-05-04T15:00:00Z");
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(TUE_THRU_SAT_10_TO_19, monLocal10am, monLocal11am, TZ),
|
|||
|
|
).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("DST spring-forward Sunday: 10:00 local works because we shift to EDT", () => {
|
|||
|
|
// Even though the local clock skipped 02:00–03:00 EST, 10:00 EDT is well after.
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(
|
|||
|
|
FULL_WEEK_10_TO_19,
|
|||
|
|
SPRING_FORWARD_SUN_LOCAL_10AM,
|
|||
|
|
D("2026-03-08T15:00:00Z"), // 11:00 EDT
|
|||
|
|
TZ,
|
|||
|
|
),
|
|||
|
|
).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("effectiveFrom/effectiveTo bracket is enforced", () => {
|
|||
|
|
const wh: WorkingHoursEntry[] = [{
|
|||
|
|
weekday: 2,
|
|||
|
|
startMin: 10 * 60,
|
|||
|
|
endMin: 19 * 60,
|
|||
|
|
effectiveFrom: D("2026-06-01T00:00:00Z"),
|
|||
|
|
}];
|
|||
|
|
// Slot before effectiveFrom should not match.
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(wh, TUE_LOCAL_10AM, TUE_LOCAL_11AM, TZ),
|
|||
|
|
).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("cross-midnight slot is rejected", () => {
|
|||
|
|
// Hypothetical 11pm-1am slot would span two local days.
|
|||
|
|
expect(
|
|||
|
|
inWorkingHours(
|
|||
|
|
FULL_WEEK_10_TO_19,
|
|||
|
|
D("2026-05-06T03:00:00Z"), // 23:00 local Tuesday
|
|||
|
|
D("2026-05-06T05:00:00Z"), // 01:00 local Wednesday
|
|||
|
|
TZ,
|
|||
|
|
),
|
|||
|
|
).toBe(false);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// therapistAvailable — direct tests
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
describe("therapistAvailable", () => {
|
|||
|
|
test("free therapist in working hours is available", () => {
|
|||
|
|
const t = baseTherapist();
|
|||
|
|
expect(therapistAvailable(t, TUE_LOCAL_10AM, TUE_LOCAL_11AM, TZ)).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("booking overlap blocks the slot", () => {
|
|||
|
|
const t = baseTherapist({
|
|||
|
|
bookings: [{ startsAt: TUE_LOCAL_10AM, endsAt: TUE_LOCAL_11AM }],
|
|||
|
|
});
|
|||
|
|
expect(
|
|||
|
|
therapistAvailable(
|
|||
|
|
t,
|
|||
|
|
D("2026-05-05T14:30:00Z"), // 10:30 EDT — overlaps 10:00–11:00
|
|||
|
|
D("2026-05-05T15:30:00Z"),
|
|||
|
|
TZ,
|
|||
|
|
),
|
|||
|
|
).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("BLOCK override during working hours blocks the slot", () => {
|
|||
|
|
const t = baseTherapist({
|
|||
|
|
overrides: [{
|
|||
|
|
kind: "BLOCK",
|
|||
|
|
startsAt: D("2026-05-05T14:30:00Z"),
|
|||
|
|
endsAt: D("2026-05-05T15:30:00Z"),
|
|||
|
|
}],
|
|||
|
|
});
|
|||
|
|
expect(therapistAvailable(t, TUE_LOCAL_10AM, TUE_LOCAL_11AM, TZ)).toBe(false);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("EXTRA_HOURS opens a slot outside regular working hours", () => {
|
|||
|
|
// Therapist's regular hours don't include Sunday; EXTRA_HOURS does.
|
|||
|
|
const t = baseTherapist({
|
|||
|
|
workingHours: TUE_THRU_SAT_10_TO_19, // no Sunday
|
|||
|
|
overrides: [{
|
|||
|
|
kind: "EXTRA_HOURS",
|
|||
|
|
startsAt: D("2026-05-10T14:00:00Z"), // Sun 10:00 EDT
|
|||
|
|
endsAt: D("2026-05-10T18:00:00Z"), // Sun 14:00 EDT
|
|||
|
|
}],
|
|||
|
|
});
|
|||
|
|
expect(
|
|||
|
|
therapistAvailable(
|
|||
|
|
t,
|
|||
|
|
D("2026-05-10T14:00:00Z"),
|
|||
|
|
D("2026-05-10T15:00:00Z"),
|
|||
|
|
TZ,
|
|||
|
|
),
|
|||
|
|
).toBe(true);
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// ============================================================
|
|||
|
|
// findSlots — integration
|
|||
|
|
// ============================================================
|
|||
|
|
|
|||
|
|
describe("findSlots", () => {
|
|||
|
|
test("baseline: 60-min service in a 2-hour window emits 5 slots at 15-min granularity", () => {
|
|||
|
|
// Window 10:00–12:00 local. Service is 60 min. Slots can start at
|
|||
|
|
// 10:00, 10:15, 10:30, 10:45, 11:00 (last finishes at 12:00).
|
|||
|
|
const slots = findSlots(baseOpts());
|
|||
|
|
expect(slots).toHaveLength(5);
|
|||
|
|
expect(slots[0].startsAt).toEqual(TUE_LOCAL_10AM);
|
|||
|
|
expect(slots[4].startsAt).toEqual(TUE_LOCAL_11AM);
|
|||
|
|
expect(slots[4].endsAt).toEqual(TUE_LOCAL_12PM);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("empty therapists list yields no slots", () => {
|
|||
|
|
expect(findSlots(baseOpts({ therapists: [] }))).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("empty rooms list yields no slots", () => {
|
|||
|
|
expect(findSlots(baseOpts({ rooms: [] }))).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("therapist tag mismatch excludes therapist", () => {
|
|||
|
|
// Service requires prenatal-cert, but the only room has the right tag.
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
service: SERVICE_90MIN_PRENATAL,
|
|||
|
|
// Window large enough for one 90-min slot
|
|||
|
|
to: D("2026-05-05T15:30:00Z"),
|
|||
|
|
therapists: [
|
|||
|
|
baseTherapist({
|
|||
|
|
tags: new Set(["swedish"]), // missing prenatal-cert
|
|||
|
|
serviceIds: new Set([SERVICE_90MIN_PRENATAL.id]),
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
rooms: [
|
|||
|
|
baseRoom({ tags: new Set(["prenatal-table"]) }),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("room tag mismatch excludes room", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
service: SERVICE_90MIN_PRENATAL,
|
|||
|
|
to: D("2026-05-05T15:30:00Z"),
|
|||
|
|
therapists: [
|
|||
|
|
baseTherapist({
|
|||
|
|
tags: new Set(["prenatal-cert"]),
|
|||
|
|
serviceIds: new Set([SERVICE_90MIN_PRENATAL.id]),
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
rooms: [
|
|||
|
|
baseRoom({ tags: new Set<string>() }), // missing prenatal-table
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("ServiceTherapist allowlist excludes therapists not opted in", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
therapists: [
|
|||
|
|
baseTherapist({ serviceIds: new Set(["some-other-service"]) }),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("inactive therapist excluded", () => {
|
|||
|
|
expect(
|
|||
|
|
findSlots(baseOpts({ therapists: [baseTherapist({ active: false })] })),
|
|||
|
|
).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("inactive room excluded", () => {
|
|||
|
|
expect(
|
|||
|
|
findSlots(baseOpts({ rooms: [baseRoom({ active: false })] })),
|
|||
|
|
).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("therapist on PTO via BLOCK override has no slots in PTO window", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
therapists: [
|
|||
|
|
baseTherapist({
|
|||
|
|
overrides: [{
|
|||
|
|
kind: "BLOCK",
|
|||
|
|
startsAt: TUE_LOCAL_10AM,
|
|||
|
|
endsAt: TUE_LOCAL_12PM,
|
|||
|
|
}],
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("room buffer prevents back-to-back booking inside the buffer window", () => {
|
|||
|
|
// Existing booking 10:00–11:00 with 15-min buffer ⇒ room locked till 11:15.
|
|||
|
|
// Window 10:00–12:00 ⇒ next legal start is 11:15. With granularity 15 and
|
|||
|
|
// duration 60, candidates 11:15 (end 12:15 – out of window!), so 0 slots
|
|||
|
|
// beyond the existing one. But the existing booking's slot itself isn't a
|
|||
|
|
// candidate (the room is occupied). Expand window to 12:30 to test legal
|
|||
|
|
// resumption at 11:15.
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
to: D("2026-05-05T16:30:00Z"), // 12:30 EDT
|
|||
|
|
rooms: [
|
|||
|
|
baseRoom({
|
|||
|
|
bookings: [{
|
|||
|
|
startsAt: TUE_LOCAL_10AM,
|
|||
|
|
endsAt: D("2026-05-05T15:15:00Z"), // = 11:00 + 15 min buffer
|
|||
|
|
}],
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
// Legal slots: 11:15 → 12:15 (only one, since 11:30 → 12:30 also fits).
|
|||
|
|
const starts = slots.map((s) => s.startsAt.toISOString());
|
|||
|
|
expect(starts).toContain("2026-05-05T15:15:00.000Z"); // 11:15 EDT
|
|||
|
|
expect(starts).toContain("2026-05-05T15:30:00.000Z"); // 11:30 EDT
|
|||
|
|
// Should NOT include any time before 11:15 (room locked).
|
|||
|
|
expect(
|
|||
|
|
starts.filter((s) => s < "2026-05-05T15:15:00.000Z"),
|
|||
|
|
).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("room block excludes that room during the block window", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
rooms: [
|
|||
|
|
baseRoom({
|
|||
|
|
blocks: [{
|
|||
|
|
startsAt: D("2026-05-05T14:30:00Z"), // 10:30 EDT
|
|||
|
|
endsAt: D("2026-05-05T15:30:00Z"), // 11:30 EDT
|
|||
|
|
}],
|
|||
|
|
}),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
// 10:00–11:00 slot? Block starts 10:30, so room buffer on slot 10:00 → 11:15
|
|||
|
|
// overlaps the block — excluded.
|
|||
|
|
const starts = slots.map((s) => s.startsAt.toISOString());
|
|||
|
|
expect(starts).not.toContain("2026-05-05T14:00:00.000Z"); // 10:00
|
|||
|
|
expect(starts).not.toContain("2026-05-05T14:15:00.000Z"); // 10:15
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("preferredTherapistId restricts results to that therapist", () => {
|
|||
|
|
const t1 = baseTherapist({ id: "t1" });
|
|||
|
|
const t2 = baseTherapist({ id: "t2" });
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
therapists: [t1, t2],
|
|||
|
|
preferredTherapistId: "t2",
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
for (const s of slots) {
|
|||
|
|
expect(s.candidateTherapistIds).toEqual(["t2"]);
|
|||
|
|
}
|
|||
|
|
expect(slots.length).toBeGreaterThan(0);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("from-time not on grid is rounded up to the next granular boundary", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
from: D("2026-05-05T14:07:00Z"), // 10:07 EDT — should round up to 10:15
|
|||
|
|
to: D("2026-05-05T16:00:00Z"), // 12:00 EDT
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots[0].startsAt).toEqual(D("2026-05-05T14:15:00Z"));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("out-of-working-hours window yields no slots", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
from: D("2026-05-05T11:00:00Z"), // 7:00 EDT
|
|||
|
|
to: D("2026-05-05T13:00:00Z"), // 9:00 EDT — entirely before open
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots).toEqual([]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("DST spring-forward Sunday: slot grid still produces clean slots after the jump", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
from: D("2026-03-08T14:00:00Z"), // 10:00 EDT (after the jump)
|
|||
|
|
to: D("2026-03-08T16:00:00Z"), // 12:00 EDT
|
|||
|
|
therapists: [
|
|||
|
|
baseTherapist({ workingHours: FULL_WEEK_10_TO_19 }),
|
|||
|
|
],
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
expect(slots.length).toBeGreaterThan(0);
|
|||
|
|
// First slot should be exactly 10:00 EDT.
|
|||
|
|
expect(slots[0].startsAt).toEqual(SPRING_FORWARD_SUN_LOCAL_10AM);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("returns slots sorted by start time, ascending", () => {
|
|||
|
|
const slots = findSlots(
|
|||
|
|
baseOpts({
|
|||
|
|
to: D("2026-05-05T17:00:00Z"), // 13:00 EDT — wider window
|
|||
|
|
}),
|
|||
|
|
);
|
|||
|
|
for (let i = 1; i < slots.length; i++) {
|
|||
|
|
expect(slots[i].startsAt.getTime()).toBeGreaterThan(
|
|||
|
|
slots[i - 1].startsAt.getTime(),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("two therapists, only one available at a given slot — slot still emitted with that one as candidate", () => {
|
|||
|
|
const t1 = baseTherapist({
|
|||
|
|
id: "t1",
|
|||
|
|
bookings: [{
|
|||
|
|
startsAt: TUE_LOCAL_10AM,
|
|||
|
|
endsAt: TUE_LOCAL_11AM,
|
|||
|
|
}],
|
|||
|
|
});
|
|||
|
|
const t2 = baseTherapist({ id: "t2" });
|
|||
|
|
const slots = findSlots(baseOpts({ therapists: [t1, t2] }));
|
|||
|
|
const tenAm = slots.find(
|
|||
|
|
(s) => s.startsAt.toISOString() === "2026-05-05T14:00:00.000Z",
|
|||
|
|
);
|
|||
|
|
expect(tenAm).toBeDefined();
|
|||
|
|
expect(tenAm!.candidateTherapistIds).toEqual(["t2"]);
|
|||
|
|
});
|
|||
|
|
});
|