[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>
This commit is contained in:
podar 2024-09-26 15:55:08 -05:00 committed by GitHub
parent cb27d32fe5
commit 34bf0069ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 372 additions and 28 deletions

238
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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;
}

View File

@ -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 {

View File

@ -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();
});
});
});