Skip to content

Commit 534091c

Browse files
committed
#155 ローディングアニメーションを修正、ユニットテストを追加
1 parent 1b5e079 commit 534091c

16 files changed

+660
-47
lines changed

src/application/content/Builder/service/ContentBuilderService.test.ts

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { execute } from "./ContentBuilderService";
22
import { ShapeContent } from "../../ShapeContent";
33
import { loaderInfoMap } from "../../../variable/LoaderInfoMap";
44
import { Shape, type LoaderInfo } from "@next2d/display";
5-
import { describe, expect, it, vi } from "vitest";
5+
import { describe, expect, it, vi, beforeEach } from "vitest";
66

77
describe("ContentBuilderService Test", () =>
88
{
9-
it("test case", () =>
9+
beforeEach(() =>
10+
{
11+
loaderInfoMap.clear();
12+
});
13+
14+
it("should sync display object when all data is valid", () =>
1015
{
1116
const displayObject = new ShapeContent();
1217

@@ -18,9 +23,8 @@ describe("ContentBuilderService Test", () =>
1823

1924
const map: Map<string, number> = new Map();
2025
map.set(Shape.namespace, 1);
21-
loaderInfoMap.clear();
2226
loaderInfoMap.set(Shape.namespace, {
23-
"data": {
27+
"data": {
2428
"stage": {
2529
"width": 100,
2630
"height": 100,
@@ -49,4 +53,72 @@ describe("ContentBuilderService Test", () =>
4953
expect(state).toBe("sync");
5054
expect(displayObject.characterId).toBe(1);
5155
});
56+
57+
it("should return early when loaderInfo is not found", () =>
58+
{
59+
const displayObject = new ShapeContent();
60+
displayObject.$sync = vi.fn();
61+
62+
execute(displayObject);
63+
64+
expect(displayObject.$sync).not.toHaveBeenCalled();
65+
expect(displayObject.characterId).toBe(-1);
66+
});
67+
68+
it("should return early when loaderInfo.data is null", () =>
69+
{
70+
const displayObject = new ShapeContent();
71+
displayObject.$sync = vi.fn();
72+
73+
loaderInfoMap.set(Shape.namespace, {
74+
"data": null
75+
} as unknown as LoaderInfo);
76+
77+
execute(displayObject);
78+
79+
expect(displayObject.$sync).not.toHaveBeenCalled();
80+
expect(displayObject.characterId).toBe(-1);
81+
});
82+
83+
it("should return early when characterId is not found in symbols", () =>
84+
{
85+
const displayObject = new ShapeContent();
86+
displayObject.$sync = vi.fn();
87+
88+
const map: Map<string, number> = new Map();
89+
// Do not set the namespace in the map
90+
loaderInfoMap.set(Shape.namespace, {
91+
"data": {
92+
"symbols": map,
93+
"characters": []
94+
}
95+
} as unknown as LoaderInfo);
96+
97+
execute(displayObject);
98+
99+
expect(displayObject.$sync).not.toHaveBeenCalled();
100+
expect(displayObject.characterId).toBe(-1);
101+
});
102+
103+
it("should return early when character is not found", () =>
104+
{
105+
const displayObject = new ShapeContent();
106+
displayObject.$sync = vi.fn();
107+
108+
const map: Map<string, number> = new Map();
109+
map.set(Shape.namespace, 5); // characterId 5 doesn't exist in characters array
110+
loaderInfoMap.set(Shape.namespace, {
111+
"data": {
112+
"symbols": map,
113+
"characters": [
114+
{ "extends": Shape.namespace }
115+
]
116+
}
117+
} as unknown as LoaderInfo);
118+
119+
execute(displayObject);
120+
121+
expect(displayObject.$sync).not.toHaveBeenCalled();
122+
expect(displayObject.characterId).toBe(-1);
123+
});
52124
});

src/application/service/QueryStringParserService.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ export const execute = (name: string = ""): IQueryObject =>
1818
* 前のシーンのクエリデータを初期化
1919
* Initialize query data from previous scene
2020
*/
21-
if (query.size) {
22-
query.clear();
23-
}
21+
query.clear();
2422

2523
/**
2624
* QueryStringがあれば分解

src/application/usecase/ApplicationGotoViewUseCase.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,95 @@ describe("ApplicationGotoViewUseCase Test", () =>
150150
expect(response.has("")).toBe(false);
151151
expect(response.get("valid")).toEqual({ data: "should be set" });
152152
});
153+
154+
it("execute test case4: navigation with loading enabled", async () =>
155+
{
156+
const { execute: queryStringParserService } = await import("../service/QueryStringParserService");
157+
const { execute: requestUseCase } = await import("../../infrastructure/usecase/RequestUseCase");
158+
const { LoadingService } = await import("../../domain/service/LoadingService");
159+
const { ScreenCaptureService } = await import("../../domain/service/ScreenCaptureService");
160+
161+
$setConfig({
162+
platform: "web",
163+
spa: false,
164+
stage: {
165+
width: 800,
166+
height: 600,
167+
fps: 60
168+
},
169+
loading: {
170+
callback: async () => null
171+
}
172+
});
173+
174+
vi.mocked(queryStringParserService).mockReturnValue({
175+
name: "home",
176+
queryString: ""
177+
});
178+
vi.mocked(requestUseCase).mockResolvedValue([]);
179+
180+
await execute(mockApplication, "home");
181+
182+
expect(ScreenCaptureService.add).toHaveBeenCalled();
183+
expect(LoadingService.start).toHaveBeenCalled();
184+
expect(LoadingService.end).toHaveBeenCalled();
185+
expect(ScreenCaptureService.dispose).toHaveBeenCalled();
186+
});
187+
188+
it("execute test case5: response with callback should execute callback", async () =>
189+
{
190+
const { execute: queryStringParserService } = await import("../service/QueryStringParserService");
191+
const { execute: requestUseCase } = await import("../../infrastructure/usecase/RequestUseCase");
192+
const { execute: executeCallbackUseCase } = await import("./ExecuteCallbackUseCase");
193+
194+
vi.mocked(queryStringParserService).mockReturnValue({
195+
name: "page",
196+
queryString: ""
197+
});
198+
199+
const mockCallback = vi.fn();
200+
const mockResponses = [
201+
{ name: "data", response: { value: 123 }, callback: mockCallback }
202+
];
203+
vi.mocked(requestUseCase).mockResolvedValue(mockResponses);
204+
205+
await execute(mockApplication, "page");
206+
207+
expect(executeCallbackUseCase).toHaveBeenCalledWith(mockCallback, { value: 123 });
208+
});
209+
210+
it("execute test case6: gotoView callback should be executed when view is returned", async () =>
211+
{
212+
const { execute: queryStringParserService } = await import("../service/QueryStringParserService");
213+
const { execute: requestUseCase } = await import("../../infrastructure/usecase/RequestUseCase");
214+
const { ViewBinderService } = await import("../../domain/service/ViewBinderService");
215+
const { execute: executeCallbackUseCase } = await import("./ExecuteCallbackUseCase");
216+
217+
const mockGotoViewCallback = vi.fn();
218+
$setConfig({
219+
platform: "web",
220+
spa: false,
221+
stage: {
222+
width: 800,
223+
height: 600,
224+
fps: 60
225+
},
226+
gotoView: {
227+
callback: mockGotoViewCallback
228+
}
229+
});
230+
231+
vi.mocked(queryStringParserService).mockReturnValue({
232+
name: "withCallback",
233+
queryString: ""
234+
});
235+
vi.mocked(requestUseCase).mockResolvedValue([]);
236+
237+
const mockView = { name: "MockView" };
238+
vi.mocked(ViewBinderService.bind).mockResolvedValue(mockView as any);
239+
240+
await execute(mockApplication, "withCallback");
241+
242+
expect(executeCallbackUseCase).toHaveBeenCalledWith(mockGotoViewCallback, mockView);
243+
});
153244
});

src/application/usecase/ApplicationInitializeUseCase.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,43 @@ describe("ApplicationInitializeUseCase", () =>
7171
execute(app, config, buildPackages);
7272
expect(state).toBe("");
7373
});
74+
75+
it("popstate handler sets popstate flag and triggers gotoView", async () =>
76+
{
77+
let popstateCallback: (() => Promise<void>) | null = null;
78+
window.addEventListener = vi.fn((name, callback) =>
79+
{
80+
if (name === "popstate") {
81+
popstateCallback = callback as () => Promise<void>;
82+
}
83+
});
84+
85+
const app = new Application();
86+
app.gotoView = vi.fn().mockResolvedValue(undefined);
87+
88+
const config: IConfig = {
89+
"platform": "web",
90+
"stage": {
91+
"width": 640,
92+
"height": 480,
93+
"fps": 60
94+
},
95+
"spa": true
96+
};
97+
98+
const buildPackages: IPackages = [[
99+
"view", View
100+
]];
101+
102+
execute(app, config, buildPackages);
103+
104+
expect(popstateCallback).not.toBeNull();
105+
expect(app.popstate).toBe(false);
106+
107+
// Trigger the popstate event handler
108+
await popstateCallback!();
109+
110+
expect(app.popstate).toBe(true);
111+
expect(app.gotoView).toHaveBeenCalled();
112+
});
74113
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
3+
describe("Context variable Test", () =>
4+
{
5+
it("$getContext should throw error when context is not initialized", async () =>
6+
{
7+
// Reset modules to get a fresh Context module state
8+
vi.resetModules();
9+
const { $getContext } = await import("./Context");
10+
11+
expect(() => $getContext()).toThrow("Context is not initialized. Call run() first.");
12+
});
13+
14+
it("$setContext should set the context and $getContext should return it", async () =>
15+
{
16+
vi.resetModules();
17+
const { $getContext, $setContext } = await import("./Context");
18+
const { Context } = await import("../Context");
19+
const { MovieClip } = await import("@next2d/display");
20+
21+
const root = new MovieClip();
22+
const context = new Context(root);
23+
24+
$setContext(context);
25+
const result = $getContext();
26+
27+
expect(result).toBe(context);
28+
expect(result.root).toBe(root);
29+
});
30+
31+
it("$getContext should return the same context that was set", async () =>
32+
{
33+
vi.resetModules();
34+
const { $getContext, $setContext } = await import("./Context");
35+
const { Context } = await import("../Context");
36+
const { MovieClip } = await import("@next2d/display");
37+
38+
const root1 = new MovieClip();
39+
const context1 = new Context(root1);
40+
$setContext(context1);
41+
expect($getContext()).toBe(context1);
42+
43+
const root2 = new MovieClip();
44+
const context2 = new Context(root2);
45+
$setContext(context2);
46+
expect($getContext()).toBe(context2);
47+
});
48+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { popstateQueue, setPopstateQueue } from "./PopstateQueue";
2+
import { describe, expect, it } from "vitest";
3+
4+
describe("PopstateQueue Test", () =>
5+
{
6+
it("popstateQueue should be a resolved Promise initially", async () =>
7+
{
8+
const result = await popstateQueue;
9+
expect(result).toBeUndefined();
10+
});
11+
12+
it("setPopstateQueue should update the popstateQueue", async () =>
13+
{
14+
let executed = false;
15+
const newQueue = Promise.resolve().then(() =>
16+
{
17+
executed = true;
18+
});
19+
20+
setPopstateQueue(newQueue);
21+
22+
await popstateQueue;
23+
24+
expect(executed).toBe(true);
25+
});
26+
});

src/domain/entity/DefaultLoader.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,60 @@ describe("DefaultLoader Test", () =>
235235
expect(sprite).toBe(loader.sprite);
236236
});
237237
});
238+
239+
describe("edge cases", () =>
240+
{
241+
it("start should handle null shape gracefully when sprite children are removed", () =>
242+
{
243+
const root = new MovieClip();
244+
$setContext(new Context(root));
245+
246+
const loader = new DefaultLoader();
247+
// Remove all children to trigger the null check
248+
while (loader.sprite.numChildren > 0) {
249+
loader.sprite.removeChildAt(0);
250+
}
251+
252+
// Should not throw
253+
loader.start();
254+
});
255+
256+
it("end should handle null shape gracefully when sprite children are removed", () =>
257+
{
258+
const root = new MovieClip();
259+
$setContext(new Context(root));
260+
261+
const loader = new DefaultLoader();
262+
root.addChild(loader.sprite);
263+
264+
// Remove all children from sprite
265+
while (loader.sprite.numChildren > 0) {
266+
loader.sprite.removeChildAt(0);
267+
}
268+
269+
// Should not throw
270+
loader.end();
271+
272+
expect(root.numChildren).toBe(0);
273+
});
274+
275+
it("start should skip redrawing when shape width matches minSize", () =>
276+
{
277+
const root = new MovieClip();
278+
$setContext(new Context(root));
279+
280+
const loader = new DefaultLoader();
281+
282+
// First start - shapes are drawn
283+
loader.start();
284+
285+
// Get the first shape's width after drawing
286+
const shape0 = loader.sprite.getChildAt<Shape>(0);
287+
expect(shape0).toBeTruthy();
288+
289+
// Second start - should skip the redraw branch (line 175)
290+
// because shape.width === minSize is already true
291+
loader.start();
292+
});
293+
});
238294
});

0 commit comments

Comments
 (0)