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, 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"; import type { NetscriptContext } from "../../src/Netscript/APIWrapper"; import { purchaseServer } from "../../src/Server/ServerPurchases"; import { purchaseHacknet } from "../../src/Hacknet/HacknetHelpers"; import { initForeignServers } from "../../src/Server/ServerHelpers"; import { generateNextPid } from "../../src/Netscript/Pid"; import { initBitNodeMultipliers } from "../../src/BitNode/BitNode"; import { resetGoPromises } from "../../src/Go/boardAnalysis/goAI"; import { enterBitNode } from "../../src/RedPill"; import { getDefaultBitNodeOptions } from "../../src/BitNode/BitNodeUtils"; 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( { purchaseHacknetServer, purchasePServer } = { purchasePServer: false, purchaseHacknetServer: false }, ): void { // We need to delete all servers before calling initForeignServers. prestigeAllServers(); setPlayer(new PlayerObject()); // Basic steps of initializing a new save file. These are the steps that we do in Engine.load() when there is no save // data. initBitNodeMultipliers(); Player.init(); initForeignServers(Player.getHomeComputer()); Player.reapplyAllAugmentations(); resetGoPromises(); // Grant SF4 for conveniently using Singularity APIs in tests. Player.sourceFiles.set(4, 3); // Simulate bitflume. This step is very important. It ensures all global states (e.g., Factions, Companies, BN mults) // are reset. enterBitNode(true, Player.bitNodeN, 1, getDefaultBitNodeOptions()); // Get some money. Player.money = 1e15; if (purchasePServer) { purchaseServer("test-server-1", 2); } if (purchaseHacknetServer) { Player.sourceFiles.set(9, 3); purchaseHacknet(); } } export function getWorkerScriptAndNS(hostname: string = SpecialServers.Home): { ws: WorkerScript; ns: NSFull; } { const home = GetServerOrThrow(hostname); 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, generateNextPid(), NetscriptFunctions); const ns = workerScript.env.vars; if (!ns) { throw new Error("Invalid NS instance"); } return { ws: workerScript, ns, }; } export function getNS(hostname: string = SpecialServers.Home): NSFull { return getWorkerScriptAndNS(hostname).ns; } export function getMockedNetscriptContext( workerScriptLogFunction: (func: string, txt: () => string) => void = () => {}, ): NetscriptContext { return { function: "", functionPath: "", workerScript: { log: workerScriptLogFunction, scriptRef: { dependencies: [], }, } as unknown as WorkerScript, }; } // WIP: Improve this function to get a better stack trace or use jest-expect-message export function expectWithMessage(actual: unknown, expected: unknown, customMessage: string) { try { expect(actual).toStrictEqual(expected); } catch (error) { throw new Error(customMessage, { cause: error }); } }