quake2ts 0.0.556 → 0.0.557
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 +1 -1
- package/packages/client/dist/browser/index.global.js +17 -17
- package/packages/client/dist/browser/index.global.js.map +1 -1
- package/packages/client/dist/cjs/index.cjs +4607 -2885
- package/packages/client/dist/cjs/index.cjs.map +1 -1
- package/packages/client/dist/esm/index.js +4607 -2885
- package/packages/client/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/cjs/index.cjs +2256 -534
- package/packages/engine/dist/cjs/index.cjs.map +1 -1
- package/packages/engine/dist/esm/index.js +2266 -538
- package/packages/engine/dist/esm/index.js.map +1 -1
- package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
- package/packages/engine/dist/types/assets/visibilityAnalyzer.d.ts +7 -2
- package/packages/engine/dist/types/assets/visibilityAnalyzer.d.ts.map +1 -1
- package/packages/test-utils/dist/index.cjs +808 -194
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +220 -1
- package/packages/test-utils/dist/index.d.ts +220 -1
- package/packages/test-utils/dist/index.js +771 -193
- package/packages/test-utils/dist/index.js.map +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -22,18 +32,37 @@ var index_exports = {};
|
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
InputInjector: () => InputInjector,
|
|
24
34
|
MockPointerLock: () => MockPointerLock,
|
|
35
|
+
captureAudioEvents: () => captureAudioEvents,
|
|
36
|
+
captureCanvasDrawCalls: () => captureCanvasDrawCalls,
|
|
37
|
+
captureGameScreenshot: () => captureGameScreenshot,
|
|
38
|
+
captureGameState: () => captureGameState,
|
|
39
|
+
compareScreenshots: () => compareScreenshots,
|
|
25
40
|
createBinaryStreamMock: () => createBinaryStreamMock,
|
|
26
41
|
createBinaryWriterMock: () => createBinaryWriterMock,
|
|
42
|
+
createCustomNetworkCondition: () => createCustomNetworkCondition,
|
|
27
43
|
createEntity: () => createEntity,
|
|
28
44
|
createEntityStateFactory: () => createEntityStateFactory,
|
|
29
45
|
createGameStateSnapshotFactory: () => createGameStateSnapshotFactory,
|
|
46
|
+
createMockAudioContext: () => createMockAudioContext,
|
|
47
|
+
createMockCanvas: () => createMockCanvas,
|
|
48
|
+
createMockCanvasContext2D: () => createMockCanvasContext2D,
|
|
30
49
|
createMockEngine: () => createMockEngine,
|
|
31
50
|
createMockGame: () => createMockGame,
|
|
51
|
+
createMockImage: () => createMockImage,
|
|
52
|
+
createMockImageData: () => createMockImageData,
|
|
53
|
+
createMockIndexedDB: () => createMockIndexedDB,
|
|
54
|
+
createMockLocalStorage: () => createMockLocalStorage,
|
|
55
|
+
createMockPerformance: () => createMockPerformance,
|
|
56
|
+
createMockRAF: () => createMockRAF,
|
|
57
|
+
createMockSessionStorage: () => createMockSessionStorage,
|
|
32
58
|
createMockWebGL2Context: () => createMockWebGL2Context,
|
|
33
59
|
createNetChanMock: () => createNetChanMock,
|
|
34
60
|
createPlayerStateFactory: () => createPlayerStateFactory,
|
|
61
|
+
createPlaywrightTestClient: () => createPlaywrightTestClient,
|
|
35
62
|
createSpawnContext: () => createSpawnContext,
|
|
63
|
+
createStorageTestScenario: () => createStorageTestScenario,
|
|
36
64
|
createTestContext: () => createTestContext,
|
|
65
|
+
createVisualTestScenario: () => createVisualTestScenario,
|
|
37
66
|
intersects: () => import_shared3.intersects,
|
|
38
67
|
ladderTrace: () => import_shared3.ladderTrace,
|
|
39
68
|
makeAxisBrush: () => makeAxisBrush,
|
|
@@ -44,9 +73,16 @@ __export(index_exports, {
|
|
|
44
73
|
makeNode: () => makeNode,
|
|
45
74
|
makePlane: () => makePlane,
|
|
46
75
|
setupBrowserEnvironment: () => setupBrowserEnvironment,
|
|
76
|
+
setupMockAudioContext: () => setupMockAudioContext,
|
|
47
77
|
setupNodeEnvironment: () => setupNodeEnvironment,
|
|
78
|
+
simulateFrames: () => simulateFrames,
|
|
79
|
+
simulateFramesWithMock: () => simulateFramesWithMock,
|
|
80
|
+
simulateNetworkCondition: () => simulateNetworkCondition,
|
|
48
81
|
stairTrace: () => import_shared3.stairTrace,
|
|
49
|
-
teardownBrowserEnvironment: () => teardownBrowserEnvironment
|
|
82
|
+
teardownBrowserEnvironment: () => teardownBrowserEnvironment,
|
|
83
|
+
teardownMockAudioContext: () => teardownMockAudioContext,
|
|
84
|
+
throttleBandwidth: () => throttleBandwidth,
|
|
85
|
+
waitForGameReady: () => waitForGameReady
|
|
50
86
|
});
|
|
51
87
|
module.exports = __toCommonJS(index_exports);
|
|
52
88
|
|
|
@@ -444,123 +480,99 @@ function createEntity() {
|
|
|
444
480
|
var import_jsdom = require("jsdom");
|
|
445
481
|
var import_canvas = require("@napi-rs/canvas");
|
|
446
482
|
var import_auto = require("fake-indexeddb/auto");
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
global.DragEvent = dom.window.DragEvent;
|
|
461
|
-
global.MouseEvent = dom.window.MouseEvent;
|
|
462
|
-
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
463
|
-
global.FocusEvent = dom.window.FocusEvent;
|
|
464
|
-
global.WheelEvent = dom.window.WheelEvent;
|
|
465
|
-
global.InputEvent = dom.window.InputEvent;
|
|
466
|
-
global.UIEvent = dom.window.UIEvent;
|
|
467
|
-
try {
|
|
468
|
-
global.localStorage = dom.window.localStorage;
|
|
469
|
-
} catch (e) {
|
|
470
|
-
}
|
|
471
|
-
if (!global.localStorage) {
|
|
472
|
-
const storage = /* @__PURE__ */ new Map();
|
|
473
|
-
global.localStorage = {
|
|
474
|
-
getItem: (key) => storage.get(key) || null,
|
|
475
|
-
setItem: (key, value) => storage.set(key, value),
|
|
476
|
-
removeItem: (key) => storage.delete(key),
|
|
477
|
-
clear: () => storage.clear(),
|
|
478
|
-
key: (index) => Array.from(storage.keys())[index] || null,
|
|
479
|
-
get length() {
|
|
480
|
-
return storage.size;
|
|
483
|
+
|
|
484
|
+
// src/e2e/input.ts
|
|
485
|
+
var MockPointerLock = class {
|
|
486
|
+
static setup(doc) {
|
|
487
|
+
let _pointerLockElement = null;
|
|
488
|
+
Object.defineProperty(doc, "pointerLockElement", {
|
|
489
|
+
get: () => _pointerLockElement,
|
|
490
|
+
configurable: true
|
|
491
|
+
});
|
|
492
|
+
doc.exitPointerLock = () => {
|
|
493
|
+
if (_pointerLockElement) {
|
|
494
|
+
_pointerLockElement = null;
|
|
495
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
481
496
|
}
|
|
482
497
|
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (tagName.toLowerCase() === "canvas") {
|
|
487
|
-
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
488
|
-
const domCanvas = originalCreateElement("canvas", options2);
|
|
489
|
-
Object.defineProperty(domCanvas, "width", {
|
|
490
|
-
get: () => napiCanvas.width,
|
|
491
|
-
set: (value) => {
|
|
492
|
-
napiCanvas.width = value;
|
|
493
|
-
},
|
|
494
|
-
enumerable: true,
|
|
495
|
-
configurable: true
|
|
496
|
-
});
|
|
497
|
-
Object.defineProperty(domCanvas, "height", {
|
|
498
|
-
get: () => napiCanvas.height,
|
|
499
|
-
set: (value) => {
|
|
500
|
-
napiCanvas.height = value;
|
|
501
|
-
},
|
|
502
|
-
enumerable: true,
|
|
503
|
-
configurable: true
|
|
504
|
-
});
|
|
505
|
-
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
506
|
-
domCanvas.getContext = function(contextId, options3) {
|
|
507
|
-
if (contextId === "2d") {
|
|
508
|
-
return napiCanvas.getContext("2d", options3);
|
|
509
|
-
}
|
|
510
|
-
if (contextId === "webgl" || contextId === "webgl2") {
|
|
511
|
-
return originalGetContext(contextId, options3);
|
|
512
|
-
}
|
|
513
|
-
return napiCanvas.getContext(contextId, options3);
|
|
514
|
-
};
|
|
515
|
-
domCanvas.__napiCanvas = napiCanvas;
|
|
516
|
-
return domCanvas;
|
|
517
|
-
}
|
|
518
|
-
return originalCreateElement(tagName, options2);
|
|
519
|
-
};
|
|
520
|
-
global.Image = import_canvas.Image;
|
|
521
|
-
global.ImageData = import_canvas.ImageData;
|
|
522
|
-
if (typeof global.createImageBitmap === "undefined") {
|
|
523
|
-
global.createImageBitmap = async function(image, _options) {
|
|
524
|
-
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
525
|
-
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
526
|
-
const ctx = canvas2.getContext("2d");
|
|
527
|
-
if (image.data) {
|
|
528
|
-
ctx.putImageData(image, 0, 0);
|
|
529
|
-
}
|
|
530
|
-
return canvas2;
|
|
531
|
-
}
|
|
532
|
-
const canvas = new import_canvas.Canvas(100, 100);
|
|
533
|
-
return canvas;
|
|
498
|
+
global.HTMLElement.prototype.requestPointerLock = function() {
|
|
499
|
+
_pointerLockElement = this;
|
|
500
|
+
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
534
501
|
};
|
|
535
502
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
503
|
+
};
|
|
504
|
+
var InputInjector = class {
|
|
505
|
+
constructor(doc, win) {
|
|
506
|
+
this.doc = doc;
|
|
507
|
+
this.win = win;
|
|
540
508
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
509
|
+
keyDown(key, code) {
|
|
510
|
+
const event = new this.win.KeyboardEvent("keydown", {
|
|
511
|
+
key,
|
|
512
|
+
code: code || key,
|
|
513
|
+
bubbles: true,
|
|
514
|
+
cancelable: true,
|
|
515
|
+
view: this.win
|
|
516
|
+
});
|
|
517
|
+
this.doc.dispatchEvent(event);
|
|
545
518
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
delete global.ImageData;
|
|
556
|
-
delete global.createImageBitmap;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// src/setup/node.ts
|
|
560
|
-
function setupNodeEnvironment() {
|
|
561
|
-
if (typeof global.fetch === "undefined") {
|
|
519
|
+
keyUp(key, code) {
|
|
520
|
+
const event = new this.win.KeyboardEvent("keyup", {
|
|
521
|
+
key,
|
|
522
|
+
code: code || key,
|
|
523
|
+
bubbles: true,
|
|
524
|
+
cancelable: true,
|
|
525
|
+
view: this.win
|
|
526
|
+
});
|
|
527
|
+
this.doc.dispatchEvent(event);
|
|
562
528
|
}
|
|
563
|
-
|
|
529
|
+
mouseMove(movementX, movementY, clientX = 0, clientY = 0) {
|
|
530
|
+
const event = new this.win.MouseEvent("mousemove", {
|
|
531
|
+
bubbles: true,
|
|
532
|
+
cancelable: true,
|
|
533
|
+
view: this.win,
|
|
534
|
+
clientX,
|
|
535
|
+
clientY,
|
|
536
|
+
movementX,
|
|
537
|
+
// Note: JSDOM might not support this standard property fully on event init
|
|
538
|
+
movementY
|
|
539
|
+
});
|
|
540
|
+
Object.defineProperty(event, "movementX", { value: movementX });
|
|
541
|
+
Object.defineProperty(event, "movementY", { value: movementY });
|
|
542
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
543
|
+
target.dispatchEvent(event);
|
|
544
|
+
}
|
|
545
|
+
mouseDown(button = 0) {
|
|
546
|
+
const event = new this.win.MouseEvent("mousedown", {
|
|
547
|
+
button,
|
|
548
|
+
bubbles: true,
|
|
549
|
+
cancelable: true,
|
|
550
|
+
view: this.win
|
|
551
|
+
});
|
|
552
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
553
|
+
target.dispatchEvent(event);
|
|
554
|
+
}
|
|
555
|
+
mouseUp(button = 0) {
|
|
556
|
+
const event = new this.win.MouseEvent("mouseup", {
|
|
557
|
+
button,
|
|
558
|
+
bubbles: true,
|
|
559
|
+
cancelable: true,
|
|
560
|
+
view: this.win
|
|
561
|
+
});
|
|
562
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
563
|
+
target.dispatchEvent(event);
|
|
564
|
+
}
|
|
565
|
+
wheel(deltaY) {
|
|
566
|
+
const event = new this.win.WheelEvent("wheel", {
|
|
567
|
+
deltaY,
|
|
568
|
+
bubbles: true,
|
|
569
|
+
cancelable: true,
|
|
570
|
+
view: this.win
|
|
571
|
+
});
|
|
572
|
+
const target = this.doc.pointerLockElement || this.doc;
|
|
573
|
+
target.dispatchEvent(event);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
564
576
|
|
|
565
577
|
// src/setup/webgl.ts
|
|
566
578
|
function createMockWebGL2Context(canvas) {
|
|
@@ -686,114 +698,709 @@ function createMockWebGL2Context(canvas) {
|
|
|
686
698
|
return gl;
|
|
687
699
|
}
|
|
688
700
|
|
|
689
|
-
// src/
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
+
// src/setup/browser.ts
|
|
702
|
+
function setupBrowserEnvironment(options = {}) {
|
|
703
|
+
const {
|
|
704
|
+
url = "http://localhost",
|
|
705
|
+
pretendToBeVisual = true,
|
|
706
|
+
resources = void 0,
|
|
707
|
+
enableWebGL2 = false,
|
|
708
|
+
enablePointerLock = false
|
|
709
|
+
} = options;
|
|
710
|
+
const dom = new import_jsdom.JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
|
|
711
|
+
url,
|
|
712
|
+
pretendToBeVisual,
|
|
713
|
+
resources
|
|
714
|
+
});
|
|
715
|
+
global.window = dom.window;
|
|
716
|
+
global.document = dom.window.document;
|
|
717
|
+
try {
|
|
718
|
+
global.navigator = dom.window.navigator;
|
|
719
|
+
} catch (e) {
|
|
720
|
+
try {
|
|
721
|
+
Object.defineProperty(global, "navigator", {
|
|
722
|
+
value: dom.window.navigator,
|
|
723
|
+
writable: true,
|
|
724
|
+
configurable: true
|
|
725
|
+
});
|
|
726
|
+
} catch (e2) {
|
|
727
|
+
console.warn("Could not assign global.navigator, skipping.");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
global.location = dom.window.location;
|
|
731
|
+
global.HTMLElement = dom.window.HTMLElement;
|
|
732
|
+
global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
|
|
733
|
+
global.Event = dom.window.Event;
|
|
734
|
+
global.CustomEvent = dom.window.CustomEvent;
|
|
735
|
+
global.DragEvent = dom.window.DragEvent;
|
|
736
|
+
global.MouseEvent = dom.window.MouseEvent;
|
|
737
|
+
global.KeyboardEvent = dom.window.KeyboardEvent;
|
|
738
|
+
global.FocusEvent = dom.window.FocusEvent;
|
|
739
|
+
global.WheelEvent = dom.window.WheelEvent;
|
|
740
|
+
global.InputEvent = dom.window.InputEvent;
|
|
741
|
+
global.UIEvent = dom.window.UIEvent;
|
|
742
|
+
try {
|
|
743
|
+
global.localStorage = dom.window.localStorage;
|
|
744
|
+
} catch (e) {
|
|
745
|
+
}
|
|
746
|
+
if (!global.localStorage) {
|
|
747
|
+
const storage = /* @__PURE__ */ new Map();
|
|
748
|
+
global.localStorage = {
|
|
749
|
+
getItem: (key) => storage.get(key) || null,
|
|
750
|
+
setItem: (key, value) => storage.set(key, value),
|
|
751
|
+
removeItem: (key) => storage.delete(key),
|
|
752
|
+
clear: () => storage.clear(),
|
|
753
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
754
|
+
get length() {
|
|
755
|
+
return storage.size;
|
|
701
756
|
}
|
|
702
757
|
};
|
|
703
|
-
global.HTMLElement.prototype.requestPointerLock = function() {
|
|
704
|
-
_pointerLockElement = this;
|
|
705
|
-
doc.dispatchEvent(new Event("pointerlockchange"));
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
var InputInjector = class {
|
|
710
|
-
constructor(doc, win) {
|
|
711
|
-
this.doc = doc;
|
|
712
|
-
this.win = win;
|
|
713
758
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
759
|
+
const originalCreateElement = document.createElement.bind(document);
|
|
760
|
+
document.createElement = function(tagName, options2) {
|
|
761
|
+
if (tagName.toLowerCase() === "canvas") {
|
|
762
|
+
const napiCanvas = new import_canvas.Canvas(300, 150);
|
|
763
|
+
const domCanvas = originalCreateElement("canvas", options2);
|
|
764
|
+
Object.defineProperty(domCanvas, "width", {
|
|
765
|
+
get: () => napiCanvas.width,
|
|
766
|
+
set: (value) => {
|
|
767
|
+
napiCanvas.width = value;
|
|
768
|
+
},
|
|
769
|
+
enumerable: true,
|
|
770
|
+
configurable: true
|
|
771
|
+
});
|
|
772
|
+
Object.defineProperty(domCanvas, "height", {
|
|
773
|
+
get: () => napiCanvas.height,
|
|
774
|
+
set: (value) => {
|
|
775
|
+
napiCanvas.height = value;
|
|
776
|
+
},
|
|
777
|
+
enumerable: true,
|
|
778
|
+
configurable: true
|
|
779
|
+
});
|
|
780
|
+
const originalGetContext = domCanvas.getContext.bind(domCanvas);
|
|
781
|
+
domCanvas.getContext = function(contextId, options3) {
|
|
782
|
+
if (contextId === "2d") {
|
|
783
|
+
return napiCanvas.getContext("2d", options3);
|
|
784
|
+
}
|
|
785
|
+
if (enableWebGL2 && contextId === "webgl2") {
|
|
786
|
+
return createMockWebGL2Context(domCanvas);
|
|
787
|
+
}
|
|
788
|
+
if (contextId === "webgl" || contextId === "webgl2") {
|
|
789
|
+
return originalGetContext(contextId, options3);
|
|
790
|
+
}
|
|
791
|
+
return napiCanvas.getContext(contextId, options3);
|
|
792
|
+
};
|
|
793
|
+
domCanvas.__napiCanvas = napiCanvas;
|
|
794
|
+
return domCanvas;
|
|
795
|
+
}
|
|
796
|
+
return originalCreateElement(tagName, options2);
|
|
797
|
+
};
|
|
798
|
+
if (enableWebGL2) {
|
|
799
|
+
const originalProtoGetContext = global.HTMLCanvasElement.prototype.getContext;
|
|
800
|
+
global.HTMLCanvasElement.prototype.getContext = function(contextId, options2) {
|
|
801
|
+
if (contextId === "webgl2") {
|
|
802
|
+
return createMockWebGL2Context(this);
|
|
803
|
+
}
|
|
804
|
+
return originalProtoGetContext.call(this, contextId, options2);
|
|
805
|
+
};
|
|
723
806
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
807
|
+
global.Image = import_canvas.Image;
|
|
808
|
+
global.ImageData = import_canvas.ImageData;
|
|
809
|
+
if (typeof global.createImageBitmap === "undefined") {
|
|
810
|
+
global.createImageBitmap = async function(image, _options) {
|
|
811
|
+
if (image && typeof image.width === "number" && typeof image.height === "number") {
|
|
812
|
+
const canvas2 = new import_canvas.Canvas(image.width, image.height);
|
|
813
|
+
const ctx = canvas2.getContext("2d");
|
|
814
|
+
if (image.data) {
|
|
815
|
+
ctx.putImageData(image, 0, 0);
|
|
816
|
+
}
|
|
817
|
+
return canvas2;
|
|
818
|
+
}
|
|
819
|
+
const canvas = new import_canvas.Canvas(100, 100);
|
|
820
|
+
return canvas;
|
|
821
|
+
};
|
|
733
822
|
}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
view: this.win,
|
|
739
|
-
clientX,
|
|
740
|
-
clientY,
|
|
741
|
-
movementX,
|
|
742
|
-
// Note: JSDOM might not support this standard property fully on event init
|
|
743
|
-
movementY
|
|
744
|
-
});
|
|
745
|
-
Object.defineProperty(event, "movementX", { value: movementX });
|
|
746
|
-
Object.defineProperty(event, "movementY", { value: movementY });
|
|
747
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
748
|
-
target.dispatchEvent(event);
|
|
823
|
+
if (typeof global.btoa === "undefined") {
|
|
824
|
+
global.btoa = function(str) {
|
|
825
|
+
return Buffer.from(str, "binary").toString("base64");
|
|
826
|
+
};
|
|
749
827
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
cancelable: true,
|
|
755
|
-
view: this.win
|
|
756
|
-
});
|
|
757
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
758
|
-
target.dispatchEvent(event);
|
|
828
|
+
if (typeof global.atob === "undefined") {
|
|
829
|
+
global.atob = function(str) {
|
|
830
|
+
return Buffer.from(str, "base64").toString("binary");
|
|
831
|
+
};
|
|
759
832
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
button,
|
|
763
|
-
bubbles: true,
|
|
764
|
-
cancelable: true,
|
|
765
|
-
view: this.win
|
|
766
|
-
});
|
|
767
|
-
const target = this.doc.pointerLockElement || this.doc;
|
|
768
|
-
target.dispatchEvent(event);
|
|
833
|
+
if (enablePointerLock) {
|
|
834
|
+
MockPointerLock.setup(global.document);
|
|
769
835
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
836
|
+
if (typeof global.requestAnimationFrame === "undefined") {
|
|
837
|
+
let lastTime = 0;
|
|
838
|
+
global.requestAnimationFrame = (callback) => {
|
|
839
|
+
const currTime = Date.now();
|
|
840
|
+
const timeToCall = Math.max(0, 16 - (currTime - lastTime));
|
|
841
|
+
const id = setTimeout(() => {
|
|
842
|
+
callback(currTime + timeToCall);
|
|
843
|
+
}, timeToCall);
|
|
844
|
+
lastTime = currTime + timeToCall;
|
|
845
|
+
return id;
|
|
846
|
+
};
|
|
847
|
+
global.cancelAnimationFrame = (id) => {
|
|
848
|
+
clearTimeout(id);
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function teardownBrowserEnvironment() {
|
|
853
|
+
delete global.window;
|
|
854
|
+
delete global.document;
|
|
855
|
+
delete global.navigator;
|
|
856
|
+
delete global.localStorage;
|
|
857
|
+
delete global.location;
|
|
858
|
+
delete global.HTMLElement;
|
|
859
|
+
delete global.HTMLCanvasElement;
|
|
860
|
+
delete global.Image;
|
|
861
|
+
delete global.ImageData;
|
|
862
|
+
delete global.createImageBitmap;
|
|
863
|
+
delete global.Event;
|
|
864
|
+
delete global.CustomEvent;
|
|
865
|
+
delete global.DragEvent;
|
|
866
|
+
delete global.MouseEvent;
|
|
867
|
+
delete global.KeyboardEvent;
|
|
868
|
+
delete global.FocusEvent;
|
|
869
|
+
delete global.WheelEvent;
|
|
870
|
+
delete global.InputEvent;
|
|
871
|
+
delete global.UIEvent;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// src/setup/node.ts
|
|
875
|
+
function setupNodeEnvironment() {
|
|
876
|
+
if (typeof global.fetch === "undefined") {
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// src/setup/canvas.ts
|
|
881
|
+
var import_canvas2 = require("@napi-rs/canvas");
|
|
882
|
+
function createMockCanvas(width = 300, height = 150) {
|
|
883
|
+
if (typeof document !== "undefined" && document.createElement) {
|
|
884
|
+
const canvas2 = document.createElement("canvas");
|
|
885
|
+
canvas2.width = width;
|
|
886
|
+
canvas2.height = height;
|
|
887
|
+
return canvas2;
|
|
888
|
+
}
|
|
889
|
+
const canvas = new import_canvas2.Canvas(width, height);
|
|
890
|
+
const originalGetContext = canvas.getContext.bind(canvas);
|
|
891
|
+
canvas.getContext = function(contextId, options) {
|
|
892
|
+
if (contextId === "webgl2") {
|
|
893
|
+
return createMockWebGL2Context(canvas);
|
|
894
|
+
}
|
|
895
|
+
if (contextId === "2d") {
|
|
896
|
+
return originalGetContext("2d", options);
|
|
897
|
+
}
|
|
898
|
+
return originalGetContext(contextId, options);
|
|
899
|
+
};
|
|
900
|
+
return canvas;
|
|
901
|
+
}
|
|
902
|
+
function createMockCanvasContext2D(canvas) {
|
|
903
|
+
if (!canvas) {
|
|
904
|
+
canvas = createMockCanvas();
|
|
905
|
+
}
|
|
906
|
+
return canvas.getContext("2d");
|
|
907
|
+
}
|
|
908
|
+
function captureCanvasDrawCalls(context) {
|
|
909
|
+
const drawCalls = [];
|
|
910
|
+
const methodsToSpy = [
|
|
911
|
+
"fillRect",
|
|
912
|
+
"strokeRect",
|
|
913
|
+
"clearRect",
|
|
914
|
+
"fillText",
|
|
915
|
+
"strokeText",
|
|
916
|
+
"drawImage",
|
|
917
|
+
"beginPath",
|
|
918
|
+
"closePath",
|
|
919
|
+
"moveTo",
|
|
920
|
+
"lineTo",
|
|
921
|
+
"arc",
|
|
922
|
+
"arcTo",
|
|
923
|
+
"bezierCurveTo",
|
|
924
|
+
"quadraticCurveTo",
|
|
925
|
+
"stroke",
|
|
926
|
+
"fill",
|
|
927
|
+
"putImageData"
|
|
928
|
+
];
|
|
929
|
+
methodsToSpy.forEach((method) => {
|
|
930
|
+
const original = context[method];
|
|
931
|
+
if (typeof original === "function") {
|
|
932
|
+
context[method] = function(...args) {
|
|
933
|
+
drawCalls.push({ method, args });
|
|
934
|
+
return original.apply(this, args);
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
return drawCalls;
|
|
939
|
+
}
|
|
940
|
+
function createMockImageData(width, height, fillColor) {
|
|
941
|
+
const imageData = new import_canvas2.ImageData(width, height);
|
|
942
|
+
if (fillColor) {
|
|
943
|
+
const [r, g, b, a] = fillColor;
|
|
944
|
+
for (let i = 0; i < imageData.data.length; i += 4) {
|
|
945
|
+
imageData.data[i] = r;
|
|
946
|
+
imageData.data[i + 1] = g;
|
|
947
|
+
imageData.data[i + 2] = b;
|
|
948
|
+
imageData.data[i + 3] = a;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return imageData;
|
|
952
|
+
}
|
|
953
|
+
function createMockImage(width, height, src) {
|
|
954
|
+
const img = new import_canvas2.Image();
|
|
955
|
+
if (width) img.width = width;
|
|
956
|
+
if (height) img.height = height;
|
|
957
|
+
if (src) img.src = src;
|
|
958
|
+
return img;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/setup/timing.ts
|
|
962
|
+
function createMockRAF() {
|
|
963
|
+
let callbacks = [];
|
|
964
|
+
let nextId = 1;
|
|
965
|
+
let currentTime = 0;
|
|
966
|
+
const originalRAF = global.requestAnimationFrame;
|
|
967
|
+
const originalCancelRAF = global.cancelAnimationFrame;
|
|
968
|
+
const raf = (callback) => {
|
|
969
|
+
const id = nextId++;
|
|
970
|
+
callbacks.push({ id, callback });
|
|
971
|
+
return id;
|
|
972
|
+
};
|
|
973
|
+
const cancel = (id) => {
|
|
974
|
+
callbacks = callbacks.filter((cb) => cb.id !== id);
|
|
975
|
+
};
|
|
976
|
+
const mock = {
|
|
977
|
+
tick(timestamp) {
|
|
978
|
+
if (typeof timestamp !== "number") {
|
|
979
|
+
currentTime += 16.6;
|
|
980
|
+
} else {
|
|
981
|
+
currentTime = timestamp;
|
|
982
|
+
}
|
|
983
|
+
const currentCallbacks = [...callbacks];
|
|
984
|
+
callbacks = [];
|
|
985
|
+
currentCallbacks.forEach(({ callback }) => {
|
|
986
|
+
callback(currentTime);
|
|
987
|
+
});
|
|
988
|
+
},
|
|
989
|
+
advance(deltaMs = 16.6) {
|
|
990
|
+
this.tick(currentTime + deltaMs);
|
|
991
|
+
},
|
|
992
|
+
getCallbacks() {
|
|
993
|
+
return callbacks.map((c) => c.callback);
|
|
994
|
+
},
|
|
995
|
+
reset() {
|
|
996
|
+
callbacks = [];
|
|
997
|
+
nextId = 1;
|
|
998
|
+
currentTime = 0;
|
|
999
|
+
},
|
|
1000
|
+
enable() {
|
|
1001
|
+
global.requestAnimationFrame = raf;
|
|
1002
|
+
global.cancelAnimationFrame = cancel;
|
|
1003
|
+
},
|
|
1004
|
+
disable() {
|
|
1005
|
+
if (originalRAF) {
|
|
1006
|
+
global.requestAnimationFrame = originalRAF;
|
|
1007
|
+
} else {
|
|
1008
|
+
delete global.requestAnimationFrame;
|
|
1009
|
+
}
|
|
1010
|
+
if (originalCancelRAF) {
|
|
1011
|
+
global.cancelAnimationFrame = originalCancelRAF;
|
|
1012
|
+
} else {
|
|
1013
|
+
delete global.cancelAnimationFrame;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
return mock;
|
|
1018
|
+
}
|
|
1019
|
+
function createMockPerformance(startTime = 0) {
|
|
1020
|
+
let currentTime = startTime;
|
|
1021
|
+
const mockPerf = {
|
|
1022
|
+
now: () => currentTime,
|
|
1023
|
+
timeOrigin: startTime,
|
|
1024
|
+
timing: {
|
|
1025
|
+
navigationStart: startTime
|
|
1026
|
+
},
|
|
1027
|
+
// Add minimal navigation/resource timing interfaces to satisfy types if needed
|
|
1028
|
+
clearMarks: () => {
|
|
1029
|
+
},
|
|
1030
|
+
clearMeasures: () => {
|
|
1031
|
+
},
|
|
1032
|
+
clearResourceTimings: () => {
|
|
1033
|
+
},
|
|
1034
|
+
getEntries: () => [],
|
|
1035
|
+
getEntriesByName: () => [],
|
|
1036
|
+
getEntriesByType: () => [],
|
|
1037
|
+
mark: () => {
|
|
1038
|
+
},
|
|
1039
|
+
measure: () => {
|
|
1040
|
+
},
|
|
1041
|
+
setResourceTimingBufferSize: () => {
|
|
1042
|
+
},
|
|
1043
|
+
toJSON: () => ({}),
|
|
1044
|
+
addEventListener: () => {
|
|
1045
|
+
},
|
|
1046
|
+
removeEventListener: () => {
|
|
1047
|
+
},
|
|
1048
|
+
dispatchEvent: () => true
|
|
1049
|
+
};
|
|
1050
|
+
mockPerf.advance = (deltaMs) => {
|
|
1051
|
+
currentTime += deltaMs;
|
|
1052
|
+
};
|
|
1053
|
+
mockPerf.setTime = (time) => {
|
|
1054
|
+
currentTime = time;
|
|
1055
|
+
};
|
|
1056
|
+
return mockPerf;
|
|
1057
|
+
}
|
|
1058
|
+
function simulateFrames(count, frameTimeMs = 16.6, callback) {
|
|
1059
|
+
const raf = global.requestAnimationFrame;
|
|
1060
|
+
if (!raf) return;
|
|
1061
|
+
}
|
|
1062
|
+
function simulateFramesWithMock(mock, count, frameTimeMs = 16.6, callback) {
|
|
1063
|
+
for (let i = 0; i < count; i++) {
|
|
1064
|
+
if (callback) callback(i);
|
|
1065
|
+
mock.advance(frameTimeMs);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/setup/storage.ts
|
|
1070
|
+
var import_auto2 = require("fake-indexeddb/auto");
|
|
1071
|
+
function createMockLocalStorage(initialData = {}) {
|
|
1072
|
+
const storage = new Map(Object.entries(initialData));
|
|
1073
|
+
return {
|
|
1074
|
+
getItem: (key) => storage.get(key) || null,
|
|
1075
|
+
setItem: (key, value) => storage.set(key, value),
|
|
1076
|
+
removeItem: (key) => storage.delete(key),
|
|
1077
|
+
clear: () => storage.clear(),
|
|
1078
|
+
key: (index) => Array.from(storage.keys())[index] || null,
|
|
1079
|
+
get length() {
|
|
1080
|
+
return storage.size;
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function createMockSessionStorage(initialData = {}) {
|
|
1085
|
+
return createMockLocalStorage(initialData);
|
|
1086
|
+
}
|
|
1087
|
+
function createMockIndexedDB() {
|
|
1088
|
+
if (typeof indexedDB === "undefined") {
|
|
1089
|
+
throw new Error("IndexedDB mock not found. Ensure fake-indexeddb is loaded.");
|
|
1090
|
+
}
|
|
1091
|
+
return indexedDB;
|
|
1092
|
+
}
|
|
1093
|
+
function createStorageTestScenario(storageType = "local") {
|
|
1094
|
+
const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
|
|
1095
|
+
return {
|
|
1096
|
+
storage,
|
|
1097
|
+
populate(data) {
|
|
1098
|
+
Object.entries(data).forEach(([k, v]) => storage.setItem(k, v));
|
|
1099
|
+
},
|
|
1100
|
+
verify(key, value) {
|
|
1101
|
+
return storage.getItem(key) === value;
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/setup/audio.ts
|
|
1107
|
+
function createMockAudioContext() {
|
|
1108
|
+
return {
|
|
1109
|
+
createGain: () => ({
|
|
1110
|
+
connect: () => {
|
|
1111
|
+
},
|
|
1112
|
+
gain: { value: 1, setValueAtTime: () => {
|
|
1113
|
+
} }
|
|
1114
|
+
}),
|
|
1115
|
+
createOscillator: () => ({
|
|
1116
|
+
connect: () => {
|
|
1117
|
+
},
|
|
1118
|
+
start: () => {
|
|
1119
|
+
},
|
|
1120
|
+
stop: () => {
|
|
1121
|
+
},
|
|
1122
|
+
frequency: { value: 440 }
|
|
1123
|
+
}),
|
|
1124
|
+
createBufferSource: () => ({
|
|
1125
|
+
connect: () => {
|
|
1126
|
+
},
|
|
1127
|
+
start: () => {
|
|
1128
|
+
},
|
|
1129
|
+
stop: () => {
|
|
1130
|
+
},
|
|
1131
|
+
buffer: null,
|
|
1132
|
+
playbackRate: { value: 1 },
|
|
1133
|
+
loop: false
|
|
1134
|
+
}),
|
|
1135
|
+
destination: {},
|
|
1136
|
+
currentTime: 0,
|
|
1137
|
+
state: "running",
|
|
1138
|
+
resume: async () => {
|
|
1139
|
+
},
|
|
1140
|
+
suspend: async () => {
|
|
1141
|
+
},
|
|
1142
|
+
close: async () => {
|
|
1143
|
+
},
|
|
1144
|
+
decodeAudioData: async (buffer) => ({
|
|
1145
|
+
duration: 1,
|
|
1146
|
+
length: 44100,
|
|
1147
|
+
sampleRate: 44100,
|
|
1148
|
+
numberOfChannels: 2,
|
|
1149
|
+
getChannelData: () => new Float32Array(44100)
|
|
1150
|
+
}),
|
|
1151
|
+
createBuffer: (channels, length, sampleRate) => ({
|
|
1152
|
+
duration: length / sampleRate,
|
|
1153
|
+
length,
|
|
1154
|
+
sampleRate,
|
|
1155
|
+
numberOfChannels: channels,
|
|
1156
|
+
getChannelData: () => new Float32Array(length)
|
|
1157
|
+
})
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
function setupMockAudioContext() {
|
|
1161
|
+
if (typeof global.AudioContext === "undefined" && typeof global.window !== "undefined") {
|
|
1162
|
+
global.AudioContext = class {
|
|
1163
|
+
constructor() {
|
|
1164
|
+
return createMockAudioContext();
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
global.window.AudioContext = global.AudioContext;
|
|
1168
|
+
global.window.webkitAudioContext = global.AudioContext;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
function teardownMockAudioContext() {
|
|
1172
|
+
if (global.AudioContext && global.AudioContext.toString().includes("class")) {
|
|
1173
|
+
delete global.AudioContext;
|
|
1174
|
+
delete global.window.AudioContext;
|
|
1175
|
+
delete global.window.webkitAudioContext;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
function captureAudioEvents(context) {
|
|
1179
|
+
return [];
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// src/e2e/playwright.ts
|
|
1183
|
+
var import_playwright = require("playwright");
|
|
1184
|
+
async function createPlaywrightTestClient(options = {}) {
|
|
1185
|
+
const browser = await import_playwright.chromium.launch({
|
|
1186
|
+
headless: options.headless ?? true,
|
|
1187
|
+
args: options.args || [
|
|
1188
|
+
"--use-gl=egl",
|
|
1189
|
+
"--ignore-gpu-blocklist",
|
|
1190
|
+
"--no-sandbox",
|
|
1191
|
+
"--disable-setuid-sandbox"
|
|
1192
|
+
]
|
|
1193
|
+
});
|
|
1194
|
+
const context = await browser.newContext({
|
|
1195
|
+
viewport: options.viewport || { width: 1280, height: 720 },
|
|
1196
|
+
recordVideo: options.recordVideo,
|
|
1197
|
+
deviceScaleFactor: 1
|
|
1198
|
+
});
|
|
1199
|
+
const page = await context.newPage();
|
|
1200
|
+
const waitForGame = async (timeout = 1e4) => {
|
|
1201
|
+
try {
|
|
1202
|
+
await page.waitForFunction(() => {
|
|
1203
|
+
return window.game || document.querySelector("canvas");
|
|
1204
|
+
}, null, { timeout });
|
|
1205
|
+
} catch (e) {
|
|
1206
|
+
throw new Error(`Game did not initialize within ${timeout}ms`);
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
const client = {
|
|
1210
|
+
browser,
|
|
1211
|
+
context,
|
|
1212
|
+
page,
|
|
1213
|
+
async navigate(url) {
|
|
1214
|
+
await page.goto(url, { waitUntil: "domcontentloaded" });
|
|
1215
|
+
},
|
|
1216
|
+
async waitForGame(timeout) {
|
|
1217
|
+
await waitForGame(timeout);
|
|
1218
|
+
},
|
|
1219
|
+
async injectInput(type, key) {
|
|
1220
|
+
if (type === "keydown") {
|
|
1221
|
+
await page.keyboard.down(key);
|
|
1222
|
+
} else {
|
|
1223
|
+
await page.keyboard.up(key);
|
|
1224
|
+
}
|
|
1225
|
+
},
|
|
1226
|
+
async injectMouse(type, x = 0, y = 0, button = 0) {
|
|
1227
|
+
if (type === "move") {
|
|
1228
|
+
await page.mouse.move(x, y);
|
|
1229
|
+
} else if (type === "down") {
|
|
1230
|
+
await page.mouse.down({ button: button === 0 ? "left" : button === 2 ? "right" : "middle" });
|
|
1231
|
+
} else if (type === "up") {
|
|
1232
|
+
await page.mouse.up({ button: button === 0 ? "left" : button === 2 ? "right" : "middle" });
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
async screenshot(path2) {
|
|
1236
|
+
await page.screenshot({ path: path2 });
|
|
1237
|
+
},
|
|
1238
|
+
async close() {
|
|
1239
|
+
await browser.close();
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
return client;
|
|
1243
|
+
}
|
|
1244
|
+
async function waitForGameReady(page, timeout = 1e4) {
|
|
1245
|
+
await page.waitForFunction(() => {
|
|
1246
|
+
return window.game || document.querySelector("canvas");
|
|
1247
|
+
}, null, { timeout });
|
|
1248
|
+
}
|
|
1249
|
+
async function captureGameState(page) {
|
|
1250
|
+
return await page.evaluate(() => {
|
|
1251
|
+
const game = window.game;
|
|
1252
|
+
if (!game) return {};
|
|
1253
|
+
return {};
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/e2e/network.ts
|
|
1258
|
+
var CONDITIONS = {
|
|
1259
|
+
"good": {
|
|
1260
|
+
offline: false,
|
|
1261
|
+
downloadThroughput: 10 * 1024 * 1024,
|
|
1262
|
+
// 10 Mbps
|
|
1263
|
+
uploadThroughput: 5 * 1024 * 1024,
|
|
1264
|
+
// 5 Mbps
|
|
1265
|
+
latency: 20
|
|
1266
|
+
},
|
|
1267
|
+
"slow": {
|
|
1268
|
+
offline: false,
|
|
1269
|
+
downloadThroughput: 500 * 1024,
|
|
1270
|
+
// 500 Kbps
|
|
1271
|
+
uploadThroughput: 500 * 1024,
|
|
1272
|
+
latency: 400
|
|
1273
|
+
},
|
|
1274
|
+
"unstable": {
|
|
1275
|
+
offline: false,
|
|
1276
|
+
downloadThroughput: 1 * 1024 * 1024,
|
|
1277
|
+
uploadThroughput: 1 * 1024 * 1024,
|
|
1278
|
+
latency: 100
|
|
1279
|
+
// Jitter needs logic not simple preset
|
|
1280
|
+
},
|
|
1281
|
+
"offline": {
|
|
1282
|
+
offline: true,
|
|
1283
|
+
downloadThroughput: 0,
|
|
1284
|
+
uploadThroughput: 0,
|
|
1285
|
+
latency: 0
|
|
779
1286
|
}
|
|
780
1287
|
};
|
|
1288
|
+
function simulateNetworkCondition(condition) {
|
|
1289
|
+
const config = CONDITIONS[condition];
|
|
1290
|
+
return createCustomNetworkCondition(config.latency, 0, 0, config);
|
|
1291
|
+
}
|
|
1292
|
+
function createCustomNetworkCondition(latency, jitter = 0, packetLoss = 0, baseConfig) {
|
|
1293
|
+
return {
|
|
1294
|
+
async apply(page) {
|
|
1295
|
+
const client = await page.context().newCDPSession(page);
|
|
1296
|
+
await client.send("Network.enable");
|
|
1297
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
1298
|
+
offline: baseConfig?.offline || false,
|
|
1299
|
+
latency: latency + Math.random() * jitter,
|
|
1300
|
+
// Basic jitter application on setup (static)
|
|
1301
|
+
downloadThroughput: baseConfig?.downloadThroughput || -1,
|
|
1302
|
+
uploadThroughput: baseConfig?.uploadThroughput || -1
|
|
1303
|
+
});
|
|
1304
|
+
},
|
|
1305
|
+
async clear(page) {
|
|
1306
|
+
const client = await page.context().newCDPSession(page);
|
|
1307
|
+
await client.send("Network.emulateNetworkConditions", {
|
|
1308
|
+
offline: false,
|
|
1309
|
+
latency: 0,
|
|
1310
|
+
downloadThroughput: -1,
|
|
1311
|
+
uploadThroughput: -1
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
async function throttleBandwidth(page, bytesPerSecond) {
|
|
1317
|
+
const simulator = createCustomNetworkCondition(0, 0, 0, {
|
|
1318
|
+
offline: false,
|
|
1319
|
+
latency: 0,
|
|
1320
|
+
downloadThroughput: bytesPerSecond,
|
|
1321
|
+
uploadThroughput: bytesPerSecond
|
|
1322
|
+
});
|
|
1323
|
+
await simulator.apply(page);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
// src/e2e/visual.ts
|
|
1327
|
+
var import_path = __toESM(require("path"), 1);
|
|
1328
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
1329
|
+
async function captureGameScreenshot(page, name, options = {}) {
|
|
1330
|
+
const dir = options.dir || "__screenshots__";
|
|
1331
|
+
const screenshotPath = import_path.default.join(dir, `${name}.png`);
|
|
1332
|
+
await import_promises.default.mkdir(dir, { recursive: true });
|
|
1333
|
+
return await page.screenshot({
|
|
1334
|
+
path: screenshotPath,
|
|
1335
|
+
fullPage: options.fullPage ?? false,
|
|
1336
|
+
animations: "disabled",
|
|
1337
|
+
// Try to freeze animations if possible
|
|
1338
|
+
caret: "hide"
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
async function compareScreenshots(baseline, current, threshold = 0.1) {
|
|
1342
|
+
if (baseline.equals(current)) {
|
|
1343
|
+
return { pixelDiff: 0, matched: true };
|
|
1344
|
+
}
|
|
1345
|
+
return {
|
|
1346
|
+
pixelDiff: -1,
|
|
1347
|
+
// Unknown magnitude
|
|
1348
|
+
matched: false
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function createVisualTestScenario(page, sceneName) {
|
|
1352
|
+
return {
|
|
1353
|
+
async capture(snapshotName) {
|
|
1354
|
+
return await captureGameScreenshot(page, `${sceneName}-${snapshotName}`);
|
|
1355
|
+
},
|
|
1356
|
+
async compare(snapshotName, baselineDir) {
|
|
1357
|
+
const name = `${sceneName}-${snapshotName}`;
|
|
1358
|
+
const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
|
|
1359
|
+
try {
|
|
1360
|
+
const baselinePath = import_path.default.join(baselineDir, `${name}.png`);
|
|
1361
|
+
const baseline = await import_promises.default.readFile(baselinePath);
|
|
1362
|
+
return await compareScreenshots(baseline, current);
|
|
1363
|
+
} catch (e) {
|
|
1364
|
+
return { pixelDiff: -1, matched: false };
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
781
1369
|
// Annotate the CommonJS export names for ESM import in node:
|
|
782
1370
|
0 && (module.exports = {
|
|
783
1371
|
InputInjector,
|
|
784
1372
|
MockPointerLock,
|
|
1373
|
+
captureAudioEvents,
|
|
1374
|
+
captureCanvasDrawCalls,
|
|
1375
|
+
captureGameScreenshot,
|
|
1376
|
+
captureGameState,
|
|
1377
|
+
compareScreenshots,
|
|
785
1378
|
createBinaryStreamMock,
|
|
786
1379
|
createBinaryWriterMock,
|
|
1380
|
+
createCustomNetworkCondition,
|
|
787
1381
|
createEntity,
|
|
788
1382
|
createEntityStateFactory,
|
|
789
1383
|
createGameStateSnapshotFactory,
|
|
1384
|
+
createMockAudioContext,
|
|
1385
|
+
createMockCanvas,
|
|
1386
|
+
createMockCanvasContext2D,
|
|
790
1387
|
createMockEngine,
|
|
791
1388
|
createMockGame,
|
|
1389
|
+
createMockImage,
|
|
1390
|
+
createMockImageData,
|
|
1391
|
+
createMockIndexedDB,
|
|
1392
|
+
createMockLocalStorage,
|
|
1393
|
+
createMockPerformance,
|
|
1394
|
+
createMockRAF,
|
|
1395
|
+
createMockSessionStorage,
|
|
792
1396
|
createMockWebGL2Context,
|
|
793
1397
|
createNetChanMock,
|
|
794
1398
|
createPlayerStateFactory,
|
|
1399
|
+
createPlaywrightTestClient,
|
|
795
1400
|
createSpawnContext,
|
|
1401
|
+
createStorageTestScenario,
|
|
796
1402
|
createTestContext,
|
|
1403
|
+
createVisualTestScenario,
|
|
797
1404
|
intersects,
|
|
798
1405
|
ladderTrace,
|
|
799
1406
|
makeAxisBrush,
|
|
@@ -804,8 +1411,15 @@ var InputInjector = class {
|
|
|
804
1411
|
makeNode,
|
|
805
1412
|
makePlane,
|
|
806
1413
|
setupBrowserEnvironment,
|
|
1414
|
+
setupMockAudioContext,
|
|
807
1415
|
setupNodeEnvironment,
|
|
1416
|
+
simulateFrames,
|
|
1417
|
+
simulateFramesWithMock,
|
|
1418
|
+
simulateNetworkCondition,
|
|
808
1419
|
stairTrace,
|
|
809
|
-
teardownBrowserEnvironment
|
|
1420
|
+
teardownBrowserEnvironment,
|
|
1421
|
+
teardownMockAudioContext,
|
|
1422
|
+
throttleBandwidth,
|
|
1423
|
+
waitForGameReady
|
|
810
1424
|
});
|
|
811
1425
|
//# sourceMappingURL=index.cjs.map
|