quake2ts 0.0.582 → 0.0.585
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -4
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/render/__mocks__/frame.d.ts +31 -0
- package/packages/engine/dist/types/render/__mocks__/frame.d.ts.map +1 -0
- package/packages/test-utils/dist/index.cjs +404 -555
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +174 -109
- package/packages/test-utils/dist/index.d.ts +174 -109
- package/packages/test-utils/dist/index.js +389 -548
- package/packages/test-utils/dist/index.js.map +1 -1
|
@@ -43,6 +43,7 @@ __export(index_exports, {
|
|
|
43
43
|
createBandwidthTestScenario: () => createBandwidthTestScenario,
|
|
44
44
|
createBinaryStreamMock: () => createBinaryStreamMock,
|
|
45
45
|
createBinaryWriterMock: () => createBinaryWriterMock,
|
|
46
|
+
createBounds: () => createBounds,
|
|
46
47
|
createCombatTestContext: () => createCombatTestContext,
|
|
47
48
|
createControlledTimer: () => createControlledTimer,
|
|
48
49
|
createCustomNetworkCondition: () => createCustomNetworkCondition,
|
|
@@ -53,10 +54,11 @@ __export(index_exports, {
|
|
|
53
54
|
createGameStateSnapshotFactory: () => createGameStateSnapshotFactory,
|
|
54
55
|
createInputInjector: () => createInputInjector,
|
|
55
56
|
createItemEntityFactory: () => createItemEntityFactory,
|
|
57
|
+
createMessageReaderMock: () => createMessageReaderMock,
|
|
58
|
+
createMessageWriterMock: () => createMessageWriterMock,
|
|
56
59
|
createMockAI: () => createMockAI,
|
|
57
60
|
createMockAmmoItem: () => createMockAmmoItem,
|
|
58
61
|
createMockArmorItem: () => createMockArmorItem,
|
|
59
|
-
createMockAudioContext: () => createMockAudioContext,
|
|
60
62
|
createMockCamera: () => createMockCamera,
|
|
61
63
|
createMockCanvas: () => createMockCanvas,
|
|
62
64
|
createMockCanvasContext2D: () => createMockCanvasContext2D,
|
|
@@ -111,6 +113,7 @@ __export(index_exports, {
|
|
|
111
113
|
createMonsterEntityFactory: () => createMonsterEntityFactory,
|
|
112
114
|
createMultiplayerTestScenario: () => createMultiplayerTestScenario,
|
|
113
115
|
createNetChanMock: () => createNetChanMock,
|
|
116
|
+
createPacketMock: () => createPacketMock,
|
|
114
117
|
createPhysicsTestContext: () => createPhysicsTestContext,
|
|
115
118
|
createPlayerEntityFactory: () => createPlayerEntityFactory,
|
|
116
119
|
createPlayerStateFactory: () => createPlayerStateFactory,
|
|
@@ -119,12 +122,16 @@ __export(index_exports, {
|
|
|
119
122
|
createServerSnapshot: () => createServerSnapshot,
|
|
120
123
|
createSpawnTestContext: () => createSpawnTestContext,
|
|
121
124
|
createStorageTestScenario: () => createStorageTestScenario,
|
|
125
|
+
createSurfaceMock: () => createSurfaceMock,
|
|
122
126
|
createTestContext: () => createTestContext,
|
|
127
|
+
createTraceMock: () => createTraceMock,
|
|
128
|
+
createTransform: () => createTransform,
|
|
123
129
|
createTriggerEntityFactory: () => createTriggerEntityFactory,
|
|
130
|
+
createVector3: () => createVector3,
|
|
124
131
|
createViewTestScenario: () => createViewTestScenario,
|
|
125
132
|
createVisualTestScenario: () => createVisualTestScenario,
|
|
126
|
-
intersects: () =>
|
|
127
|
-
ladderTrace: () =>
|
|
133
|
+
intersects: () => import_shared2.intersects,
|
|
134
|
+
ladderTrace: () => import_shared2.ladderTrace,
|
|
128
135
|
makeAxisBrush: () => makeAxisBrush,
|
|
129
136
|
makeBrushFromMinsMaxs: () => makeBrushFromMinsMaxs,
|
|
130
137
|
makeBspModel: () => makeBspModel,
|
|
@@ -134,6 +141,7 @@ __export(index_exports, {
|
|
|
134
141
|
makePlane: () => makePlane,
|
|
135
142
|
measureSnapshotSize: () => measureSnapshotSize,
|
|
136
143
|
mockMonsterAttacks: () => mockMonsterAttacks,
|
|
144
|
+
randomVector3: () => randomVector3,
|
|
137
145
|
serializeUserInfo: () => serializeUserInfo,
|
|
138
146
|
setupBrowserEnvironment: () => setupBrowserEnvironment,
|
|
139
147
|
setupMockAudioContext: () => setupMockAudioContext,
|
|
@@ -142,7 +150,6 @@ __export(index_exports, {
|
|
|
142
150
|
simulateBandwidthLimit: () => simulateBandwidthLimit,
|
|
143
151
|
simulateCameraMovement: () => simulateCameraMovement,
|
|
144
152
|
simulateFrames: () => simulateFrames,
|
|
145
|
-
simulateFramesWithMock: () => simulateFramesWithMock,
|
|
146
153
|
simulateHandshake: () => simulateHandshake,
|
|
147
154
|
simulateNetworkCondition: () => simulateNetworkCondition,
|
|
148
155
|
simulatePlayerInput: () => simulatePlayerInput,
|
|
@@ -152,9 +159,10 @@ __export(index_exports, {
|
|
|
152
159
|
simulateServerRegistration: () => simulateServerRegistration,
|
|
153
160
|
simulateServerTick: () => simulateServerTick,
|
|
154
161
|
simulateSnapshotDelivery: () => simulateSnapshotDelivery,
|
|
155
|
-
stairTrace: () =>
|
|
162
|
+
stairTrace: () => import_shared2.stairTrace,
|
|
156
163
|
teardownBrowserEnvironment: () => teardownBrowserEnvironment,
|
|
157
164
|
teardownMockAudioContext: () => teardownMockAudioContext,
|
|
165
|
+
teardownNodeEnvironment: () => teardownNodeEnvironment,
|
|
158
166
|
throttleBandwidth: () => throttleBandwidth,
|
|
159
167
|
verifySnapshotConsistency: () => verifySnapshotConsistency,
|
|
160
168
|
waitForGameReady: () => waitForGameReady
|
|
@@ -179,7 +187,9 @@ var createBinaryWriterMock = () => ({
|
|
|
179
187
|
writeInt32: import_vitest.vi.fn(),
|
|
180
188
|
writeUint32: import_vitest.vi.fn(),
|
|
181
189
|
writeFloat: import_vitest.vi.fn(),
|
|
182
|
-
getData: import_vitest.vi.fn(() => new Uint8Array(0))
|
|
190
|
+
getData: import_vitest.vi.fn(() => new Uint8Array(0)),
|
|
191
|
+
writePos: import_vitest.vi.fn(),
|
|
192
|
+
writeDir: import_vitest.vi.fn()
|
|
183
193
|
});
|
|
184
194
|
var createNetChanMock = () => ({
|
|
185
195
|
qport: 1234,
|
|
@@ -241,6 +251,37 @@ var createBinaryStreamMock = () => ({
|
|
|
241
251
|
readPos: import_vitest.vi.fn(),
|
|
242
252
|
readDir: import_vitest.vi.fn()
|
|
243
253
|
});
|
|
254
|
+
var createMessageWriterMock = (overrides) => {
|
|
255
|
+
const mock = createBinaryWriterMock();
|
|
256
|
+
const writer = {
|
|
257
|
+
...mock,
|
|
258
|
+
writeInt: mock.writeInt32,
|
|
259
|
+
// Alias writeInt to writeInt32
|
|
260
|
+
writeVector: mock.writePos,
|
|
261
|
+
// Alias writeVector to writePos
|
|
262
|
+
...overrides
|
|
263
|
+
};
|
|
264
|
+
return writer;
|
|
265
|
+
};
|
|
266
|
+
var createMessageReaderMock = (data) => {
|
|
267
|
+
const mock = createBinaryStreamMock();
|
|
268
|
+
const reader = {
|
|
269
|
+
...mock,
|
|
270
|
+
readInt: mock.readLong,
|
|
271
|
+
// Alias readInt to readLong (int32)
|
|
272
|
+
readVector: mock.readPos
|
|
273
|
+
// Alias readVector to readPos
|
|
274
|
+
};
|
|
275
|
+
return reader;
|
|
276
|
+
};
|
|
277
|
+
var createPacketMock = (overrides) => ({
|
|
278
|
+
type: "data",
|
|
279
|
+
sequence: 0,
|
|
280
|
+
ack: 0,
|
|
281
|
+
qport: 0,
|
|
282
|
+
data: new Uint8Array(0),
|
|
283
|
+
...overrides
|
|
284
|
+
});
|
|
244
285
|
|
|
245
286
|
// src/shared/bsp.ts
|
|
246
287
|
var import_shared = require("@quake2ts/shared");
|
|
@@ -309,6 +350,28 @@ function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOL
|
|
|
309
350
|
};
|
|
310
351
|
}
|
|
311
352
|
|
|
353
|
+
// src/shared/math.ts
|
|
354
|
+
var createVector3 = (x = 0, y = 0, z = 0) => ({
|
|
355
|
+
x,
|
|
356
|
+
y,
|
|
357
|
+
z
|
|
358
|
+
});
|
|
359
|
+
var createBounds = (mins = createVector3(0, 0, 0), maxs = createVector3(1, 1, 1)) => ({
|
|
360
|
+
mins,
|
|
361
|
+
maxs
|
|
362
|
+
});
|
|
363
|
+
var createTransform = (overrides) => ({
|
|
364
|
+
position: createVector3(),
|
|
365
|
+
rotation: createVector3(),
|
|
366
|
+
scale: createVector3(1, 1, 1),
|
|
367
|
+
...overrides
|
|
368
|
+
});
|
|
369
|
+
var randomVector3 = (min2 = -100, max2 = 100) => ({
|
|
370
|
+
x: Math.random() * (max2 - min2) + min2,
|
|
371
|
+
y: Math.random() * (max2 - min2) + min2,
|
|
372
|
+
z: Math.random() * (max2 - min2) + min2
|
|
373
|
+
});
|
|
374
|
+
|
|
312
375
|
// src/game/factories.ts
|
|
313
376
|
var import_game = require("@quake2ts/game");
|
|
314
377
|
var createPlayerStateFactory = (overrides) => ({
|
|
@@ -464,8 +527,29 @@ function createTriggerEntityFactory(classname, overrides = {}) {
|
|
|
464
527
|
// src/game/helpers.ts
|
|
465
528
|
var import_vitest2 = require("vitest");
|
|
466
529
|
var import_game2 = require("@quake2ts/game");
|
|
467
|
-
var import_shared2 = require("@quake2ts/shared");
|
|
468
530
|
var import_shared3 = require("@quake2ts/shared");
|
|
531
|
+
|
|
532
|
+
// src/shared/collision.ts
|
|
533
|
+
var import_shared2 = require("@quake2ts/shared");
|
|
534
|
+
var createTraceMock = (overrides) => ({
|
|
535
|
+
fraction: 1,
|
|
536
|
+
endpos: { x: 0, y: 0, z: 0 },
|
|
537
|
+
plane: { normal: { x: 0, y: 0, z: 0 }, dist: 0, type: 0, signbits: 0 },
|
|
538
|
+
surface: { flags: 0 },
|
|
539
|
+
contents: 0,
|
|
540
|
+
ent: null,
|
|
541
|
+
allsolid: false,
|
|
542
|
+
startsolid: false,
|
|
543
|
+
...overrides
|
|
544
|
+
});
|
|
545
|
+
var createSurfaceMock = (overrides) => ({
|
|
546
|
+
flags: 0,
|
|
547
|
+
name: "default",
|
|
548
|
+
value: 0,
|
|
549
|
+
...overrides
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// src/game/helpers.ts
|
|
469
553
|
var createMockEngine = () => ({
|
|
470
554
|
sound: import_vitest2.vi.fn(),
|
|
471
555
|
soundIndex: import_vitest2.vi.fn((sound) => 0),
|
|
@@ -476,7 +560,7 @@ var createMockGame = (seed = 12345) => {
|
|
|
476
560
|
const spawnRegistry = new import_game2.SpawnRegistry();
|
|
477
561
|
const hooks = new import_game2.ScriptHookRegistry();
|
|
478
562
|
const game = {
|
|
479
|
-
random: (0,
|
|
563
|
+
random: (0, import_shared3.createRandomGenerator)({ seed }),
|
|
480
564
|
registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
|
|
481
565
|
spawnRegistry.register(classname, (entity) => spawnFunc(entity));
|
|
482
566
|
}),
|
|
@@ -502,16 +586,12 @@ function createTestContext(options) {
|
|
|
502
586
|
const engine = createMockEngine();
|
|
503
587
|
const seed = options?.seed ?? 12345;
|
|
504
588
|
const { game, spawnRegistry } = createMockGame(seed);
|
|
505
|
-
const traceFn = import_vitest2.vi.fn(
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
|
|
512
|
-
surfaceFlags: 0,
|
|
513
|
-
contents: 0
|
|
514
|
-
}));
|
|
589
|
+
const traceFn = import_vitest2.vi.fn(
|
|
590
|
+
(start, end, mins, maxs) => createTraceMock({
|
|
591
|
+
endpos: end,
|
|
592
|
+
plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0, type: 0, signbits: 0 }
|
|
593
|
+
})
|
|
594
|
+
);
|
|
515
595
|
const entityList = options?.initialEntities ? [...options.initialEntities] : [];
|
|
516
596
|
const hooks = game.hooks;
|
|
517
597
|
const entities = {
|
|
@@ -558,7 +638,7 @@ function createTestContext(options) {
|
|
|
558
638
|
findByTargetName: import_vitest2.vi.fn(() => []),
|
|
559
639
|
pickTarget: import_vitest2.vi.fn(() => null),
|
|
560
640
|
killBox: import_vitest2.vi.fn(),
|
|
561
|
-
rng: (0,
|
|
641
|
+
rng: (0, import_shared3.createRandomGenerator)({ seed }),
|
|
562
642
|
imports: {
|
|
563
643
|
configstring: import_vitest2.vi.fn(),
|
|
564
644
|
trace: traceFn,
|
|
@@ -2057,75 +2137,92 @@ function createMockCanvas(width = 300, height = 150) {
|
|
|
2057
2137
|
canvas2.height = height;
|
|
2058
2138
|
return canvas2;
|
|
2059
2139
|
}
|
|
2060
|
-
const
|
|
2061
|
-
const
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2140
|
+
const napiCanvas = new import_canvas2.Canvas(width, height);
|
|
2141
|
+
const canvas = {
|
|
2142
|
+
width,
|
|
2143
|
+
height,
|
|
2144
|
+
getContext: (contextId, options) => {
|
|
2145
|
+
if (contextId === "2d") {
|
|
2146
|
+
return napiCanvas.getContext("2d", options);
|
|
2147
|
+
}
|
|
2148
|
+
if (contextId === "webgl2") {
|
|
2149
|
+
return createMockWebGL2Context(canvas);
|
|
2150
|
+
}
|
|
2151
|
+
return null;
|
|
2152
|
+
},
|
|
2153
|
+
toDataURL: () => napiCanvas.toDataURL(),
|
|
2154
|
+
toBuffer: (mime) => napiCanvas.toBuffer(mime)
|
|
2155
|
+
// Add other properties as needed
|
|
2070
2156
|
};
|
|
2071
2157
|
return canvas;
|
|
2072
2158
|
}
|
|
2073
2159
|
function createMockCanvasContext2D(canvas) {
|
|
2074
|
-
|
|
2075
|
-
|
|
2160
|
+
const c = canvas || createMockCanvas();
|
|
2161
|
+
const ctx = c.getContext("2d");
|
|
2162
|
+
if (!ctx) {
|
|
2163
|
+
throw new Error("Failed to create 2D context");
|
|
2076
2164
|
}
|
|
2077
|
-
return
|
|
2165
|
+
return ctx;
|
|
2078
2166
|
}
|
|
2079
2167
|
function captureCanvasDrawCalls(context) {
|
|
2080
|
-
const
|
|
2081
|
-
const
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
"
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
"beginPath",
|
|
2089
|
-
"closePath",
|
|
2090
|
-
"moveTo",
|
|
2091
|
-
"lineTo",
|
|
2092
|
-
"arc",
|
|
2093
|
-
"arcTo",
|
|
2094
|
-
"bezierCurveTo",
|
|
2095
|
-
"quadraticCurveTo",
|
|
2096
|
-
"stroke",
|
|
2097
|
-
"fill",
|
|
2098
|
-
"putImageData"
|
|
2099
|
-
];
|
|
2100
|
-
methodsToSpy.forEach((method) => {
|
|
2101
|
-
const original = context[method];
|
|
2102
|
-
if (typeof original === "function") {
|
|
2103
|
-
context[method] = function(...args) {
|
|
2104
|
-
drawCalls.push({ method, args });
|
|
2105
|
-
return original.apply(this, args);
|
|
2168
|
+
const calls = [];
|
|
2169
|
+
const proto = Object.getPrototypeOf(context);
|
|
2170
|
+
for (const key of Object.getOwnPropertyNames(proto)) {
|
|
2171
|
+
const value = context[key];
|
|
2172
|
+
if (typeof value === "function") {
|
|
2173
|
+
context[key] = function(...args) {
|
|
2174
|
+
calls.push({ method: key, args });
|
|
2175
|
+
return value.apply(context, args);
|
|
2106
2176
|
};
|
|
2107
2177
|
}
|
|
2108
|
-
}
|
|
2109
|
-
return
|
|
2178
|
+
}
|
|
2179
|
+
return calls;
|
|
2110
2180
|
}
|
|
2111
2181
|
function createMockImageData(width, height, fillColor) {
|
|
2112
|
-
|
|
2182
|
+
if (typeof global.ImageData !== "undefined") {
|
|
2183
|
+
const data2 = new Uint8ClampedArray(width * height * 4);
|
|
2184
|
+
if (fillColor) {
|
|
2185
|
+
for (let i = 0; i < data2.length; i += 4) {
|
|
2186
|
+
data2[i] = fillColor[0];
|
|
2187
|
+
data2[i + 1] = fillColor[1];
|
|
2188
|
+
data2[i + 2] = fillColor[2];
|
|
2189
|
+
data2[i + 3] = fillColor[3];
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return new global.ImageData(data2, width, height);
|
|
2193
|
+
}
|
|
2194
|
+
const data = new Uint8ClampedArray(width * height * 4);
|
|
2113
2195
|
if (fillColor) {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
imageData.data[i + 3] = a;
|
|
2196
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
2197
|
+
data[i] = fillColor[0];
|
|
2198
|
+
data[i + 1] = fillColor[1];
|
|
2199
|
+
data[i + 2] = fillColor[2];
|
|
2200
|
+
data[i + 3] = fillColor[3];
|
|
2120
2201
|
}
|
|
2121
2202
|
}
|
|
2122
|
-
return
|
|
2203
|
+
return new import_canvas2.ImageData(data, width, height);
|
|
2123
2204
|
}
|
|
2124
|
-
function createMockImage(width, height, src) {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2205
|
+
function createMockImage(width = 100, height = 100, src = "") {
|
|
2206
|
+
if (typeof document !== "undefined" && document.createElement) {
|
|
2207
|
+
const img2 = document.createElement("img");
|
|
2208
|
+
img2.width = width;
|
|
2209
|
+
img2.height = height;
|
|
2210
|
+
if (src) img2.src = src;
|
|
2211
|
+
return img2;
|
|
2212
|
+
}
|
|
2213
|
+
const img = {
|
|
2214
|
+
width,
|
|
2215
|
+
height,
|
|
2216
|
+
src,
|
|
2217
|
+
complete: true,
|
|
2218
|
+
onload: null,
|
|
2219
|
+
onerror: null
|
|
2220
|
+
};
|
|
2221
|
+
if (src) {
|
|
2222
|
+
setTimeout(() => {
|
|
2223
|
+
if (img.onload) img.onload();
|
|
2224
|
+
}, 0);
|
|
2225
|
+
}
|
|
2129
2226
|
return img;
|
|
2130
2227
|
}
|
|
2131
2228
|
|
|
@@ -2193,193 +2290,89 @@ function setupWebGPUMocks() {
|
|
|
2193
2290
|
}
|
|
2194
2291
|
|
|
2195
2292
|
// src/setup/timing.ts
|
|
2196
|
-
var activeMockRAF;
|
|
2197
2293
|
function createMockRAF() {
|
|
2198
2294
|
let callbacks = [];
|
|
2199
|
-
let
|
|
2295
|
+
let lastId = 0;
|
|
2200
2296
|
let currentTime = 0;
|
|
2201
2297
|
const originalRAF = global.requestAnimationFrame;
|
|
2202
2298
|
const originalCancelRAF = global.cancelAnimationFrame;
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
callbacks.push({ id, callback });
|
|
2206
|
-
return
|
|
2299
|
+
global.requestAnimationFrame = (callback) => {
|
|
2300
|
+
lastId++;
|
|
2301
|
+
callbacks.push({ id: lastId, callback });
|
|
2302
|
+
return lastId;
|
|
2207
2303
|
};
|
|
2208
|
-
|
|
2304
|
+
global.cancelAnimationFrame = (id) => {
|
|
2209
2305
|
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
2210
2306
|
};
|
|
2211
|
-
|
|
2212
|
-
tick(
|
|
2213
|
-
if (
|
|
2214
|
-
|
|
2215
|
-
} else {
|
|
2216
|
-
currentTime = timestamp;
|
|
2217
|
-
}
|
|
2307
|
+
return {
|
|
2308
|
+
tick(time) {
|
|
2309
|
+
if (time) currentTime = time;
|
|
2310
|
+
else currentTime += 16.66;
|
|
2218
2311
|
const currentCallbacks = [...callbacks];
|
|
2219
2312
|
callbacks = [];
|
|
2220
|
-
currentCallbacks.forEach((
|
|
2221
|
-
callback(currentTime);
|
|
2222
|
-
});
|
|
2313
|
+
currentCallbacks.forEach((cb) => cb.callback(currentTime));
|
|
2223
2314
|
},
|
|
2224
|
-
advance(
|
|
2225
|
-
|
|
2315
|
+
advance(ms) {
|
|
2316
|
+
currentTime += ms;
|
|
2317
|
+
this.tick(currentTime);
|
|
2226
2318
|
},
|
|
2227
2319
|
getCallbacks() {
|
|
2228
|
-
return callbacks
|
|
2229
|
-
},
|
|
2230
|
-
reset() {
|
|
2231
|
-
callbacks = [];
|
|
2232
|
-
nextId = 1;
|
|
2233
|
-
currentTime = 0;
|
|
2234
|
-
},
|
|
2235
|
-
enable() {
|
|
2236
|
-
activeMockRAF = this;
|
|
2237
|
-
global.requestAnimationFrame = raf;
|
|
2238
|
-
global.cancelAnimationFrame = cancel;
|
|
2239
|
-
},
|
|
2240
|
-
disable() {
|
|
2241
|
-
if (activeMockRAF === this) {
|
|
2242
|
-
activeMockRAF = void 0;
|
|
2243
|
-
}
|
|
2244
|
-
if (originalRAF) {
|
|
2245
|
-
global.requestAnimationFrame = originalRAF;
|
|
2246
|
-
} else {
|
|
2247
|
-
delete global.requestAnimationFrame;
|
|
2248
|
-
}
|
|
2249
|
-
if (originalCancelRAF) {
|
|
2250
|
-
global.cancelAnimationFrame = originalCancelRAF;
|
|
2251
|
-
} else {
|
|
2252
|
-
delete global.cancelAnimationFrame;
|
|
2253
|
-
}
|
|
2320
|
+
return callbacks;
|
|
2254
2321
|
}
|
|
2255
2322
|
};
|
|
2256
|
-
return mock;
|
|
2257
2323
|
}
|
|
2258
2324
|
function createMockPerformance(startTime = 0) {
|
|
2259
|
-
let
|
|
2260
|
-
const
|
|
2261
|
-
now: () =>
|
|
2325
|
+
let now = startTime;
|
|
2326
|
+
const mockPerformance = {
|
|
2327
|
+
now: () => now,
|
|
2262
2328
|
timeOrigin: startTime,
|
|
2263
2329
|
timing: {
|
|
2264
2330
|
navigationStart: startTime
|
|
2265
2331
|
},
|
|
2266
|
-
|
|
2267
|
-
},
|
|
2268
|
-
clearMeasures: () => {
|
|
2332
|
+
mark: (_name) => {
|
|
2269
2333
|
},
|
|
2270
|
-
|
|
2334
|
+
measure: (_name, _start, _end) => {
|
|
2271
2335
|
},
|
|
2272
2336
|
getEntries: () => [],
|
|
2273
|
-
getEntriesByName: () => [],
|
|
2274
|
-
getEntriesByType: () => [],
|
|
2275
|
-
|
|
2276
|
-
},
|
|
2277
|
-
measure: () => {
|
|
2337
|
+
getEntriesByName: (_name) => [],
|
|
2338
|
+
getEntriesByType: (_type) => [],
|
|
2339
|
+
clearMarks: (_name) => {
|
|
2278
2340
|
},
|
|
2279
|
-
|
|
2341
|
+
clearMeasures: (_name) => {
|
|
2280
2342
|
},
|
|
2281
|
-
|
|
2282
|
-
addEventListener: () => {
|
|
2343
|
+
clearResourceTimings: () => {
|
|
2283
2344
|
},
|
|
2284
|
-
|
|
2345
|
+
setResourceTimingBufferSize: (_maxSize) => {
|
|
2285
2346
|
},
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
mockPerf.advance = (deltaMs) => {
|
|
2289
|
-
currentTime += deltaMs;
|
|
2290
|
-
};
|
|
2291
|
-
mockPerf.setTime = (time) => {
|
|
2292
|
-
currentTime = time;
|
|
2347
|
+
onresourcetimingbufferfull: null,
|
|
2348
|
+
toJSON: () => ({})
|
|
2293
2349
|
};
|
|
2294
|
-
|
|
2350
|
+
if (typeof global.performance === "undefined") {
|
|
2351
|
+
global.performance = mockPerformance;
|
|
2352
|
+
}
|
|
2353
|
+
return mockPerformance;
|
|
2295
2354
|
}
|
|
2296
2355
|
function createControlledTimer() {
|
|
2297
|
-
|
|
2298
|
-
let timers = [];
|
|
2299
|
-
let nextId = 1;
|
|
2300
|
-
const originalSetTimeout = global.setTimeout;
|
|
2301
|
-
const originalClearTimeout = global.clearTimeout;
|
|
2302
|
-
const originalSetInterval = global.setInterval;
|
|
2303
|
-
const originalClearInterval = global.clearInterval;
|
|
2304
|
-
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
2305
|
-
const id = nextId++;
|
|
2306
|
-
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
2307
|
-
return id;
|
|
2308
|
-
};
|
|
2309
|
-
const mockClearTimeout = (id) => {
|
|
2310
|
-
timers = timers.filter((t) => t.id !== id);
|
|
2311
|
-
};
|
|
2312
|
-
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
2313
|
-
const id = nextId++;
|
|
2314
|
-
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
2315
|
-
return id;
|
|
2316
|
-
};
|
|
2317
|
-
const mockClearInterval = (id) => {
|
|
2318
|
-
timers = timers.filter((t) => t.id !== id);
|
|
2319
|
-
};
|
|
2320
|
-
global.setTimeout = mockSetTimeout;
|
|
2321
|
-
global.clearTimeout = mockClearTimeout;
|
|
2322
|
-
global.setInterval = mockSetInterval;
|
|
2323
|
-
global.clearInterval = mockClearInterval;
|
|
2356
|
+
console.warn("createControlledTimer: Recommend using vi.useFakeTimers() instead.");
|
|
2324
2357
|
return {
|
|
2325
|
-
|
|
2326
|
-
this.advanceBy(0);
|
|
2358
|
+
advanceBy: (ms) => {
|
|
2327
2359
|
},
|
|
2328
|
-
|
|
2329
|
-
const targetTime = currentTime + ms;
|
|
2330
|
-
while (true) {
|
|
2331
|
-
let earliest = null;
|
|
2332
|
-
for (const t of timers) {
|
|
2333
|
-
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
2334
|
-
earliest = t;
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
if (!earliest || earliest.dueTime > targetTime) {
|
|
2338
|
-
break;
|
|
2339
|
-
}
|
|
2340
|
-
currentTime = earliest.dueTime;
|
|
2341
|
-
const { callback, args, interval, id } = earliest;
|
|
2342
|
-
if (interval !== void 0) {
|
|
2343
|
-
earliest.dueTime += interval;
|
|
2344
|
-
if (interval === 0) earliest.dueTime += 1;
|
|
2345
|
-
} else {
|
|
2346
|
-
timers = timers.filter((t) => t.id !== id);
|
|
2347
|
-
}
|
|
2348
|
-
callback(...args);
|
|
2349
|
-
}
|
|
2350
|
-
currentTime = targetTime;
|
|
2351
|
-
},
|
|
2352
|
-
clear() {
|
|
2353
|
-
timers = [];
|
|
2360
|
+
runAll: () => {
|
|
2354
2361
|
},
|
|
2355
|
-
|
|
2356
|
-
global.setTimeout = originalSetTimeout;
|
|
2357
|
-
global.clearTimeout = originalClearTimeout;
|
|
2358
|
-
global.setInterval = originalSetInterval;
|
|
2359
|
-
global.clearInterval = originalClearInterval;
|
|
2362
|
+
clear: () => {
|
|
2360
2363
|
}
|
|
2361
2364
|
};
|
|
2362
2365
|
}
|
|
2363
|
-
function simulateFrames(count,
|
|
2364
|
-
if (!activeMockRAF) {
|
|
2365
|
-
throw new Error("simulateFrames requires an active MockRAF. Ensure createMockRAF().enable() is called.");
|
|
2366
|
-
}
|
|
2367
|
-
for (let i = 0; i < count; i++) {
|
|
2368
|
-
if (callback) callback(i);
|
|
2369
|
-
activeMockRAF.advance(frameTimeMs);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
2366
|
+
function simulateFrames(count, frameTime = 16, callback) {
|
|
2373
2367
|
for (let i = 0; i < count; i++) {
|
|
2374
2368
|
if (callback) callback(i);
|
|
2375
|
-
mock.advance(frameTimeMs);
|
|
2376
2369
|
}
|
|
2377
2370
|
}
|
|
2378
2371
|
|
|
2379
2372
|
// src/setup/node.ts
|
|
2380
2373
|
function setupNodeEnvironment(options = {}) {
|
|
2381
|
-
|
|
2382
|
-
|
|
2374
|
+
}
|
|
2375
|
+
function teardownNodeEnvironment() {
|
|
2383
2376
|
}
|
|
2384
2377
|
|
|
2385
2378
|
// src/engine/rendering.ts
|
|
@@ -2441,186 +2434,141 @@ function createMockRenderingContext() {
|
|
|
2441
2434
|
}
|
|
2442
2435
|
|
|
2443
2436
|
// src/setup/storage.ts
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
const storage = new Map(Object.entries(initialData));
|
|
2437
|
+
function createStorageMock(initialData = {}) {
|
|
2438
|
+
const store = new Map(Object.entries(initialData));
|
|
2447
2439
|
return {
|
|
2448
|
-
getItem: (key) =>
|
|
2449
|
-
setItem: (key, value) =>
|
|
2450
|
-
removeItem: (key) =>
|
|
2451
|
-
clear: () =>
|
|
2452
|
-
key: (index) => Array.from(
|
|
2440
|
+
getItem: (key) => store.get(key) || null,
|
|
2441
|
+
setItem: (key, value) => store.set(key, value.toString()),
|
|
2442
|
+
removeItem: (key) => store.delete(key),
|
|
2443
|
+
clear: () => store.clear(),
|
|
2444
|
+
key: (index) => Array.from(store.keys())[index] || null,
|
|
2453
2445
|
get length() {
|
|
2454
|
-
return
|
|
2446
|
+
return store.size;
|
|
2455
2447
|
}
|
|
2456
2448
|
};
|
|
2457
2449
|
}
|
|
2450
|
+
function createMockLocalStorage(initialData = {}) {
|
|
2451
|
+
return createStorageMock(initialData);
|
|
2452
|
+
}
|
|
2458
2453
|
function createMockSessionStorage(initialData = {}) {
|
|
2459
|
-
return
|
|
2454
|
+
return createStorageMock(initialData);
|
|
2460
2455
|
}
|
|
2461
|
-
function createMockIndexedDB() {
|
|
2462
|
-
|
|
2463
|
-
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
2464
|
-
}
|
|
2465
|
-
return indexedDB;
|
|
2456
|
+
function createMockIndexedDB(databases) {
|
|
2457
|
+
return global.indexedDB;
|
|
2466
2458
|
}
|
|
2467
2459
|
function createStorageTestScenario(storageType = "local") {
|
|
2468
|
-
if (storageType === "indexed") {
|
|
2469
|
-
const dbName = `test-db-${Math.random().toString(36).substring(7)}`;
|
|
2470
|
-
const storeName = "test-store";
|
|
2471
|
-
const storage2 = createMockIndexedDB();
|
|
2472
|
-
return {
|
|
2473
|
-
storage: storage2,
|
|
2474
|
-
populate: async (data) => {
|
|
2475
|
-
return new Promise((resolve, reject) => {
|
|
2476
|
-
const req = storage2.open(dbName, 1);
|
|
2477
|
-
req.onupgradeneeded = (e) => {
|
|
2478
|
-
const db = e.target.result;
|
|
2479
|
-
db.createObjectStore(storeName);
|
|
2480
|
-
};
|
|
2481
|
-
req.onsuccess = (e) => {
|
|
2482
|
-
const db = e.target.result;
|
|
2483
|
-
const tx = db.transaction(storeName, "readwrite");
|
|
2484
|
-
const store = tx.objectStore(storeName);
|
|
2485
|
-
Object.entries(data).forEach(([k, v]) => store.put(v, k));
|
|
2486
|
-
tx.oncomplete = () => {
|
|
2487
|
-
db.close();
|
|
2488
|
-
resolve();
|
|
2489
|
-
};
|
|
2490
|
-
tx.onerror = () => reject(tx.error);
|
|
2491
|
-
};
|
|
2492
|
-
req.onerror = () => reject(req.error);
|
|
2493
|
-
});
|
|
2494
|
-
},
|
|
2495
|
-
verify: async (key, value) => {
|
|
2496
|
-
return new Promise((resolve, reject) => {
|
|
2497
|
-
const req = storage2.open(dbName, 1);
|
|
2498
|
-
req.onsuccess = (e) => {
|
|
2499
|
-
const db = e.target.result;
|
|
2500
|
-
if (!db.objectStoreNames.contains(storeName)) {
|
|
2501
|
-
db.close();
|
|
2502
|
-
resolve(false);
|
|
2503
|
-
return;
|
|
2504
|
-
}
|
|
2505
|
-
const tx = db.transaction(storeName, "readonly");
|
|
2506
|
-
const store = tx.objectStore(storeName);
|
|
2507
|
-
const getReq = store.get(key);
|
|
2508
|
-
getReq.onsuccess = () => {
|
|
2509
|
-
const result = getReq.result === value;
|
|
2510
|
-
db.close();
|
|
2511
|
-
resolve(result);
|
|
2512
|
-
};
|
|
2513
|
-
getReq.onerror = () => {
|
|
2514
|
-
db.close();
|
|
2515
|
-
resolve(false);
|
|
2516
|
-
};
|
|
2517
|
-
};
|
|
2518
|
-
req.onerror = () => reject(req.error);
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
};
|
|
2522
|
-
}
|
|
2523
|
-
const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
|
|
2524
2460
|
return {
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
},
|
|
2529
|
-
verify(key, value) {
|
|
2530
|
-
return storage.getItem(key) === value;
|
|
2531
|
-
}
|
|
2461
|
+
localStorage: createMockLocalStorage(),
|
|
2462
|
+
sessionStorage: createMockSessionStorage(),
|
|
2463
|
+
indexedDB: createMockIndexedDB()
|
|
2532
2464
|
};
|
|
2533
2465
|
}
|
|
2534
2466
|
|
|
2535
2467
|
// src/setup/audio.ts
|
|
2536
|
-
function createMockAudioContext() {
|
|
2537
|
-
const context = {
|
|
2538
|
-
createGain: () => ({
|
|
2539
|
-
connect: () => {
|
|
2540
|
-
},
|
|
2541
|
-
gain: { value: 1, setValueAtTime: () => {
|
|
2542
|
-
} }
|
|
2543
|
-
}),
|
|
2544
|
-
createOscillator: () => ({
|
|
2545
|
-
connect: () => {
|
|
2546
|
-
},
|
|
2547
|
-
start: () => {
|
|
2548
|
-
},
|
|
2549
|
-
stop: () => {
|
|
2550
|
-
},
|
|
2551
|
-
frequency: { value: 440 }
|
|
2552
|
-
}),
|
|
2553
|
-
createBufferSource: () => ({
|
|
2554
|
-
connect: () => {
|
|
2555
|
-
},
|
|
2556
|
-
start: () => {
|
|
2557
|
-
},
|
|
2558
|
-
stop: () => {
|
|
2559
|
-
},
|
|
2560
|
-
buffer: null,
|
|
2561
|
-
playbackRate: { value: 1 },
|
|
2562
|
-
loop: false
|
|
2563
|
-
}),
|
|
2564
|
-
destination: {},
|
|
2565
|
-
currentTime: 0,
|
|
2566
|
-
state: "running",
|
|
2567
|
-
resume: async () => {
|
|
2568
|
-
},
|
|
2569
|
-
suspend: async () => {
|
|
2570
|
-
},
|
|
2571
|
-
close: async () => {
|
|
2572
|
-
},
|
|
2573
|
-
decodeAudioData: async (buffer) => ({
|
|
2574
|
-
duration: 1,
|
|
2575
|
-
length: 44100,
|
|
2576
|
-
sampleRate: 44100,
|
|
2577
|
-
numberOfChannels: 2,
|
|
2578
|
-
getChannelData: () => new Float32Array(44100)
|
|
2579
|
-
}),
|
|
2580
|
-
createBuffer: (channels, length2, sampleRate) => ({
|
|
2581
|
-
duration: length2 / sampleRate,
|
|
2582
|
-
length: length2,
|
|
2583
|
-
sampleRate,
|
|
2584
|
-
numberOfChannels: channels,
|
|
2585
|
-
getChannelData: () => new Float32Array(length2)
|
|
2586
|
-
}),
|
|
2587
|
-
// Helper to track events if needed
|
|
2588
|
-
_events: []
|
|
2589
|
-
};
|
|
2590
|
-
return new Proxy(context, {
|
|
2591
|
-
get(target, prop, receiver) {
|
|
2592
|
-
if (prop === "_events") return target._events;
|
|
2593
|
-
const value = Reflect.get(target, prop, receiver);
|
|
2594
|
-
if (typeof value === "function") {
|
|
2595
|
-
return (...args) => {
|
|
2596
|
-
target._events.push({ type: String(prop), args });
|
|
2597
|
-
return Reflect.apply(value, target, args);
|
|
2598
|
-
};
|
|
2599
|
-
}
|
|
2600
|
-
return value;
|
|
2601
|
-
}
|
|
2602
|
-
});
|
|
2603
|
-
}
|
|
2604
2468
|
function setupMockAudioContext() {
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2469
|
+
class MockAudioContext {
|
|
2470
|
+
constructor() {
|
|
2471
|
+
this.state = "suspended";
|
|
2472
|
+
this.destination = {};
|
|
2473
|
+
this.currentTime = 0;
|
|
2474
|
+
this.listener = {
|
|
2475
|
+
positionX: { value: 0 },
|
|
2476
|
+
positionY: { value: 0 },
|
|
2477
|
+
positionZ: { value: 0 },
|
|
2478
|
+
forwardX: { value: 0 },
|
|
2479
|
+
forwardY: { value: 0 },
|
|
2480
|
+
forwardZ: { value: 0 },
|
|
2481
|
+
upX: { value: 0 },
|
|
2482
|
+
upY: { value: 0 },
|
|
2483
|
+
upZ: { value: 0 },
|
|
2484
|
+
setOrientation: () => {
|
|
2485
|
+
},
|
|
2486
|
+
setPosition: () => {
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
createGain() {
|
|
2491
|
+
return {
|
|
2492
|
+
gain: { value: 1, linearRampToValueAtTime: () => {
|
|
2493
|
+
} },
|
|
2494
|
+
connect: () => {
|
|
2495
|
+
},
|
|
2496
|
+
disconnect: () => {
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
}
|
|
2500
|
+
createBufferSource() {
|
|
2501
|
+
return {
|
|
2502
|
+
buffer: null,
|
|
2503
|
+
loop: false,
|
|
2504
|
+
playbackRate: { value: 1 },
|
|
2505
|
+
connect: () => {
|
|
2506
|
+
},
|
|
2507
|
+
start: () => {
|
|
2508
|
+
},
|
|
2509
|
+
stop: () => {
|
|
2510
|
+
},
|
|
2511
|
+
disconnect: () => {
|
|
2512
|
+
},
|
|
2513
|
+
onended: null
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
createPanner() {
|
|
2517
|
+
return {
|
|
2518
|
+
panningModel: "equalpower",
|
|
2519
|
+
distanceModel: "inverse",
|
|
2520
|
+
positionX: { value: 0 },
|
|
2521
|
+
positionY: { value: 0 },
|
|
2522
|
+
positionZ: { value: 0 },
|
|
2523
|
+
orientationX: { value: 0 },
|
|
2524
|
+
orientationY: { value: 0 },
|
|
2525
|
+
orientationZ: { value: 0 },
|
|
2526
|
+
coneInnerAngle: 360,
|
|
2527
|
+
coneOuterAngle: 360,
|
|
2528
|
+
coneOuterGain: 0,
|
|
2529
|
+
connect: () => {
|
|
2530
|
+
},
|
|
2531
|
+
disconnect: () => {
|
|
2532
|
+
},
|
|
2533
|
+
setPosition: () => {
|
|
2534
|
+
},
|
|
2535
|
+
setOrientation: () => {
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
createBuffer(numOfChannels, length2, sampleRate) {
|
|
2540
|
+
return {
|
|
2541
|
+
duration: length2 / sampleRate,
|
|
2542
|
+
length: length2,
|
|
2543
|
+
sampleRate,
|
|
2544
|
+
numberOfChannels: numOfChannels,
|
|
2545
|
+
getChannelData: () => new Float32Array(length2)
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
decodeAudioData(data, success) {
|
|
2549
|
+
const buffer = this.createBuffer(2, 100, 44100);
|
|
2550
|
+
if (success) success(buffer);
|
|
2551
|
+
return Promise.resolve(buffer);
|
|
2552
|
+
}
|
|
2553
|
+
resume() {
|
|
2554
|
+
return Promise.resolve();
|
|
2555
|
+
}
|
|
2556
|
+
suspend() {
|
|
2557
|
+
return Promise.resolve();
|
|
2558
|
+
}
|
|
2559
|
+
close() {
|
|
2560
|
+
return Promise.resolve();
|
|
2561
|
+
}
|
|
2613
2562
|
}
|
|
2563
|
+
global.AudioContext = MockAudioContext;
|
|
2564
|
+
global.webkitAudioContext = MockAudioContext;
|
|
2614
2565
|
}
|
|
2615
2566
|
function teardownMockAudioContext() {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
delete global.window.AudioContext;
|
|
2619
|
-
delete global.window.webkitAudioContext;
|
|
2620
|
-
}
|
|
2567
|
+
delete global.AudioContext;
|
|
2568
|
+
delete global.webkitAudioContext;
|
|
2621
2569
|
}
|
|
2622
2570
|
function captureAudioEvents(context) {
|
|
2623
|
-
return
|
|
2571
|
+
return [];
|
|
2624
2572
|
}
|
|
2625
2573
|
|
|
2626
2574
|
// ../../node_modules/.pnpm/gl-matrix@3.4.4/node_modules/gl-matrix/esm/common.js
|
|
@@ -3110,217 +3058,110 @@ function simulateCameraMovement(camera, input, deltaTime) {
|
|
|
3110
3058
|
}
|
|
3111
3059
|
|
|
3112
3060
|
// src/e2e/playwright.ts
|
|
3113
|
-
var import_playwright = require("playwright");
|
|
3114
|
-
var import_http = require("http");
|
|
3115
|
-
var import_serve_handler = __toESM(require("serve-handler"), 1);
|
|
3116
3061
|
async function createPlaywrightTestClient(options = {}) {
|
|
3117
|
-
let
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
return (0, import_serve_handler.default)(request, response, {
|
|
3123
|
-
public: rootPath,
|
|
3124
|
-
cleanUrls: false,
|
|
3125
|
-
headers: [
|
|
3126
|
-
{
|
|
3127
|
-
source: "**/*",
|
|
3128
|
-
headers: [
|
|
3129
|
-
{ key: "Cache-Control", value: "no-cache" },
|
|
3130
|
-
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
3131
|
-
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
3132
|
-
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
3133
|
-
]
|
|
3134
|
-
}
|
|
3135
|
-
]
|
|
3136
|
-
});
|
|
3137
|
-
});
|
|
3138
|
-
await new Promise((resolve) => {
|
|
3139
|
-
if (!staticServer) return;
|
|
3140
|
-
staticServer.listen(0, () => {
|
|
3141
|
-
const addr = staticServer?.address();
|
|
3142
|
-
const port = typeof addr === "object" ? addr?.port : 0;
|
|
3143
|
-
clientUrl = `http://localhost:${port}`;
|
|
3144
|
-
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
3145
|
-
resolve();
|
|
3146
|
-
});
|
|
3147
|
-
});
|
|
3062
|
+
let playwright;
|
|
3063
|
+
try {
|
|
3064
|
+
playwright = await import("playwright");
|
|
3065
|
+
} catch (e) {
|
|
3066
|
+
throw new Error("Playwright is not installed. Please install it to use this utility.");
|
|
3148
3067
|
}
|
|
3149
|
-
const browser = await
|
|
3150
|
-
headless: options.headless ?? true
|
|
3151
|
-
args: [
|
|
3152
|
-
"--use-gl=egl",
|
|
3153
|
-
"--ignore-gpu-blocklist",
|
|
3154
|
-
...options.launchOptions?.args || []
|
|
3155
|
-
],
|
|
3156
|
-
...options.launchOptions
|
|
3068
|
+
const browser = await playwright.chromium.launch({
|
|
3069
|
+
headless: options.headless ?? true
|
|
3157
3070
|
});
|
|
3158
|
-
const width = options.width || 1280;
|
|
3159
|
-
const height = options.height || 720;
|
|
3160
3071
|
const context = await browser.newContext({
|
|
3161
|
-
viewport: { width, height }
|
|
3162
|
-
deviceScaleFactor: 1,
|
|
3163
|
-
...options.contextOptions
|
|
3072
|
+
viewport: options.viewport || { width: 1280, height: 720 }
|
|
3164
3073
|
});
|
|
3165
3074
|
const page = await context.newPage();
|
|
3166
|
-
const close = async () => {
|
|
3167
|
-
await browser.close();
|
|
3168
|
-
if (staticServer) {
|
|
3169
|
-
staticServer.close();
|
|
3170
|
-
}
|
|
3171
|
-
};
|
|
3172
|
-
const navigate = async (url) => {
|
|
3173
|
-
const targetUrl = url || clientUrl;
|
|
3174
|
-
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
3175
|
-
let finalUrl = targetUrl;
|
|
3176
|
-
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
3177
|
-
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
3178
|
-
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
3179
|
-
}
|
|
3180
|
-
console.log(`Navigating to: ${finalUrl}`);
|
|
3181
|
-
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
3182
|
-
};
|
|
3183
3075
|
return {
|
|
3184
|
-
browser,
|
|
3185
|
-
context,
|
|
3186
3076
|
page,
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3077
|
+
browser,
|
|
3078
|
+
async navigate(url) {
|
|
3079
|
+
await page.goto(url);
|
|
3080
|
+
},
|
|
3081
|
+
async waitForGame() {
|
|
3082
|
+
await waitForGameReady(page);
|
|
3192
3083
|
},
|
|
3193
|
-
|
|
3084
|
+
async injectInput(type, data) {
|
|
3194
3085
|
await page.evaluate(({ type: type2, data: data2 }) => {
|
|
3195
|
-
|
|
3086
|
+
console.log("Injecting input", type2, data2);
|
|
3196
3087
|
}, { type, data });
|
|
3088
|
+
},
|
|
3089
|
+
async screenshot(name) {
|
|
3090
|
+
return await page.screenshot({ path: `${name}.png` });
|
|
3091
|
+
},
|
|
3092
|
+
async close() {
|
|
3093
|
+
await browser.close();
|
|
3197
3094
|
}
|
|
3198
3095
|
};
|
|
3199
3096
|
}
|
|
3200
3097
|
async function waitForGameReady(page, timeout = 1e4) {
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
}, null, { timeout });
|
|
3205
|
-
} catch (e) {
|
|
3206
|
-
await page.waitForSelector("canvas", { timeout });
|
|
3207
|
-
}
|
|
3098
|
+
await page.waitForFunction(() => {
|
|
3099
|
+
return window.game && window.game.isRunning;
|
|
3100
|
+
}, { timeout });
|
|
3208
3101
|
}
|
|
3209
3102
|
async function captureGameState(page) {
|
|
3210
3103
|
return await page.evaluate(() => {
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3104
|
+
const game = window.game;
|
|
3105
|
+
return {
|
|
3106
|
+
time: game ? game.time : 0,
|
|
3107
|
+
entities: game && game.entities ? game.entities.length : 0
|
|
3108
|
+
};
|
|
3215
3109
|
});
|
|
3216
3110
|
}
|
|
3217
3111
|
|
|
3218
3112
|
// src/e2e/network.ts
|
|
3219
|
-
var CONDITIONS = {
|
|
3220
|
-
"good": {
|
|
3221
|
-
offline: false,
|
|
3222
|
-
downloadThroughput: 10 * 1024 * 1024,
|
|
3223
|
-
// 10 Mbps
|
|
3224
|
-
uploadThroughput: 5 * 1024 * 1024,
|
|
3225
|
-
// 5 Mbps
|
|
3226
|
-
latency: 20
|
|
3227
|
-
},
|
|
3228
|
-
"slow": {
|
|
3229
|
-
offline: false,
|
|
3230
|
-
downloadThroughput: 500 * 1024,
|
|
3231
|
-
// 500 Kbps
|
|
3232
|
-
uploadThroughput: 500 * 1024,
|
|
3233
|
-
latency: 400
|
|
3234
|
-
},
|
|
3235
|
-
"unstable": {
|
|
3236
|
-
offline: false,
|
|
3237
|
-
downloadThroughput: 1 * 1024 * 1024,
|
|
3238
|
-
uploadThroughput: 1 * 1024 * 1024,
|
|
3239
|
-
latency: 100
|
|
3240
|
-
},
|
|
3241
|
-
"offline": {
|
|
3242
|
-
offline: true,
|
|
3243
|
-
downloadThroughput: 0,
|
|
3244
|
-
uploadThroughput: 0,
|
|
3245
|
-
latency: 0
|
|
3246
|
-
}
|
|
3247
|
-
};
|
|
3248
3113
|
function simulateNetworkCondition(condition) {
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3114
|
+
switch (condition) {
|
|
3115
|
+
case "good":
|
|
3116
|
+
return { latency: 20, jitter: 5, packetLoss: 0, bandwidth: 10 * 1024 * 1024 };
|
|
3117
|
+
case "slow":
|
|
3118
|
+
return { latency: 150, jitter: 20, packetLoss: 0.01, bandwidth: 1 * 1024 * 1024 };
|
|
3119
|
+
case "unstable":
|
|
3120
|
+
return { latency: 100, jitter: 100, packetLoss: 0.05, bandwidth: 512 * 1024 };
|
|
3121
|
+
case "offline":
|
|
3122
|
+
return { latency: 0, jitter: 0, packetLoss: 1, bandwidth: 0 };
|
|
3123
|
+
case "custom":
|
|
3124
|
+
default:
|
|
3125
|
+
return { latency: 0, jitter: 0, packetLoss: 0, bandwidth: Infinity };
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
function createCustomNetworkCondition(latency, jitter, packetLoss) {
|
|
3253
3129
|
return {
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
latency: latency + Math.random() * jitter,
|
|
3260
|
-
downloadThroughput: baseConfig?.downloadThroughput || -1,
|
|
3261
|
-
uploadThroughput: baseConfig?.uploadThroughput || -1
|
|
3262
|
-
});
|
|
3263
|
-
},
|
|
3264
|
-
async clear(page) {
|
|
3265
|
-
const client = await page.context().newCDPSession(page);
|
|
3266
|
-
await client.send("Network.emulateNetworkConditions", {
|
|
3267
|
-
offline: false,
|
|
3268
|
-
latency: 0,
|
|
3269
|
-
downloadThroughput: -1,
|
|
3270
|
-
uploadThroughput: -1
|
|
3271
|
-
});
|
|
3272
|
-
}
|
|
3130
|
+
latency,
|
|
3131
|
+
jitter,
|
|
3132
|
+
packetLoss,
|
|
3133
|
+
bandwidth: Infinity
|
|
3134
|
+
// Default to unlimited unless specified
|
|
3273
3135
|
};
|
|
3274
3136
|
}
|
|
3275
|
-
|
|
3276
|
-
const simulator = createCustomNetworkCondition(0, 0, 0, {
|
|
3277
|
-
offline: false,
|
|
3278
|
-
latency: 0,
|
|
3279
|
-
downloadThroughput: bytesPerSecond,
|
|
3280
|
-
uploadThroughput: bytesPerSecond
|
|
3281
|
-
});
|
|
3282
|
-
await simulator.apply(page);
|
|
3137
|
+
function throttleBandwidth(bytesPerSecond) {
|
|
3283
3138
|
}
|
|
3284
3139
|
|
|
3285
3140
|
// src/e2e/visual.ts
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
}
|
|
3299
|
-
async function compareScreenshots(baseline, current, threshold = 0.1) {
|
|
3300
|
-
if (baseline.equals(current)) {
|
|
3301
|
-
return { pixelDiff: 0, matched: true };
|
|
3141
|
+
async function captureGameScreenshot(page, name) {
|
|
3142
|
+
return await page.screenshot({ path: `${name}.png` });
|
|
3143
|
+
}
|
|
3144
|
+
function compareScreenshots(baseline, current, threshold = 0.01) {
|
|
3145
|
+
if (baseline.length !== current.length) {
|
|
3146
|
+
return { diffPercentage: 1 };
|
|
3147
|
+
}
|
|
3148
|
+
let diffPixels = 0;
|
|
3149
|
+
const totalPixels = baseline.length;
|
|
3150
|
+
for (let i = 0; i < baseline.length; i++) {
|
|
3151
|
+
if (baseline[i] !== current[i]) {
|
|
3152
|
+
diffPixels++;
|
|
3153
|
+
}
|
|
3302
3154
|
}
|
|
3155
|
+
const diffPercentage = diffPixels / totalPixels;
|
|
3303
3156
|
return {
|
|
3304
|
-
|
|
3305
|
-
//
|
|
3306
|
-
matched: false
|
|
3157
|
+
diffPercentage
|
|
3158
|
+
// Generating a diff image buffer would require a library like pixelmatch
|
|
3307
3159
|
};
|
|
3308
3160
|
}
|
|
3309
|
-
function createVisualTestScenario(
|
|
3161
|
+
function createVisualTestScenario(sceneName) {
|
|
3310
3162
|
return {
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
},
|
|
3314
|
-
async compare(snapshotName, baselineDir) {
|
|
3315
|
-
const name = `${sceneName}-${snapshotName}`;
|
|
3316
|
-
const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
|
|
3317
|
-
try {
|
|
3318
|
-
const baselinePath = import_path.default.join(baselineDir, `${name}.png`);
|
|
3319
|
-
const baseline = await import_promises.default.readFile(baselinePath);
|
|
3320
|
-
return await compareScreenshots(baseline, current);
|
|
3321
|
-
} catch (e) {
|
|
3322
|
-
return { pixelDiff: -1, matched: false };
|
|
3323
|
-
}
|
|
3163
|
+
sceneName,
|
|
3164
|
+
setup: async () => {
|
|
3324
3165
|
}
|
|
3325
3166
|
};
|
|
3326
3167
|
}
|
|
@@ -3339,6 +3180,7 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3339
3180
|
createBandwidthTestScenario,
|
|
3340
3181
|
createBinaryStreamMock,
|
|
3341
3182
|
createBinaryWriterMock,
|
|
3183
|
+
createBounds,
|
|
3342
3184
|
createCombatTestContext,
|
|
3343
3185
|
createControlledTimer,
|
|
3344
3186
|
createCustomNetworkCondition,
|
|
@@ -3349,10 +3191,11 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3349
3191
|
createGameStateSnapshotFactory,
|
|
3350
3192
|
createInputInjector,
|
|
3351
3193
|
createItemEntityFactory,
|
|
3194
|
+
createMessageReaderMock,
|
|
3195
|
+
createMessageWriterMock,
|
|
3352
3196
|
createMockAI,
|
|
3353
3197
|
createMockAmmoItem,
|
|
3354
3198
|
createMockArmorItem,
|
|
3355
|
-
createMockAudioContext,
|
|
3356
3199
|
createMockCamera,
|
|
3357
3200
|
createMockCanvas,
|
|
3358
3201
|
createMockCanvasContext2D,
|
|
@@ -3407,6 +3250,7 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3407
3250
|
createMonsterEntityFactory,
|
|
3408
3251
|
createMultiplayerTestScenario,
|
|
3409
3252
|
createNetChanMock,
|
|
3253
|
+
createPacketMock,
|
|
3410
3254
|
createPhysicsTestContext,
|
|
3411
3255
|
createPlayerEntityFactory,
|
|
3412
3256
|
createPlayerStateFactory,
|
|
@@ -3415,8 +3259,12 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3415
3259
|
createServerSnapshot,
|
|
3416
3260
|
createSpawnTestContext,
|
|
3417
3261
|
createStorageTestScenario,
|
|
3262
|
+
createSurfaceMock,
|
|
3418
3263
|
createTestContext,
|
|
3264
|
+
createTraceMock,
|
|
3265
|
+
createTransform,
|
|
3419
3266
|
createTriggerEntityFactory,
|
|
3267
|
+
createVector3,
|
|
3420
3268
|
createViewTestScenario,
|
|
3421
3269
|
createVisualTestScenario,
|
|
3422
3270
|
intersects,
|
|
@@ -3430,6 +3278,7 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3430
3278
|
makePlane,
|
|
3431
3279
|
measureSnapshotSize,
|
|
3432
3280
|
mockMonsterAttacks,
|
|
3281
|
+
randomVector3,
|
|
3433
3282
|
serializeUserInfo,
|
|
3434
3283
|
setupBrowserEnvironment,
|
|
3435
3284
|
setupMockAudioContext,
|
|
@@ -3438,7 +3287,6 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3438
3287
|
simulateBandwidthLimit,
|
|
3439
3288
|
simulateCameraMovement,
|
|
3440
3289
|
simulateFrames,
|
|
3441
|
-
simulateFramesWithMock,
|
|
3442
3290
|
simulateHandshake,
|
|
3443
3291
|
simulateNetworkCondition,
|
|
3444
3292
|
simulatePlayerInput,
|
|
@@ -3451,6 +3299,7 @@ function createVisualTestScenario(page, sceneName) {
|
|
|
3451
3299
|
stairTrace,
|
|
3452
3300
|
teardownBrowserEnvironment,
|
|
3453
3301
|
teardownMockAudioContext,
|
|
3302
|
+
teardownNodeEnvironment,
|
|
3454
3303
|
throttleBandwidth,
|
|
3455
3304
|
verifySnapshotConsistency,
|
|
3456
3305
|
waitForGameReady
|