1
0

add timestamps to session model

This commit is contained in:
Adrian Wannenmacher 2026-02-27 17:53:46 +01:00
parent 80c65212ca
commit 5974e161a6
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
2 changed files with 144 additions and 8 deletions

View File

@ -36,6 +36,30 @@ export default class Session extends EventTarget {
if (this.#id !== null) if (this.#id !== null)
throw new Error("the ID cannot be changed if it has been set"); throw new Error("the ID cannot be changed if it has been set");
this.#id = value; this.#id = value;
}
/** The time when this session was initially created. */
#created = new Date();
/** Get the time when this session was initially created. */
get created() {
return this.#created;
}
/** The time when this session was last updated. */
#updated = new Date();
/** Get the time when this session was last updated. */
get updated() {
return this.#updated;
}
/** Mark this session as changed.
*
* Triggers the `Session.EVENT_CHANGE` event and sets the update time.
*/
#changed() {
this.#updated = new Date();
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE));
} }
@ -57,7 +81,7 @@ export default class Session extends EventTarget {
if (!Number.isInteger(value) || value < 1) if (!Number.isInteger(value) || value < 1)
throw new RangeError("goal must be integer >= 1"); throw new RangeError("goal must be integer >= 1");
this.#goal = value; this.#goal = value;
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.#changed();
} }
/** The name or members of the "we" team. */ /** The name or members of the "we" team. */
@ -71,7 +95,7 @@ export default class Session extends EventTarget {
/** Set the name or members of the "we" team. */ /** Set the name or members of the "we" team. */
set ourTeam(value) { set ourTeam(value) {
this.#ourTeam = value; this.#ourTeam = value;
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.#changed();
} }
/** The name or members of the "they" team. */ /** The name or members of the "they" team. */
@ -85,7 +109,7 @@ export default class Session extends EventTarget {
/** Set the name or members of the "they" team. */ /** Set the name or members of the "they" team. */
set theirTeam(value) { set theirTeam(value) {
this.#theirTeam = value; this.#theirTeam = value;
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.#changed();
} }
/** The finished games. /** The finished games.
@ -117,7 +141,7 @@ export default class Session extends EventTarget {
this.#currentGame = new Game(this.goal); this.#currentGame = new Game(this.goal);
this.#currentGame.addEventListener( this.#currentGame.addEventListener(
Game.EVENT_CHANGE, this.#boundHandleGameChange); Game.EVENT_CHANGE, this.#boundHandleGameChange);
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.#changed();
} }
} }
@ -149,7 +173,7 @@ export default class Session extends EventTarget {
this.#games.push(this.#currentGame); this.#games.push(this.#currentGame);
this.#currentGame = null; this.#currentGame = null;
} }
this.dispatchEvent(new CustomEvent(Session.EVENT_CHANGE)); this.#changed();
} }
/** #handleGameChange, but bound to this instance. */ /** #handleGameChange, but bound to this instance. */
@ -186,6 +210,8 @@ export default class Session extends EventTarget {
games: this.#games.map((g) => g.toStruct()), games: this.#games.map((g) => g.toStruct()),
currentGame: currentGame:
this.#currentGame !== null ? this.#currentGame.toStruct() : null, this.#currentGame !== null ? this.#currentGame.toStruct() : null,
created: this.#created,
updated: this.#updated,
}; };
if (this.#id !== null) if (this.#id !== null)
@ -216,7 +242,7 @@ export default class Session extends EventTarget {
if (typeof value.ourTeam !== "string") if (typeof value.ourTeam !== "string")
throw new TypeError("struct must contain ourTeam as string"); throw new TypeError("struct must contain ourTeam as string");
this.ourTeam = value.ourTeam; this.#ourTeam = value.ourTeam;
if (typeof value.theirTeam !== "string") if (typeof value.theirTeam !== "string")
throw new TypeError("struct must contain theirTeam as string"); throw new TypeError("struct must contain theirTeam as string");
@ -240,5 +266,21 @@ export default class Session extends EventTarget {
if (this.#currentGame.result.winner !== null) if (this.#currentGame.result.winner !== null)
throw new Error("currentGame in struct must not be finished"); throw new Error("currentGame in struct must not be finished");
} }
if ("created" in value) {
if (!(value.created instanceof Date))
throw new TypeError(
"if struct contains creation time, it must be a date");
this.#created = value.created;
} else
this.#created = new Date("2026-02-26T22:00:00");
if ("updated" in value) {
if (!(value.updated instanceof Date))
throw new TypeError(
"if struct contains update time, it must be a date");
this.#updated = value.updated;
} else
this.#updated = new Date("2026-02-26T22:00:00");
} }
} }

View File

