285 lines
7.5 KiB
TypeScript
285 lines
7.5 KiB
TypeScript
|
import { ClientSchedule, ClientScheduleEventSlot, ClientScheduleLocation, toIso } from "./client-schedule";
|
||
|
import { describe, expect, test } from "vitest";
|
||
|
import type { ApiSchedule } from "~/shared/types/api";
|
||
|
import type { Living } from "~/shared/types/common";
|
||
|
import { DateTime, FixedOffsetZone } from "~/shared/utils/luxon";
|
||
|
|
||
|
const locale = "en-GB";
|
||
|
const now = DateTime.now().setLocale(locale);
|
||
|
const zone = now.zone;
|
||
|
const nowIso = now.setZone(FixedOffsetZone.utcInstance).toISO();
|
||
|
|
||
|
function fixtureClientSchedule() {
|
||
|
const left = new ClientScheduleLocation(1, now, false, "Left", "");
|
||
|
const right = new ClientScheduleLocation(2, now, false, "Right", "This is the right place");
|
||
|
|
||
|
const events = [
|
||
|
new ClientScheduleEvent(
|
||
|
1, now, false, "Up", false, "", false, "What's Up?", 0,
|
||
|
[new ClientScheduleEventSlot(1, now, now.plus({ hours: 1 }), [left], new Set(), 0)],
|
||
|
),
|
||
|
new ClientScheduleEvent(
|
||
|
2, now, false, "Down", false, "", false, "", 0,
|
||
|
[new ClientScheduleEventSlot(2, now, now.plus({ hours: 2 }), [right], new Set(), 0)],
|
||
|
),
|
||
|
];
|
||
|
|
||
|
const red = new ClientScheduleRole(1, now, false, "Red", "Is a color.");
|
||
|
const blue = new ClientScheduleRole(2, now, false, "Blue", "");
|
||
|
const shifts = [
|
||
|
new ClientScheduleShift(
|
||
|
1, now, false, red, "White", "",
|
||
|
[new ClientScheduleShiftSlot(1, now, now.plus({ hours: 1 }), new Set())],
|
||
|
),
|
||
|
new ClientScheduleShift(
|
||
|
2, now, false, blue, "Black", "Is dark.",
|
||
|
[new ClientScheduleShiftSlot(2, now, now.plus({ hours: 2 }), new Set())],
|
||
|
),
|
||
|
];
|
||
|
|
||
|
return new ClientSchedule(
|
||
|
111,
|
||
|
now,
|
||
|
false,
|
||
|
new Map([
|
||
|
[left.id, left],
|
||
|
[right.id, right],
|
||
|
]),
|
||
|
new Map(events.map(event => [event.id, event])),
|
||
|
new Map([
|
||
|
[red.id, red],
|
||
|
[blue.id, blue],
|
||
|
]),
|
||
|
new Map(shifts.map(shift => [shift.id, shift])),
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function fixtureApiSchedule(): Living<ApiSchedule> {
|
||
|
return {
|
||
|
id: 111,
|
||
|
updatedAt: nowIso,
|
||
|
locations: [
|
||
|
{
|
||
|
id: 1,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Left",
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Right",
|
||
|
description: "This is the right place",
|
||
|
},
|
||
|
],
|
||
|
events: [
|
||
|
{
|
||
|
id: 1,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Up",
|
||
|
description: "What's Up?",
|
||
|
slots: [{
|
||
|
id: 1,
|
||
|
start: nowIso,
|
||
|
end: toIso(now.plus({ hours: 1 })),
|
||
|
locationIds: [1],
|
||
|
}],
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Down",
|
||
|
slots: [{
|
||
|
id: 2,
|
||
|
start: nowIso,
|
||
|
end: toIso(now.plus({ hours: 2 })),
|
||
|
locationIds: [2],
|
||
|
}],
|
||
|
},
|
||
|
],
|
||
|
roles: [
|
||
|
{
|
||
|
id: 1,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Red",
|
||
|
description: "Is a color.",
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Blue",
|
||
|
},
|
||
|
],
|
||
|
shifts: [
|
||
|
{
|
||
|
id: 1,
|
||
|
updatedAt: nowIso,
|
||
|
name: "White",
|
||
|
roleId: 1,
|
||
|
slots: [{
|
||
|
id: 1,
|
||
|
start: nowIso,
|
||
|
end: toIso(now.plus({ hours: 1 })),
|
||
|
}],
|
||
|
},
|
||
|
{
|
||
|
id: 2,
|
||
|
updatedAt: nowIso,
|
||
|
name: "Black",
|
||
|
description: "Is dark.",
|
||
|
roleId: 2,
|
||
|
slots: [{
|
||
|
id: 2,
|
||
|
start: nowIso,
|
||
|
end: toIso(now.plus({ hours: 2 })),
|
||
|
}],
|
||
|
},
|
||
|
],
|
||
|
};
|
||
|
}
|
||
|
|
||
|
describe("class ClientSchedule", () => {
|
||
|
test("load from api", () => {
|
||
|
const schedule = ClientSchedule.fromApi(fixtureApiSchedule(), { zone, locale })
|
||
|
expect(schedule).toStrictEqual(fixtureClientSchedule());
|
||
|
});
|
||
|
|
||
|
test("save to api", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
expect(schedule.toApi(false)).toEqual(fixtureApiSchedule())
|
||
|
});
|
||
|
|
||
|
const updatePatterns = [
|
||
|
"aa a aa",
|
||
|
"ba a aa",
|
||
|
"-a a aa",
|
||
|
"ab a ab",
|
||
|
"bb a aa",
|
||
|
"-b a ab",
|
||
|
"ax a ax",
|
||
|
"bx a ax",
|
||
|
"-- a aa",
|
||
|
"-x a ax",
|
||
|
"aa x --",
|
||
|
"ba x -a",
|
||
|
"-a x -a",
|
||
|
"ab x -b",
|
||
|
"bb x --",
|
||
|
"-b x -b",
|
||
|
"ax x --",
|
||
|
"bx x --",
|
||
|
"-x x --",
|
||
|
"-- x --",
|
||
|
];
|
||
|
for (const pattern of updatePatterns) {
|
||
|
test(`apply diff pattern ${pattern}`, () => {
|
||
|
const fixture: Record<string, ClientScheduleLocation> = {
|
||
|
a: new ClientScheduleLocation(1, now, false, "A", ""),
|
||
|
b: new ClientScheduleLocation(1, now, false, "B", ""),
|
||
|
x: new ClientScheduleLocation(1, now, true, "X", ""),
|
||
|
};
|
||
|
const schedule = new ClientSchedule(111, now, false, new Map(), new Map(), new Map(), new Map());
|
||
|
if (fixture[pattern[0]])
|
||
|
schedule.originalLocations.set(1, fixture[pattern[0]]);
|
||
|
if (fixture[pattern[1]])
|
||
|
schedule.locations.set(1, fixture[pattern[1]]);
|
||
|
const update = fixture[pattern[3]];
|
||
|
const expectedOriginalLocation = pattern[5] === "x" ? undefined : fixture[pattern[5]];
|
||
|
const expectedLocation = fixture[pattern[6]];
|
||
|
|
||
|
schedule.applyUpdate({
|
||
|
id: 111,
|
||
|
updatedAt: nowIso,
|
||
|
locations: [update.toApi()],
|
||
|
}, { zone, locale });
|
||
|
expect(schedule.originalLocations.get(1)).toEqual(expectedOriginalLocation);
|
||
|
expect(schedule.locations.get(1)).toEqual(expectedLocation);
|
||
|
if (pattern.slice(5) === "aa")
|
||
|
expect(schedule.originalLocations.get(1)).toBe(schedule.locations.get(1));
|
||
|
});
|
||
|
}
|
||
|
|
||
|
test("create location", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const location = new ClientScheduleLocation(3, now, false, "New location", "");
|
||
|
schedule.setLocation(location);
|
||
|
expect(schedule.originalLocations.get(3)).toBe(undefined);
|
||
|
expect(schedule.locations.get(3)).toBe(location);
|
||
|
});
|
||
|
|
||
|
test("update location", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const original = schedule.locations.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.name = "Modified Location";
|
||
|
schedule.setLocation(copy);
|
||
|
expect(schedule.originalLocations.get(1)).toBe(original);
|
||
|
expect(schedule.locations.get(1)).toBe(copy);
|
||
|
expect(schedule.events.get(1)!.slots[0].locations[0]).toBe(copy);
|
||
|
});
|
||
|
|
||
|
test("delete location in use throws", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const original = schedule.locations.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.deleted = true;
|
||
|
expect(
|
||
|
() => { schedule.setLocation(copy); }
|
||
|
).toThrow(new Error('Cannot delete location, event "Up" depends on it'));
|
||
|
});
|
||
|
|
||
|
test("delete location", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const event = schedule.events.get(1)!.clone();
|
||
|
event.slots = [];
|
||
|
schedule.setEvent(event);
|
||
|
const original = schedule.locations.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.deleted = true;
|
||
|
schedule.setLocation(copy);
|
||
|
expect(schedule.originalLocations.get(1)).toBe(original);
|
||
|
expect(schedule.locations.get(1)).toBe(copy);
|
||
|
});
|
||
|
|
||
|
test("create role", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const role = new ClientScheduleRole(3, now, false, "New role", "");
|
||
|
schedule.setRole(role);
|
||
|
expect(schedule.originalRoles.get(3)).toBe(undefined);
|
||
|
expect(schedule.roles.get(3)).toBe(role);
|
||
|
});
|
||
|
|
||
|
test("update role", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const original = schedule.roles.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.name = "Modified Role";
|
||
|
schedule.setRole(copy);
|
||
|
expect(schedule.originalRoles.get(1)).toBe(original);
|
||
|
expect(schedule.roles.get(1)).toBe(copy);
|
||
|
expect(schedule.shifts.get(1)!.role).toBe(copy);
|
||
|
});
|
||
|
|
||
|
test("delete role in use throws", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const original = schedule.roles.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.deleted = true;
|
||
|
expect(
|
||
|
() => { schedule.setRole(copy); }
|
||
|
).toThrow(new Error('Cannot delete role, shift "White" depends on it'));
|
||
|
});
|
||
|
|
||
|
test("delete role", () => {
|
||
|
const schedule = fixtureClientSchedule();
|
||
|
const shift = schedule.shifts.get(1)!.clone();
|
||
|
shift.role = schedule.roles.get(2)!;
|
||
|
schedule.setShift(shift);
|
||
|
const original = schedule.roles.get(1)!;
|
||
|
const copy = original.clone();
|
||
|
copy.deleted = true;
|
||
|
schedule.setRole(copy);
|
||
|
expect(schedule.originalRoles.get(1)).toBe(original);
|
||
|
expect(schedule.roles.get(1)).toBe(copy);
|
||
|
});
|
||
|
});
|