From 34bf0069caeb625d226fb75ae29ce5151908ef84 Mon Sep 17 00:00:00 2001 From: podar <1999688+podarsmarty@users.noreply.github.com> Date: Thu, 26 Sep 2024 15:55:08 -0500 Subject: [PATCH] [P1] Fixing continue menu option when game finishes (#4287) * [Bug] Fixing continue error when game finishes * migrate: `GameData.tryClearSession` to async/await * install dev: msw module * add: game-data test => tryClearSession tests --------- Co-authored-by: flx-sta <50131232+flx-sta@users.noreply.github.com> --- package-lock.json | 238 ++++++++++++++++++++++++++++++ package.json | 1 + src/@types/pokerogue-api.ts | 9 ++ src/system/game-data.ts | 63 ++++---- src/test/system/game_data.test.ts | 89 +++++++++++ 5 files changed, 372 insertions(+), 28 deletions(-) create mode 100644 src/@types/pokerogue-api.ts create mode 100644 src/test/system/game_data.test.ts diff --git a/package-lock.json b/package-lock.json index 00942ee3312..316306e3299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "inquirer": "^11.0.2", "jsdom": "^24.0.0", "lefthook": "^1.6.12", + "msw": "^2.4.9", "phaser3spectorjs": "^0.0.8", "typedoc": "^0.26.4", "typescript": "^5.5.3", @@ -486,6 +487,37 @@ "node": ">=6.9.0" } }, + "node_modules/@bundled-es-modules/cookie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/cookie/-/cookie-2.0.0.tgz", + "integrity": "sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cookie": "^0.5.0" + } + }, + "node_modules/@bundled-es-modules/statuses": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/statuses/-/statuses-1.0.1.tgz", + "integrity": "sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==", + "dev": true, + "license": "ISC", + "dependencies": { + "statuses": "^2.0.1" + } + }, + "node_modules/@bundled-es-modules/tough-cookie": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@bundled-es-modules/tough-cookie/-/tough-cookie-0.1.6.tgz", + "integrity": "sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/tough-cookie": "^4.0.5", + "tough-cookie": "^4.1.4" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1461,6 +1493,24 @@ "integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ==", "license": "Apache-2.0" }, + "node_modules/@mswjs/interceptors": { + "version": "0.35.8", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.35.8.tgz", + "integrity": "sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1499,6 +1549,31 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1777,6 +1852,13 @@ "eslint": ">=8.40.0" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.56.11", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.11.tgz", @@ -1842,6 +1924,13 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/statuses": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz", + "integrity": "sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -2622,6 +2711,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", @@ -3740,6 +3839,16 @@ "dev": true, "license": "MIT" }, + "node_modules/graphql": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", + "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3797,6 +3906,13 @@ "node": ">= 0.4" } }, + "node_modules/headers-polyfill": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", + "integrity": "sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -4072,6 +4188,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -4805,6 +4928,90 @@ "dev": true, "license": "MIT" }, + "node_modules/msw": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.4.9.tgz", + "integrity": "sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@bundled-es-modules/cookie": "^2.0.0", + "@bundled-es-modules/statuses": "^1.0.1", + "@bundled-es-modules/tough-cookie": "^0.1.6", + "@inquirer/confirm": "^3.0.0", + "@mswjs/interceptors": "^0.35.8", + "@open-draft/until": "^2.1.0", + "@types/cookie": "^0.6.0", + "@types/statuses": "^2.0.4", + "chalk": "^4.1.2", + "graphql": "^16.8.1", + "headers-polyfill": "^4.0.2", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.2", + "path-to-regexp": "^6.3.0", + "strict-event-emitter": "^0.5.1", + "type-fest": "^4.9.0", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/msw/node_modules/@inquirer/confirm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.2.0.tgz", + "integrity": "sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/msw/node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/msw/node_modules/type-fest": { + "version": "4.26.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz", + "integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -4983,6 +5190,13 @@ "node": ">=0.10.0" } }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5105,6 +5319,13 @@ "dev": true, "license": "ISC" }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5667,6 +5888,16 @@ "dev": true, "license": "MIT" }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/std-env": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", @@ -5674,6 +5905,13 @@ "dev": true, "license": "MIT" }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/package.json b/package.json index 2109604c969..37418014d93 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "inquirer": "^11.0.2", "jsdom": "^24.0.0", "lefthook": "^1.6.12", + "msw": "^2.4.9", "phaser3spectorjs": "^0.0.8", "typedoc": "^0.26.4", "typescript": "^5.5.3", diff --git a/src/@types/pokerogue-api.ts b/src/@types/pokerogue-api.ts new file mode 100644 index 00000000000..892869968bb --- /dev/null +++ b/src/@types/pokerogue-api.ts @@ -0,0 +1,9 @@ +/** + * Pokerogue API response for path: `/savedata/session/clear` + */ +export interface PokerogueApiClearSessionData { + /** Contains the error message if any occured */ + error?: string; + /** Is `true` if the request was successfully processed */ + success?: boolean; +} diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 22a793e9aca..34c4f67328d 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -48,6 +48,7 @@ import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler"; import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter"; import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data"; import { MysteryEncounterType } from "#enums/mystery-encounter-type"; +import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api"; export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -1186,37 +1187,43 @@ export class GameData { }); } - tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { - return new Promise<[boolean, boolean]>(resolve => { - if (bypassLogin) { - localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); - return resolve([true, true]); + + /** + * Attempt to clear session data. After session data is removed, attempt to update user info so the menu updates + */ + async tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { + let result: [boolean, boolean] = [false, false]; + + if (bypassLogin) { + localStorage.removeItem(`sessionData${slotId ? slotId : ""}_${loggedInUser?.username}`); + result = [true, true]; + } else { + const sessionData = this.getSessionSaveData(scene); + const response = await Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true); + + if (response.ok) { + loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? + localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); } - updateUserInfo().then(success => { - if (success !== null && !success) { - return resolve([false, false]); + const jsonResponse: PokerogueApiClearSessionData = await response.json(); + + if (!jsonResponse.error) { + result = [true, jsonResponse.success ?? false]; + } else { + if (jsonResponse && jsonResponse.error?.startsWith("session out of date")) { + this.scene.clearPhaseQueue(); + this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); } - const sessionData = this.getSessionSaveData(scene); - Utils.apiPost(`savedata/session/clear?slot=${slotId}&trainerId=${this.trainerId}&secretId=${this.secretId}&clientSessionId=${clientSessionId}`, JSON.stringify(sessionData), undefined, true).then(response => { - if (response.ok) { - loggedInUser!.lastSessionSlot = -1; // TODO: is the bang correct? - localStorage.removeItem(`sessionData${this.scene.sessionSlotId ? this.scene.sessionSlotId : ""}_${loggedInUser?.username}`); - } - return response.json(); - }).then(jsonResponse => { - if (!jsonResponse.error) { - return resolve([true, jsonResponse.success as boolean]); - } - if (jsonResponse && jsonResponse.error.startsWith("session out of date")) { - this.scene.clearPhaseQueue(); - this.scene.unshiftPhase(new ReloadSessionPhase(this.scene)); - } - console.error(jsonResponse); - resolve([false, false]); - }); - }); - }); + + console.error(jsonResponse); + result = [false, false]; + } + } + + await updateUserInfo(); + + return result; } parseSessionData(dataStr: string): SessionSaveData { diff --git a/src/test/system/game_data.test.ts b/src/test/system/game_data.test.ts new file mode 100644 index 00000000000..e3550e44fff --- /dev/null +++ b/src/test/system/game_data.test.ts @@ -0,0 +1,89 @@ +import * as BattleScene from "#app/battle-scene"; +import { SessionSaveData } from "#app/system/game-data"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import GameManager from "#test/utils/gameManager"; +import { http, HttpResponse } from "msw"; +import { setupServer } from "msw/node"; +import Phaser from "phaser"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import * as account from "../../account"; + +const apiBase = import.meta.env.VITE_API_BASE_URL ?? "http://localhost:8001"; + +export const server = setupServer(); + +describe("System - Game Data", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + server.listen(); + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterAll(() => { + server.close(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([Moves.SPLASH]) + .battleType("single") + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + afterEach(() => { + server.resetHandlers(); + game.phaseInterceptor.restoreOg(); + }); + + describe("tryClearSession", () => { + beforeEach(() => { + vi.spyOn(BattleScene, "bypassLogin", "get").mockReturnValue(false); + vi.spyOn(game.scene.gameData, "getSessionSaveData").mockReturnValue({} as SessionSaveData); + vi.spyOn(account, "updateUserInfo").mockImplementation(async () => [true, 1]); + }); + + it("should return [true, true] if bypassLogin is true", async () => { + vi.spyOn(BattleScene, "bypassLogin", "get").mockReturnValue(true); + + const result = await game.scene.gameData.tryClearSession(game.scene, 0); + + expect(result).toEqual([true, true]); + }); + + it("should return [true, true] if successful", async () => { + server.use(http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ success: true }))); + + const result = await game.scene.gameData.tryClearSession(game.scene, 0); + + expect(result).toEqual([true, true]); + expect(account.updateUserInfo).toHaveBeenCalled(); + }); + + it("should return [true, false] if not successful", async () => { + server.use(http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ success: false }))); + + const result = await game.scene.gameData.tryClearSession(game.scene, 0); + + expect(result).toEqual([true, false]); + expect(account.updateUserInfo).toHaveBeenCalled(); + }); + + it("should return [false, false] session is out of date", async () => { + server.use( + http.post(`${apiBase}/savedata/session/clear`, () => HttpResponse.json({ error: "session out of date" })) + ); + + const result = await game.scene.gameData.tryClearSession(game.scene, 0); + + expect(result).toEqual([false, false]); + expect(account.updateUserInfo).toHaveBeenCalled(); + }); + }); +});