From 996cfe69adf5dd5f8de157ea606a303a1be15211 Mon Sep 17 00:00:00 2001 From: rushabhcodes Date: Tue, 3 Mar 2026 02:49:47 +0530 Subject: [PATCH 1/3] feat: add camera presets for 3D snapshots and validation --- cli/snapshot/register.ts | 20 +++ lib/shared/camera-presets.ts | 107 ++++++++++++++++ lib/shared/snapshot-project.ts | 13 +- .../snapshot/__snapshots__/3d-front.snap.png | Bin 0 -> 3190 bytes .../__snapshots__/3d-left-sideview.snap.png | Bin 0 -> 3098 bytes .../__snapshots__/3d-right-sideview.snap.png | Bin 0 -> 3102 bytes .../3d-top-center-angled.snap.png | Bin 0 -> 7526 bytes .../__snapshots__/3d-top-down.snap.png | Bin 0 -> 25036 bytes .../__snapshots__/3d-top-left-corner.snap.png | Bin 0 -> 21714 bytes .../__snapshots__/3d-top-left.snap.png | Bin 0 -> 5444 bytes .../3d-top-right-corner.snap.png | Bin 0 -> 21339 bytes .../__snapshots__/3d-top-right.snap.png | Bin 0 -> 5595 bytes .../snapshot/snapshot-camera-preset.test.ts | 114 ++++++++++++++++++ 13 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 lib/shared/camera-presets.ts create mode 100644 tests/cli/snapshot/__snapshots__/3d-front.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-left-sideview.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-right-sideview.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-center-angled.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-down.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-left-corner.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-left.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-right-corner.snap.png create mode 100644 tests/cli/snapshot/__snapshots__/3d-top-right.snap.png create mode 100644 tests/cli/snapshot/snapshot-camera-preset.test.ts diff --git a/cli/snapshot/register.ts b/cli/snapshot/register.ts index 71048535..2fa7ae5f 100644 --- a/cli/snapshot/register.ts +++ b/cli/snapshot/register.ts @@ -1,4 +1,8 @@ import type { Command } from "commander" +import { + CAMERA_PRESET_NAMES, + type CameraPreset, +} from "lib/shared/camera-presets" import { snapshotProject } from "lib/shared/snapshot-project" export const registerSnapshot = (program: Command) => { @@ -17,6 +21,10 @@ export const registerSnapshot = (program: Command) => { .option("--pcb-only", "Generate only PCB snapshots") .option("--schematic-only", "Generate only schematic snapshots") .option("--disable-parts-engine", "Disable the parts engine") + .option( + "--camera-preset ", + `Camera angle preset for 3D snapshots (implies --3d). Valid presets: ${CAMERA_PRESET_NAMES.join(", ")}`, + ) .option("--ci", "Enable CI mode with snapshot diff artifacts") .option("--test", "Enable test mode with snapshot diff artifacts") .action( @@ -29,10 +37,21 @@ export const registerSnapshot = (program: Command) => { schematicOnly?: boolean forceUpdate?: boolean disablePartsEngine?: boolean + cameraPreset?: string ci?: boolean test?: boolean }, ) => { + if ( + options.cameraPreset && + !CAMERA_PRESET_NAMES.includes(options.cameraPreset as CameraPreset) + ) { + console.error( + `Unknown camera preset "${options.cameraPreset}". Valid presets: ${CAMERA_PRESET_NAMES.join(", ")}`, + ) + process.exit(1) + } + await snapshotProject({ update: options.update ?? false, threeD: options["3d"] ?? false, @@ -43,6 +62,7 @@ export const registerSnapshot = (program: Command) => { platformConfig: options.disablePartsEngine ? { partsEngineDisabled: true } : undefined, + cameraPreset: options.cameraPreset as CameraPreset | undefined, createDiff: (options.ci ?? false) || (options.test ?? false), onExit: (code) => process.exit(code), onError: (msg) => console.error(msg), diff --git a/lib/shared/camera-presets.ts b/lib/shared/camera-presets.ts new file mode 100644 index 00000000..ced118ed --- /dev/null +++ b/lib/shared/camera-presets.ts @@ -0,0 +1,107 @@ +/** + * Camera presets for 3D snapshot rendering. + * + * Each preset is a function that takes the default camera result from + * `getBestCameraPosition` and returns modified camera options for + * the desired viewpoint. + * + * Coordinate system (GLTF / circuit-json-to-gltf): + * Y = up (perpendicular to PCB) + * X, Z = PCB plane + * camPos/lookAt use negated X (camPos = [-camX, camY, camZ]) + */ + +type CameraResult = { + camPos: readonly [number, number, number] + lookAt: readonly [number, number, number] + fov: number +} + +function distance( + a: readonly [number, number, number], + b: readonly [number, number, number], +): number { + return Math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2) +} + +/** + * Place camera along a unit direction from lookAt at the same distance + * as the default camera. + */ +function repositionCamera( + cam: CameraResult, + dir: [number, number, number], +): CameraResult { + const dist = distance(cam.camPos, cam.lookAt) + const len = Math.sqrt(dir[0] ** 2 + dir[1] ** 2 + dir[2] ** 2) + const nx = dir[0] / len + const ny = dir[1] / len + const nz = dir[2] / len + return { + camPos: [ + cam.lookAt[0] + nx * dist, + cam.lookAt[1] + ny * dist, + cam.lookAt[2] + nz * dist, + ] as const, + lookAt: cam.lookAt, + fov: cam.fov, + } +} + +export const CAMERA_PRESETS = { + /** Directly above the board looking straight down */ + "top-down": (cam: CameraResult): CameraResult => + repositionCamera(cam, [0.001, 1, -0.001]), + + /** Angled view from top-left corner */ + "top-left-corner": (cam: CameraResult): CameraResult => + repositionCamera(cam, [0.7, 1.2, -0.8]), + + /** From the left side, angled from above */ + "top-left": (cam: CameraResult): CameraResult => + repositionCamera(cam, [1, 1.2, 0]), + + /** Angled view from top-right corner */ + "top-right-corner": (cam: CameraResult): CameraResult => + repositionCamera(cam, [-0.7, 1.2, -0.8]), + + /** From the right side, angled from above */ + "top-right": (cam: CameraResult): CameraResult => + repositionCamera(cam, [-1, 1.2, 0]), + + /** Side view from the left (eye level) */ + "left-sideview": (cam: CameraResult): CameraResult => + repositionCamera(cam, [1, 0.05, 0]), + + /** Side view from the right (eye level) */ + "right-sideview": (cam: CameraResult): CameraResult => + repositionCamera(cam, [-1, 0.05, 0]), + + /** Front view (eye level) */ + front: (cam: CameraResult): CameraResult => + repositionCamera(cam, [0, 0.05, -1]), + + /** Top-center with a moderate angle (the default 3D view) */ + "top-center-angled": (cam: CameraResult): CameraResult => + repositionCamera(cam, [0, 1, -1]), +} as const satisfies Record CameraResult> + +export type CameraPreset = keyof typeof CAMERA_PRESETS + +export const CAMERA_PRESET_NAMES = Object.keys(CAMERA_PRESETS) as CameraPreset[] + +/** + * Apply a camera preset to a default camera result from getBestCameraPosition. + * Throws if the preset is unknown. + */ +export function applyCameraPreset( + preset: string, + cam: CameraResult, +): CameraResult { + if (!(preset in CAMERA_PRESETS)) { + throw new Error( + `Unknown camera preset "${preset}". Valid presets: ${CAMERA_PRESET_NAMES.join(", ")}`, + ) + } + return CAMERA_PRESETS[preset as CameraPreset](cam) +} diff --git a/lib/shared/snapshot-project.ts b/lib/shared/snapshot-project.ts index 52cd50f8..38852252 100644 --- a/lib/shared/snapshot-project.ts +++ b/lib/shared/snapshot-project.ts @@ -6,6 +6,7 @@ import { convertCircuitJsonToGltf, getBestCameraPosition, } from "circuit-json-to-gltf" +import { type CameraPreset, applyCameraPreset } from "lib/shared/camera-presets" import { convertCircuitJsonToPcbSvg, convertCircuitJsonToSchematicSvg, @@ -41,6 +42,8 @@ type SnapshotOptions = { platformConfig?: PlatformConfig /** Create visual diff artifacts when snapshots mismatch */ createDiff?: boolean + /** Camera preset name for 3D snapshots (implies --3d) */ + cameraPreset?: CameraPreset onExit?: (code: number) => void onError?: (message: string) => void onSuccess?: (message: string) => void @@ -59,7 +62,12 @@ export const snapshotProject = async ({ onSuccess = (msg) => console.log(msg), platformConfig, createDiff = false, + cameraPreset, }: SnapshotOptions = {}) => { + // --camera-preset implies --3d + if (cameraPreset) { + threeD = true + } const projectDir = process.cwd() const ignore = [ ...DEFAULT_IGNORED_PATTERNS, @@ -162,7 +170,10 @@ export const snapshotProject = async ({ ) } - const cameraOptions = getBestCameraPosition(circuitJson) + let cameraOptions = getBestCameraPosition(circuitJson) + if (cameraPreset) { + cameraOptions = applyCameraPreset(cameraPreset, cameraOptions) + } png3d = await renderGLTFToPNGBufferFromGLBBuffer( glbBuffer, diff --git a/tests/cli/snapshot/__snapshots__/3d-front.snap.png b/tests/cli/snapshot/__snapshots__/3d-front.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..9d6a395b8b01228969bfd4fcf8c29e69502eabf8 GIT binary patch literal 3190 zcmeHHYfzI%6y1D{hM){itfi6wIwPe@jap)i4u};*0}_kaM#C$Pm?%L-LF849qGal; zTIC^xw2IOy4Imhapb{$<@j=vJ9+H@ZSEvCK2ncF0-48mpfBL(f4m-2EvuAhDy?gfF z`|X#El`hWi&Hw-x`l{fy0DLe50A#(>M2Hlo9!P={LJ$68T^gpxeeV+hCeK4xy|?O* zY-VC`R!cxm%1mtOHR}n=Rswnbg$SppxgX8UrXFzc@7($|UtfuFM3&DDm#5NOdCB$k`zN zI^A6?K`}fqt>wiajuK@pmY+;SWHpy0WX;KkoT=OMo+jkRY!$m;3{kKXpR)3M?sBT} zixdfxd*xS@Ily`>6q;Nz9g(TtU%LAK5-%5SrmRI|j*AlV{xEm~V}MxF1!_^a^ferS zG$RNMA7+`q_b4C3W zgdyR6k1~|CJ`_lIDJX}Ec!j0WEwtkpLt7}|TPVjji9T*z0HPUq09{CTR!}-yq2GfU z0SAfP8?mlX0q)WEf}JE=10`JJ-nbyj$HxbqO%YHMS$-%;OR)J1xUIC~ujATj9G=Jo zDv918`l9mLl50;x2iy0Z&Qg$u(Yn|YAm_}lslRZ#Qq2Ez=l z2ZUs*b+)#>D{v50zD)rQ3&hiuS#GEfk5m>aM?gW42@sJYH#;$X*lR1`NIo z2BW{Bsfpg4G0+_`IsjTdfgi9As;fE=1CG7M1*7&uefb6YLVBLoe#|=seVH%_#_BX4 zj6JoVd;)t-3oBnc-#nP%lv$>%okfT8$V6#mATMB<3DOm)?q?-btYLa2B4fZ1vOYks zvY`y>Gw4?xg9Avtgyk1+5xW?bXiT%Y=Hx?d8SdnNL?Ch}^<>A;b8`L#J-*GKk>hRu3Mo_?fr$k_G9&^u&nD6))D zn4Mg`gAN0Vj1n2`p4z%VtTZ)?j@AO#B87mS09tgb{Z{rG#EAY<>$hO(M3Q0_+X4&aa&{OmkjNtO+>;r!Qs2a^o literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-left-sideview.snap.png b/tests/cli/snapshot/__snapshots__/3d-left-sideview.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..b155fe0832def2636706c61e99f28f2f3ec5308d GIT binary patch literal 3098 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i1B%QlYbpRzOr9=|Ar*{or0Om)FmPW6 z@!lTXm>>I8lKsK`pK;tzG?{~xFS6?AoH;Y^U5n}!OO6+p6uVf&oO3&Mw+FX`q-L}! z{A<+f>?sjX|8yWHRk2xpTHaLl(}hcd8>JFnFnpP(A2a`XB@@u9Q7{?;!zcu$1_lRpXNI)AoiTlld_d5~aORFcgXHc#dt^4J{r>&C^7C19k~IU;_3eoO}L(Tp=@E?&I&;iE@M zXWDk{*l|J)PKfmN^#S8CfSW-&IX_?jPfB{aIz!GhF$RVPKSWAc!rIWgarbWNx}xXj z<}z%EM3w=22&8-)lY#T?+qZAJfQS$ni!q@VS#`rrYoN2I$k|qjF#L!ECn;TEf}5Me znvgcnzTPg4X@1?W%w=VtpPkKFHQPKtO}G}AWPZL_+`lRB{Cs=)`0BS?6&Z{-i#E)Z zY*P&W@u*w>(@FLDAZPEt^7r@m_CH*Q4SHrCICsu3Zdd*Pe-li1*8cuB`rD6>k9~5tM)e*)e*9(#keGQW zYpaxa_t6QRH9wzD|9LMufA7@N=(S;wlQv2)87S*A9Qa>SRVBr6Z8apuP=hT+o56l_ z^$rPV^WE*;hWm{{T%?<4_}UL-t#4{PJZ6KifQqW=k5`%+kN-&(HZ|QPu{&+@xa!Nq7SCtwvFjY z%$589b$;c`-SxH8_uK3dzQOYU$LIT>kK6w-)ZhDkyL~IjrZnDpcVc-N<8~?@$lnHZ z1oMG4HamAqG5tw%PFPmPFP_ZsK(qqnmb5I680Y-=d#%|R%x-M&-zUj*9hgLb`5zov zTYxcEatV|dJO_&)Ph1ZWWg b4|e_J&-3|pxcqVOZjkw&u6{1-oD!Mi1B%QlYbpRzOr9=|Ar*{or0Om)FmPW7 z@!lTXn5FWqk^RB_KTGR4U-?*V^gUwJ$i4Au)5BS|0yp#%cs?^F@*Z38bcfp113y@g zh@NZmEX>);bK`Hr8k6X^mI|icH`opKKPWi2)N{k(Gxh$n-v4F-S~UtrLtq$%!1s$v z41dnPe3{7{y(MnFIOBtSF~$e${rvrfQFsMbcB%Gf+Z=RXK!K%G+K(;Q^hU544R9?=x86ICd_4Mh}Z%%!Fett8z z|2&(M>ul=(+3f!CUaqy3RTN7?zbspVe{6jG_ix|K%Fn)io4f7p z?EHNv^=iM}O#k_0vVYO_JNNFT-CnnTeRWaMCXNI1q!<|(W;`)qsDAbJ_4S+E-tYf! zxBJ_#udna8u8-e;F81?L@%Sgr{B|4+w~HkjY`5Ob*_7+K>lPIwgi4z8JQ<~Ru&c~VoM7OChUIk z;DG`nNP%j47&d&@)7RhZ4Gap#2KiouIf)G4&TPM5cl*tO^82;ZW38>NPwVBF&3<$1 z=FP~m#LYKTrq}&=*j@v~H@9uxym{aMf4_llo_QFEA3uKl=FrEFh5P>fdaX14^dFhi zPmSd3|9n*1e*h@DnHk8n-??K)M2dIV>d?>o`}+GgbHDytH8DN`7{&V!ET3N|#jx!; zF!iKtjr#iMkIn6EF?yS`S6^NA{Q2{ptIwSWrdJ?GTwJ{S&555sE6Y-W${syR5^S(F zVmfdwCN`Fp;m2{5aAHW91P(kg#vj)G^X&58ZJfNFe=|48A-i_%I;CIu_d#ggI-r>G z;jhi%H>bf^KX>}wvpe%&{W^c#Q@iS)Z;mQ4)TawRxL5JzOW|Sr(of$K%My3SJahm5 z)&BfuZS{QlwD5KEdrqyJyQg+W?9RFmTDyxtYF_?2xcbBSy*1yGfO;)>KAf|y`n4l5 z``?G#_n)i(|0e%mZihC6b3bR_+4_Iq-#dMJ@$zvRcbr}IMvf2XbeaB~mb0zB!>}P7 zBBCG7^6kpv<^9qO73^SkceG;-fH literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-top-center-angled.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-center-angled.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..c648f9d3a0172258ea0c70b19f72aac56d41f206 GIT binary patch literal 7526 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 T7Ry-UyByeSwdcXt)F1vAH)Dnr literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-top-down.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-down.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..7e1f8b728e7818a59c7cc0821d1699d4dbcfd855 GIT binary patch literal 25036 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 aGCX6BG{1E&EocuqWS4^K_5^v;zy1%rCs@h= literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-top-left-corner.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-left-corner.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..705c0dfe76659cd7a867570ba86db872d098680b GIT binary patch literal 21714 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 literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-top-left.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-left.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..e9fc2b79b39bfcd179aa6bd662a939abcba6155d GIT binary patch literal 5444 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 literal 0 HcmV?d00001 diff --git a/tests/cli/snapshot/__snapshots__/3d-top-right-corner.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-right-corner.snap.png new file mode 100644 index 0000000000000000000000000000000000000000..59b8ea591fd9f8e072dff23366dc75dbad8923a0 GIT binary patch literal 21339 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 ( + + + +) +` + +/** Directory next to this test file where preset snapshots are saved */ +const SAVED_SNAPSHOTS_DIR = join(import.meta.dir, "__snapshots__") + +for (const preset of CAMERA_PRESET_NAMES) { + test(`snapshot --camera-preset=${preset} creates 3D snapshot`, async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + const { stdout, exitCode } = await runCommand( + `tsci snapshot --update --camera-preset=${preset}`, + ) + + expect(exitCode).toBe(0) + expect(stdout).toContain("Created snapshots") + + // --camera-preset implies --3d, so 3D snapshot must exist + const snapshotDir = join(tmpDir, "__snapshots__") + expect(fs.existsSync(join(snapshotDir, "test.board-3d.snap.png"))).toBe( + true, + ) + + // PCB and schematic snapshots should also be generated + expect(fs.existsSync(join(snapshotDir, "test.board-pcb.snap.svg"))).toBe( + true, + ) + expect( + fs.existsSync(join(snapshotDir, "test.board-schematic.snap.svg")), + ).toBe(true) + + // The 3D snapshot should be a valid PNG (starts with PNG magic bytes) + const pngBuf = fs.readFileSync(join(snapshotDir, "test.board-3d.snap.png")) + expect(pngBuf.length).toBeGreaterThan(0) + // PNG magic: 0x89 P N G + expect(pngBuf[0]).toBe(0x89) + expect(pngBuf[1]).toBe(0x50) // P + expect(pngBuf[2]).toBe(0x4e) // N + expect(pngBuf[3]).toBe(0x47) // G + + // Save the generated snapshot with a preset-specific name for inspection + fs.mkdirSync(SAVED_SNAPSHOTS_DIR, { recursive: true }) + fs.copyFileSync( + join(snapshotDir, "test.board-3d.snap.png"), + join(SAVED_SNAPSHOTS_DIR, `3d-${preset}.snap.png`), + ) + }, 60_000) +} + +test("snapshot --camera-preset with invalid preset fails", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + const { stderr, exitCode } = await runCommand( + "tsci snapshot --update --camera-preset=bogus-preset", + ) + + expect(exitCode).not.toBe(0) + expect(stderr).toContain('Unknown camera preset "bogus-preset"') +}, 30_000) + +test("snapshot --camera-preset without --3d still generates 3D snapshot", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + // No explicit --3d flag; --camera-preset should imply it + const { stdout, exitCode } = await runCommand( + "tsci snapshot --update --camera-preset=top-down", + ) + + expect(exitCode).toBe(0) + expect(stdout).toContain("Created snapshots") + + const snapshotDir = join(tmpDir, "__snapshots__") + expect(fs.existsSync(join(snapshotDir, "test.board-3d.snap.png"))).toBe(true) +}, 60_000) + +test("different camera presets produce different 3D snapshots", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + // Generate with top-down + await runCommand("tsci snapshot --update --camera-preset=top-down") + const snapshotDir = join(tmpDir, "__snapshots__") + const topDownPng = fs.readFileSync( + join(snapshotDir, "test.board-3d.snap.png"), + ) + + // Generate with front (overwrites the snapshot) + await runCommand("tsci snapshot --update --camera-preset=front") + const frontPng = fs.readFileSync(join(snapshotDir, "test.board-3d.snap.png")) + + // The two images should differ + expect(Buffer.compare(topDownPng, frontPng)).not.toBe(0) +}, 90_000) From 2fcbc9774836ee40d3aedaeab1df938c5464dc48 Mon Sep 17 00:00:00 2001 From: rushabhcodes Date: Wed, 4 Mar 2026 01:19:50 +0530 Subject: [PATCH 2/3] feat: add tests for camera presets including valid, invalid, and default behavior --- .../snapshot-camera-preset-different.test.ts | 32 +++++++++++ .../snapshot-camera-preset-invalid.test.ts | 25 +++++++++ .../snapshot-camera-preset-without-3d.test.ts | 29 ++++++++++ .../snapshot/snapshot-camera-preset.test.ts | 55 +------------------ 4 files changed, 87 insertions(+), 54 deletions(-) create mode 100644 tests/cli/snapshot/snapshot-camera-preset-different.test.ts create mode 100644 tests/cli/snapshot/snapshot-camera-preset-invalid.test.ts create mode 100644 tests/cli/snapshot/snapshot-camera-preset-without-3d.test.ts diff --git a/tests/cli/snapshot/snapshot-camera-preset-different.test.ts b/tests/cli/snapshot/snapshot-camera-preset-different.test.ts new file mode 100644 index 00000000..02ce1991 --- /dev/null +++ b/tests/cli/snapshot/snapshot-camera-preset-different.test.ts @@ -0,0 +1,32 @@ +import { test, expect } from "bun:test" +import { join } from "node:path" +import fs from "node:fs" +import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture" + +const BOARD_TSX = ` +export const TestBoard = () => ( + + + +) +` + +test("different camera presets produce different 3D snapshots", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + // Generate with top-down + await runCommand("tsci snapshot --update --camera-preset=top-down") + const snapshotDir = join(tmpDir, "__snapshots__") + const topDownPng = fs.readFileSync( + join(snapshotDir, "test.board-3d.snap.png"), + ) + + // Generate with front (overwrites the snapshot) + await runCommand("tsci snapshot --update --camera-preset=front") + const frontPng = fs.readFileSync(join(snapshotDir, "test.board-3d.snap.png")) + + // The two images should differ + expect(Buffer.compare(topDownPng, frontPng)).not.toBe(0) +}, 90_000) diff --git a/tests/cli/snapshot/snapshot-camera-preset-invalid.test.ts b/tests/cli/snapshot/snapshot-camera-preset-invalid.test.ts new file mode 100644 index 00000000..d3bf5f2c --- /dev/null +++ b/tests/cli/snapshot/snapshot-camera-preset-invalid.test.ts @@ -0,0 +1,25 @@ +import { test, expect } from "bun:test" +import { join } from "node:path" +import fs from "node:fs" +import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture" + +const BOARD_TSX = ` +export const TestBoard = () => ( + + + +) +` + +test("snapshot --camera-preset with invalid preset fails", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + const { stderr, exitCode } = await runCommand( + "tsci snapshot --update --camera-preset=bogus-preset", + ) + + expect(exitCode).not.toBe(0) + expect(stderr).toContain('Unknown camera preset "bogus-preset"') +}, 30_000) diff --git a/tests/cli/snapshot/snapshot-camera-preset-without-3d.test.ts b/tests/cli/snapshot/snapshot-camera-preset-without-3d.test.ts new file mode 100644 index 00000000..3983499d --- /dev/null +++ b/tests/cli/snapshot/snapshot-camera-preset-without-3d.test.ts @@ -0,0 +1,29 @@ +import { test, expect } from "bun:test" +import { join } from "node:path" +import fs from "node:fs" +import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture" + +const BOARD_TSX = ` +export const TestBoard = () => ( + + + +) +` + +test("snapshot --camera-preset without --3d still generates 3D snapshot", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + + await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) + + // No explicit --3d flag; --camera-preset should imply it + const { stdout, exitCode } = await runCommand( + "tsci snapshot --update --camera-preset=top-down", + ) + + expect(exitCode).toBe(0) + expect(stdout).toContain("Created snapshots") + + const snapshotDir = join(tmpDir, "__snapshots__") + expect(fs.existsSync(join(snapshotDir, "test.board-3d.snap.png"))).toBe(true) +}, 60_000) diff --git a/tests/cli/snapshot/snapshot-camera-preset.test.ts b/tests/cli/snapshot/snapshot-camera-preset.test.ts index a9d9f504..2b6b3616 100644 --- a/tests/cli/snapshot/snapshot-camera-preset.test.ts +++ b/tests/cli/snapshot/snapshot-camera-preset.test.ts @@ -2,10 +2,7 @@ import { test, expect } from "bun:test" import { join } from "node:path" import fs from "node:fs" import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture" -import { - CAMERA_PRESET_NAMES, - type CameraPreset, -} from "lib/shared/camera-presets" +import { CAMERA_PRESET_NAMES } from "lib/shared/camera-presets" const BOARD_TSX = ` export const TestBoard = () => ( @@ -62,53 +59,3 @@ for (const preset of CAMERA_PRESET_NAMES) { ) }, 60_000) } - -test("snapshot --camera-preset with invalid preset fails", async () => { - const { tmpDir, runCommand } = await getCliTestFixture() - - await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) - - const { stderr, exitCode } = await runCommand( - "tsci snapshot --update --camera-preset=bogus-preset", - ) - - expect(exitCode).not.toBe(0) - expect(stderr).toContain('Unknown camera preset "bogus-preset"') -}, 30_000) - -test("snapshot --camera-preset without --3d still generates 3D snapshot", async () => { - const { tmpDir, runCommand } = await getCliTestFixture() - - await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) - - // No explicit --3d flag; --camera-preset should imply it - const { stdout, exitCode } = await runCommand( - "tsci snapshot --update --camera-preset=top-down", - ) - - expect(exitCode).toBe(0) - expect(stdout).toContain("Created snapshots") - - const snapshotDir = join(tmpDir, "__snapshots__") - expect(fs.existsSync(join(snapshotDir, "test.board-3d.snap.png"))).toBe(true) -}, 60_000) - -test("different camera presets produce different 3D snapshots", async () => { - const { tmpDir, runCommand } = await getCliTestFixture() - - await Bun.write(join(tmpDir, "test.board.tsx"), BOARD_TSX) - - // Generate with top-down - await runCommand("tsci snapshot --update --camera-preset=top-down") - const snapshotDir = join(tmpDir, "__snapshots__") - const topDownPng = fs.readFileSync( - join(snapshotDir, "test.board-3d.snap.png"), - ) - - // Generate with front (overwrites the snapshot) - await runCommand("tsci snapshot --update --camera-preset=front") - const frontPng = fs.readFileSync(join(snapshotDir, "test.board-3d.snap.png")) - - // The two images should differ - expect(Buffer.compare(topDownPng, frontPng)).not.toBe(0) -}, 90_000) From f2fb92d4d91415c87d70afd5a9a92d1fac72ef7f Mon Sep 17 00:00:00 2001 From: rushabhcodes Date: Wed, 4 Mar 2026 01:59:26 +0530 Subject: [PATCH 3/3] fix: adjust camera preset parameters for improved positioning --- lib/shared/camera-presets.ts | 2 +- .../__snapshots__/3d-top-down.snap.png | Bin 25036 -> 5282 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shared/camera-presets.ts b/lib/shared/camera-presets.ts index ced118ed..3af419de 100644 --- a/lib/shared/camera-presets.ts +++ b/lib/shared/camera-presets.ts @@ -51,7 +51,7 @@ function repositionCamera( export const CAMERA_PRESETS = { /** Directly above the board looking straight down */ "top-down": (cam: CameraResult): CameraResult => - repositionCamera(cam, [0.001, 1, -0.001]), + repositionCamera(cam, [0.0001, 1, -0.01]), /** Angled view from top-left corner */ "top-left-corner": (cam: CameraResult): CameraResult => diff --git a/tests/cli/snapshot/__snapshots__/3d-top-down.snap.png b/tests/cli/snapshot/__snapshots__/3d-top-down.snap.png index 7e1f8b728e7818a59c7cc0821d1699d4dbcfd855..15262632c4edc560d57a72b165b5463b70d163a9 100644 GIT binary patch literal 5282 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 aGCX6BG{1E&EocuqWS4^K_5^v;zy1%rCs@h=