1
0

test exception types and messages

As I noted in commit 85b9c2459c I noticed
that the testing of the invalidity of invalid struct "deserialization"
was not sound. That was because if one error was not actually thrown,
the test would still be satisfied by the following error. However, if
only that specific thing was wrong, no error would be thrown and an
invalid struct would be accepted.

Therefore I now changed those tests to also check the exception type and
its message. This means that if an exception is missing, it is
immediately picked up.

I also found out that one exception was actually wrong. I had mistyped
`TypeError` as `TypError`. Funnily enough that still causes an exception
to be thrown at that location, but a `ReferenceError` instead of the
`TypeError`. Such bugs should now be more easily noticable.
This commit is contained in:
Adrian Wannenmacher 2026-02-15 04:27:08 +01:00
parent 729d70c80f
commit 2af6cb4f6a
Signed by: tfld
GPG Key ID: 19D986ECB1E492D5
9 changed files with 164 additions and 77 deletions

View File

@ -61,17 +61,17 @@ export default function() {
}); });
QUnit.test("cannot call constructor", function(assert) { QUnit.test("cannot call constructor", function(assert) {
assert.throws(function() { assert.throws(
new WbDb(); function() { new WbDb(); },
}); new TypeError("WbDb may not be constructed externally"));
assert.throws(function() { assert.throws(
new WbDb(true); function() { new WbDb(true); },
}); new TypeError("WbDb may not be constructed externally"));
assert.throws(function() { assert.throws(
new WbDb(true, 1); function() { new WbDb(true, 1); },
}); new TypeError("WbDb may not be constructed externally"));
}); });
QUnit.test("open db", async function(assert) { QUnit.test("open db", async function(assert) {

View File

@ -179,12 +179,12 @@ export default class Game extends EventTarget {
throw new TypeError("struct must contain currentRound as object"); throw new TypeError("struct must contain currentRound as object");
if (this.result.winner === null) { if (this.result.winner === null) {
if (value.currentRound === null) if (value.currentRound === null)
throw new TypeError( throw new Error(
"struct of ongoing game must contain current round"); "struct of ongoing game must contain current round");
else else
this.#currentRound = new Round(value.currentRound); this.#currentRound = new Round(value.currentRound);
} else if (value.currentRound !== null) } else if (value.currentRound !== null)
throw new TypeError( throw new Error(
"struct of finished game must not contain current round"); "struct of finished game must not contain current round");
} }
} }

View File

@ -24,7 +24,10 @@ export default function() {
}); });
QUnit.test("low goal", function(assert) { QUnit.test("low goal", function(assert) {
assert.throws(function() { new Game(0); }, "goal must be 1 or higher"); assert.throws(
function() { new Game(0); },
new RangeError("goal must be at least 1"),
"goal must be 1 or higher");
}); });
QUnit.test("higher goal", function(assert) { QUnit.test("higher goal", function(assert) {
@ -265,36 +268,52 @@ export default function() {
QUnit.test("fromStruct - invalid", function(assert) { QUnit.test("fromStruct - invalid", function(assert) {
let struct = {}; let struct = {};
function doIt(message) { function doIt(message, error) {
assert.throws(function() { new Game(struct); }, message); assert.throws(function() { new Game(struct); }, error, message);
} }
doIt("no goal"); doIt("no goal", new TypeError("struct must contain goal as number"));
struct.goal = "3"; struct.goal = "3";
doIt("string goal"); doIt("string goal", new TypeError("struct must contain goal as number"));
struct.goal = Math.PI; struct.goal = Math.PI;
doIt("non-int goal"); doIt(
"non-int goal",
new RangeError("struct must contain goal >= 1 as integer"));
struct.goal = 0; struct.goal = 0;
doIt("small goal"); doIt(
"small goal",
new RangeError("struct must contain goal >= 1 as integer"));
struct.goal = 3; struct.goal = 3;
doIt("no rounds"); doIt("no rounds", new TypeError("struct must contain rounds"));
struct.rounds = "nope"; struct.rounds = "nope";
doIt("rounds not array"); doIt(
"rounds not array",
new TypeError("struct must contain rounds as array"));
struct.rounds = ["nope", "again"]; struct.rounds = ["nope", "again"];
doIt("string array rounds"); doIt(
"string array rounds",
new TypeError("unknown form of RoundResult constructor"));
struct.rounds = []; struct.rounds = [];
doIt("no currentRound"); doIt(
"no currentRound",
new TypeError("struct must contain currentRound as object"));
struct.currentRound = "nope"; struct.currentRound = "nope";
doIt("string currentround"); doIt(
"string currentRound",
new TypeError("struct must contain currentRound as object"));
struct.currentRound = null; struct.currentRound = null;
doIt("missing currentRound"); doIt(
"missing currentRound",
new Error("struct of ongoing game must contain current round"));
struct.currentRound = new Round().toStruct(); struct.currentRound = new Round().toStruct();
new Game(struct); new Game(struct);
struct.rounds = [ new RoundResult(3, Team.They).toStruct() ]; struct.rounds = [ new RoundResult(3, Team.They).toStruct() ];
doIt("unneeded currentRound"); doIt(
"unneeded currentRound",
new Error("struct of finished game must not contain current round"));
struct.currentRound = null; struct.currentRound = null;
new Game(struct); new Game(struct);
}); });

View File

@ -56,7 +56,7 @@ export class Round extends EventTarget {
} else if (typeof value === "object" && theyLimit === undefined) { } else if (typeof value === "object" && theyLimit === undefined) {
this.#fromStruct(value); this.#fromStruct(value);
} else { } else {
throw new TypeError("unknown form for Round constructor"); throw new TypeError("unknown form of Round constructor");
} }
} }

View File

@ -26,9 +26,10 @@ export default function() {
QUnit.test("multiple victories", function(assert) { QUnit.test("multiple victories", function(assert) {
let round = new Round(); let round = new Round();
round.winner = Team.They; round.winner = Team.They;
assert.throws(function() { assert.throws(
round.winner = Team.We; function() { round.winner = Team.We; },
}, "victory cannot be stolen"); new Error("decided round cannot be won again"),
"victory cannot be stolen");
}); });
QUnit.test("single raise", function(assert) { QUnit.test("single raise", function(assert) {
@ -155,45 +156,71 @@ export default function() {
QUnit.test("fromStruct - invalid", function(assert) { QUnit.test("fromStruct - invalid", function(assert) {
let struct = {}; let struct = {};
function doIt(message) { function doIt(message, error) {
assert.throws(function() { new Round(struct); }, message); assert.throws(function() { new Round(struct); }, error, message);
} }
doIt("no points"); doIt("no points", new TypeError("struct must contain points as number"));
struct.points = "2"; struct.points = "2";
doIt("string points"); doIt(
"string points",
new TypeError("struct must contain points as number"));
struct.points = 1.5; struct.points = 1.5;
doIt("non-int points"); doIt(
"non-int points",
new RangeError("struct must contain points >= 2 as integer"));
struct.points = 1; struct.points = 1;
doIt("small points"); doIt(
"small points",
new RangeError("struct must contain points >= 2 as integer"));
struct.points = 2; struct.points = 2;
doIt("no raisedLast"); doIt("no raisedLast", new TypeError("struct must contain raisedLast"));
struct.raisedLast = "we"; struct.raisedLast = "we";
doIt("string raisedLast"); doIt(
"string raisedLast",
new TypeError("struct must contain raisedLast as Team or null"));
struct.raisedLast = -1; struct.raisedLast = -1;
doIt("raisedLast not actual team"); doIt(
"raisedLast not actual team",
new TypeError("struct must contain raisedLast as Team or null"));
struct.raisedLast = null; struct.raisedLast = null;
doIt("no winner"); doIt("no winner", new TypeError("struct must contain winner"));
struct.winner = "they"; struct.winner = "they";
doIt("string winner"); doIt(
"string winner",
new TypeError("struct must contain winner as Team or null"));
struct.winner = -1; struct.winner = -1;
doIt("winner not actual team"); doIt(
"winner not actual team",
new TypeError("struct must contain winner as Team or null"));
struct.winner = null; struct.winner = null;
doIt("no ourLimit"); doIt(
"no ourLimit",
new TypeError("struct must contain ourLimit as number"));
struct.ourLimit = "11"; struct.ourLimit = "11";
doIt("string ourLimit"); doIt(
"string ourLimit",
new TypeError("struct must contain ourLimit as number"));
struct.ourLimit = 1; struct.ourLimit = 1;
doIt("small ourLimit"); doIt(
"small ourLimit",
new RangeError("struct must contain ourLimit >= 2 as integer"));
struct.ourLimit = 11; struct.ourLimit = 11;
doIt("no theirLimit"); doIt(
"no theirLimit",
new TypeError("struct must contain theirLimit as number"));
struct.theirLimit = "11"; struct.theirLimit = "11";
doIt("string theirLimit"); doIt(
"string theirLimit",
new TypeError("struct must contain theirLimit as number"));
struct.theirLimit = 1; struct.theirLimit = 1;
doIt("small theirLimit"); doIt(
"small theirLimit",
new RangeError("struct must contain theirLimit >= 2 as integer"));
struct.theirLimit = 11; struct.theirLimit = 11;
new Round(struct); new Round(struct);

View File

@ -21,7 +21,7 @@ export default class RoundResult {
} else if (typeof value === "object" && winner === undefined) { } else if (typeof value === "object" && winner === undefined) {
this.#fromStruct(value); this.#fromStruct(value);
} else { } else {
throw new TypeError("unknown form for RoundResult constructor"); throw new TypeError("unknown form of RoundResult constructor");
} }
} }

View File

@ -32,24 +32,33 @@ export default function() {
QUnit.test("fromStruct - invalid", function(assert) { QUnit.test("fromStruct - invalid", function(assert) {
let struct = {}; let struct = {};
function doIt(message) { function doIt(message, error) {
assert.throws(function() { new Round(struct); }, message); assert.throws(function() { new RoundResult(struct); }, error, message);
} }
doIt("no points"); doIt("no points", new TypeError("struct must contain points as number"));
struct.points = "4"; struct.points = "4";
doIt("string points"); doIt(
"string points",
new TypeError("struct must contain points as number"));
struct.points = 4.1; struct.points = 4.1;
doIt("non-int points"); doIt(
"non-int points",
new RangeError("struct must contain points >= 2 as integer"));
struct.points = 1; struct.points = 1;
doIt("small points"); doIt(
"small points",
new RangeError("struct must contain points >= 2 as integer"));
struct.points = 4; struct.points = 4;
doIt("no winner"); doIt("no winner", new TypeError("struct must contain winner"));
struct.winner = "they"; struct.winner = "they";
doIt("string winner"); doIt(
"string winner", new TypeError("struct must contain winner as Team"));
struct.winner = -1; struct.winner = -1;
doIt("non-team winner"); doIt(
"non-team winner",
new TypeError("struct must contain winner as Team"));
struct.winner = Team.They; struct.winner = Team.They;
new RoundResult(struct); new RoundResult(struct);

View File

@ -145,7 +145,7 @@ export default class Session {
} }
if (typeof value.goal !== "number") if (typeof value.goal !== "number")
throw new TypError("struct must contain goal as number"); throw new TypeError("struct must contain goal as number");
if (!Number.isInteger(value.goal) || value.goal < 1) if (!Number.isInteger(value.goal) || value.goal < 1)
throw new RangeError("struct must contain goal >= 1 as integer"); throw new RangeError("struct must contain goal >= 1 as integer");
this.#goal = value.goal; this.#goal = value.goal;
@ -172,7 +172,7 @@ export default class Session {
if (value.currentGame !== null) { if (value.currentGame !== null) {
this.#currentGame = new Game(value.currentGame); this.#currentGame = new Game(value.currentGame);
if (this.#currentGame.result.winner !== null) if (this.#currentGame.result.winner !== null)
throw new Error("currentGame in struct mustnot be finished"); throw new Error("currentGame in struct must not be finished");
} }
} }
} }

View File

@ -24,7 +24,18 @@ export default function() {
assert.strictEqual(session.goal, 11, "initial goal"); assert.strictEqual(session.goal, 11, "initial goal");
session.goal = 3; session.goal = 3;
assert.strictEqual(session.goal, 3, "changed goal"); assert.strictEqual(session.goal, 3, "changed goal");
assert.throws(function() { session.goal = 0; }, "invalid goal"); assert.throws(
function() { session.goal = "0"; },
new TypeError("goal must be a number"),
"string goal");
assert.throws(
function() { session.goal = 0.5; },
new RangeError("goal must be integer >= 1"),
"float goal");
assert.throws(
function() { session.goal = 0; },
new RangeError("goal must be integer >= 1"),
"small goal");
}); });
QUnit.test("start game", function(assert) { QUnit.test("start game", function(assert) {
@ -187,8 +198,8 @@ export default function() {
QUnit.test("fromStruct - invalid", function(assert) { QUnit.test("fromStruct - invalid", function(assert) {
let struct = {}; let struct = {};
function doIt(message) { function doIt(message, error) {
assert.throws(function() { new Session(struct); }, message); assert.throws(function() { new Session(struct); }, error, message);
} }
let unfinished = new Game(3); let unfinished = new Game(3);
@ -197,39 +208,60 @@ export default function() {
finished.currentRound.raise(Team.We); finished.currentRound.raise(Team.We);
finished.currentRound.winner = Team.They; finished.currentRound.winner = Team.They;
doIt("no goal"); doIt("no goal", new TypeError("struct must contain goal as number"));
struct.goal = "3"; struct.goal = "3";
doIt("string goal"); doIt("string goal", new TypeError("struct must contain goal as number"));
struct.goal = Math.PI; struct.goal = Math.PI;
doIt("non-int goal"); doIt(
"non-int goal",
new RangeError("struct must contain goal >= 1 as integer"));
struct.goal = 0; struct.goal = 0;
doIt("small goal"); doIt(
"small goal",
new RangeError("struct must contain goal >= 1 as integer"));
struct.goal = 3; struct.goal = 3;
doIt("no ourTeam"); doIt(
"no ourTeam", new TypeError("struct must contain ourTeam as string"));
struct.ourTeam = 5; struct.ourTeam = 5;
doIt("number ourTeam"); doIt(
"number ourTeam",
new TypeError("struct must contain ourTeam as string"));
struct.ourTeam = ""; struct.ourTeam = "";
doIt("no theirTeam"); doIt(
"no theirTeam",
new TypeError("struct must contain theirTeam as string"));
struct.theirTeam = 6; struct.theirTeam = 6;
doIt("number theirTeam"); doIt(
"number theirTeam",
new TypeError("struct must contain theirTeam as string"));
struct.theirTeam = ""; struct.theirTeam = "";
doIt("no games"); doIt("no games", new TypeError("struct must contain games"));
struct.games = "nope"; struct.games = "nope";
doIt("string games"); doIt(
"string games", new TypeError("struct must contain games as array"));
struct.games = ["nope", "again"]; struct.games = ["nope", "again"];
doIt("string array games"); doIt(
"string array games",
new TypeError("unknown form of Game constructor"));
struct.games = [unfinished.toStruct()]; struct.games = [unfinished.toStruct()];
doIt("unfinished game in games"); doIt(
"unfinished game in games", new Error("past games must be finished"));
struct.games = [finished.toStruct()]; struct.games = [finished.toStruct()];
doIt("no currentGame"); doIt(
"no currentGame",
new TypeError("struct must contain currentGame as object"));
struct.currentGame = "nope"; struct.currentGame = "nope";
doIt("string currentGame"); doIt(
"string currentGame",
new TypeError("struct must contain currentGame as object"));
struct.currentGame = finished.toStruct(); struct.currentGame = finished.toStruct();
doIt("finished currentGame"); doIt(
"finished currentGame",
new Error("currentGame in struct must not be finished"));
struct.currentGame = unfinished.toStruct(); struct.currentGame = unfinished.toStruct();
new Session(struct); new Session(struct);