// SPDX-FileCopyrightText: 2022 Johannes Loher // // SPDX-License-Identifier: MIT import { describe, expect, it } from "vitest"; import { calculateSpellPrice } from "../../../../src/documents/item/spell/calculate-spell-price"; import type { CooldownDuration, DS4SpellDataSourceData } from "../../../../src/documents/item/spell/spell-data-source"; const defaultData: DS4SpellDataSourceData = { description: "", equipped: false, spellType: "spellcasting", spellModifier: { numerical: 0, complex: "", }, spellGroups: { lightning: false, earth: false, water: false, ice: false, fire: false, healing: false, light: false, air: false, transport: false, damage: false, shadow: false, protection: false, mindAffecting: false, demonology: false, necromancy: false, transmutation: false, area: false, }, maxDistance: { value: "", unit: "meter", }, effectRadius: { value: "", unit: "meter", }, duration: { value: "", unit: "custom", }, cooldownDuration: "0r", minimumLevels: { healer: null, wizard: null, sorcerer: null, }, }; type TestCase = { minimumLevel: number | null; expected: number | null; }; type CombinedTestCase = { minimumLevels: DS4SpellDataSourceData["minimumLevels"]; expected: number | null; description: string; }; 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; const minimumLevels = { healer: healerTestCase.minimumLevel, sorcerer: sorcererTestCase.minimumLevel, wizard: wizardTestCase.minimumLevel, }; const description = JSON.stringify(minimumLevels); combinedTestCases.push({ minimumLevels, expected, description, }); } } } // 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)) { const minimumLevels = { ...defaultData.minimumLevels, [spellCasterClass]: testCase.minimumLevel, }; const description = JSON.stringify(minimumLevels); combinedTestCases.push({ minimumLevels, expected: testCase.expected, description, }); } } return combinedTestCases; } describe("calculateSpellPrice", () => { const cooldownDurations: { cooldownDuration: CooldownDuration; factor: number }[] = [ { cooldownDuration: "0r", factor: 1 }, { cooldownDuration: "1r", factor: 1 }, { cooldownDuration: "2r", factor: 1 }, { cooldownDuration: "5r", factor: 1 }, { cooldownDuration: "10r", factor: 1 }, { cooldownDuration: "100r", factor: 1 }, { cooldownDuration: "1d", factor: 2 }, { cooldownDuration: "d20d", factor: 3 }, ]; describe.each(cooldownDurations)( "with cooldown duration set to $cooldownDuration", ({ cooldownDuration, factor }) => { const dataWithCooldownDuration = { ...defaultData, cooldownDuration, }; it.each(buildCombinedTestCases())( `returns ${factor} × $expected if the minimum leves are $description`, ({ minimumLevels, expected }) => { // given const data: DS4SpellDataSourceData = { ...dataWithCooldownDuration, minimumLevels, }; // when const spellPrice = calculateSpellPrice(data); // then expect(spellPrice).toBe(expected !== null ? expected * factor : expected); }, ); }, ); });