From 020b185377cd84d41731ed231c50b3f704ed429c Mon Sep 17 00:00:00 2001 From: catloversg <152669316+catloversg@users.noreply.github.com> Date: Sun, 5 Oct 2025 04:38:50 +0700 Subject: [PATCH] JEST: Enable restoreMocks option and fix lint errors (#2333) * JEST: Enable restoreMocks option and fix lint errors * Fix test\jest\Save.test.ts --- jest.config.js | 1 + test/jest/Bladeburner/Actions.test.ts | 31 ++++++-- test/jest/Messages/MessageHelper.test.ts | 1 - test/jest/Netscript/DisableLog.test.ts | 7 +- test/jest/Netscript/RamCalculation.test.ts | 29 +++++--- test/jest/Netscript/RunScript.test.ts | 34 ++------- test/jest/Netscript/Singularity.test.ts | 2 +- .../StaticRamParsingCalculation.test.ts | 56 ++++++++------ test/jest/Netscript/UserInterface.test.ts | 2 +- test/jest/Netscript/Utilities.ts | 50 ------------- test/jest/Save.test.ts | 6 +- test/jest/StockMarket.test.ts | 6 +- test/jest/Utilities.ts | 73 +++++++++++++++++++ test/jest/__snapshots__/Save.test.ts.snap | 4 +- 14 files changed, 173 insertions(+), 129 deletions(-) delete mode 100644 test/jest/Netscript/Utilities.ts create mode 100644 test/jest/Utilities.ts diff --git a/jest.config.js b/jest.config.js index 133cd9ef7..4e6fe89b1 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,4 +21,5 @@ module.exports = { "/utils/Protections$": "/test/__mocks__/NullMock.js", "@swc/wasm-web": "@swc/core", }, + restoreMocks: true, }; diff --git a/test/jest/Bladeburner/Actions.test.ts b/test/jest/Bladeburner/Actions.test.ts index 42b76abcf..a86c23d38 100644 --- a/test/jest/Bladeburner/Actions.test.ts +++ b/test/jest/Bladeburner/Actions.test.ts @@ -3,6 +3,7 @@ import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject"; import { Player, setPlayer } from "@player"; import { BlackOperation, Contract, GeneralAction, Operation } from "../../../src/Bladeburner/Actions"; import { + AugmentationName, BladeburnerActionType, BladeburnerContractName, BladeburnerGeneralActionName, @@ -15,6 +16,8 @@ import { CrimeWork } from "../../../src/Work/CrimeWork"; import type { Action, ActionIdentifier } from "../../../src/Bladeburner/Types"; import type { Skills } from "@nsdefs"; import { BlackOperations } from "../../../src/Bladeburner/data/BlackOperations"; +import { applyAugmentation } from "../../../src/Augmentation/AugmentationHelpers"; +import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation"; describe("Bladeburner Actions", () => { const SampleContract = Contract.createId(BladeburnerContractName.Tracking); @@ -24,6 +27,7 @@ describe("Bladeburner Actions", () => { const ENOUGH_TIME_TO_FINISH_ACTION = 1e5; const BASE_STAT_EXP = 1e6; + const HIGH_CHAOS = 1e12; let bb: Bladeburner; @@ -83,26 +87,30 @@ describe("Bladeburner Actions", () => { it("mildly reduces chaos in the current city", () => { allCitiesHighChaos(); - let { chaos } = bb.getCurrentCity(); + const { chaos } = bb.getCurrentCity(); complete(diplomacy); expect(bb.getCurrentCity().chaos).toBeGreaterThan(chaos * 0.9); expect(bb.getCurrentCity().chaos).toBeLessThan(chaos); }); it("effect scales significantly with player charisma", () => { - Player.gainCharismaExp(1e500); + gainHighCharismaLevel(); allCitiesHighChaos(); complete(diplomacy); - expect(bb.getCurrentCity().chaos).toBe(0); + expect(bb.getCurrentCity().chaos).toStrictEqual(0); }); it("does NOT affect chaos in other cities", () => { - const otherCity = cities.find((c) => c !== bb.getCurrentCity().name); + const otherCity = cities.find((c) => c !== bb.getCurrentCity().name); + if (!otherCity) { + throw new Error("Invalid otherCity"); + } /** Testing against a guaranteed 0-chaos level of charisma */ - Player.gainCharismaExp(1e500); + gainHighCharismaLevel(); allCitiesHighChaos(); complete(diplomacy); - expect(bb.cities[otherCity].chaos).toBeGreaterThan(0); + expect(bb.getCurrentCity().chaos).toStrictEqual(0); + expect(bb.cities[otherCity].chaos).toStrictEqual(HIGH_CHAOS); }); }); @@ -284,6 +292,15 @@ describe("Bladeburner Actions", () => { resetCity(); } + function gainHighCharismaLevel() { + for (let i = 1; i <= 1000; ++i) { + const aug = new PlayerOwnedAugmentation(AugmentationName.NeuroFluxGovernor); + aug.level = i; + applyAugmentation(aug); + } + Player.gainCharismaExp(1e6); + } + function resetCity() { bb.cities[bb.city].chaos = 0; bb.cities[bb.city].comms = 100; @@ -295,7 +312,7 @@ describe("Bladeburner Actions", () => { function allCitiesHighChaos() { for (const city of Object.values(bb.cities)) { - city.chaos = 1e12; + city.chaos = HIGH_CHAOS; } } diff --git a/test/jest/Messages/MessageHelper.test.ts b/test/jest/Messages/MessageHelper.test.ts index e0507e90c..504aad58d 100644 --- a/test/jest/Messages/MessageHelper.test.ts +++ b/test/jest/Messages/MessageHelper.test.ts @@ -45,7 +45,6 @@ describe("MessageHelpers tests", () => { it("Should not repeatedly send the Icarus message after the player's first bitnode completion", () => { initSourceFiles(); Player.sourceFiles.set(1, 1); - jest.spyOn(console, "warn").mockImplementation(() => {}); // Prevent test spam Player.queueAugmentation(AugmentationName.TheRedPill); installAugmentations(); Player.gainHackingExp(2 ** 200); diff --git a/test/jest/Netscript/DisableLog.test.ts b/test/jest/Netscript/DisableLog.test.ts index c7ae21acf..19b89f633 100644 --- a/test/jest/Netscript/DisableLog.test.ts +++ b/test/jest/Netscript/DisableLog.test.ts @@ -25,6 +25,9 @@ test("Edge cases of disableLog", function () { const ws = new WorkerScript(runningScript, 1, NetscriptFunctions); const ns = ws.env.vars; + if (!ns) { + throw new Error("Invalid ws.env.vars"); + } // Generate logs in a specific pattern that checks edge cases in // disableLog. We want to check various combinations of things that @@ -82,6 +85,8 @@ test("Edge cases of disableLog", function () { "end", ]); } finally { - DeleteServer(server.hostname); + if (server) { + DeleteServer(server.hostname); + } } }); diff --git a/test/jest/Netscript/RamCalculation.test.ts b/test/jest/Netscript/RamCalculation.test.ts index 00c8e76d3..fe784d83c 100644 --- a/test/jest/Netscript/RamCalculation.test.ts +++ b/test/jest/Netscript/RamCalculation.test.ts @@ -1,15 +1,15 @@ import { Player } from "../../../src/Player"; import { NetscriptFunctions } from "../../../src/NetscriptFunctions"; -import { RamCosts, getRamCost, RamCostConstants, RamCostTree } from "../../../src/Netscript/RamCostGenerator"; +import { RamCosts, getRamCost, RamCostConstants, type RamCostTree } from "../../../src/Netscript/RamCostGenerator"; import { Environment } from "../../../src/Netscript/Environment"; import { RunningScript } from "../../../src/Script/RunningScript"; import { Script } from "../../../src/Script/Script"; -import { WorkerScript } from "../../../src/Netscript/WorkerScript"; +import type { WorkerScript } from "../../../src/Netscript/WorkerScript"; import { calculateRamUsage } from "../../../src/Script/RamCalculations"; import { ns } from "../../../src/NetscriptFunctions"; -import { InternalAPI } from "src/Netscript/APIWrapper"; -import { Singularity } from "@nsdefs"; -import { ScriptFilePath } from "src/Paths/ScriptFilePath"; +import type { InternalAPI } from "../../../src/Netscript/APIWrapper"; +import type { Singularity } from "@nsdefs"; +import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; type PotentiallyAsyncFunction = (arg?: unknown) => { catch?: PotentiallyAsyncFunction }; @@ -21,12 +21,10 @@ function getFunction(fn: unknown) { function grabCost(ramEntry: RamCostTree[keyof API]) { if (typeof ramEntry === "function") return ramEntry(); if (typeof ramEntry === "number") return ramEntry; - throw new Error("Invalid ramcost: " + ramEntry); + throw new Error("Invalid ramcost: " + String(ramEntry)); } describe("Netscript RAM Calculation/Generation Tests", function () { - jest.spyOn(console, "warn").mockImplementation(() => {}); - jest.spyOn(console, "error").mockImplementation(() => {}); Player.sourceFiles.set(4, 3); // For simulating costs of singularity functions. const baseCost = RamCostConstants.Base; @@ -96,12 +94,23 @@ describe("Netscript RAM Calculation/Generation Tests", function () { workerScript.env.vars = nsExternal; // Run the function through the workerscript's args + const fnPathAsString = fnPath.join("."); if (typeof fn === "function") { + let consoleError; + let consoleWarning; + if (fnPathAsString === "ui.setTheme" || fnPathAsString === "ui.setStyles") { + consoleError = jest.spyOn(console, "error").mockImplementation(jest.fn()); + } + if (fnPathAsString === "alterReality") { + consoleWarning = jest.spyOn(console, "warn").mockImplementation(jest.fn()); + } tryFunction(fn); tryFunction(fn); tryFunction(fn); + consoleError?.mockRestore(); + consoleWarning?.mockRestore(); } else { - throw new Error(`Invalid function specified: [${fnPath.toString()}]`); + throw new Error(`Invalid function specified: [${fnPathAsString}]`); } if (expectedRamCost !== 0) { @@ -180,7 +189,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () { ])("%s", (code, expected) => { const fullCode = `export function main(ns) { ${code} }`; - const result = calculateRamUsage(fullCode, "testfile.js", new Map(), "testserver"); + const result = calculateRamUsage(fullCode, "testfile.js" as ScriptFilePath, "testserver", new Map()); expect(result.errorMessage).toBe(undefined); expect(result.cost).toBe(expected); }); diff --git a/test/jest/Netscript/RunScript.test.ts b/test/jest/Netscript/RunScript.test.ts index f02db673a..25a962881 100644 --- a/test/jest/Netscript/RunScript.test.ts +++ b/test/jest/Netscript/RunScript.test.ts @@ -2,12 +2,11 @@ import type { Script } from "../../../src/Script/Script"; import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; import { runScriptFromScript, startWorkerScript } from "../../../src/NetscriptWorker"; import { workerScripts } from "../../../src/Netscript/WorkerScripts"; -import { config as EvaluatorConfig } from "../../../src/NetscriptJSEvaluator"; import { Server } from "../../../src/Server/Server"; import { RunningScript } from "../../../src/Script/RunningScript"; import { AddToAllServers, DeleteServer, GetServerOrThrow } from "../../../src/Server/AllServers"; import { AlertEvents } from "../../../src/ui/React/AlertManager"; -import { initGameEnvironment, setupBasicTestingEnvironment } from "./Utilities"; +import { fixDoImportIssue, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities"; import { Terminal } from "../../../src/Terminal"; import { runScript } from "../../../src/Terminal/commands/runScript"; import { Player } from "@player"; @@ -18,25 +17,7 @@ import { NetscriptFunctions } from "../../../src/NetscriptFunctions"; import type { PositiveInteger } from "../../../src/types"; import { ErrorState } from "../../../src/ErrorHandling/ErrorState"; -declare const importActual: (typeof EvaluatorConfig)["doImport"]; - -// Replace Blob/ObjectURL functions, because they don't work natively in Jest -global.Blob = class extends Blob { - code: string; - constructor(blobParts?: BlobPart[], __options?: BlobPropertyBag) { - super(); - this.code = String((blobParts ?? [])[0]); - } -}; -global.URL.revokeObjectURL = function () {}; -// Critical: We have to overwrite this, otherwise we get Jest's hooked -// implementation, which will not work without passing special flags to Node, -// and tends to crash even if you do. -EvaluatorConfig.doImport = importActual; - -global.URL.createObjectURL = function (blob) { - return "data:text/javascript," + encodeURIComponent((blob as unknown as { code: string }).code); -}; +fixDoImportIssue(); initGameEnvironment(); @@ -122,11 +103,6 @@ async function expectErrorWhenRunningScript( errorShown: Promise, errorMessage: string, ): Promise { - /** - * Suppress console.error(). When there is a thrown error in the player's script, we print it to the console. In - * this test, we intentionally throw an error, so we can ignore it. - */ - jest.spyOn(console, "error").mockImplementation(jest.fn()); for (const script of scripts) { Player.getHomeComputer().writeToScriptFile(script.filePath, script.code); } @@ -135,10 +111,16 @@ async function expectErrorWhenRunningScript( if (!workerScript) { throw new Error(`Invalid worker script`); } + /** + * Suppress console.error(). When there is a thrown error in the player's script, we print it to the console. In + * this test, we intentionally throw an error, so we can ignore it. + */ + const consoleError = jest.spyOn(console, "error").mockImplementation(jest.fn()); const result = await Promise.race([ errorShown, new Promise((resolve) => (workerScript.atExit = new Map([["default", resolve]]))), ]); + consoleError.mockRestore(); expect(result).toBeDefined(); expect(workerScript.scriptRef.logs[0]).toContain(errorMessage); } diff --git a/test/jest/Netscript/Singularity.test.ts b/test/jest/Netscript/Singularity.test.ts index 8d73257f6..aab8df8d6 100644 --- a/test/jest/Netscript/Singularity.test.ts +++ b/test/jest/Netscript/Singularity.test.ts @@ -7,7 +7,7 @@ import { GetServerOrThrow } from "../../../src/Server/AllServers"; import { SpecialServers } from "../../../src/Server/data/SpecialServers"; import { Factions } from "../../../src/Faction/Factions"; import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation"; -import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "./Utilities"; +import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities"; import { Terminal } from "../../../src/Terminal"; import type { NSFull } from "../../../src/NetscriptFunctions"; import { Companies } from "../../../src/Company/Companies"; diff --git a/test/jest/Netscript/StaticRamParsingCalculation.test.ts b/test/jest/Netscript/StaticRamParsingCalculation.test.ts index f71b4f407..1658b660b 100644 --- a/test/jest/Netscript/StaticRamParsingCalculation.test.ts +++ b/test/jest/Netscript/StaticRamParsingCalculation.test.ts @@ -3,6 +3,8 @@ import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; import { calculateRamUsage } from "../../../src/Script/RamCalculations"; import { RamCosts } from "../../../src/Netscript/RamCostGenerator"; import { Script } from "../../../src/Script/Script"; +import { setPlayer } from "@player"; +import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject"; const BaseCost = 1.6; const HackCost = 0.1; @@ -14,8 +16,14 @@ const MaxCost = 1024; const filename = "testfile.js" as ScriptFilePath; const folderFilename = "test/testfile.js" as ScriptFilePath; const server = "testserver"; + +/** + * Init the player object. When calculating the RAM usage of singularity APIs, RamCostGenerator.ts needs to access some + * properties and functions of the player object. + */ +setPlayer(new PlayerObject()); + describe("Parsing NetScript code to work out static RAM costs", function () { - jest.spyOn(console, "error").mockImplementation(() => {}); /** Tests numeric equality, allowing for floating point imprecision - and includes script base cost */ function expectCost(val: number | undefined, expected: number) { const expectedWithBase = Math.min(expected + BaseCost, MaxCost); @@ -24,7 +32,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { } describe("Single files with basic NS functions", function () { - it("Empty main function", async function () { + it("Empty main function", function () { const code = ` export async function main(ns) { } `; @@ -32,7 +40,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, 0); }); - it("Free NS function directly in main", async function () { + it("Free NS function directly in main", function () { const code = ` export async function main(ns) { ns.print("Slum snakes r00l!"); @@ -42,7 +50,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, 0); }); - it("Single simple base NS function directly in main", async function () { + it("Single simple base NS function directly in main", function () { const code = ` export async function main(ns) { await ns.hack("joesguns"); @@ -52,7 +60,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Single simple base NS function directly in main with differing arg name", async function () { + it("Single simple base NS function directly in main with differing arg name", function () { const code = ` export async function main(X) { await X.hack("joesguns"); @@ -62,7 +70,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Repeated simple base NS function directly in main", async function () { + it("Repeated simple base NS function directly in main", function () { const code = ` export async function main(ns) { await ns.hack("joesguns"); @@ -73,7 +81,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Multiple simple base NS functions directly in main", async function () { + it("Multiple simple base NS functions directly in main", function () { const code = ` export async function main(ns) { await ns.hack("joesguns"); @@ -84,7 +92,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost + GrowCost); }); - it("Simple base NS functions in a referenced function", async function () { + it("Simple base NS functions in a referenced function", function () { const code = ` export async function main(ns) { doHacking(ns); @@ -97,7 +105,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Simple base NS functions in a referenced class", async function () { + it("Simple base NS functions in a referenced class", function () { const code = ` export async function main(ns) { await new Hacker(ns).doHacking(); @@ -112,7 +120,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Simple base NS functions in a referenced class", async function () { + it("Simple base NS functions in a referenced class", function () { const code = ` export async function main(ns) { await new Hacker(ns).doHacking(); @@ -129,7 +137,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { }); describe("Functions that can be confused with NS functions", function () { - it("Function 'get' that can be confused with Stanek.get", async function () { + it("Function 'get' that can be confused with Stanek.get", function () { const code = ` export async function main(ns) { get(); @@ -140,7 +148,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, 0); }); - it("Function 'purchaseNode' that can be confused with Hacknet.purchaseNode", async function () { + it("Function 'purchaseNode' that can be confused with Hacknet.purchaseNode", function () { const code = ` export async function main(ns) { purchaseNode(); @@ -153,7 +161,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { }); // TODO: once we fix static parsing this should pass - it.skip("Function 'getTask' that can be confused with Sleeve.getTask", async function () { + it.skip("Function 'getTask' that can be confused with Sleeve.getTask", function () { const code = ` export async function main(ns) { getTask(); @@ -166,7 +174,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { }); describe("Single files with non-core NS functions", function () { - it("Hacknet NS function with a cost from namespace", async function () { + it("Hacknet NS function with a cost from namespace", function () { const code = ` export async function main(ns) { ns.hacknet.purchaseNode(0); @@ -176,7 +184,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HacknetCost); }); - it("Sleeve functions with an individual cost", async function () { + it("Sleeve functions with an individual cost", function () { const code = ` export async function main(ns) { ns.sleeve.getTask(3); @@ -188,7 +196,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { }); describe("Imported files", function () { - it("Simple imported function with no cost", async function () { + it("Simple imported function with no cost", function () { const libCode = ` export function dummy() { return 0; } `; @@ -209,7 +217,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, 0); }); - it("Imported ns function", async function () { + it("Imported ns function", function () { const libCode = ` export async function doHack(ns) { return await ns.hack("joesguns"); } `; @@ -230,7 +238,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Importing a single function from a library that exports multiple", async function () { + it("Importing a single function from a library that exports multiple", function () { const libCode = ` export async function doHack(ns) { return await ns.hack("joesguns"); } export async function doGrow(ns) { return await ns.grow("joesguns"); } @@ -252,7 +260,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Importing all functions from a library that exports multiple", async function () { + it("Importing all functions from a library that exports multiple", function () { const libCode = ` export async function doHack(ns) { return await ns.hack("joesguns"); } export async function doGrow(ns) { return await ns.grow("joesguns"); } @@ -296,7 +304,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { }); // TODO: once we fix static parsing this should pass - it.skip("Importing a function from a library that contains a class", async function () { + it.skip("Importing a function from a library that contains a class", function () { const libCode = ` export async function doHack(ns) { return await ns.hack("joesguns"); } class Grower { @@ -322,7 +330,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, HackCost); }); - it("Importing a function from a library that creates a class in a function", async function () { + it("Importing a function from a library that creates a class in a function", function () { const libCode = ` export function createClass() { class Grower { @@ -353,7 +361,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { expectCost(calculated, GrowCost); }); - it("Importing with a relative path - One Layer Deep", async function () { + it("Importing with a relative path - One Layer Deep", function () { const libCode = ` export async function testRelative(ns) { await ns.hack("n00dles") @@ -375,7 +383,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { ).cost; expectCost(calculated, HackCost); }); - it("Importing with a relative path - Two Layer Deep", async function () { + it("Importing with a relative path - Two Layer Deep", function () { const libNameOne = "test/libTestOne.js" as ScriptFilePath; const libNameTwo = "test/libTestTwo.js" as ScriptFilePath; @@ -412,7 +420,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () { ).cost; expectCost(calculated, HackCost); }); - it("Importing with a relative path - possible path conflict", async function () { + it("Importing with a relative path - possible path conflict", function () { const libNameOne = "foo/libTestOne.js" as ScriptFilePath; const libNameTwo = "foo/libTestTwo.js" as ScriptFilePath; const incorrect_libNameTwo = "test/libTestTwo.js" as ScriptFilePath; diff --git a/test/jest/Netscript/UserInterface.test.ts b/test/jest/Netscript/UserInterface.test.ts index f3ec05c5e..f0a483167 100644 --- a/test/jest/Netscript/UserInterface.test.ts +++ b/test/jest/Netscript/UserInterface.test.ts @@ -2,7 +2,7 @@ import { IStyleSettings, UserInterfaceTheme } from "../../../src/ScriptEditor/Ne import { Settings } from "../../../src/Settings/Settings"; import { defaultStyles } from "../../../src/Themes/Styles"; import { defaultTheme } from "../../../src/Themes/Themes"; -import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "./Utilities"; +import { getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities"; const themeHexColor = "#abc"; const fontFamily = "monospace"; diff --git a/test/jest/Netscript/Utilities.ts b/test/jest/Netscript/Utilities.ts deleted file mode 100644 index 414f0f173..000000000 --- a/test/jest/Netscript/Utilities.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { WorkerScript } from "../../../src/Netscript/WorkerScript"; -import { NetscriptFunctions, type NSFull } from "../../../src/NetscriptFunctions"; -import type { ScriptFilePath } from "../../../src/Paths/ScriptFilePath"; -import { PlayerObject } from "../../../src/PersonObjects/Player/PlayerObject"; -import { Player, setPlayer } from "../../../src/Player"; -import { RunningScript } from "../../../src/Script/RunningScript"; -import { GetServerOrThrow, initForeignServers, prestigeAllServers } from "../../../src/Server/AllServers"; -import { SpecialServers } from "../../../src/Server/data/SpecialServers"; -import { initSourceFiles } from "../../../src/SourceFile/SourceFiles"; -import { FormatsNeedToChange } from "../../../src/ui/formatNumber"; -import { Router } from "../../../src/ui/GameRoot"; - -export function initGameEnvironment() { - // We need to patch this function. Some APIs call it, but it only works properly after the main UI is loaded. - Router.toPage = () => {}; - - /** - * In src\ui\formatNumber.ts, there are some variables that need to be initialized before other functions can be - * called. We have to call FormatsNeedToChange.emit() to initialize those variables. - */ - FormatsNeedToChange.emit(); - - initSourceFiles(); -} - -export function setupBasicTestingEnvironment(): void { - prestigeAllServers(); - setPlayer(new PlayerObject()); - Player.init(); - Player.sourceFiles.set(4, 3); - initForeignServers(Player.getHomeComputer()); -} - -export function getNS(): NSFull { - const home = GetServerOrThrow(SpecialServers.Home); - home.maxRam = 1024; - const filePath = "test.js" as ScriptFilePath; - home.writeToScriptFile(filePath, ""); - const script = home.scripts.get(filePath); - if (!script) { - throw new Error("Invalid script"); - } - const runningScript = new RunningScript(script, 1024); - const workerScript = new WorkerScript(runningScript, 1, NetscriptFunctions); - const ns = workerScript.env.vars; - if (!ns) { - throw new Error("Invalid NS instance"); - } - return ns; -} diff --git a/test/jest/Save.test.ts b/test/jest/Save.test.ts index f7b7adc46..858b8df38 100644 --- a/test/jest/Save.test.ts +++ b/test/jest/Save.test.ts @@ -6,8 +6,12 @@ import { Settings } from "../../src/Settings/Settings"; import { Player, setPlayer } from "../../src/Player"; import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject"; import { UIEventEmitter, UIEventType } from "../../src/ui/UIEventEmitter"; +import { fixDoImportIssue } from "./Utilities"; + jest.useFakeTimers(); +fixDoImportIssue(); + // Direct tests of loading and saving. // Tests here should try to be comprehensive (cover as much stuff as possible) // without requiring burdensome levels of maintenance when legitimate changes @@ -98,7 +102,7 @@ function loadStandardServers() { { "ctor": "Script", "data": { - "code": "/** @param {NS} ns */\\nexport async function main(ns) {\\n return ns.asleep(1000000);\\n}", + "code": "/** @param {NS} ns */\nexport async function main(ns) {\n return ns.asleep(1000000);\n}", "filename": "script.js", "module": {}, "dependencies": [ diff --git a/test/jest/StockMarket.test.ts b/test/jest/StockMarket.test.ts index a497a29dc..0152411ce 100644 --- a/test/jest/StockMarket.test.ts +++ b/test/jest/StockMarket.test.ts @@ -33,10 +33,6 @@ import { } from "../../src/StockMarket/StockMarketHelpers"; import { CompanyName, LocationName, OrderType, PositionType } from "../../src/Enums"; -// jest.mock("../src/ui/React/createPopup.tsx", () => ({ -// createPopup: jest.fn(), -// })); - describe("Stock Market Tests", function () { const commission = StockMarketConstants.StockMarketCommission; @@ -451,7 +447,7 @@ describe("Stock Market Tests", function () { it("should trigger a price update when it has enough cycles", function () { // Get the initial prices - const initialValues: Record = {}; + const initialValues: Record = {}; for (const stockName in StockMarket) { const stock = StockMarket[stockName]; if (!(stock instanceof Stock)) { diff --git a/test/jest/Utilities.ts b/test/jest/Utilities.ts new file mode 100644 index 000000000..3deea168a --- /dev/null +++ b/test/jest/Utilities.ts @@ -0,0 +1,73 @@ +import { WorkerScript } from "../../src/Netscript/WorkerScript"; +import { NetscriptFunctions, type NSFull } from "../../src/NetscriptFunctions"; +import type { ScriptFilePath } from "../../src/Paths/ScriptFilePath"; +import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject"; +import { Player, setPlayer } from "../../src/Player"; +import { RunningScript } from "../../src/Script/RunningScript"; +import { GetServerOrThrow, initForeignServers, prestigeAllServers } from "../../src/Server/AllServers"; +import { SpecialServers } from "../../src/Server/data/SpecialServers"; +import { initSourceFiles } from "../../src/SourceFile/SourceFiles"; +import { FormatsNeedToChange } from "../../src/ui/formatNumber"; +import { Router } from "../../src/ui/GameRoot"; +import { config } from "../../src/NetscriptJSEvaluator"; + +declare const importActual: (typeof config)["doImport"]; + +export function fixDoImportIssue() { + // Replace Blob/ObjectURL functions, because they don't work natively in Jest + global.Blob = class extends Blob { + code: string; + constructor(blobParts?: BlobPart[], __options?: BlobPropertyBag) { + super(); + this.code = String((blobParts ?? [])[0]); + } + }; + global.URL.revokeObjectURL = function () {}; + // Critical: We have to overwrite this, otherwise we get Jest's hooked + // implementation, which will not work without passing special flags to Node, + // and tends to crash even if you do. + config.doImport = importActual; + + global.URL.createObjectURL = function (blob) { + return "data:text/javascript," + encodeURIComponent((blob as unknown as { code: string }).code); + }; +} + +export function initGameEnvironment() { + // We need to patch this function. Some APIs call it, but it only works properly after the main UI is loaded. + Router.toPage = () => {}; + + /** + * In src\ui\formatNumber.ts, there are some variables that need to be initialized before other functions can be + * called. We have to call FormatsNeedToChange.emit() to initialize those variables. + */ + FormatsNeedToChange.emit(); + + initSourceFiles(); +} + +export function setupBasicTestingEnvironment(): void { + prestigeAllServers(); + setPlayer(new PlayerObject()); + Player.init(); + Player.sourceFiles.set(4, 3); + initForeignServers(Player.getHomeComputer()); +} + +export function getNS(): NSFull { + const home = GetServerOrThrow(SpecialServers.Home); + home.maxRam = 1024; + const filePath = "test.js" as ScriptFilePath; + home.writeToScriptFile(filePath, ""); + const script = home.scripts.get(filePath); + if (!script) { + throw new Error("Invalid script"); + } + const runningScript = new RunningScript(script, 1024); + const workerScript = new WorkerScript(runningScript, 1, NetscriptFunctions); + const ns = workerScript.env.vars; + if (!ns) { + throw new Error("Invalid NS instance"); + } + return ns; +} diff --git a/test/jest/__snapshots__/Save.test.ts.snap b/test/jest/__snapshots__/Save.test.ts.snap index d3d1dfa9d..117eaf458 100644 --- a/test/jest/__snapshots__/Save.test.ts.snap +++ b/test/jest/__snapshots__/Save.test.ts.snap @@ -29,7 +29,7 @@ exports[`load/saveAllServers 1`] = ` { "ctor": "Script", "data": { - "code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}", + "code": "/** @param {NS} ns */\\nexport async function main(ns) {\\n return ns.asleep(1000000);\\n}", "filename": "script.js", "server": "home", "metadata": { @@ -165,7 +165,7 @@ exports[`load/saveAllServers pruning RunningScripts 1`] = ` { "ctor": "Script", "data": { - "code": "/** @param {NS} ns */\\\\nexport async function main(ns) {\\\\n return ns.asleep(1000000);\\\\n}", + "code": "/** @param {NS} ns */\\nexport async function main(ns) {\\n return ns.asleep(1000000);\\n}", "filename": "script.js", "server": "home", "metadata": {