diff --git a/src/init/init.ts b/src/init/init.ts index 72488bdc409..9937a0ca90e 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -18,7 +18,6 @@ import { initStatsKeys } from "#ui/game-stats-ui-handler"; /** Initialize the game. */ export function initializeGame() { if (allMoves.length > 0) { - console.warn("Game initialization ran twice during same session!"); return; } initModifierTypes(); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index cd7c7a8f48f..10b60b8faad 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -118,7 +118,10 @@ export class MovePhase extends BattlePhase { public start(): void { super.start(); - console.log(MoveId[this.move.moveId], enumValueToKey(MoveUseMode, this.useMode)); + console.log( + `%cMove: ${MoveId[this.move.moveId]}; Use Mode: ${enumValueToKey(MoveUseMode, this.useMode)}`, + "color:RebeccaPurple", + ); // Check if move is unusable (e.g. running out of PP due to a mid-turn Spite // or the user no longer being on field), ending the phase early if not. diff --git a/test/test-utils/game-wrapper.ts b/test/test-utils/game-wrapper.ts index 1a906bf8492..d18ea9301ea 100644 --- a/test/test-utils/game-wrapper.ts +++ b/test/test-utils/game-wrapper.ts @@ -6,17 +6,13 @@ import * as bypassLoginModule from "#app/global-vars/bypass-login"; import { MoveAnim } from "#data/battle-anims"; import { Pokemon } from "#field/pokemon"; import { version } from "#package.json"; -import { blobToString } from "#test/test-utils/game-manager-utils"; import { MockClock } from "#test/test-utils/mocks/mock-clock"; -import { MockFetch } from "#test/test-utils/mocks/mock-fetch"; import { MockGameObjectCreator } from "#test/test-utils/mocks/mock-game-object-creator"; import { MockLoader } from "#test/test-utils/mocks/mock-loader"; import { MockTextureManager } from "#test/test-utils/mocks/mock-texture-manager"; import { MockTimedEventManager } from "#test/test-utils/mocks/mock-timed-event-manager"; import { MockContainer } from "#test/test-utils/mocks/mocks-container/mock-container"; import { PokedexMonContainer } from "#ui/pokedex-mon-container"; -import { sessionIdKey } from "#utils/common"; -import { setCookie } from "#utils/cookies"; import fs from "node:fs"; import Phaser from "phaser"; import { vi } from "vitest"; @@ -28,20 +24,6 @@ const GamepadPlugin = Phaser.Input.Gamepad.GamepadPlugin; const EventEmitter = Phaser.Events.EventEmitter; const UpdateList = Phaser.GameObjects.UpdateList; -window.URL.createObjectURL = (blob: Blob) => { - blobToString(blob).then((data: string) => { - localStorage.setItem("toExport", data); - }); - return null; -}; -navigator.getGamepads = () => []; -global.fetch = vi.fn(MockFetch); -setCookie(sessionIdKey, "fake_token"); - -window.matchMedia = () => ({ - matches: false, -}); - export class GameWrapper { public game: Phaser.Game; public scene: BattleScene; @@ -99,6 +81,7 @@ export class GameWrapper { removeAll: () => null, }; + // TODO: Can't we just turn on `noAudio` in audio config? this.scene.sound = { play: () => null, pause: () => null, diff --git a/test/test-utils/helpers/overrides-helper.ts b/test/test-utils/helpers/overrides-helper.ts index d67ceedf891..3eca15cea21 100644 --- a/test/test-utils/helpers/overrides-helper.ts +++ b/test/test-utils/helpers/overrides-helper.ts @@ -19,6 +19,7 @@ import type { ModifierOverride } from "#modifiers/modifier-type"; import type { Variant } from "#sprites/variant"; import { GameManagerHelper } from "#test/test-utils/helpers/game-manager-helper"; import { coerceArray, shiftCharCodes } from "#utils/common"; +import chalk from "chalk"; import { vi } from "vitest"; /** @@ -665,6 +666,6 @@ export class OverridesHelper extends GameManagerHelper { } private log(...params: any[]) { - console.log("Overrides:", ...params); + console.log(chalk.hex("#b0b01eff")("Overrides:", ...params)); } } diff --git a/test/test-utils/mocks/mock-console/color-map.json b/test/test-utils/mocks/mock-console/color-map.json new file mode 100644 index 00000000000..ded83e889b0 --- /dev/null +++ b/test/test-utils/mocks/mock-console/color-map.json @@ -0,0 +1,150 @@ +{ + "AliceBlue": "f0f8ff", + "AntiqueWhite": "faebd7", + "Aqua": "00ffff", + "Aquamarine": "7fffd4", + "Azure": "f0ffff", + "Beige": "f5f5dc", + "Bisque": "ffe4c4", + "Black": "000000", + "BlanchedAlmond": "ffebcd", + "Blue": "0000ff", + "BlueViolet": "8a2be2", + "Brown": "a52a2a", + "BurlyWood": "deb887", + "CadetBlue": "5f9ea0", + "Chartreuse": "7fff00", + "Chocolate": "d2691e", + "Coral": "ff7f50", + "CornflowerBlue": "6495ed", + "Cornsilk": "fff8dc", + "Crimson": "dc143c", + "Cyan": "00ffff", + "DarkBlue": "00008b", + "DarkCyan": "008b8b", + "DarkGoldenRod": "b8860b", + "DarkGray": "a9a9a9", + "DarkGrey": "a9a9a9", + "DarkGreen": "006400", + "DarkKhaki": "bdb76b", + "DarkMagenta": "8b008b", + "DarkOliveGreen": "556b2f", + "DarkOrange": "ff8c00", + "DarkOrchid": "9932cc", + "DarkRed": "8b0000", + "DarkSalmon": "e9967a", + "DarkSeaGreen": "8fbc8f", + "DarkSlateBlue": "483d8b", + "DarkSlateGray": "2f4f4f", + "DarkSlateGrey": "2f4f4f", + "DarkTurquoise": "00ced1", + "DarkViolet": "9400d3", + "DeepPink": "ff1493", + "DeepSkyBlue": "00bfff", + "DimGray": "696969", + "DimGrey": "696969", + "DodgerBlue": "1e90ff", + "FireBrick": "b22222", + "FloralWhite": "fffaf0", + "ForestGreen": "228b22", + "Fuchsia": "ff00ff", + "Gainsboro": "dcdcdc", + "GhostWhite": "f8f8ff", + "Gold": "ffd700", + "GoldenRod": "daa520", + "Gray": "808080", + "Grey": "808080", + "Green": "008000", + "GreenYellow": "adff2f", + "HoneyDew": "f0fff0", + "HotPink": "ff69b4", + "IndianRed": "cd5c5c", + "Indigo": "4b0082", + "Ivory": "fffff0", + "Khaki": "f0e68c", + "Lavender": "e6e6fa", + "LavenderBlush": "fff0f5", + "LawnGreen": "7cfc00", + "LemonChiffon": "fffacd", + "LightBlue": "add8e6", + "LightCoral": "f08080", + "LightCyan": "e0ffff", + "LightGoldenRodYellow": "fafad2", + "LightGray": "d3d3d3", + "LightGrey": "d3d3d3", + "LightGreen": "90ee90", + "LightPink": "ffb6c1", + "LightSalmon": "ffa07a", + "LightSeaGreen": "20b2aa", + "LightSkyBlue": "87cefa", + "LightSlateGray": "778899", + "LightSlateGrey": "778899", + "LightSteelBlue": "b0c4de", + "LightYellow": "ffffe0", + "Lime": "00ff00", + "LimeGreen": "32cd32", + "Linen": "faf0e6", + "Magenta": "ff00ff", + "Maroon": "800000", + "MediumAquaMarine": "66cdaa", + "MediumBlue": "0000cd", + "MediumOrchid": "ba55d3", + "MediumPurple": "9370db", + "MediumSeaGreen": "3cb371", + "MediumSlateBlue": "7b68ee", + "MediumSpringGreen": "00fa9a", + "MediumTurquoise": "48d1cc", + "MediumVioletRed": "c71585", + "MidnightBlue": "191970", + "MintCream": "f5fffa", + "MistyRose": "ffe4e1", + "Moccasin": "ffe4b5", + "NavajoWhite": "ffdead", + "Navy": "000080", + "OldLace": "fdf5e6", + "Olive": "808000", + "OliveDrab": "6b8e23", + "Orange": "ffa500", + "OrangeRed": "ff4500", + "Orchid": "da70d6", + "PaleGoldenRod": "eee8aa", + "PaleGreen": "98fb98", + "PaleTurquoise": "afeeee", + "PaleVioletRed": "db7093", + "PapayaWhip": "ffefd5", + "PeachPuff": "ffdab9", + "Peru": "cd853f", + "Pink": "ffc0cb", + "Plum": "dda0dd", + "PowderBlue": "b0e0e6", + "Purple": "800080", + "RebeccaPurple": "663399", + "Red": "ff0000", + "RosyBrown": "bc8f8f", + "RoyalBlue": "4169e1", + "SaddleBrown": "8b4513", + "Salmon": "fa8072", + "SandyBrown": "f4a460", + "SeaGreen": "2e8b57", + "SeaShell": "fff5ee", + "Sienna": "a0522d", + "Silver": "c0c0c0", + "SkyBlue": "87ceeb", + "SlateBlue": "6a5acd", + "SlateGray": "708090", + "SlateGrey": "708090", + "Snow": "fffafa", + "SpringGreen": "00ff7f", + "SteelBlue": "4682b4", + "Tan": "d2b48c", + "Teal": "008080", + "Thistle": "d8bfd8", + "Tomato": "ff6347", + "Turquoise": "40e0d0", + "Violet": "ee82ee", + "Wheat": "f5deb3", + "White": "ffffff", + "WhiteSmoke": "f5f5f5", + "Yellow": "ffff00", + "YellowGreen": "9acd32" +} diff --git a/test/test-utils/mocks/mock-console/infer-color.ts b/test/test-utils/mocks/mock-console/infer-color.ts new file mode 100644 index 00000000000..a643905b6f2 --- /dev/null +++ b/test/test-utils/mocks/mock-console/infer-color.ts @@ -0,0 +1,62 @@ +import { hslToHex } from "#utils/common"; +import chalk, { type ChalkInstance, type ForegroundColorName, foregroundColorNames } from "chalk"; +import colorMap from "./color-map.json"; + +export function inferColorFormat(data: [string, ...unknown[]]): ChalkInstance { + // Remove all CSS format strings and find the first one containing something vaguely resembling a color + data[0] = data[0].replaceAll("%c", ""); + const args = data.slice(1).filter(t => typeof t === "string"); + const color = findColorPrefix(args); + + // If the color is within Chalk's native roster, use it directly. + if ((foregroundColorNames as string[]).includes(color)) { + return chalk[color as ForegroundColorName]; + } + + // Otherwise, coerce it to hex before feeding it in. + return getColor(color); +} + +/** + * Find the first string with a "color:" CSS directive in an argument list. + * @param args - The arguments containing the color directive + * @returns The found color, or `"green"` if none were found + */ +function findColorPrefix(args: string[]): string { + for (const arg of args) { + const match = /color:\s*(.+?)(?:;|$)/g.exec(arg); + if (match === null) { + // no match + continue; + } + + return match[1]; + } + return "green"; +} + +/** + * Coerce an arbitrary CSS color string to a Chalk instance. + * @param color - The color to coerce + * @returns The Chalk color equivalent. + */ +function getColor(color: string): ChalkInstance { + if (/^#([a-z0-9]{3,4}|[a-z0-9]{6}|[a-z0-9]{8})$/i.test(color)) { + // already in hex + return chalk.hex(color); + } + + const rgbMatch = /^rgba?\((\d{1,3})%?,\s*(\d{1,3})%?,?\s*(\d{1,3})%?,\s*/i.exec(color); + if (rgbMatch) { + const [red, green, blue] = rgbMatch; + return chalk.rgb(+red, +green, +blue); + } + + const hslMatch = /^hslv?\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%\)$/i.exec(color); + if (hslMatch) { + const [hue, saturation, light] = hslMatch; + return chalk.hex(hslToHex(+hue, +saturation / 100, +light / 100)); + } + + return chalk.hex(colorMap[color] ?? "#00ff95ff"); +} diff --git a/test/test-utils/mocks/mock-console.ts b/test/test-utils/mocks/mock-console/mock-console.ts similarity index 55% rename from test/test-utils/mocks/mock-console.ts rename to test/test-utils/mocks/mock-console/mock-console.ts index 45bad0b2f12..4062f85aeb5 100644 --- a/test/test-utils/mocks/mock-console.ts +++ b/test/test-utils/mocks/mock-console/mock-console.ts @@ -1,13 +1,22 @@ +import { inferColorFormat } from "#test/test-utils/mocks/mock-console/infer-color"; +import { coerceArray } from "#utils/common"; +import { Console } from "node:console"; import { stderr, stdout } from "node:process"; import util from "node:util"; import chalk, { type ChalkInstance } from "chalk"; +// Tell chalk we support truecolor +chalk.level = 3; + // TODO: Review this const blacklist = [ "variant icon does not exist", // Repetitive warnings about icons not found 'Texture "%s" not found', // Repetitive warnings about textures not found "type: 'Pokemon',", // Large Pokemon objects "gameVersion: ", // Large session-data and system-data objects + "Phaser v", // Phaser version text + "Seed:", // Stuff about wave seed (we should really stop logging this shit) + "Wave Seed:", // Stuff about wave seed (we should really stop logging this shit) ]; const whitelist = ["Start Phase"]; @@ -15,7 +24,7 @@ const whitelist = ["Start Phase"]; * The {@linkcode MockConsole} is a wrapper around the global {@linkcode console} object. * It automatically colors text and such. */ -export class MockConsole extends console.Console { +export class MockConsole extends Console { /** * A list of warnings that are queued to be displayed after all tests in the same file are finished. */ @@ -59,7 +68,7 @@ export class MockConsole extends console.Console { } // TODO: Figure out how to add color to the full trace text - super.trace(this.addColor(chalk.hex("#b700ff"), ...data)); + super.trace(...this.format(chalk.hex("#b700ff"), data)); } public debug(...data: unknown[]) { @@ -67,7 +76,7 @@ export class MockConsole extends console.Console { return; } - super.debug(this.addColor(chalk.hex("#874600ff"), ...data)); + super.debug(...this.format(chalk.hex("#874600ff"), data)); } public log(...data: unknown[]): void { @@ -75,17 +84,21 @@ export class MockConsole extends console.Console { return; } - if (typeof data[0] === "string" && data[0].includes("%c")) { - // Strip all CSS from console logs in place of green format - // (such as for "Start Phase" messages) - data[0] = data[0].replace("%c", ""); - super.log(this.addColor(chalk.green, data[0])); + let formatter: ChalkInstance | undefined; + + if (data.some(d => typeof d === "string" && d.includes("color:"))) { + // Infer the color format from the arguments, then remove everything but the message. + formatter = inferColorFormat(data as [string, ...unknown[]]); + data.splice(1); } else if (data[0] === "[UI]") { - // Orange for UI debug messages - super.log(this.addColor(chalk.hex("#ffa500"), ...data)); - } else { - super.log(...data); + // Cyan for UI debug messages + formatter = chalk.hex("#009dffff"); + } else if (typeof data[0] === "string" && data[0].startsWith("=====")) { + // Orange logging for "New Turn"/etc messages + formatter = chalk.hex("#ffad00ff"); } + + super.log(...this.format(formatter, data)); } public warn(...data: unknown[]) { @@ -93,7 +106,7 @@ export class MockConsole extends console.Console { return; } - super.warn(this.addColor(chalk.yellow, ...data)); + super.warn(...this.format(chalk.yellow, data)); } public error(...data: unknown[]) { @@ -101,7 +114,7 @@ export class MockConsole extends console.Console { return; } - super.error(this.addColor(chalk.redBright, ...data)); + super.error(...this.format(chalk.redBright, data)); } /** @@ -112,14 +125,16 @@ export class MockConsole extends console.Console { } /** - * Prepends the given color to every argument in the given data. - * Also appends the white ANSI code as an extra argument, so that the added color does not leak to future messages. - * @param color - A Chalk instance used to color the output. - * @param data - The data that the color should be applied to. - * @returns A stringified copy of `data` with the color prepended to every argument. - * @todo Do we need to prepend it? + * Stringify the given data in a manner fit for logging. + * @param color - A Chalk instance or other transformation function used to transform the output, + * or `undefined` to not transform it at all. + * @param data - The data that the format should be applied to. + * @returns A stringified copy of `data` with {@linkcode color} applied to each individual argument. + * @todo Do we need to apply color to each entry or just run it through `util.format`? */ - private addColor(color: ChalkInstance, ...data: unknown[]): string[] { - return data.map(a => `${color(typeof a === "string" ? a : this.getStr(a))}`); + private format(color: ((s: unknown) => unknown) | undefined, data: unknown | unknown[]): unknown[] { + data = coerceArray(data); + color ??= a => a; + return (data as unknown[]).map(a => color(typeof a === "function" || typeof a === "object" ? this.getStr(a) : a)); } } diff --git a/test/test-utils/test-file-initialization.ts b/test/test-utils/test-file-initialization.ts index 578a6129adf..40035d96cba 100644 --- a/test/test-utils/test-file-initialization.ts +++ b/test/test-utils/test-file-initialization.ts @@ -1,7 +1,7 @@ import { SESSION_ID_COOKIE_NAME } from "#app/constants"; import { blobToString } from "#test/test-utils/game-manager-utils"; import { manageListeners } from "#test/test-utils/listeners-manager"; -import { MockConsole } from "#test/test-utils/mocks/mock-console"; +import { MockConsole } from "#test/test-utils/mocks/mock-console/mock-console"; import { mockContext } from "#test/test-utils/mocks/mock-context-canvas"; import { mockLocalStorage } from "#test/test-utils/mocks/mock-local-storage"; import { MockImage } from "#test/test-utils/mocks/mocks-container/mock-image"; @@ -27,7 +27,7 @@ export function initTests(): void { function setupStubs(): void { console.log(console instanceof MockConsole); console.log(Phaser.GameObjects.Image instanceof MockImage); - Object.defineProperties(window, { + Object.defineProperties(global, { localStorage: { value: mockLocalStorage(), }, diff --git a/test/test-utils/text-interceptor.ts b/test/test-utils/text-interceptor.ts index 2e39fe03449..40f82e41045 100644 --- a/test/test-utils/text-interceptor.ts +++ b/test/test-utils/text-interceptor.ts @@ -25,7 +25,7 @@ export class TextInterceptor { } showDialogue(text: string, name: string): void { - console.log(this.formatText(`${name}: \n${text}`)); + console.log(`${name}: \n${this.formatText(text)}`); this.logs.push(name, text); } @@ -40,7 +40,7 @@ export class TextInterceptor { private formatText(text: string): string { return chalk.blue( text - .replace(/\n/g, "\n ") + .replace(/\n/g, " ") .replace(/\$/g, "\n ") .replace(/@\w{.*?}/g, ""), ); diff --git a/test/vitest.setup.ts b/test/vitest.setup.ts index dcc0074a680..f6f5d309de0 100644 --- a/test/vitest.setup.ts +++ b/test/vitest.setup.ts @@ -1,7 +1,8 @@ import "vitest-canvas-mock"; import { initializeGame } from "#app/init/init"; -import { MockConsole } from "#test/test-utils/mocks/mock-console"; +import { MockConsole } from "#test/test-utils/mocks/mock-console/mock-console"; import { initTests } from "#test/test-utils/test-file-initialization"; +import chalk from "chalk"; import { afterAll, beforeAll, vi } from "vitest"; /** Set the timezone to UTC for tests. */ @@ -88,5 +89,5 @@ beforeAll(() => { afterAll(() => { global.server.close(); MockConsole.printPostTestWarnings(); - console.log("Closing i18n MSW server!"); + console.log(chalk.hex("#dfb8d8")("Closing i18n MSW server!")); });