quake2ts 0.0.634 → 0.0.635
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/test-utils/dist/index.cjs +200 -7
- package/packages/test-utils/dist/index.cjs.map +1 -1
- package/packages/test-utils/dist/index.d.cts +38 -1
- package/packages/test-utils/dist/index.d.ts +38 -1
- package/packages/test-utils/dist/index.js +193 -7
- package/packages/test-utils/dist/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -50,10 +50,12 @@ __export(index_exports, {
|
|
|
50
50
|
MockWebGL2RenderingContext: () => MockWebGL2RenderingContext,
|
|
51
51
|
captureAudioEvents: () => captureAudioEvents,
|
|
52
52
|
captureCanvasDrawCalls: () => captureCanvasDrawCalls,
|
|
53
|
+
captureFramebufferAsPNG: () => captureFramebufferAsPNG,
|
|
53
54
|
captureGameScreenshot: () => captureGameScreenshot,
|
|
54
55
|
captureGameState: () => captureGameState,
|
|
55
56
|
compareScreenshot: () => compareScreenshot,
|
|
56
57
|
compareScreenshots: () => compareScreenshots,
|
|
58
|
+
compareSnapshots: () => compareSnapshots,
|
|
57
59
|
createBandwidthTestScenario: () => createBandwidthTestScenario,
|
|
58
60
|
createBinaryStreamMock: () => createBinaryStreamMock,
|
|
59
61
|
createBinaryWriterMock: () => createBinaryWriterMock,
|
|
@@ -199,9 +201,12 @@ __export(index_exports, {
|
|
|
199
201
|
createVector3: () => createVector3,
|
|
200
202
|
createViewTestScenario: () => createViewTestScenario,
|
|
201
203
|
createVisualTestScenario: () => createVisualTestScenario,
|
|
204
|
+
expectSnapshot: () => expectSnapshot,
|
|
205
|
+
getSnapshotPath: () => getSnapshotPath,
|
|
202
206
|
initHeadlessWebGPU: () => initHeadlessWebGPU,
|
|
203
207
|
intersects: () => import_shared2.intersects,
|
|
204
208
|
ladderTrace: () => import_shared2.ladderTrace,
|
|
209
|
+
loadPNG: () => loadPNG,
|
|
205
210
|
makeAxisBrush: () => makeAxisBrush,
|
|
206
211
|
makeBrushFromMinsMaxs: () => makeBrushFromMinsMaxs,
|
|
207
212
|
makeBspModel: () => makeBspModel,
|
|
@@ -213,7 +218,9 @@ __export(index_exports, {
|
|
|
213
218
|
mockMonsterAttacks: () => mockMonsterAttacks,
|
|
214
219
|
randomVector3: () => randomVector3,
|
|
215
220
|
renderAndCapture: () => renderAndCapture,
|
|
221
|
+
renderAndExpectSnapshot: () => renderAndExpectSnapshot,
|
|
216
222
|
runComputeAndReadback: () => runComputeAndReadback,
|
|
223
|
+
savePNG: () => savePNG,
|
|
217
224
|
serializeUserInfo: () => serializeUserInfo,
|
|
218
225
|
setupBrowserEnvironment: () => setupBrowserEnvironment,
|
|
219
226
|
setupMockAudioContext: () => setupMockAudioContext,
|
|
@@ -3259,7 +3266,7 @@ function createMockAssetManager(overrides) {
|
|
|
3259
3266
|
getMemoryUsage: import_vitest14.vi.fn().mockReturnValue({ textures: 0, audio: 0 }),
|
|
3260
3267
|
clearCache: import_vitest14.vi.fn(),
|
|
3261
3268
|
preloadAssets: import_vitest14.vi.fn().mockResolvedValue(void 0),
|
|
3262
|
-
queueLoad: import_vitest14.vi.fn().mockImplementation((
|
|
3269
|
+
queueLoad: import_vitest14.vi.fn().mockImplementation((path3) => Promise.resolve({})),
|
|
3263
3270
|
...overrides
|
|
3264
3271
|
};
|
|
3265
3272
|
}
|
|
@@ -4643,6 +4650,185 @@ var verifySmoothing = (states) => {
|
|
|
4643
4650
|
};
|
|
4644
4651
|
};
|
|
4645
4652
|
|
|
4653
|
+
// src/visual/snapshots.ts
|
|
4654
|
+
var import_pngjs = require("pngjs");
|
|
4655
|
+
var import_pixelmatch = __toESM(require("pixelmatch"), 1);
|
|
4656
|
+
var import_promises = __toESM(require("fs/promises"), 1);
|
|
4657
|
+
var import_fs = require("fs");
|
|
4658
|
+
var import_path = __toESM(require("path"), 1);
|
|
4659
|
+
function getBytesPerRow(width) {
|
|
4660
|
+
const bytesPerPixel = 4;
|
|
4661
|
+
const unpaddedBytesPerRow = width * bytesPerPixel;
|
|
4662
|
+
const align = 256;
|
|
4663
|
+
const paddedBytesPerRow = Math.max(
|
|
4664
|
+
unpaddedBytesPerRow,
|
|
4665
|
+
Math.ceil(unpaddedBytesPerRow / align) * align
|
|
4666
|
+
);
|
|
4667
|
+
return paddedBytesPerRow;
|
|
4668
|
+
}
|
|
4669
|
+
async function captureFramebufferAsPNG(device, texture, options) {
|
|
4670
|
+
const { width, height, format = "rgba8unorm" } = options;
|
|
4671
|
+
const bytesPerRow = getBytesPerRow(width);
|
|
4672
|
+
const bufferSize = bytesPerRow * height;
|
|
4673
|
+
const outputBuffer = device.createBuffer({
|
|
4674
|
+
size: bufferSize,
|
|
4675
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
4676
|
+
label: "captureFramebufferAsPNG output buffer"
|
|
4677
|
+
});
|
|
4678
|
+
const commandEncoder = device.createCommandEncoder({ label: "captureFramebufferAsPNG encoder" });
|
|
4679
|
+
commandEncoder.copyTextureToBuffer(
|
|
4680
|
+
{ texture },
|
|
4681
|
+
{ buffer: outputBuffer, bytesPerRow, rowsPerImage: height },
|
|
4682
|
+
{ width, height, depthOrArrayLayers: 1 }
|
|
4683
|
+
);
|
|
4684
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
4685
|
+
await outputBuffer.mapAsync(GPUMapMode.READ);
|
|
4686
|
+
const mappedRange = outputBuffer.getMappedRange();
|
|
4687
|
+
const rawData = new Uint8Array(mappedRange);
|
|
4688
|
+
const bytesPerPixel = 4;
|
|
4689
|
+
const tightData = new Uint8ClampedArray(width * height * bytesPerPixel);
|
|
4690
|
+
for (let y = 0; y < height; y++) {
|
|
4691
|
+
const srcOffset = y * bytesPerRow;
|
|
4692
|
+
const dstOffset = y * width * bytesPerPixel;
|
|
4693
|
+
const rowSize = width * bytesPerPixel;
|
|
4694
|
+
for (let i = 0; i < rowSize; i++) {
|
|
4695
|
+
tightData[dstOffset + i] = rawData[srcOffset + i];
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
outputBuffer.unmap();
|
|
4699
|
+
if (format === "bgra8unorm" || format === "bgra8unorm-srgb") {
|
|
4700
|
+
for (let i = 0; i < tightData.length; i += 4) {
|
|
4701
|
+
const b = tightData[i];
|
|
4702
|
+
const r = tightData[i + 2];
|
|
4703
|
+
tightData[i] = r;
|
|
4704
|
+
tightData[i + 2] = b;
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4707
|
+
const png = new import_pngjs.PNG({ width, height });
|
|
4708
|
+
png.data = Buffer.from(tightData);
|
|
4709
|
+
return import_pngjs.PNG.sync.write(png);
|
|
4710
|
+
}
|
|
4711
|
+
async function savePNG(pixels, width, height, filepath) {
|
|
4712
|
+
const png = new import_pngjs.PNG({ width, height });
|
|
4713
|
+
png.data = Buffer.from(pixels);
|
|
4714
|
+
await import_promises.default.mkdir(import_path.default.dirname(filepath), { recursive: true });
|
|
4715
|
+
return new Promise((resolve, reject) => {
|
|
4716
|
+
const stream = (0, import_fs.createWriteStream)(filepath);
|
|
4717
|
+
stream.on("error", reject);
|
|
4718
|
+
stream.on("finish", resolve);
|
|
4719
|
+
png.pack().pipe(stream);
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
async function loadPNG(filepath) {
|
|
4723
|
+
return new Promise((resolve, reject) => {
|
|
4724
|
+
const stream = (0, import_fs.createReadStream)(filepath);
|
|
4725
|
+
stream.on("error", reject);
|
|
4726
|
+
const png = new import_pngjs.PNG();
|
|
4727
|
+
png.on("error", reject);
|
|
4728
|
+
png.on("parsed", (data) => {
|
|
4729
|
+
resolve({
|
|
4730
|
+
data: new Uint8ClampedArray(data),
|
|
4731
|
+
width: png.width,
|
|
4732
|
+
height: png.height
|
|
4733
|
+
});
|
|
4734
|
+
});
|
|
4735
|
+
stream.pipe(png);
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
async function compareSnapshots(actual, expected, width, height, options) {
|
|
4739
|
+
const {
|
|
4740
|
+
threshold = 0.1,
|
|
4741
|
+
includeAA = false,
|
|
4742
|
+
diffColor = [255, 0, 0],
|
|
4743
|
+
maxDifferencePercent = 0.1
|
|
4744
|
+
} = options || {};
|
|
4745
|
+
if (actual.length !== expected.length) {
|
|
4746
|
+
throw new Error(`Size mismatch: actual length ${actual.length} vs expected length ${expected.length}`);
|
|
4747
|
+
}
|
|
4748
|
+
const diff = new Uint8ClampedArray(width * height * 4);
|
|
4749
|
+
const numDiffPixels = (0, import_pixelmatch.default)(
|
|
4750
|
+
actual,
|
|
4751
|
+
expected,
|
|
4752
|
+
diff,
|
|
4753
|
+
width,
|
|
4754
|
+
height,
|
|
4755
|
+
{
|
|
4756
|
+
threshold,
|
|
4757
|
+
includeAA,
|
|
4758
|
+
diffColor,
|
|
4759
|
+
alpha: 1
|
|
4760
|
+
// Default alpha
|
|
4761
|
+
}
|
|
4762
|
+
);
|
|
4763
|
+
const totalPixels = width * height;
|
|
4764
|
+
const percentDifferent = numDiffPixels / totalPixels * 100;
|
|
4765
|
+
const passed = percentDifferent <= (maxDifferencePercent || 0);
|
|
4766
|
+
return {
|
|
4767
|
+
pixelsDifferent: numDiffPixels,
|
|
4768
|
+
totalPixels,
|
|
4769
|
+
percentDifferent,
|
|
4770
|
+
passed,
|
|
4771
|
+
diffImage: diff
|
|
4772
|
+
};
|
|
4773
|
+
}
|
|
4774
|
+
function getSnapshotPath(name, type, snapshotDir = "__snapshots__") {
|
|
4775
|
+
const dirMap = {
|
|
4776
|
+
baseline: "baselines",
|
|
4777
|
+
actual: "actual",
|
|
4778
|
+
diff: "diff"
|
|
4779
|
+
};
|
|
4780
|
+
return import_path.default.join(snapshotDir, dirMap[type], `${name}.png`);
|
|
4781
|
+
}
|
|
4782
|
+
async function expectSnapshot(pixels, options) {
|
|
4783
|
+
const {
|
|
4784
|
+
name,
|
|
4785
|
+
width,
|
|
4786
|
+
height,
|
|
4787
|
+
updateBaseline = false,
|
|
4788
|
+
snapshotDir = import_path.default.join(process.cwd(), "tests", "__snapshots__")
|
|
4789
|
+
// Default to current working dir/tests/__snapshots__
|
|
4790
|
+
} = options;
|
|
4791
|
+
if (!width || !height) {
|
|
4792
|
+
throw new Error("Width and height are required for expectSnapshot");
|
|
4793
|
+
}
|
|
4794
|
+
const baselinePath = getSnapshotPath(name, "baseline", snapshotDir);
|
|
4795
|
+
const actualPath = getSnapshotPath(name, "actual", snapshotDir);
|
|
4796
|
+
const diffPath = getSnapshotPath(name, "diff", snapshotDir);
|
|
4797
|
+
if (updateBaseline || !(0, import_fs.existsSync)(baselinePath)) {
|
|
4798
|
+
console.log(`Creating/Updating baseline for ${name} at ${baselinePath}`);
|
|
4799
|
+
await savePNG(pixels, width, height, baselinePath);
|
|
4800
|
+
return;
|
|
4801
|
+
}
|
|
4802
|
+
let baseline;
|
|
4803
|
+
try {
|
|
4804
|
+
baseline = await loadPNG(baselinePath);
|
|
4805
|
+
} catch (e) {
|
|
4806
|
+
throw new Error(`Failed to load baseline for ${name} at ${baselinePath}: ${e}`);
|
|
4807
|
+
}
|
|
4808
|
+
if (baseline.width !== width || baseline.height !== height) {
|
|
4809
|
+
await savePNG(pixels, width, height, actualPath);
|
|
4810
|
+
throw new Error(`Snapshot dimension mismatch for ${name}: expected ${baseline.width}x${baseline.height}, got ${width}x${height}`);
|
|
4811
|
+
}
|
|
4812
|
+
const result = await compareSnapshots(pixels, baseline.data, width, height, options);
|
|
4813
|
+
if (!result.passed) {
|
|
4814
|
+
await savePNG(pixels, width, height, actualPath);
|
|
4815
|
+
if (result.diffImage) {
|
|
4816
|
+
await savePNG(result.diffImage, width, height, diffPath);
|
|
4817
|
+
}
|
|
4818
|
+
throw new Error(
|
|
4819
|
+
`Snapshot comparison failed for ${name}: ${result.percentDifferent.toFixed(2)}% different (${result.pixelsDifferent} pixels). See ${diffPath} for details.`
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
async function renderAndExpectSnapshot(setup, renderFn, options) {
|
|
4824
|
+
const pixels = await renderAndCapture(setup, renderFn);
|
|
4825
|
+
await expectSnapshot(pixels, {
|
|
4826
|
+
...options,
|
|
4827
|
+
width: setup.width,
|
|
4828
|
+
height: setup.height
|
|
4829
|
+
});
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4646
4832
|
// src/e2e/playwright.ts
|
|
4647
4833
|
async function createPlaywrightTestClient(options = {}) {
|
|
4648
4834
|
let playwright;
|
|
@@ -4725,8 +4911,8 @@ function throttleBandwidth(bytesPerSecond) {
|
|
|
4725
4911
|
|
|
4726
4912
|
// src/e2e/visual.ts
|
|
4727
4913
|
var import_canvas3 = require("@napi-rs/canvas");
|
|
4728
|
-
var
|
|
4729
|
-
var
|
|
4914
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
4915
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
4730
4916
|
async function captureGameScreenshot(page, name) {
|
|
4731
4917
|
return await page.screenshot({ path: `${name}.png` });
|
|
4732
4918
|
}
|
|
@@ -4741,18 +4927,18 @@ async function takeScreenshot(canvas, filepath) {
|
|
|
4741
4927
|
} else {
|
|
4742
4928
|
throw new Error("Unsupported canvas type for screenshot");
|
|
4743
4929
|
}
|
|
4744
|
-
await
|
|
4745
|
-
await
|
|
4930
|
+
await import_promises2.default.mkdir(import_path2.default.dirname(filepath), { recursive: true });
|
|
4931
|
+
await import_promises2.default.writeFile(filepath, buffer);
|
|
4746
4932
|
}
|
|
4747
4933
|
async function compareScreenshot(canvas, baselinePath) {
|
|
4748
4934
|
try {
|
|
4749
|
-
await
|
|
4935
|
+
await import_promises2.default.access(baselinePath);
|
|
4750
4936
|
} catch {
|
|
4751
4937
|
console.warn(`Baseline not found at ${baselinePath}, saving current as baseline.`);
|
|
4752
4938
|
await takeScreenshot(canvas, baselinePath);
|
|
4753
4939
|
return true;
|
|
4754
4940
|
}
|
|
4755
|
-
const baselineBuffer = await
|
|
4941
|
+
const baselineBuffer = await import_promises2.default.readFile(baselinePath);
|
|
4756
4942
|
const baselineImage = new import_canvas3.Image();
|
|
4757
4943
|
baselineImage.src = baselineBuffer;
|
|
4758
4944
|
const width = baselineImage.width;
|
|
@@ -4844,10 +5030,12 @@ function createVisualTestScenario(sceneName) {
|
|
|
4844
5030
|
MockWebGL2RenderingContext,
|
|
4845
5031
|
captureAudioEvents,
|
|
4846
5032
|
captureCanvasDrawCalls,
|
|
5033
|
+
captureFramebufferAsPNG,
|
|
4847
5034
|
captureGameScreenshot,
|
|
4848
5035
|
captureGameState,
|
|
4849
5036
|
compareScreenshot,
|
|
4850
5037
|
compareScreenshots,
|
|
5038
|
+
compareSnapshots,
|
|
4851
5039
|
createBandwidthTestScenario,
|
|
4852
5040
|
createBinaryStreamMock,
|
|
4853
5041
|
createBinaryWriterMock,
|
|
@@ -4993,9 +5181,12 @@ function createVisualTestScenario(sceneName) {
|
|
|
4993
5181
|
createVector3,
|
|
4994
5182
|
createViewTestScenario,
|
|
4995
5183
|
createVisualTestScenario,
|
|
5184
|
+
expectSnapshot,
|
|
5185
|
+
getSnapshotPath,
|
|
4996
5186
|
initHeadlessWebGPU,
|
|
4997
5187
|
intersects,
|
|
4998
5188
|
ladderTrace,
|
|
5189
|
+
loadPNG,
|
|
4999
5190
|
makeAxisBrush,
|
|
5000
5191
|
makeBrushFromMinsMaxs,
|
|
5001
5192
|
makeBspModel,
|
|
@@ -5007,7 +5198,9 @@ function createVisualTestScenario(sceneName) {
|
|
|
5007
5198
|
mockMonsterAttacks,
|
|
5008
5199
|
randomVector3,
|
|
5009
5200
|
renderAndCapture,
|
|
5201
|
+
renderAndExpectSnapshot,
|
|
5010
5202
|
runComputeAndReadback,
|
|
5203
|
+
savePNG,
|
|
5011
5204
|
serializeUserInfo,
|
|
5012
5205
|
setupBrowserEnvironment,
|
|
5013
5206
|
setupMockAudioContext,
|