Open History to see the last commit
1import { expect, it, vi } from "vite-plus/test";
2import { createDeviceModelSlot, type DeviceModelSource } from "./model.ts";
3
4const pro: DeviceModelSource = { id: "iphone-18-pro", url: "/pro.glb" };
5const max: DeviceModelSource = { id: "iphone-18-pro-max", url: "/max.glb" };
6
7function fixture() {
8 const pending: {
9 signal: AbortSignal;
10 resolve: (model: { dispose: () => void }) => void;
11 reject: (error: Error) => void;
12 }[] = [];
13 const install = vi.fn();
14 const load = vi.fn(
15 (_source: DeviceModelSource, signal: AbortSignal) =>
16 new Promise<{ dispose: () => void }>((resolve, reject) =>
17 pending.push({ signal, resolve, reject }),
18 ),
19 );
20 const slot = createDeviceModelSlot({ load, install });
21 return { slot, pending, install, load };
22}
23
24it("cancels superseded downloads and releases a late decoded model without installing it", async () => {
25 const { slot, pending, install, load } = fixture();
26 slot.set(pro);
27 slot.set({ ...pro });
28 expect(load).toHaveBeenCalledTimes(1);
29 slot.set(max);
30 expect(pending[0]!.signal.aborted).toBe(true);
31 const latest = { dispose: vi.fn() };
32 const stale = { dispose: vi.fn() };
33 pending[1]!.resolve(latest);
34 await Promise.resolve();
35 pending[0]!.resolve(stale);
36 await Promise.resolve();
37 expect(install).toHaveBeenCalledExactlyOnceWith(latest);
38 expect(stale.dispose).toHaveBeenCalledOnce();
39 slot.set(null);
40 expect(install).toHaveBeenLastCalledWith(null);
41 expect(latest.dispose).toHaveBeenCalledOnce();
42 slot.dispose();
43 expect(latest.dispose).toHaveBeenCalledOnce();
44});
45
46it("keeps the fallback on request failure and disposes results that finish after teardown", async () => {
47 const { slot, pending, install } = fixture();
48 slot.set(pro);
49 pending[0]!.reject(new Error("offline"));
50 await Promise.resolve();
51 expect(install).not.toHaveBeenCalled();
52 slot.set(max);
53 slot.dispose();
54 expect(pending[1]!.signal.aborted).toBe(true);
55 const late = { dispose: vi.fn() };
56 pending[1]!.resolve(late);
57 await Promise.resolve();
58 expect(late.dispose).toHaveBeenCalledOnce();
59 expect(install).not.toHaveBeenCalled();
60});
61
62it("retries a failed source while ignoring rejection of a superseded request", async () => {
63 const { slot, pending, install, load } = fixture();
64 slot.set(pro);
65 pending[0]!.reject(new Error("offline"));
66 await Promise.resolve();
67 slot.set({ ...pro });
68 expect(load).toHaveBeenCalledTimes(2);
69 slot.set(max);
70 pending[1]!.reject(new Error("cancelled"));
71 await Promise.resolve();
72 slot.set({ ...max });
73 expect(load).toHaveBeenCalledTimes(3);
74 const recovered = { dispose: vi.fn() };
75 pending[2]!.resolve(recovered);
76 await Promise.resolve();
77 expect(install).toHaveBeenCalledExactlyOnceWith(recovered);
78 slot.dispose();
79});
80
81it("releases an invalid model and restores the fallback if scene preparation rejects it", async () => {
82 const { slot, pending, install } = fixture();
83 install.mockImplementationOnce(() => {
84 throw new Error("invalid display");
85 });
86 slot.set(pro);
87 const model = { dispose: vi.fn() };
88 pending[0]!.resolve(model);
89 await Promise.resolve();
90 expect(model.dispose).toHaveBeenCalledOnce();
91 expect(install).toHaveBeenLastCalledWith(null);
92 slot.set({ ...pro });
93 const recovered = { dispose: vi.fn() };
94 pending[1]!.resolve(recovered);
95 await Promise.resolve();
96 expect(install).toHaveBeenLastCalledWith(recovered);
97 slot.dispose();
98});