mirror of
https://gitlab.dit.htwk-leipzig.de/htwk-software/htwkalender-pwa.git
synced 2025-07-16 17:48:51 +02:00
652 lines
20 KiB
TypeScript
652 lines
20 KiB
TypeScript
//Calendar implementation for the HTWK Leipzig timetable. Evaluation and display of the individual dates in iCal format.
|
|
//Copyright (C) 2024 HTWKalender support@htwkalender.de
|
|
|
|
//This program is free software: you can redistribute it and/or modify
|
|
//it under the terms of the GNU Affero General Public License as published by
|
|
//the Free Software Foundation, either version 3 of the License, or
|
|
//(at your option) any later version.
|
|
|
|
//This program is distributed in the hope that it will be useful,
|
|
//but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
//GNU Affero General Public License for more details.
|
|
|
|
//You should have received a copy of the GNU Affero General Public License
|
|
//along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import { beforeEach, describe, expect, test } from "vitest";
|
|
import { RoomOccupancyList } from "./roomOccupancyList";
|
|
import { Binary } from "bson";
|
|
import { add, addHours, addMinutes, interval, subHours } from "date-fns";
|
|
import { fromZonedTime, toZonedTime } from "date-fns-tz";
|
|
|
|
const testListStart = new Date("2022-01-01T00:00:00Z");
|
|
var testList : RoomOccupancyList; //= RoomOccupancyList.fromJSON({});
|
|
var alternating : Uint8Array = new Uint8Array(Array(4).fill(0xF0));
|
|
var booked : Uint8Array = new Uint8Array(Array(4).fill(0xFF));
|
|
var empty : Uint8Array = new Uint8Array(Array(4).fill(0x00));
|
|
var counting : Uint8Array = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
|
|
|
const localTimezone = "Europe/Berlin";
|
|
|
|
describe("RoomOccupancyList", () => {
|
|
beforeEach(() => {
|
|
alternating = new Uint8Array(Array(4).fill(0xF0));
|
|
booked = new Uint8Array(Array(4).fill(0xFF));
|
|
empty = new Uint8Array(Array(4).fill(0x00));
|
|
counting = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
|
|
testList = RoomOccupancyList.fromJSON({
|
|
start: testListStart,
|
|
granularity: 60,
|
|
blocks: 32,
|
|
rooms: [
|
|
{
|
|
name: "BOOKED",
|
|
occupancy: new Binary(booked, 0),
|
|
},
|
|
{
|
|
name: "EMPTY",
|
|
occupancy: new Binary(empty, 0),
|
|
},
|
|
{
|
|
name: "ALTERNATING",
|
|
occupancy: new Binary(alternating, 0),
|
|
},
|
|
{
|
|
name: "COUNTING",
|
|
occupancy: new Binary(counting, 0),
|
|
}
|
|
]
|
|
});
|
|
});
|
|
|
|
describe("getRooms", () => {
|
|
test("get rooms", () => {
|
|
// act
|
|
const rooms = testList["getRooms"]();
|
|
|
|
// assert
|
|
expect(rooms).toEqual([
|
|
"BOOKED",
|
|
"EMPTY",
|
|
"ALTERNATING",
|
|
"COUNTING"
|
|
]);
|
|
});
|
|
|
|
test("get empty rooms", () => {
|
|
// arrange
|
|
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
|
|
start: testListStart,
|
|
granularity: 60,
|
|
blocks: 32,
|
|
rooms: []
|
|
});
|
|
|
|
// act
|
|
const rooms = emptyRoomOccupancy["getRooms"]();
|
|
|
|
// assert
|
|
expect(rooms).toEqual([]);
|
|
});
|
|
})
|
|
|
|
describe("decodeOccupancy", () => {
|
|
test("generate stubs for missing room", () => {
|
|
// arrange
|
|
|
|
// act
|
|
const decoded = testList["decodeOccupancy"](
|
|
"MISSING",
|
|
testListStart,
|
|
addHours(testListStart, 8),
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([
|
|
{
|
|
start: "2022-01-01T06:00:00.000Z",
|
|
end: "2022-01-01T08:00:00.000Z",
|
|
rooms: "MISSING",
|
|
free: true,
|
|
stub: true
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("generate stubs out of range", () => {
|
|
// arrange
|
|
|
|
// act
|
|
const decoded = testList["decodeOccupancy"](
|
|
"BOOKED",
|
|
subHours(testListStart, 10),
|
|
testListStart,
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([
|
|
{
|
|
start: "2021-12-31T14:00:00.000Z",
|
|
end: "2021-12-31T19:00:00.000Z",
|
|
rooms: "BOOKED",
|
|
free: true,
|
|
stub: true
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("decode occupancy in range", () => {
|
|
// arrange
|
|
// act
|
|
const decoded = testList["decodeOccupancy"](
|
|
"BOOKED",
|
|
subHours(testListStart, 2),
|
|
addHours(testListStart, 8),
|
|
);
|
|
|
|
expect(decoded).toEqual([
|
|
{
|
|
start: "2022-01-01T00:00:00.000Z",
|
|
end: "2022-01-01T08:00:00.000Z",
|
|
rooms: "BOOKED",
|
|
free: false,
|
|
stub: false
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("sliceOccupancy", () => {
|
|
test.each([
|
|
booked,
|
|
empty,
|
|
alternating
|
|
])("getCompleteOccupancy of %j", (occupancy) => {
|
|
// arrange
|
|
const startTime = new Date(testList.start);
|
|
const endTime = new Date(addHours(startTime, 32));
|
|
const sliceInterval = interval(startTime, endTime);
|
|
|
|
// act
|
|
const sliced = testList["sliceOccupancy"](
|
|
sliceInterval,
|
|
occupancy
|
|
)
|
|
|
|
// assert
|
|
expect(sliced).toEqual({
|
|
decodeSliceStart: startTime,
|
|
decodeSlice: new Uint8Array(occupancy),
|
|
});
|
|
});
|
|
|
|
test("throws start out of bounds", () => {
|
|
// arrange
|
|
const startTime = new Date(subHours(testList.start,1));
|
|
const endTime = new Date(addHours(startTime, 32));
|
|
const sliceInterval = interval(startTime, endTime);
|
|
|
|
// act and assert
|
|
expect(() => {
|
|
testList["sliceOccupancy"](
|
|
sliceInterval,
|
|
alternating
|
|
)
|
|
}).toThrowError();
|
|
});
|
|
|
|
test("throws end out of bounds", () => {
|
|
// arrange
|
|
const startTime = new Date(testList.start);
|
|
const endTime = new Date(addHours(startTime, 33));
|
|
const sliceInterval = interval(startTime, endTime);
|
|
|
|
// act and assert
|
|
expect(() => {
|
|
testList["sliceOccupancy"](
|
|
sliceInterval,
|
|
alternating
|
|
)
|
|
}).toThrowError();
|
|
});
|
|
|
|
test("range at byte boundaries", () => {
|
|
// arrange
|
|
const startTime = new Date(addHours(testList.start, 8));
|
|
const endTime = new Date(addHours(testList.start, 24));
|
|
const sliceInterval = interval(startTime, endTime);
|
|
|
|
// act
|
|
const sliced = testList["sliceOccupancy"](
|
|
sliceInterval,
|
|
counting
|
|
)
|
|
|
|
// assert
|
|
expect(sliced).toEqual({
|
|
decodeSliceStart: startTime,
|
|
decodeSlice: new Uint8Array([0x01, 0x02]),
|
|
});
|
|
});
|
|
|
|
test("range within byte boundaries", () => {
|
|
// arrange
|
|
const startTime = new Date(addMinutes(testListStart, 500));
|
|
const endTime = new Date(addHours(testListStart, 15));
|
|
const sliceInterval = interval(startTime, endTime);
|
|
|
|
// act
|
|
const sliced = testList["sliceOccupancy"](
|
|
sliceInterval,
|
|
counting
|
|
)
|
|
|
|
// assert
|
|
expect(sliced).toEqual({
|
|
decodeSliceStart: addHours(testListStart,8),
|
|
decodeSlice: new Uint8Array([0x01]),
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("getOccupancyInterval", () => {
|
|
test("get empty interval", () => {
|
|
// arrange
|
|
const emptyRoomOccupancy = RoomOccupancyList.fromJSON({
|
|
start: testListStart,
|
|
granularity: 60,
|
|
blocks: 0,
|
|
rooms: []
|
|
});
|
|
|
|
// act
|
|
const emptyInterval = emptyRoomOccupancy["getOccupancyInterval"]();
|
|
|
|
// assert
|
|
expect(emptyInterval).toEqual(interval(testListStart, testListStart));
|
|
});
|
|
|
|
test("get interval of valid room occupancy list", () => {
|
|
// act
|
|
const testInterval = testList["getOccupancyInterval"]();
|
|
|
|
// assert
|
|
expect(testInterval).toEqual(interval(testListStart, addHours(testListStart, 32)));
|
|
});
|
|
});
|
|
|
|
describe("decodeOccupancyData", () => {
|
|
test("decode occupancy without length", () => {
|
|
// arrange
|
|
// act
|
|
const decoded = RoomOccupancyList["decodeOccupancyData"](
|
|
new Uint8Array([]),
|
|
testListStart,
|
|
15,
|
|
"Raum"
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([]);
|
|
});
|
|
|
|
test("decode blocked occupancy", () => {
|
|
// arrange
|
|
// act
|
|
const decoded = RoomOccupancyList["decodeOccupancyData"](
|
|
booked,
|
|
testListStart,
|
|
15,
|
|
"BOOKED"
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([
|
|
{
|
|
start: testListStart.toISOString(),
|
|
end: addHours(testListStart, 8).toISOString(),
|
|
rooms: "BOOKED",
|
|
free: false,
|
|
stub: false
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("decode empty occupancy", () => {
|
|
// arrange
|
|
// act
|
|
const decoded = RoomOccupancyList["decodeOccupancyData"](
|
|
empty,
|
|
testListStart,
|
|
15,
|
|
"BOOKED"
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([]);
|
|
});
|
|
|
|
test("decode alternating occupancy", () => {
|
|
// arrange
|
|
// act
|
|
const decoded = RoomOccupancyList["decodeOccupancyData"](
|
|
alternating,
|
|
new Date("2024-01-01T00:00:00Z"),
|
|
15,
|
|
"ALTERNATING"
|
|
);
|
|
|
|
// assert
|
|
expect(decoded).toEqual([
|
|
{
|
|
start: "2024-01-01T00:00:00.000Z",
|
|
end: "2024-01-01T01:00:00.000Z",
|
|
rooms: "ALTERNATING",
|
|
free: false,
|
|
stub: false
|
|
},
|
|
{
|
|
start: "2024-01-01T02:00:00.000Z",
|
|
end: "2024-01-01T03:00:00.000Z",
|
|
rooms: "ALTERNATING",
|
|
free: false,
|
|
stub: false
|
|
},
|
|
{
|
|
start: "2024-01-01T04:00:00.000Z",
|
|
end: "2024-01-01T05:00:00.000Z",
|
|
rooms: "ALTERNATING",
|
|
free: false,
|
|
stub: false
|
|
},
|
|
{
|
|
start: "2024-01-01T06:00:00.000Z",
|
|
end: "2024-01-01T07:00:00.000Z",
|
|
rooms: "ALTERNATING",
|
|
free: false,
|
|
stub: false
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("generateStubEvents", () => {
|
|
test("no events if negative range", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-01T00:00:00Z");
|
|
const endTime = new Date("2021-01-01T00:00:00Z");
|
|
|
|
// act
|
|
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
|
"ROOM",
|
|
startTime,
|
|
endTime
|
|
);
|
|
|
|
// assert
|
|
expect(stubEvents).toEqual([]);
|
|
});
|
|
|
|
test("no events if start and end the same", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-01T00:00:00Z");
|
|
const endTime = new Date("2022-01-01T00:00:00Z");
|
|
|
|
// act
|
|
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
|
"ROOM",
|
|
startTime,
|
|
endTime
|
|
);
|
|
|
|
// assert
|
|
expect(stubEvents).toEqual([]);
|
|
});
|
|
|
|
test("generate week", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-01T12:00:00Z");
|
|
const endTime = new Date("2022-01-07T12:30:00Z");
|
|
|
|
// act
|
|
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
|
"ROOM",
|
|
startTime,
|
|
endTime
|
|
);
|
|
|
|
// assert
|
|
expect(stubEvents).toEqual([
|
|
{
|
|
start: "2022-01-01T12:00:00.000Z",
|
|
end: "2022-01-01T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-02T06:00:00.000Z",
|
|
end: "2022-01-02T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-03T06:00:00.000Z",
|
|
end: "2022-01-03T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-04T06:00:00.000Z",
|
|
end: "2022-01-04T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-05T06:00:00.000Z",
|
|
end: "2022-01-05T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-06T06:00:00.000Z",
|
|
end: "2022-01-06T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
},
|
|
{
|
|
start: "2022-01-07T06:00:00.000Z",
|
|
end: "2022-01-07T12:30:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
}
|
|
]);
|
|
});
|
|
|
|
test("generate day", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-01T16:00:00Z");
|
|
const endTime = new Date("2022-01-01T19:00:00Z");
|
|
|
|
// act
|
|
const stubEvents = RoomOccupancyList["generateStubEvents"](
|
|
"ROOM",
|
|
startTime,
|
|
endTime
|
|
);
|
|
|
|
// assert
|
|
expect(stubEvents).toEqual([
|
|
{
|
|
start: "2022-01-01T16:00:00.000Z",
|
|
end: "2022-01-01T19:00:00.000Z",
|
|
rooms: "ROOM",
|
|
free: true,
|
|
stub: true,
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("shiftTimeForwardInsideWorkday", () => {
|
|
test("shift time to next day", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-01T20:00:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T23:00:00Z"));
|
|
});
|
|
|
|
test("don't shift time on the same day", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T01:00:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T01:00:00Z"));
|
|
});
|
|
|
|
test("don't shift if already inside workday", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeForwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
|
|
});
|
|
});
|
|
|
|
describe("shiftTimeBackwardInsideWorkday", () => {
|
|
test("shift time to last day", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T05:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-01T22:59:59.999Z"));
|
|
});
|
|
|
|
test("don't shift time on the same day", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T22:00:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T22:00:00Z"));
|
|
});
|
|
|
|
test("don't shift if already inside workday", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["shiftTimeBackwardInsideWorkday"](startTime);
|
|
|
|
// assert
|
|
expect(toZonedTime(shiftedTime, localTimezone)).toEqual(new Date("2022-01-02T12:30:00Z"));
|
|
});
|
|
});
|
|
|
|
describe("setTimeOfDay", () => {
|
|
test("set time to 00:00:00", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {});
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
|
|
});
|
|
|
|
test("set time to 23:59:59", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-06-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 23, minutes: 59, seconds: 59});
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-06-02T21:59:59Z"));
|
|
});
|
|
|
|
test("set same time", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["setTimeOfDay"](startTime, {hours: 13, minutes: 30, seconds: 0});
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-01-02T12:30:00Z"));
|
|
});
|
|
});
|
|
|
|
describe("startOfDay", () => {
|
|
test("in the winter", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
|
|
});
|
|
|
|
test("in the summer", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-06-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
|
|
});
|
|
});
|
|
|
|
describe("endOfDay", () => {
|
|
test("in the winter", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-01-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-01-01T23:00:00Z"));
|
|
});
|
|
|
|
test("in the summer", () => {
|
|
// arrange
|
|
const startTime = new Date("2022-06-02T12:30:00Z");
|
|
|
|
// act
|
|
const shiftedTime = RoomOccupancyList["startOfDay"](startTime);
|
|
|
|
// assert
|
|
expect(shiftedTime).toEqual(new Date("2022-06-01:22:00Z"));
|
|
});
|
|
});
|
|
|
|
});
|