Write the logic of keeping track of location modifications and applying updates from the server into the ClientSchedule class. This should serve as the foundation for replacing the prototype in-component update logic which have turned into an unmaintainable spagetti.
284 lines
7.5 KiB
TypeScript
284 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);
|
|
});
|
|
});
|