diff --git a/src/enums/ui-mode.ts b/src/enums/ui-mode.ts index dcf6bd2a238..ac6480098d8 100644 --- a/src/enums/ui-mode.ts +++ b/src/enums/ui-mode.ts @@ -38,6 +38,7 @@ export enum UiMode { UNAVAILABLE, CHALLENGE_SELECT, RENAME_POKEMON, + RENAME_RUN, RUN_HISTORY, RUN_INFO, TEST_DIALOGUE, diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 84b6367c38f..27c5a60769e 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -979,7 +979,7 @@ export class GameData { }); } - renameSession(slotId: number, newName: string): Promise { + async renameSession(slotId: number, newName: string): Promise { return new Promise(async resolve => { if (slotId < 0) { return resolve(false); @@ -1010,7 +1010,8 @@ export class GameData { ); if (!error) { - localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); // Keep local in sync + localStorage.setItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`, encrypted); + await updateUserInfo(); resolve(true); } else { console.error("Failed to update session name:", error); diff --git a/src/ui/rename-run-ui-handler.ts b/src/ui/rename-run-ui-handler.ts new file mode 100644 index 00000000000..0ba27f760be --- /dev/null +++ b/src/ui/rename-run-ui-handler.ts @@ -0,0 +1,49 @@ +import type { InputFieldConfig } from "./form-modal-ui-handler"; +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import type { ModalConfig } from "./modal-ui-handler"; +import i18next from "i18next"; + +export default class RenameRunFormUiHandler extends FormModalUiHandler { + getModalTitle(_config?: ModalConfig): string { + return i18next.t("menu:renamerun"); + } + + getWidth(_config?: ModalConfig): number { + return 160; + } + + getMargin(_config?: ModalConfig): [number, number, number, number] { + return [0, 0, 48, 0]; + } + + getButtonLabels(_config?: ModalConfig): string[] { + return [i18next.t("menu:rename"), i18next.t("menu:cancel")]; + } + + getReadableErrorMessage(error: string): string { + const colonIndex = error?.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + + return super.getReadableErrorMessage(error); + } + + override getInputFieldConfigs(): InputFieldConfig[] { + return [{ label: i18next.t("menu:runName") }]; + } + + show(args: any[]): boolean { + if (super.show(args)) { + const config = args[0] as ModalConfig; + this.submitAction = _ => { + this.sanitizeInputs(); + const sanitizedName = btoa(encodeURIComponent(this.inputs[0].text)); + config.buttonActions[0](sanitizedName); + return true; + }; + return true; + } + return false; + } +} diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 07f1ff14474..6675d06bf00 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -131,11 +131,11 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { handler: () => { globalScene.ui.revertMode(); ui.setOverlayMode( - UiMode.RENAME_POKEMON, + UiMode.RENAME_RUN, { buttonActions: [ (sanitizedName: string) => { - const name = decodeURIComponent((atob(sanitizedName))); + const name = decodeURIComponent(atob(sanitizedName)); globalScene.gameData.renameSession(cursor, name).then(response => { if (response[0] === false) { globalScene.reset(true); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index ad496df6382..a5d6cd8f5b9 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -58,6 +58,7 @@ import PokedexScanUiHandler from "./pokedex-scan-ui-handler"; import PokedexPageUiHandler from "./pokedex-page-ui-handler"; import { NavigationManager } from "./settings/navigationMenu"; import { UiMode } from "#enums/ui-mode"; +import RenameRunFormUiHandler from "./rename-run-ui-handler"; const transitionModes = [ UiMode.SAVE_SLOT, @@ -96,6 +97,7 @@ const noTransitionModes = [ UiMode.SESSION_RELOAD, UiMode.UNAVAILABLE, UiMode.RENAME_POKEMON, + UiMode.RENAME_RUN, UiMode.TEST_DIALOGUE, UiMode.AUTO_COMPLETE, UiMode.ADMIN, @@ -165,6 +167,7 @@ export default class UI extends Phaser.GameObjects.Container { new UnavailableModalUiHandler(), new GameChallengesUiHandler(), new RenameFormUiHandler(), + new RenameRunFormUiHandler(), new RunHistoryUiHandler(), new RunInfoUiHandler(), new TestDialogueUiHandler(UiMode.TEST_DIALOGUE), diff --git a/test/system/rename_run.test.ts b/test/system/rename_run.test.ts new file mode 100644 index 00000000000..2b728525a7c --- /dev/null +++ b/test/system/rename_run.test.ts @@ -0,0 +1,82 @@ +import * as bypassLoginModule from "#app/global-vars/bypass-login"; +import { pokerogueApi } from "#app/plugins/api/pokerogue-api"; +import type { SessionSaveData } from "#app/system/game-data"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import * as account from "#app/account"; + +describe("System - Rename Run", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([Moves.SPLASH]) + .battleStyle("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + describe("renameSession", () => { + beforeEach(() => { + vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(false); + vi.spyOn(account, "updateUserInfo").mockImplementation(async () => [true, 1]); + }); + + it("should return false if slotId < 0", async () => { + const result = await game.scene.gameData.renameSession(-1, "Named Run"); + + expect(result).toEqual(false); + }); + + it("should return false if getSession returns null", async () => { + vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue(null as unknown as SessionSaveData); + + const result = await game.scene.gameData.renameSession(-1, "Named Run"); + + expect(result).toEqual(false); + }); + + it("should return true if bypassLogin is true", async () => { + vi.spyOn(bypassLoginModule, "bypassLogin", "get").mockReturnValue(true); + vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData); + + const result = await game.scene.gameData.renameSession(0, "Named Run"); + + expect(result).toEqual(true); + }); + + it("should return false if api returns error", async () => { + vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData); + vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue("Unknown Error!"); + + const result = await game.scene.gameData.renameSession(0, "Named Run"); + + expect(result).toEqual(false); + }); + + it("should return true if api is succesfull", async () => { + vi.spyOn(game.scene.gameData, "getSession").mockResolvedValue({} as SessionSaveData); + vi.spyOn(pokerogueApi.savedata.session, "update").mockResolvedValue(""); + + const result = await game.scene.gameData.renameSession(0, "Named Run"); + + expect(result).toEqual(true); + expect(account.updateUserInfo).toHaveBeenCalled(); + }); + }); +});