From 8318da81c8dc0bdcdc9d5314ac9b5af4da4711f4 Mon Sep 17 00:00:00 2001 From: Vaclav Vancura Date: Sat, 16 May 2026 21:46:03 +0200 Subject: [PATCH] test(api): add clone-per-read contract tests for BT snapshot getters Verify that BT.canvasDisplaySize, BT.outputSize, and BT.camera each return an independent clone on every read, so mutating the returned vector does not affect subsequent reads. Also fixes a JSDoc {@link HardwareSettings} reference in docs/api-core.md that rendered as a literal tag in Markdown; replaced with a plain backtick reference. Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Vaclav Vancura --- docs/api-core.md | 5 ++--- src/BlitTech.test.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/api-core.md b/docs/api-core.md index 1d6a1e5..2b8a5a8 100644 --- a/docs/api-core.md +++ b/docs/api-core.md @@ -72,9 +72,8 @@ set `canvas.tabIndex = 0` and call `canvas.focus()` so keyboard events reach the | `detectDroppedFrames` | `boolean` | `false` | Log a console warning on missed vsync | **`BT` getters vs `configure()` fields:** `displaySize`, `canvasDisplaySize`, and `targetFPS` on `BT` mirror the same -names on {@link HardwareSettings}. `outputSize` is the effective drawing-buffer size -(`canvasDisplaySize ?? displaySize`). `activeBackend` is the backend that actually started (after fallback), not the -`renderer` value from `configure()`. +names on `HardwareSettings`. `outputSize` is the effective drawing-buffer size (`canvasDisplaySize ?? displaySize`). +`activeBackend` is the backend that actually started (after fallback), not the `renderer` value from `configure()`. --- diff --git a/src/BlitTech.test.ts b/src/BlitTech.test.ts index 8bbb13f..ed6442c 100644 --- a/src/BlitTech.test.ts +++ b/src/BlitTech.test.ts @@ -155,6 +155,18 @@ describe('BT.canvasDisplaySize', () => { expect(size?.x).toBe(640); expect(size?.y).toBe(480); }); + + it('returns an independent clone per read', () => { + vi.spyOn(BTAPI.instance, 'getHardwareSettings').mockReturnValue({ + ...mockHardwareSettings(), + canvasDisplaySize: new Vector2i(640, 480), + }); + + const first = BT.canvasDisplaySize; + first!.x = 999; + + expect(BT.canvasDisplaySize?.x).toBe(640); + }); }); describe('BT.outputSize', () => { @@ -191,6 +203,15 @@ describe('BT.outputSize', () => { expect(size.x).toBe(640); expect(size.y).toBe(480); }); + + it('returns an independent clone per read', () => { + vi.spyOn(BTAPI.instance, 'getHardwareSettings').mockReturnValue(mockHardwareSettings(new Vector2i(320, 240))); + + const first = BT.outputSize; + first.x = 999; + + expect(BT.outputSize.x).toBe(320); + }); }); // #endregion @@ -545,6 +566,16 @@ describe('BT.camera', () => { expect(result).toBe(expected); }); + + it('returns an independent clone per read', () => { + const stored = new Vector2i(64, 32); + vi.spyOn(BTAPI.instance, 'getCameraOffset').mockImplementation(() => stored.clone()); + + const first = BT.camera; + first.x = 999; + + expect(BT.camera.x).toBe(64); + }); }); describe('BT.cameraClamp', () => { -- 2.51.2