@ -7,6 +7,7 @@ import Session from "/models/session.js";
export default function() { export default function() {
QUnit.module("session", function() { QUnit.module("session", function() {
QUnit.test("initial state", function(assert) { QUnit.test("initial state", function(assert) {
let now = new Date();
let session = new Session(); let session = new Session();
assert.strictEqual(session.goal, 11, "initial goal"); assert.strictEqual(session.goal, 11, "initial goal");
assert.strictEqual(session.games.length, 0, "no finished games"); assert.strictEqual(session.games.length, 0, "no finished games");
@ -17,6 +18,9 @@ export default function() {
"initially no points"); "initially no points");
assert.strictEqual(session.ourTeam, "", "our team name"); assert.strictEqual(session.ourTeam, "", "our team name");
assert.strictEqual(session.theirTeam, "", "their team name"); assert.strictEqual(session.theirTeam, "", "their team name");
assert.true(session.created >= now, "was created after start");
assert.true(
session.created >= session.updated, "updated at or after creation");
}); });
QUnit.test("invalid constructor", function(assert) { QUnit.test("invalid constructor", function(assert) {
@ -49,6 +53,7 @@ export default function() {
"small goal"); "small goal");
assert.verifySteps(["event"], "event happened once"); assert.verifySteps(["event"], "event happened once");
assert.true(session.updated >= session.created, "was updated");
}); });
QUnit.test("start game", function(assert) { QUnit.test("start game", function(assert) {
@ -131,6 +136,7 @@ export default function() {
}); });
session.anotherGame(); session.anotherGame();
assert.verifySteps(["event"], "event was triggered"); assert.verifySteps(["event"], "event was triggered");
assert.true(session.updated >= session.created, "was updated");
}); });
QUnit.test("game change triggers change event", function(assert) { QUnit.test("game change triggers change event", function(assert) {
@ -141,6 +147,7 @@ export default function() {
}); });
session.currentGame.currentRound.raise(Team.They); session.currentGame.currentRound.raise(Team.They);
assert.verifySteps(["event"], "event was triggered"); assert.verifySteps(["event"], "event was triggered");
assert.true(session.updated >= session.created, "was updated");
}); });
QUnit.test("setting ID", function(assert){ QUnit.test("setting ID", function(assert){
@ -152,7 +159,7 @@ export default function() {
session.id = 18; session.id = 18;
assert.strictEqual(session.id, 18, "correct id"); assert.strictEqual(session.id, 18, "correct id");
assert.verifySteps(["event"], "event happened"); assert.verifySteps([], "no event happened");
}); });
QUnit.test("setting our team", function(assert){ QUnit.test("setting our team", function(assert){
@ -165,6 +172,7 @@ export default function() {
session.ourTeam = "This is us!"; session.ourTeam = "This is us!";
assert.strictEqual(session.ourTeam, "This is us!", "correct ourTeam"); assert.strictEqual(session.ourTeam, "This is us!", "correct ourTeam");
assert.verifySteps(["event"], "event happened"); assert.verifySteps(["event"], "event happened");
assert.true(session.updated >= session.created, "was updated");
}); });
QUnit.test("setting their team", function(assert){ QUnit.test("setting their team", function(assert){
@ -178,6 +186,7 @@ export default function() {
assert.strictEqual( assert.strictEqual(
session.theirTeam, "This is them!", "correct theirTeam"); session.theirTeam, "This is them!", "correct theirTeam");
assert.verifySteps(["event"], "event happened"); assert.verifySteps(["event"], "event happened");
assert.true(session.updated >= session.created, "was updated");
}); });
QUnit.test("toStruct - new session", function(assert) { QUnit.test("toStruct - new session", function(assert) {
@ -190,6 +199,8 @@ export default function() {
theirTeam: "", theirTeam: "",
games: [], games: [],
currentGame: null, currentGame: null,
created: session.created,
updated: session.updated,
}; };
assert.deepEqual(struct, expected, "successfull structurizing"); assert.deepEqual(struct, expected, "successfull structurizing");
@ -219,7 +230,9 @@ export default function() {
ourTeam: "This is us!", ourTeam: "This is us!",
theirTeam: "This is them!", theirTeam: "This is them!",
games: [ finished.toStruct() ], games: [ finished.toStruct() ],
currentGame: unfinished.toStruct() currentGame: unfinished.toStruct(),
created: session.created,
updated: session.updated,
}; };
assert.deepEqual(struct, expected, "successfull structurizing"); assert.deepEqual(struct, expected, "successfull structurizing");
@ -242,6 +255,8 @@ export default function() {
assert.strictEqual( assert.strictEqual(
copy.currentGame, orig.currentGame, "no current games"); copy.currentGame, orig.currentGame, "no current games");
assert.deepEqual(copy.result, orig.result, "results match"); assert.deepEqual(copy.result, orig.result, "results match");
assert.strictEqual(copy.created, orig.created, "same creation time");
assert.strictEqual(copy.updated, orig.updated, "same update time");
orig.anotherGame(); orig.anotherGame();
orig.id = 15; orig.id = 15;
@ -263,6 +278,8 @@ export default function() {
orig.currentGame.toStruct(), orig.currentGame.toStruct(),
"current game"); "current game");
assert.deepEqual(copy.result, orig.result, "results match"); assert.deepEqual(copy.result, orig.result, "results match");
assert.strictEqual(copy.created, orig.created, "same creation time");
assert.strictEqual(copy.updated, orig.updated, "same update time");
}); });
QUnit.test("fromStruct - invalid", function(assert) { QUnit.test("fromStruct - invalid", function(assert) {
@ -347,6 +364,17 @@ export default function() {
new Error("currentGame in struct must not be finished")); new Error("currentGame in struct must not be finished"));
struct.currentGame = unfinished.toStruct(); struct.currentGame = unfinished.toStruct();
struct.created = "2026-02-26T22:00:00";
doIt(
"string created",
new TypeError("if struct contains creation time, it must be a date"));
struct.created = new Date(struct.created);
struct.updated = "2026-02-26T22:00:00";
doIt(
"string updated",
new TypeError("if struct contains update time, it must be a date"));
struct.updated = new Date(struct.updated);
new Session(struct); new Session(struct);
struct.games = []; struct.games = [];
@ -386,6 +414,8 @@ export default function() {
theirTeam: "", theirTeam: "",
games: [], games: [],
currentGame: null, currentGame: null,
created: new Date("2026-02-26T22:00:00"),
updated: new Date("2026-02-26T22:00:00"),
}; };
assert.deepEqual(session.toStruct(), expected, "reexport matches"); assert.deepEqual(session.toStruct(), expected, "reexport matches");
}); });
@ -412,6 +442,8 @@ export default function() {
theirTeam: "This is them!", theirTeam: "This is them!",
games: [ finished.toStruct() ], games: [ finished.toStruct() ],
currentGame: unfinished.toStruct(), currentGame: unfinished.toStruct(),
created: new Date("2026-02-26T22:00:00"),
updated: new Date("2026-02-26T22:00:00"),
}; };
assert.deepEqual(session.toStruct(), expected, "reexport matches"); assert.deepEqual(session.toStruct(), expected, "reexport matches");
}); });
@ -434,6 +466,8 @@ export default function() {
theirTeam: "", theirTeam: "",
games: [], games: [],
currentGame: null, currentGame: null,
created: new Date("2026-02-26T22:00:00"),
updated: new Date("2026-02-26T22:00:00"),
}; };
assert.deepEqual(session.toStruct(), expected, "reexport matches"); assert.deepEqual(session.toStruct(), expected, "reexport matches");
}); });
@ -462,6 +496,66 @@ export default function() {
theirTeam: "This is them!", theirTeam: "This is them!",
games: [ finished.toStruct() ], games: [ finished.toStruct() ],
currentGame: unfinished.toStruct(), currentGame: unfinished.toStruct(),
created: new Date("2026-02-26T22:00:00"),
updated: new Date("2026-02-26T22:00:00"),
};
assert.deepEqual(session.toStruct(), expected, "reexport matches");
});
QUnit.test("fromStruct - v3 - new session", function(assert) {
let struct = {
id: 23,
goal: 3,
ourTeam: "",
theirTeam: "",
games: [],
currentGame: null,
created: new Date("2026-02-26T20:05:00"),
updated: new Date("2026-02-26T20:05:00"),
};
let session = new Session(struct);
let expected = {
id: 23,
goal: 3,
ourTeam: "",
theirTeam: "",
games: [],
currentGame: null,
created: new Date("2026-02-26T20:05:00"),
updated: new Date("2026-02-26T20:05:00"),
};
assert.deepEqual(session.toStruct(), expected, "reexport matches");
});
QUnit.test("fromStruct - v3 - finished & unfinished", function(assert) {
let finished = new Game(3);
finished.currentRound.raise(Team.We);
finished.currentRound.winner = Team.They;
let unfinished = new Game(3);
unfinished.currentRound.winner = Team.We;
let struct = {
id: 17,
goal: 3,
ourTeam: "This is us!",
theirTeam: "This is them!",
games: [ finished.toStruct() ],
currentGame: unfinished.toStruct(),
created: new Date("2026-02-26T20:05:00"),
updated: new Date("2026-02-26T20:05:00"),
};
let session = new Session(struct);
let expected = {
id: 17,
goal: 3,
ourTeam: "This is us!",
theirTeam: "This is them!",
games: [ finished.toStruct() ],
currentGame: unfinished.toStruct(),
created: new Date("2026-02-26T20:05:00"),
updated: new Date("2026-02-26T20:05:00"),
}; };
assert.deepEqual(session.toStruct(), expected, "reexport matches"); assert.deepEqual(session.toStruct(), expected, "reexport matches");
}); });