test: add tests for calculating the spell price
This commit is contained in:
parent
de060b381e
commit
666b61ec09
2 changed files with 261 additions and 0 deletions
231
spec/item/type-specific-helpers/spell.spec.ts
Normal file
231
spec/item/type-specific-helpers/spell.spec.ts
Normal file
|
@ -0,0 +1,231 @@
|
|||
// SPDX-FileCopyrightText: 2022 Johannes Loher
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { DS4SpellDataSourceData, TemporalUnit, UnitData } from "../../../src/item/item-data-source";
|
||||
import { calculateSpellPrice } from "../../../src/item/type-specific-helpers/spell";
|
||||
|
||||
const defaultData: DS4SpellDataSourceData = {
|
||||
description: "",
|
||||
equipped: false,
|
||||
spellType: "spellcasting",
|
||||
bonus: "",
|
||||
spellCategory: "unset",
|
||||
maxDistance: {
|
||||
value: "",
|
||||
unit: "meter",
|
||||
},
|
||||
effectRadius: {
|
||||
value: "",
|
||||
unit: "meter",
|
||||
},
|
||||
duration: {
|
||||
value: "",
|
||||
unit: "custom",
|
||||
},
|
||||
cooldownDuration: {
|
||||
value: "",
|
||||
unit: "rounds",
|
||||
},
|
||||
minimumLevels: {
|
||||
healer: null,
|
||||
wizard: null,
|
||||
sorcerer: null,
|
||||
},
|
||||
};
|
||||
|
||||
type TestCase = {
|
||||
minimumLevel: number | null;
|
||||
expected: number | null;
|
||||
};
|
||||
|
||||
type CombinedTestCase = {
|
||||
minimumLevels: DS4SpellDataSourceData["minimumLevels"];
|
||||
expected: number | null;
|
||||
};
|
||||
|
||||
const testCases: Record<keyof DS4SpellDataSourceData["minimumLevels"], TestCase[]> = {
|
||||
healer: [
|
||||
{ minimumLevel: null, expected: null },
|
||||
{ minimumLevel: 1, expected: 10 },
|
||||
{ minimumLevel: 2, expected: 45 },
|
||||
{ minimumLevel: 3, expected: 80 },
|
||||
{ minimumLevel: 4, expected: 115 },
|
||||
{ minimumLevel: 5, expected: 150 },
|
||||
{ minimumLevel: 6, expected: 185 },
|
||||
{ minimumLevel: 7, expected: 220 },
|
||||
{ minimumLevel: 8, expected: 255 },
|
||||
{ minimumLevel: 9, expected: 290 },
|
||||
{ minimumLevel: 10, expected: 325 },
|
||||
{ minimumLevel: 11, expected: 360 },
|
||||
{ minimumLevel: 12, expected: 395 },
|
||||
{ minimumLevel: 13, expected: 430 },
|
||||
{ minimumLevel: 14, expected: 465 },
|
||||
{ minimumLevel: 15, expected: 500 },
|
||||
{ minimumLevel: 16, expected: 535 },
|
||||
{ minimumLevel: 17, expected: 570 },
|
||||
{ minimumLevel: 18, expected: 605 },
|
||||
{ minimumLevel: 19, expected: 640 },
|
||||
{ minimumLevel: 20, expected: 675 },
|
||||
],
|
||||
sorcerer: [
|
||||
{ minimumLevel: null, expected: null },
|
||||
{ minimumLevel: 1, expected: 10 },
|
||||
{ minimumLevel: 2, expected: 75 },
|
||||
{ minimumLevel: 3, expected: 140 },
|
||||
{ minimumLevel: 4, expected: 205 },
|
||||
{ minimumLevel: 5, expected: 270 },
|
||||
{ minimumLevel: 6, expected: 335 },
|
||||
{ minimumLevel: 7, expected: 400 },
|
||||
{ minimumLevel: 8, expected: 465 },
|
||||
{ minimumLevel: 9, expected: 530 },
|
||||
{ minimumLevel: 10, expected: 595 },
|
||||
{ minimumLevel: 11, expected: 660 },
|
||||
{ minimumLevel: 12, expected: 725 },
|
||||
{ minimumLevel: 13, expected: 790 },
|
||||
{ minimumLevel: 14, expected: 855 },
|
||||
{ minimumLevel: 15, expected: 920 },
|
||||
{ minimumLevel: 16, expected: 985 },
|
||||
{ minimumLevel: 17, expected: 1050 },
|
||||
{ minimumLevel: 18, expected: 1115 },
|
||||
{ minimumLevel: 19, expected: 1180 },
|
||||
{ minimumLevel: 20, expected: 1245 },
|
||||
],
|
||||
wizard: [
|
||||
{ minimumLevel: null, expected: null },
|
||||
{ minimumLevel: 1, expected: 10 },
|
||||
{ minimumLevel: 2, expected: 60 },
|
||||
{ minimumLevel: 3, expected: 110 },
|
||||
{ minimumLevel: 4, expected: 160 },
|
||||
{ minimumLevel: 5, expected: 210 },
|
||||
{ minimumLevel: 6, expected: 260 },
|
||||
{ minimumLevel: 7, expected: 310 },
|
||||
{ minimumLevel: 8, expected: 360 },
|
||||
{ minimumLevel: 9, expected: 410 },
|
||||
{ minimumLevel: 10, expected: 460 },
|
||||
{ minimumLevel: 11, expected: 510 },
|
||||
{ minimumLevel: 12, expected: 560 },
|
||||
{ minimumLevel: 13, expected: 610 },
|
||||
{ minimumLevel: 14, expected: 660 },
|
||||
{ minimumLevel: 15, expected: 710 },
|
||||
{ minimumLevel: 16, expected: 760 },
|
||||
{ minimumLevel: 17, expected: 810 },
|
||||
{ minimumLevel: 18, expected: 860 },
|
||||
{ minimumLevel: 19, expected: 910 },
|
||||
{ minimumLevel: 20, expected: 960 },
|
||||
],
|
||||
};
|
||||
|
||||
function buildCombinedTestCases(): CombinedTestCase[] {
|
||||
const combinedTestCases: CombinedTestCase[] = [];
|
||||
|
||||
// permutation test cases
|
||||
const isRelevantPermutationTestCase = (t: TestCase) =>
|
||||
([null, 1, 10, 20] as (number | null)[]).includes(t.minimumLevel);
|
||||
|
||||
for (const healerTestCase of testCases.healer.filter(isRelevantPermutationTestCase)) {
|
||||
for (const sorcererTestCase of testCases.sorcerer.filter(isRelevantPermutationTestCase)) {
|
||||
for (const wizardTestCase of testCases.wizard.filter(isRelevantPermutationTestCase)) {
|
||||
const expected =
|
||||
healerTestCase.expected !== null ||
|
||||
sorcererTestCase.expected !== null ||
|
||||
wizardTestCase.expected !== null
|
||||
? Math.min(
|
||||
healerTestCase.expected ?? Infinity,
|
||||
sorcererTestCase.expected ?? Infinity,
|
||||
wizardTestCase.expected ?? Infinity,
|
||||
)
|
||||
: null;
|
||||
combinedTestCases.push({
|
||||
minimumLevels: {
|
||||
healer: healerTestCase.minimumLevel,
|
||||
sorcerer: sorcererTestCase.minimumLevel,
|
||||
wizard: wizardTestCase.minimumLevel,
|
||||
},
|
||||
expected,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// single test cases
|
||||
const isRelevantSingleTestCase = (t: TestCase) => t.minimumLevel !== null;
|
||||
|
||||
for (const spellCasterClass of ["healer", "sorcerer", "wizard"] as const) {
|
||||
for (const testCase of testCases[spellCasterClass].filter(isRelevantSingleTestCase)) {
|
||||
combinedTestCases.push({
|
||||
minimumLevels: {
|
||||
...defaultData.minimumLevels,
|
||||
[spellCasterClass]: testCase.minimumLevel,
|
||||
},
|
||||
expected: testCase.expected,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return combinedTestCases;
|
||||
}
|
||||
|
||||
describe("calculateSpellPrice", () => {
|
||||
const cooldownDurations: (UnitData<TemporalUnit> & { factor: number })[] = [
|
||||
{ value: "", unit: "rounds", factor: 1 },
|
||||
{ value: "foo", unit: "rounds", factor: 1 },
|
||||
{ value: "0", unit: "rounds", factor: 1 },
|
||||
{ value: "1", unit: "rounds", factor: 1 },
|
||||
{ value: "17279", unit: "rounds", factor: 1 },
|
||||
{ value: "17280", unit: "rounds", factor: 2 },
|
||||
{ value: "34559", unit: "rounds", factor: 2 },
|
||||
{ value: "34560", unit: "rounds", factor: 3 },
|
||||
|
||||
{ value: "", unit: "minutes", factor: 1 },
|
||||
{ value: "foo", unit: "minutes", factor: 1 },
|
||||
{ value: "0", unit: "minutes", factor: 1 },
|
||||
{ value: "1", unit: "minutes", factor: 1 },
|
||||
{ value: "1439", unit: "minutes", factor: 1 },
|
||||
{ value: "1440", unit: "minutes", factor: 2 },
|
||||
{ value: "2879", unit: "minutes", factor: 2 },
|
||||
{ value: "2880", unit: "minutes", factor: 3 },
|
||||
|
||||
{ value: "", unit: "hours", factor: 1 },
|
||||
{ value: "foo", unit: "hours", factor: 1 },
|
||||
{ value: "0", unit: "hours", factor: 1 },
|
||||
{ value: "1", unit: "hours", factor: 1 },
|
||||
{ value: "23", unit: "hours", factor: 1 },
|
||||
{ value: "24", unit: "hours", factor: 2 },
|
||||
{ value: "47", unit: "hours", factor: 2 },
|
||||
{ value: "48", unit: "hours", factor: 3 },
|
||||
|
||||
{ value: "", unit: "days", factor: 3 },
|
||||
{ value: "foo", unit: "days", factor: 3 },
|
||||
{ value: "0", unit: "days", factor: 1 },
|
||||
{ value: "1", unit: "days", factor: 2 },
|
||||
{ value: "2", unit: "days", factor: 3 },
|
||||
];
|
||||
|
||||
describe.each(cooldownDurations)("with cooldown duration set to $value $unit", ({ value, unit, factor }) => {
|
||||
const dataWithCooldownDuration = {
|
||||
...defaultData,
|
||||
cooldownDuration: {
|
||||
value,
|
||||
unit,
|
||||
},
|
||||
};
|
||||
|
||||
it.each(buildCombinedTestCases())(
|
||||
`returns ${factor} × $expected if the minimum leves are $minimumLevels`,
|
||||
({ minimumLevels, expected }) => {
|
||||
// given
|
||||
const data: DS4SpellDataSourceData = {
|
||||
...dataWithCooldownDuration,
|
||||
minimumLevels,
|
||||
};
|
||||
|
||||
// when
|
||||
const spellPrice = calculateSpellPrice(data);
|
||||
|
||||
// then
|
||||
expect(spellPrice).toBe(expected !== null ? expected * factor : expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
|
@ -4,6 +4,35 @@
|
|||
|
||||
import en from "../lang/en.json";
|
||||
|
||||
function setupPrimitives() {
|
||||
Object.defineProperties(Number, {
|
||||
isNumeric: {
|
||||
value: function (n: unknown) {
|
||||
if (n instanceof Array) return false;
|
||||
else if (([null, ""] as unknown[]).includes(n)) return false;
|
||||
// @ts-expect-error Abusing JavaScript a bit here, but it's the implementation from foundry
|
||||
return +n === +n;
|
||||
},
|
||||
},
|
||||
fromString: {
|
||||
value: function (str: unknown) {
|
||||
if (typeof str !== "string" || !str.length) return NaN;
|
||||
// Remove whitespace.
|
||||
str = str.replace(/\s+/g, "");
|
||||
return Number(str);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperties(Math, {
|
||||
clamped: {
|
||||
value: function (num: number, min: number, max: number) {
|
||||
return Math.min(max, Math.max(num, min));
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setupStubs() {
|
||||
class StubGame {
|
||||
constructor() {
|
||||
|
@ -24,4 +53,5 @@ function setupStubs() {
|
|||
});
|
||||
}
|
||||
|
||||
setupPrimitives();
|
||||
setupStubs();
|
||||
|
|
Loading…
Reference in a new issue