|
|
@ -1,5 +1,74 @@ |
|
|
import { describe, expect, it, vi } from "vitest"; |
|
|
import { beforeEach, describe, expect, it, vi } from "vitest"; |
|
|
import { TransitionTransaction } from "@/scene/TransitionTransaction"; |
|
|
import { TransitionTransaction } from "@/scene/TransitionTransaction"; |
|
|
|
|
|
import { SceneType } from "@/enums/SceneType"; |
|
|
|
|
|
import type { IBaseScene } from "@/scene/types"; |
|
|
|
|
|
|
|
|
|
|
|
class MockContainer { |
|
|
|
|
|
visible = true; |
|
|
|
|
|
label = ""; |
|
|
|
|
|
_isHolderLast?: boolean; |
|
|
|
|
|
children: MockContainer[] = []; |
|
|
|
|
|
|
|
|
|
|
|
addChild(child: MockContainer): MockContainer { |
|
|
|
|
|
this.children.push(child); |
|
|
|
|
|
return child; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
removeChild(child: MockContainer): MockContainer { |
|
|
|
|
|
this.children = this.children.filter((item) => item !== child); |
|
|
|
|
|
return child; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
destroy(options?: { children?: boolean }): void { |
|
|
|
|
|
if (options?.children) { |
|
|
|
|
|
this.children = []; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
vi.mock("pixi.js", () => ({ |
|
|
|
|
|
Container: MockContainer, |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
vi.mock("@/core/Game", () => { |
|
|
|
|
|
class MockGame { |
|
|
|
|
|
private static instance: MockGame; |
|
|
|
|
|
readonly stage = new MockContainer(); |
|
|
|
|
|
|
|
|
|
|
|
static getInstance(): MockGame { |
|
|
|
|
|
if (!MockGame.instance) { |
|
|
|
|
|
MockGame.instance = new MockGame(); |
|
|
|
|
|
} |
|
|
|
|
|
return MockGame.instance; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { default: MockGame }; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
function createScene( |
|
|
|
|
|
name: string, |
|
|
|
|
|
type: SceneType = SceneType.Normal, |
|
|
|
|
|
overrides?: Partial<IBaseScene> |
|
|
|
|
|
): IBaseScene { |
|
|
|
|
|
const stage = new MockContainer(); |
|
|
|
|
|
stage.label = name; |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
name, |
|
|
|
|
|
type, |
|
|
|
|
|
stage: stage as never, |
|
|
|
|
|
_assetsLoaded: true, |
|
|
|
|
|
_layoutDone: true, |
|
|
|
|
|
changeScene: () => {}, |
|
|
|
|
|
loadBundle: async () => {}, |
|
|
|
|
|
unLoadBundle: async () => {}, |
|
|
|
|
|
layout: async () => {}, |
|
|
|
|
|
onLoad: async () => {}, |
|
|
|
|
|
onUnLoad: async () => {}, |
|
|
|
|
|
...overrides, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
describe("TransitionTransaction", () => { |
|
|
describe("TransitionTransaction", () => { |
|
|
it("rolls back when prepare fails and never commits", async () => { |
|
|
it("rolls back when prepare fails and never commits", async () => { |
|
|
@ -42,3 +111,45 @@ describe("TransitionTransaction", () => { |
|
|
expect(rollback).toHaveBeenCalledTimes(1); |
|
|
expect(rollback).toHaveBeenCalledTimes(1); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe("SceneManager rollback safety", () => { |
|
|
|
|
|
beforeEach(async () => { |
|
|
|
|
|
vi.resetModules(); |
|
|
|
|
|
const module = await import("@/scene/SceneManager"); |
|
|
|
|
|
(module.default as unknown as { instance?: unknown }).instance = undefined; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it("keeps previous stage content intact when target load fails", async () => { |
|
|
|
|
|
const module = await import("@/scene/SceneManager"); |
|
|
|
|
|
const SceneManager = module.default; |
|
|
|
|
|
const manager = SceneManager.getInstance(); |
|
|
|
|
|
|
|
|
|
|
|
const previous = createScene("previous"); |
|
|
|
|
|
const previousContent = new MockContainer(); |
|
|
|
|
|
(previous.stage as unknown as MockContainer).addChild(previousContent); |
|
|
|
|
|
|
|
|
|
|
|
const target = createScene("target", SceneType.Normal, { |
|
|
|
|
|
onLoad: async () => { |
|
|
|
|
|
throw new Error("target onLoad failed"); |
|
|
|
|
|
}, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
manager.registerScene(previous); |
|
|
|
|
|
manager.registerScene(target); |
|
|
|
|
|
await manager.initScene("previous"); |
|
|
|
|
|
|
|
|
|
|
|
await expect(manager.changeScene("target")).rejects.toThrowError( |
|
|
|
|
|
"target onLoad failed" |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const stage = manager["game"].stage as unknown as MockContainer; |
|
|
|
|
|
expect(manager.currentScene).toBe(previous); |
|
|
|
|
|
expect(previous.stage).not.toBeUndefined(); |
|
|
|
|
|
expect((previous.stage as unknown as MockContainer).children).toContain( |
|
|
|
|
|
previousContent |
|
|
|
|
|
); |
|
|
|
|
|
expect((previous.stage as unknown as MockContainer).visible).toBe(true); |
|
|
|
|
|
expect(stage.children.includes(previous.stage as never)).toBe(true); |
|
|
|
|
|
expect(stage.children.includes(target.stage as never)).toBe(false); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|