quake2ts 0.0.557 → 0.0.562
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 +3 -1
- package/packages/client/dist/browser/index.global.js +15 -15
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +343 -1
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +343 -1
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/client/dist/types/index.d.ts.map +1 -1
- package/packages/client/dist/types/net/connection.d.ts +2 -0
- package/packages/client/dist/types/net/connection.d.ts.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/render/bloom.d.ts +19 -0
- package/packages/engine/dist/types/render/bloom.d.ts.map +1 -0
- package/packages/engine/dist/types/render/frame.d.ts +2 -0
- package/packages/engine/dist/types/render/frame.d.ts.map +1 -1
- package/packages/engine/dist/types/render/renderer.d.ts +2 -0
- package/packages/engine/dist/types/render/renderer.d.ts.map +1 -1
- package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/server/dist/client.d.ts +51 -0
- package/packages/server/dist/client.js +100 -0
- package/packages/server/dist/dedicated.d.ts +69 -0
- package/packages/server/dist/dedicated.js +1013 -0
- package/packages/server/dist/index.cjs +27 -2
- package/packages/server/dist/index.d.ts +7 -161
- package/packages/server/dist/index.js +26 -2
- package/packages/server/dist/net/nodeWsDriver.d.ts +16 -0
- package/packages/server/dist/net/nodeWsDriver.js +122 -0
- package/packages/server/dist/protocol/player.d.ts +23 -0
- package/packages/server/dist/protocol/player.js +137 -0
- package/packages/server/dist/protocol/write.d.ts +7 -0
- package/packages/server/dist/protocol/write.js +167 -0
- package/packages/server/dist/protocol.d.ts +17 -0
- package/packages/server/dist/protocol.js +71 -0
- package/packages/server/dist/server.d.ts +50 -0
- package/packages/server/dist/server.js +12 -0
- package/packages/server/dist/server.test.d.ts +1 -0
- package/packages/server/dist/server.test.js +69 -0
- package/packages/server/dist/transport.d.ts +7 -0
- package/packages/server/dist/transport.js +1 -0
- package/packages/server/dist/transports/websocket.d.ts +11 -0
- package/packages/server/dist/transports/websocket.js +38 -0
- package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/test-utils/dist/index.cjs +498 -284
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +215 -146
- package/packages/test-utils/dist/index.d.ts +215 -146
- package/packages/test-utils/dist/index.js +488 -278
- package/packages/test-utils/dist/index.js.map +1 -1
- package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/server/dist/index.d.cts +0 -161
|
@@ -391,6 +391,218 @@ function createEntity() {
|
|
|
391
391
|
return new Entity(1);
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
// src/game/mocks.ts
|
|
395
|
+
function createMockGameState(overrides) {
|
|
396
|
+
return {
|
|
397
|
+
levelName: "test_level",
|
|
398
|
+
time: 0,
|
|
399
|
+
entities: [],
|
|
400
|
+
clients: [],
|
|
401
|
+
...overrides
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// src/server/mocks/transport.ts
|
|
406
|
+
import { vi as vi3 } from "vitest";
|
|
407
|
+
var MockTransport = class {
|
|
408
|
+
constructor() {
|
|
409
|
+
this.address = "127.0.0.1";
|
|
410
|
+
this.port = 27910;
|
|
411
|
+
this.sentMessages = [];
|
|
412
|
+
this.receivedMessages = [];
|
|
413
|
+
this.listening = false;
|
|
414
|
+
this.listenSpy = vi3.fn().mockImplementation(async (port) => {
|
|
415
|
+
this.port = port;
|
|
416
|
+
this.listening = true;
|
|
417
|
+
});
|
|
418
|
+
this.closeSpy = vi3.fn().mockImplementation(() => {
|
|
419
|
+
this.listening = false;
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Start listening on the specified port.
|
|
424
|
+
*/
|
|
425
|
+
async listen(port) {
|
|
426
|
+
return this.listenSpy(port);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Close the transport.
|
|
430
|
+
*/
|
|
431
|
+
close() {
|
|
432
|
+
this.closeSpy();
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Register a callback for new connections.
|
|
436
|
+
*/
|
|
437
|
+
onConnection(callback) {
|
|
438
|
+
this.onConnectionCallback = callback;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Register a callback for errors.
|
|
442
|
+
*/
|
|
443
|
+
onError(callback) {
|
|
444
|
+
this.onErrorCallback = callback;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Check if the transport is currently listening.
|
|
448
|
+
*/
|
|
449
|
+
isListening() {
|
|
450
|
+
return this.listening;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Helper to simulate a new connection.
|
|
454
|
+
* @param driver The network driver for the connection.
|
|
455
|
+
* @param info Optional connection info.
|
|
456
|
+
*/
|
|
457
|
+
simulateConnection(driver, info) {
|
|
458
|
+
if (this.onConnectionCallback) {
|
|
459
|
+
this.onConnectionCallback(driver, info);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Helper to simulate an error.
|
|
464
|
+
* @param error The error to simulate.
|
|
465
|
+
*/
|
|
466
|
+
simulateError(error) {
|
|
467
|
+
if (this.onErrorCallback) {
|
|
468
|
+
this.onErrorCallback(error);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
function createMockUDPSocket(overrides) {
|
|
473
|
+
const socket = {
|
|
474
|
+
send: vi3.fn(),
|
|
475
|
+
on: vi3.fn(),
|
|
476
|
+
close: vi3.fn(),
|
|
477
|
+
bind: vi3.fn(),
|
|
478
|
+
address: vi3.fn().mockReturnValue({ address: "127.0.0.1", family: "IPv4", port: 0 }),
|
|
479
|
+
...overrides
|
|
480
|
+
};
|
|
481
|
+
return socket;
|
|
482
|
+
}
|
|
483
|
+
function createMockNetworkAddress(ip = "127.0.0.1", port = 27910) {
|
|
484
|
+
return { ip, port };
|
|
485
|
+
}
|
|
486
|
+
function createMockTransport(address = "127.0.0.1", port = 27910, overrides) {
|
|
487
|
+
const transport = new MockTransport();
|
|
488
|
+
transport.address = address;
|
|
489
|
+
transport.port = port;
|
|
490
|
+
Object.assign(transport, overrides);
|
|
491
|
+
return transport;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/server/mocks/state.ts
|
|
495
|
+
import { ServerState, ClientState } from "@quake2ts/server";
|
|
496
|
+
import { MAX_CONFIGSTRINGS, MAX_EDICTS } from "@quake2ts/shared";
|
|
497
|
+
import { vi as vi4 } from "vitest";
|
|
498
|
+
function createMockServerState(overrides) {
|
|
499
|
+
return {
|
|
500
|
+
state: ServerState.Game,
|
|
501
|
+
attractLoop: false,
|
|
502
|
+
loadGame: false,
|
|
503
|
+
startTime: Date.now(),
|
|
504
|
+
time: 0,
|
|
505
|
+
frame: 0,
|
|
506
|
+
name: "test_map",
|
|
507
|
+
collisionModel: null,
|
|
508
|
+
configStrings: new Array(MAX_CONFIGSTRINGS).fill(""),
|
|
509
|
+
baselines: new Array(MAX_EDICTS).fill(null),
|
|
510
|
+
multicastBuf: new Uint8Array(0),
|
|
511
|
+
...overrides
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function createMockServerStatic(maxClients = 16, overrides) {
|
|
515
|
+
return {
|
|
516
|
+
initialized: true,
|
|
517
|
+
realTime: Date.now(),
|
|
518
|
+
mapCmd: "",
|
|
519
|
+
spawnCount: 1,
|
|
520
|
+
clients: new Array(maxClients).fill(null),
|
|
521
|
+
lastHeartbeat: 0,
|
|
522
|
+
challenges: [],
|
|
523
|
+
...overrides
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function createMockServerClient(clientNum, overrides) {
|
|
527
|
+
const mockNet = {
|
|
528
|
+
connect: vi4.fn(),
|
|
529
|
+
disconnect: vi4.fn(),
|
|
530
|
+
send: vi4.fn(),
|
|
531
|
+
onMessage: vi4.fn(),
|
|
532
|
+
onClose: vi4.fn(),
|
|
533
|
+
onError: vi4.fn(),
|
|
534
|
+
isConnected: vi4.fn().mockReturnValue(true)
|
|
535
|
+
};
|
|
536
|
+
return {
|
|
537
|
+
index: clientNum,
|
|
538
|
+
state: ClientState.Connected,
|
|
539
|
+
edict: { index: clientNum + 1 },
|
|
540
|
+
net: mockNet,
|
|
541
|
+
netchan: {
|
|
542
|
+
qport: 0,
|
|
543
|
+
remoteAddress: "127.0.0.1",
|
|
544
|
+
incomingSequence: 0,
|
|
545
|
+
outgoingSequence: 0,
|
|
546
|
+
lastReceived: 0,
|
|
547
|
+
process: vi4.fn(),
|
|
548
|
+
transmit: vi4.fn(),
|
|
549
|
+
writeReliableByte: vi4.fn(),
|
|
550
|
+
writeReliableShort: vi4.fn(),
|
|
551
|
+
writeReliableLong: vi4.fn(),
|
|
552
|
+
writeReliableString: vi4.fn(),
|
|
553
|
+
writeReliableData: vi4.fn()
|
|
554
|
+
},
|
|
555
|
+
// Cast as any because NetChan might be complex to fully mock here
|
|
556
|
+
userInfo: "",
|
|
557
|
+
lastMessage: 0,
|
|
558
|
+
lastCommandTime: 0,
|
|
559
|
+
commandCount: 0,
|
|
560
|
+
messageQueue: [],
|
|
561
|
+
frames: [],
|
|
562
|
+
lastFrame: 0,
|
|
563
|
+
lastPacketEntities: [],
|
|
564
|
+
challenge: 0,
|
|
565
|
+
lastConnect: 0,
|
|
566
|
+
ping: 0,
|
|
567
|
+
rate: 0,
|
|
568
|
+
name: `Client${clientNum}`,
|
|
569
|
+
messageLevel: 0,
|
|
570
|
+
datagram: new Uint8Array(0),
|
|
571
|
+
downloadSize: 0,
|
|
572
|
+
downloadCount: 0,
|
|
573
|
+
commandMsec: 0,
|
|
574
|
+
frameLatency: [],
|
|
575
|
+
messageSize: [],
|
|
576
|
+
suppressCount: 0,
|
|
577
|
+
commandQueue: [],
|
|
578
|
+
lastCmd: {
|
|
579
|
+
msec: 0,
|
|
580
|
+
buttons: 0,
|
|
581
|
+
angles: { x: 0, y: 0, z: 0 },
|
|
582
|
+
forwardmove: 0,
|
|
583
|
+
sidemove: 0,
|
|
584
|
+
upmove: 0,
|
|
585
|
+
sequence: 0,
|
|
586
|
+
lightlevel: 0,
|
|
587
|
+
impulse: 0,
|
|
588
|
+
serverFrame: 0
|
|
589
|
+
},
|
|
590
|
+
...overrides
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function createMockServer(overrides) {
|
|
594
|
+
return {
|
|
595
|
+
start: vi4.fn().mockResolvedValue(void 0),
|
|
596
|
+
stop: vi4.fn(),
|
|
597
|
+
multicast: vi4.fn(),
|
|
598
|
+
unicast: vi4.fn(),
|
|
599
|
+
configstring: vi4.fn(),
|
|
600
|
+
kickPlayer: vi4.fn(),
|
|
601
|
+
changeMap: vi4.fn().mockResolvedValue(void 0),
|
|
602
|
+
...overrides
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
|
|
394
606
|
// src/setup/browser.ts
|
|
395
607
|
import { JSDOM } from "jsdom";
|
|
396
608
|
import { Canvas, Image, ImageData } from "@napi-rs/canvas";
|
|
@@ -786,12 +998,6 @@ function teardownBrowserEnvironment() {
|
|
|
786
998
|
delete global.UIEvent;
|
|
787
999
|
}
|
|
788
1000
|
|
|
789
|
-
// src/setup/node.ts
|
|
790
|
-
function setupNodeEnvironment() {
|
|
791
|
-
if (typeof global.fetch === "undefined") {
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
|
|
795
1001
|
// src/setup/canvas.ts
|
|
796
1002
|
import { Canvas as Canvas2, Image as Image2, ImageData as ImageData2 } from "@napi-rs/canvas";
|
|
797
1003
|
function createMockCanvas(width = 300, height = 150) {
|
|
@@ -873,111 +1079,9 @@ function createMockImage(width, height, src) {
|
|
|
873
1079
|
return img;
|
|
874
1080
|
}
|
|
875
1081
|
|
|
876
|
-
// src/setup/
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
let nextId = 1;
|
|
880
|
-
let currentTime = 0;
|
|
881
|
-
const originalRAF = global.requestAnimationFrame;
|
|
882
|
-
const originalCancelRAF = global.cancelAnimationFrame;
|
|
883
|
-
const raf = (callback) => {
|
|
884
|
-
const id = nextId++;
|
|
885
|
-
callbacks.push({ id, callback });
|
|
886
|
-
return id;
|
|
887
|
-
};
|
|
888
|
-
const cancel = (id) => {
|
|
889
|
-
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
890
|
-
};
|
|
891
|
-
const mock = {
|
|
892
|
-
tick(timestamp) {
|
|
893
|
-
if (typeof timestamp !== "number") {
|
|
894
|
-
currentTime += 16.6;
|
|
895
|
-
} else {
|
|
896
|
-
currentTime = timestamp;
|
|
897
|
-
}
|
|
898
|
-
const currentCallbacks = [...callbacks];
|
|
899
|
-
callbacks = [];
|
|
900
|
-
currentCallbacks.forEach(({ callback }) => {
|
|
901
|
-
callback(currentTime);
|
|
902
|
-
});
|
|
903
|
-
},
|
|
904
|
-
advance(deltaMs = 16.6) {
|
|
905
|
-
this.tick(currentTime + deltaMs);
|
|
906
|
-
},
|
|
907
|
-
getCallbacks() {
|
|
908
|
-
return callbacks.map((c) => c.callback);
|
|
909
|
-
},
|
|
910
|
-
reset() {
|
|
911
|
-
callbacks = [];
|
|
912
|
-
nextId = 1;
|
|
913
|
-
currentTime = 0;
|
|
914
|
-
},
|
|
915
|
-
enable() {
|
|
916
|
-
global.requestAnimationFrame = raf;
|
|
917
|
-
global.cancelAnimationFrame = cancel;
|
|
918
|
-
},
|
|
919
|
-
disable() {
|
|
920
|
-
if (originalRAF) {
|
|
921
|
-
global.requestAnimationFrame = originalRAF;
|
|
922
|
-
} else {
|
|
923
|
-
delete global.requestAnimationFrame;
|
|
924
|
-
}
|
|
925
|
-
if (originalCancelRAF) {
|
|
926
|
-
global.cancelAnimationFrame = originalCancelRAF;
|
|
927
|
-
} else {
|
|
928
|
-
delete global.cancelAnimationFrame;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
};
|
|
932
|
-
return mock;
|
|
933
|
-
}
|
|
934
|
-
function createMockPerformance(startTime = 0) {
|
|
935
|
-
let currentTime = startTime;
|
|
936
|
-
const mockPerf = {
|
|
937
|
-
now: () => currentTime,
|
|
938
|
-
timeOrigin: startTime,
|
|
939
|
-
timing: {
|
|
940
|
-
navigationStart: startTime
|
|
941
|
-
},
|
|
942
|
-
// Add minimal navigation/resource timing interfaces to satisfy types if needed
|
|
943
|
-
clearMarks: () => {
|
|
944
|
-
},
|
|
945
|
-
clearMeasures: () => {
|
|
946
|
-
},
|
|
947
|
-
clearResourceTimings: () => {
|
|
948
|
-
},
|
|
949
|
-
getEntries: () => [],
|
|
950
|
-
getEntriesByName: () => [],
|
|
951
|
-
getEntriesByType: () => [],
|
|
952
|
-
mark: () => {
|
|
953
|
-
},
|
|
954
|
-
measure: () => {
|
|
955
|
-
},
|
|
956
|
-
setResourceTimingBufferSize: () => {
|
|
957
|
-
},
|
|
958
|
-
toJSON: () => ({}),
|
|
959
|
-
addEventListener: () => {
|
|
960
|
-
},
|
|
961
|
-
removeEventListener: () => {
|
|
962
|
-
},
|
|
963
|
-
dispatchEvent: () => true
|
|
964
|
-
};
|
|
965
|
-
mockPerf.advance = (deltaMs) => {
|
|
966
|
-
currentTime += deltaMs;
|
|
967
|
-
};
|
|
968
|
-
mockPerf.setTime = (time) => {
|
|
969
|
-
currentTime = time;
|
|
970
|
-
};
|
|
971
|
-
return mockPerf;
|
|
972
|
-
}
|
|
973
|
-
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
974
|
-
const raf = global.requestAnimationFrame;
|
|
975
|
-
if (!raf) return;
|
|
976
|
-
}
|
|
977
|
-
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
978
|
-
for (let i = 0; i < count; i++) {
|
|
979
|
-
if (callback) callback(i);
|
|
980
|
-
mock.advance(frameTimeMs);
|
|
1082
|
+
// src/setup/node.ts
|
|
1083
|
+
function setupNodeEnvironment() {
|
|
1084
|
+
if (typeof global.fetch === "undefined") {
|
|
981
1085
|
}
|
|
982
1086
|
}
|
|
983
1087
|
|
|
@@ -1094,204 +1198,305 @@ function captureAudioEvents(context) {
|
|
|
1094
1198
|
return [];
|
|
1095
1199
|
}
|
|
1096
1200
|
|
|
1201
|
+
// src/setup/timing.ts
|
|
1202
|
+
var activeMockRAF;
|
|
1203
|
+
function createMockRAF() {
|
|
1204
|
+
let callbacks = [];
|
|
1205
|
+
let nextId = 1;
|
|
1206
|
+
let currentTime = 0;
|
|
1207
|
+
const originalRAF = global.requestAnimationFrame;
|
|
1208
|
+
const originalCancelRAF = global.cancelAnimationFrame;
|
|
1209
|
+
const raf = (callback) => {
|
|
1210
|
+
const id = nextId++;
|
|
1211
|
+
callbacks.push({ id, callback });
|
|
1212
|
+
return id;
|
|
1213
|
+
};
|
|
1214
|
+
const cancel = (id) => {
|
|
1215
|
+
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
1216
|
+
};
|
|
1217
|
+
const mock = {
|
|
1218
|
+
tick(timestamp) {
|
|
1219
|
+
if (typeof timestamp !== "number") {
|
|
1220
|
+
currentTime += 16.6;
|
|
1221
|
+
} else {
|
|
1222
|
+
currentTime = timestamp;
|
|
1223
|
+
}
|
|
1224
|
+
const currentCallbacks = [...callbacks];
|
|
1225
|
+
callbacks = [];
|
|
1226
|
+
currentCallbacks.forEach(({ callback }) => {
|
|
1227
|
+
callback(currentTime);
|
|
1228
|
+
});
|
|
1229
|
+
},
|
|
1230
|
+
advance(deltaMs = 16.6) {
|
|
1231
|
+
this.tick(currentTime + deltaMs);
|
|
1232
|
+
},
|
|
1233
|
+
getCallbacks() {
|
|
1234
|
+
return callbacks.map((c) => c.callback);
|
|
1235
|
+
},
|
|
1236
|
+
reset() {
|
|
1237
|
+
callbacks = [];
|
|
1238
|
+
nextId = 1;
|
|
1239
|
+
currentTime = 0;
|
|
1240
|
+
},
|
|
1241
|
+
enable() {
|
|
1242
|
+
activeMockRAF = this;
|
|
1243
|
+
global.requestAnimationFrame = raf;
|
|
1244
|
+
global.cancelAnimationFrame = cancel;
|
|
1245
|
+
},
|
|
1246
|
+
disable() {
|
|
1247
|
+
if (activeMockRAF === this) {
|
|
1248
|
+
activeMockRAF = void 0;
|
|
1249
|
+
}
|
|
1250
|
+
if (originalRAF) {
|
|
1251
|
+
global.requestAnimationFrame = originalRAF;
|
|
1252
|
+
} else {
|
|
1253
|
+
delete global.requestAnimationFrame;
|
|
1254
|
+
}
|
|
1255
|
+
if (originalCancelRAF) {
|
|
1256
|
+
global.cancelAnimationFrame = originalCancelRAF;
|
|
1257
|
+
} else {
|
|
1258
|
+
delete global.cancelAnimationFrame;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
return mock;
|
|
1263
|
+
}
|
|
1264
|
+
function createMockPerformance(startTime = 0) {
|
|
1265
|
+
let currentTime = startTime;
|
|
1266
|
+
const mockPerf = {
|
|
1267
|
+
now: () => currentTime,
|
|
1268
|
+
timeOrigin: startTime,
|
|
1269
|
+
timing: {
|
|
1270
|
+
navigationStart: startTime
|
|
1271
|
+
},
|
|
1272
|
+
clearMarks: () => {
|
|
1273
|
+
},
|
|
1274
|
+
clearMeasures: () => {
|
|
1275
|
+
},
|
|
1276
|
+
clearResourceTimings: () => {
|
|
1277
|
+
},
|
|
1278
|
+
getEntries: () => [],
|
|
1279
|
+
getEntriesByName: () => [],
|
|
1280
|
+
getEntriesByType: () => [],
|
|
1281
|
+
mark: () => {
|
|
1282
|
+
},
|
|
1283
|
+
measure: () => {
|
|
1284
|
+
},
|
|
1285
|
+
setResourceTimingBufferSize: () => {
|
|
1286
|
+
},
|
|
1287
|
+
toJSON: () => ({}),
|
|
1288
|
+
addEventListener: () => {
|
|
1289
|
+
},
|
|
1290
|
+
removeEventListener: () => {
|
|
1291
|
+
},
|
|
1292
|
+
dispatchEvent: () => true
|
|
1293
|
+
};
|
|
1294
|
+
mockPerf.advance = (deltaMs) => {
|
|
1295
|
+
currentTime += deltaMs;
|
|
1296
|
+
};
|
|
1297
|
+
mockPerf.setTime = (time) => {
|
|
1298
|
+
currentTime = time;
|
|
1299
|
+
};
|
|
1300
|
+
return mockPerf;
|
|
1301
|
+
}
|
|
1302
|
+
function createControlledTimer() {
|
|
1303
|
+
let currentTime = 0;
|
|
1304
|
+
let timers = [];
|
|
1305
|
+
let nextId = 1;
|
|
1306
|
+
const originalSetTimeout = global.setTimeout;
|
|
1307
|
+
const originalClearTimeout = global.clearTimeout;
|
|
1308
|
+
const originalSetInterval = global.setInterval;
|
|
1309
|
+
const originalClearInterval = global.clearInterval;
|
|
1310
|
+
const mockSetTimeout = (callback, delay = 0, ...args) => {
|
|
1311
|
+
const id = nextId++;
|
|
1312
|
+
timers.push({ id, callback, dueTime: currentTime + delay, args });
|
|
1313
|
+
return id;
|
|
1314
|
+
};
|
|
1315
|
+
const mockClearTimeout = (id) => {
|
|
1316
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1317
|
+
};
|
|
1318
|
+
const mockSetInterval = (callback, delay = 0, ...args) => {
|
|
1319
|
+
const id = nextId++;
|
|
1320
|
+
timers.push({ id, callback, dueTime: currentTime + delay, interval: delay, args });
|
|
1321
|
+
return id;
|
|
1322
|
+
};
|
|
1323
|
+
const mockClearInterval = (id) => {
|
|
1324
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1325
|
+
};
|
|
1326
|
+
global.setTimeout = mockSetTimeout;
|
|
1327
|
+
global.clearTimeout = mockClearTimeout;
|
|
1328
|
+
global.setInterval = mockSetInterval;
|
|
1329
|
+
global.clearInterval = mockClearInterval;
|
|
1330
|
+
return {
|
|
1331
|
+
tick() {
|
|
1332
|
+
this.advanceBy(0);
|
|
1333
|
+
},
|
|
1334
|
+
advanceBy(ms) {
|
|
1335
|
+
const targetTime = currentTime + ms;
|
|
1336
|
+
while (true) {
|
|
1337
|
+
let earliest = null;
|
|
1338
|
+
for (const t of timers) {
|
|
1339
|
+
if (!earliest || t.dueTime < earliest.dueTime) {
|
|
1340
|
+
earliest = t;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (!earliest || earliest.dueTime > targetTime) {
|
|
1344
|
+
break;
|
|
1345
|
+
}
|
|
1346
|
+
currentTime = earliest.dueTime;
|
|
1347
|
+
const { callback, args, interval, id } = earliest;
|
|
1348
|
+
if (interval !== void 0) {
|
|
1349
|
+
earliest.dueTime += interval;
|
|
1350
|
+
if (interval === 0) earliest.dueTime += 1;
|
|
1351
|
+
} else {
|
|
1352
|
+
timers = timers.filter((t) => t.id !== id);
|
|
1353
|
+
}
|
|
1354
|
+
callback(...args);
|
|
1355
|
+
}
|
|
1356
|
+
currentTime = targetTime;
|
|
1357
|
+
},
|
|
1358
|
+
clear() {
|
|
1359
|
+
timers = [];
|
|
1360
|
+
},
|
|
1361
|
+
restore() {
|
|
1362
|
+
global.setTimeout = originalSetTimeout;
|
|
1363
|
+
global.clearTimeout = originalClearTimeout;
|
|
1364
|
+
global.setInterval = originalSetInterval;
|
|
1365
|
+
global.clearInterval = originalClearInterval;
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
1370
|
+
if (!activeMockRAF) {
|
|
1371
|
+
throw new Error("simulateFrames requires an active MockRAF. Ensure createMockRAF().enable() is called.");
|
|
1372
|
+
}
|
|
1373
|
+
for (let i = 0; i < count; i++) {
|
|
1374
|
+
if (callback) callback(i);
|
|
1375
|
+
activeMockRAF.advance(frameTimeMs);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
1379
|
+
for (let i = 0; i < count; i++) {
|
|
1380
|
+
if (callback) callback(i);
|
|
1381
|
+
mock.advance(frameTimeMs);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1097
1385
|
// src/e2e/playwright.ts
|
|
1098
1386
|
import { chromium } from "playwright";
|
|
1387
|
+
import { createServer } from "http";
|
|
1388
|
+
import handler from "serve-handler";
|
|
1099
1389
|
async function createPlaywrightTestClient(options = {}) {
|
|
1390
|
+
let staticServer;
|
|
1391
|
+
let clientUrl = options.clientUrl;
|
|
1392
|
+
const rootPath = options.rootPath || process.cwd();
|
|
1393
|
+
if (!clientUrl) {
|
|
1394
|
+
staticServer = createServer((request, response) => {
|
|
1395
|
+
return handler(request, response, {
|
|
1396
|
+
public: rootPath,
|
|
1397
|
+
cleanUrls: false,
|
|
1398
|
+
headers: [
|
|
1399
|
+
{
|
|
1400
|
+
source: "**/*",
|
|
1401
|
+
headers: [
|
|
1402
|
+
{ key: "Cache-Control", value: "no-cache" },
|
|
1403
|
+
{ key: "Access-Control-Allow-Origin", value: "*" },
|
|
1404
|
+
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
|
|
1405
|
+
{ key: "Cross-Origin-Embedder-Policy", value: "require-corp" }
|
|
1406
|
+
]
|
|
1407
|
+
}
|
|
1408
|
+
]
|
|
1409
|
+
});
|
|
1410
|
+
});
|
|
1411
|
+
await new Promise((resolve) => {
|
|
1412
|
+
if (!staticServer) return;
|
|
1413
|
+
staticServer.listen(0, () => {
|
|
1414
|
+
const addr = staticServer?.address();
|
|
1415
|
+
const port = typeof addr === "object" ? addr?.port : 0;
|
|
1416
|
+
clientUrl = `http://localhost:${port}`;
|
|
1417
|
+
console.log(`Test client serving from ${rootPath} at ${clientUrl}`);
|
|
1418
|
+
resolve();
|
|
1419
|
+
});
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1100
1422
|
const browser = await chromium.launch({
|
|
1101
1423
|
headless: options.headless ?? true,
|
|
1102
|
-
args:
|
|
1424
|
+
args: [
|
|
1103
1425
|
"--use-gl=egl",
|
|
1104
1426
|
"--ignore-gpu-blocklist",
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1427
|
+
...options.launchOptions?.args || []
|
|
1428
|
+
],
|
|
1429
|
+
...options.launchOptions
|
|
1108
1430
|
});
|
|
1431
|
+
const width = options.width || 1280;
|
|
1432
|
+
const height = options.height || 720;
|
|
1109
1433
|
const context = await browser.newContext({
|
|
1110
|
-
viewport:
|
|
1111
|
-
|
|
1112
|
-
|
|
1434
|
+
viewport: { width, height },
|
|
1435
|
+
deviceScaleFactor: 1,
|
|
1436
|
+
...options.contextOptions
|
|
1113
1437
|
});
|
|
1114
1438
|
const page = await context.newPage();
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}, null, { timeout });
|
|
1120
|
-
} catch (e) {
|
|
1121
|
-
throw new Error(`Game did not initialize within ${timeout}ms`);
|
|
1439
|
+
const close = async () => {
|
|
1440
|
+
await browser.close();
|
|
1441
|
+
if (staticServer) {
|
|
1442
|
+
staticServer.close();
|
|
1122
1443
|
}
|
|
1123
1444
|
};
|
|
1124
|
-
const
|
|
1445
|
+
const navigate = async (url) => {
|
|
1446
|
+
const targetUrl = url || clientUrl;
|
|
1447
|
+
if (!targetUrl) throw new Error("No URL to navigate to");
|
|
1448
|
+
let finalUrl = targetUrl;
|
|
1449
|
+
if (options.serverUrl && !targetUrl.includes("connect=")) {
|
|
1450
|
+
const separator = targetUrl.includes("?") ? "&" : "?";
|
|
1451
|
+
finalUrl = `${targetUrl}${separator}connect=${encodeURIComponent(options.serverUrl)}`;
|
|
1452
|
+
}
|
|
1453
|
+
console.log(`Navigating to: ${finalUrl}`);
|
|
1454
|
+
await page.goto(finalUrl, { waitUntil: "domcontentloaded" });
|
|
1455
|
+
};
|
|
1456
|
+
return {
|
|
1125
1457
|
browser,
|
|
1126
1458
|
context,
|
|
1127
1459
|
page,
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
async
|
|
1132
|
-
await
|
|
1133
|
-
},
|
|
1134
|
-
async
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
}
|
|
1138
|
-
await page.keyboard.up(key);
|
|
1139
|
-
}
|
|
1140
|
-
},
|
|
1141
|
-
async injectMouse(type, x = 0, y = 0, button = 0) {
|
|
1142
|
-
if (type === "move") {
|
|
1143
|
-
await page.mouse.move(x, y);
|
|
1144
|
-
} else if (type === "down") {
|
|
1145
|
-
await page.mouse.down({ button: button === 0 ? "left" : button === 2 ? "right" : "middle" });
|
|
1146
|
-
} else if (type === "up") {
|
|
1147
|
-
await page.mouse.up({ button: button === 0 ? "left" : button === 2 ? "right" : "middle" });
|
|
1148
|
-
}
|
|
1149
|
-
},
|
|
1150
|
-
async screenshot(path2) {
|
|
1151
|
-
await page.screenshot({ path: path2 });
|
|
1152
|
-
},
|
|
1153
|
-
async close() {
|
|
1154
|
-
await browser.close();
|
|
1460
|
+
server: staticServer,
|
|
1461
|
+
close,
|
|
1462
|
+
navigate,
|
|
1463
|
+
waitForGame: async (timeout = 1e4) => {
|
|
1464
|
+
await waitForGameReady(page, timeout);
|
|
1465
|
+
},
|
|
1466
|
+
injectInput: async (type, data) => {
|
|
1467
|
+
await page.evaluate(({ type: type2, data: data2 }) => {
|
|
1468
|
+
if (window.injectGameInput) window.injectGameInput(type2, data2);
|
|
1469
|
+
}, { type, data });
|
|
1155
1470
|
}
|
|
1156
1471
|
};
|
|
1157
|
-
return client;
|
|
1158
1472
|
}
|
|
1159
1473
|
async function waitForGameReady(page, timeout = 1e4) {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1474
|
+
try {
|
|
1475
|
+
await page.waitForFunction(() => {
|
|
1476
|
+
return window.gameInstance && window.gameInstance.isReady;
|
|
1477
|
+
}, null, { timeout });
|
|
1478
|
+
} catch (e) {
|
|
1479
|
+
await page.waitForSelector("canvas", { timeout });
|
|
1480
|
+
}
|
|
1163
1481
|
}
|
|
1164
1482
|
async function captureGameState(page) {
|
|
1165
1483
|
return await page.evaluate(() => {
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
return {};
|
|
1169
|
-
});
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
// src/e2e/network.ts
|
|
1173
|
-
var CONDITIONS = {
|
|
1174
|
-
"good": {
|
|
1175
|
-
offline: false,
|
|
1176
|
-
downloadThroughput: 10 * 1024 * 1024,
|
|
1177
|
-
// 10 Mbps
|
|
1178
|
-
uploadThroughput: 5 * 1024 * 1024,
|
|
1179
|
-
// 5 Mbps
|
|
1180
|
-
latency: 20
|
|
1181
|
-
},
|
|
1182
|
-
"slow": {
|
|
1183
|
-
offline: false,
|
|
1184
|
-
downloadThroughput: 500 * 1024,
|
|
1185
|
-
// 500 Kbps
|
|
1186
|
-
uploadThroughput: 500 * 1024,
|
|
1187
|
-
latency: 400
|
|
1188
|
-
},
|
|
1189
|
-
"unstable": {
|
|
1190
|
-
offline: false,
|
|
1191
|
-
downloadThroughput: 1 * 1024 * 1024,
|
|
1192
|
-
uploadThroughput: 1 * 1024 * 1024,
|
|
1193
|
-
latency: 100
|
|
1194
|
-
// Jitter needs logic not simple preset
|
|
1195
|
-
},
|
|
1196
|
-
"offline": {
|
|
1197
|
-
offline: true,
|
|
1198
|
-
downloadThroughput: 0,
|
|
1199
|
-
uploadThroughput: 0,
|
|
1200
|
-
latency: 0
|
|
1201
|
-
}
|
|
1202
|
-
};
|
|
1203
|
-
function simulateNetworkCondition(condition) {
|
|
1204
|
-
const config = CONDITIONS[condition];
|
|
1205
|
-
return createCustomNetworkCondition(config.latency, 0, 0, config);
|
|
1206
|
-
}
|
|
1207
|
-
function createCustomNetworkCondition(latency, jitter = 0, packetLoss = 0, baseConfig) {
|
|
1208
|
-
return {
|
|
1209
|
-
async apply(page) {
|
|
1210
|
-
const client = await page.context().newCDPSession(page);
|
|
1211
|
-
await client.send("Network.enable");
|
|
1212
|
-
await client.send("Network.emulateNetworkConditions", {
|
|
1213
|
-
offline: baseConfig?.offline || false,
|
|
1214
|
-
latency: latency + Math.random() * jitter,
|
|
1215
|
-
// Basic jitter application on setup (static)
|
|
1216
|
-
downloadThroughput: baseConfig?.downloadThroughput || -1,
|
|
1217
|
-
uploadThroughput: baseConfig?.uploadThroughput || -1
|
|
1218
|
-
});
|
|
1219
|
-
},
|
|
1220
|
-
async clear(page) {
|
|
1221
|
-
const client = await page.context().newCDPSession(page);
|
|
1222
|
-
await client.send("Network.emulateNetworkConditions", {
|
|
1223
|
-
offline: false,
|
|
1224
|
-
latency: 0,
|
|
1225
|
-
downloadThroughput: -1,
|
|
1226
|
-
uploadThroughput: -1
|
|
1227
|
-
});
|
|
1484
|
+
if (window.gameInstance && window.gameInstance.getState) {
|
|
1485
|
+
return window.gameInstance.getState();
|
|
1228
1486
|
}
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
async function throttleBandwidth(page, bytesPerSecond) {
|
|
1232
|
-
const simulator = createCustomNetworkCondition(0, 0, 0, {
|
|
1233
|
-
offline: false,
|
|
1234
|
-
latency: 0,
|
|
1235
|
-
downloadThroughput: bytesPerSecond,
|
|
1236
|
-
uploadThroughput: bytesPerSecond
|
|
1237
|
-
});
|
|
1238
|
-
await simulator.apply(page);
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
// src/e2e/visual.ts
|
|
1242
|
-
import path from "path";
|
|
1243
|
-
import fs from "fs/promises";
|
|
1244
|
-
async function captureGameScreenshot(page, name, options = {}) {
|
|
1245
|
-
const dir = options.dir || "__screenshots__";
|
|
1246
|
-
const screenshotPath = path.join(dir, `${name}.png`);
|
|
1247
|
-
await fs.mkdir(dir, { recursive: true });
|
|
1248
|
-
return await page.screenshot({
|
|
1249
|
-
path: screenshotPath,
|
|
1250
|
-
fullPage: options.fullPage ?? false,
|
|
1251
|
-
animations: "disabled",
|
|
1252
|
-
// Try to freeze animations if possible
|
|
1253
|
-
caret: "hide"
|
|
1487
|
+
return {};
|
|
1254
1488
|
});
|
|
1255
1489
|
}
|
|
1256
|
-
async function compareScreenshots(baseline, current, threshold = 0.1) {
|
|
1257
|
-
if (baseline.equals(current)) {
|
|
1258
|
-
return { pixelDiff: 0, matched: true };
|
|
1259
|
-
}
|
|
1260
|
-
return {
|
|
1261
|
-
pixelDiff: -1,
|
|
1262
|
-
// Unknown magnitude
|
|
1263
|
-
matched: false
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
function createVisualTestScenario(page, sceneName) {
|
|
1267
|
-
return {
|
|
1268
|
-
async capture(snapshotName) {
|
|
1269
|
-
return await captureGameScreenshot(page, `${sceneName}-${snapshotName}`);
|
|
1270
|
-
},
|
|
1271
|
-
async compare(snapshotName, baselineDir) {
|
|
1272
|
-
const name = `${sceneName}-${snapshotName}`;
|
|
1273
|
-
const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
|
|
1274
|
-
try {
|
|
1275
|
-
const baselinePath = path.join(baselineDir, `${name}.png`);
|
|
1276
|
-
const baseline = await fs.readFile(baselinePath);
|
|
1277
|
-
return await compareScreenshots(baseline, current);
|
|
1278
|
-
} catch (e) {
|
|
1279
|
-
return { pixelDiff: -1, matched: false };
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
1490
|
export {
|
|
1285
1491
|
InputInjector,
|
|
1286
1492
|
MockPointerLock,
|
|
1493
|
+
MockTransport,
|
|
1287
1494
|
captureAudioEvents,
|
|
1288
1495
|
captureCanvasDrawCalls,
|
|
1289
|
-
captureGameScreenshot,
|
|
1290
1496
|
captureGameState,
|
|
1291
|
-
compareScreenshots,
|
|
1292
1497
|
createBinaryStreamMock,
|
|
1293
1498
|
createBinaryWriterMock,
|
|
1294
|
-
|
|
1499
|
+
createControlledTimer,
|
|
1295
1500
|
createEntity,
|
|
1296
1501
|
createEntityStateFactory,
|
|
1297
1502
|
createGameStateSnapshotFactory,
|
|
@@ -1300,13 +1505,21 @@ export {
|
|
|
1300
1505
|
createMockCanvasContext2D,
|
|
1301
1506
|
createMockEngine,
|
|
1302
1507
|
createMockGame,
|
|
1508
|
+
createMockGameState,
|
|
1303
1509
|
createMockImage,
|
|
1304
1510
|
createMockImageData,
|
|
1305
1511
|
createMockIndexedDB,
|
|
1306
1512
|
createMockLocalStorage,
|
|
1513
|
+
createMockNetworkAddress,
|
|
1307
1514
|
createMockPerformance,
|
|
1308
1515
|
createMockRAF,
|
|
1516
|
+
createMockServer,
|
|
1517
|
+
createMockServerClient,
|
|
1518
|
+
createMockServerState,
|
|
1519
|
+
createMockServerStatic,
|
|
1309
1520
|
createMockSessionStorage,
|
|
1521
|
+
createMockTransport,
|
|
1522
|
+
createMockUDPSocket,
|
|
1310
1523
|
createMockWebGL2Context,
|
|
1311
1524
|
createNetChanMock,
|
|
1312
1525
|
createPlayerStateFactory,
|
|
@@ -1314,7 +1527,6 @@ export {
|
|
|
1314
1527
|
createSpawnContext,
|
|
1315
1528
|
createStorageTestScenario,
|
|
1316
1529
|
createTestContext,
|
|
1317
|
-
createVisualTestScenario,
|
|
1318
1530
|
intersects,
|
|
1319
1531
|
ladderTrace,
|
|
1320
1532
|
makeAxisBrush,
|
|
@@ -1329,11 +1541,9 @@ export {
|
|
|
1329
1541
|
setupNodeEnvironment,
|
|
1330
1542
|
simulateFrames,
|
|
1331
1543
|
simulateFramesWithMock,
|
|
1332
|
-
simulateNetworkCondition,
|
|
1333
1544
|
stairTrace,
|
|
1334
1545
|
teardownBrowserEnvironment,
|
|
1335
1546
|
teardownMockAudioContext,
|
|
1336
|
-
throttleBandwidth,
|
|
1337
1547
|
waitForGameReady
|
|
1338
1548
|
};
|
|
1339
1549
|
//# sourceMappingURL=index.js.map
|