import assert from 'node:assert/strict'; import { existsSync } from 'node:fs'; import * as THREE from 'three'; assert.ok(existsSync(new URL('../src/objects/WindowFinish.ts', import.meta.url)), 'real frame finish is missing'); const { WindowFinish } = await import('../src/objects/WindowFinish.ts'); const { WindowReveal } = await import('../src/objects/WindowReveal.ts'); const frameGeometry = new THREE.BoxGeometry(1,1,1); const colors=[0xb4a0c5,0xc3afd3,0x9d86b0,0xd9c7e6,0xaa91bd,0xc0a9d2]; const materials=colors.map(color=>new THREE.MeshBasicMaterial({color})); const frames=Array.from({length:4},()=>new THREE.Mesh(frameGeometry,materials)); const revealMaterial=new THREE.MeshBasicMaterial({color:colors[1],side:THREE.DoubleSide}); const reveal=new WindowReveal(revealMaterial);const scene=new THREE.Scene();scene.add(...frames,...reveal.meshes); const finish=new WindowFinish(scene,frames,reveal,materials,revealMaterial); finish.update(1,.8,.6,.035,.0525);finish.apply('wood'); assert.ok(frames.every(frame=>frame.material.every(m=>m.isMeshStandardMaterial&&m.map&&m.bumpMap)),'wood must be textured and lit in WebGL'); assert.notEqual(frames[0].material[4].map,frames[2].material[4].map,'vertical stiles and horizontal rails need separately oriented grain'); assert.ok(reveal.meshes.every(mesh=>mesh.material.map),'inner reveal must receive the wooden finish'); assert.ok(reveal.meshes.every(mesh=>mesh.geometry.getAttribute('uv')),'textured reveals need UVs'); for (const mesh of reveal.meshes) { const uv=mesh.geometry.getAttribute('uv');mesh.material.map.updateMatrix(); const a=new THREE.Vector2(uv.getX(0),uv.getY(0)).applyMatrix3(mesh.material.map.matrix); const b=new THREE.Vector2(uv.getX(1),uv.getY(1)).applyMatrix3(mesh.material.map.matrix); assert.ok(Math.abs(b.x-a.x)<1e-8 && Math.abs(b.y-a.y)>.9,'longitudinal reveal edge must follow the wood fibers on every side'); } const decorative=scene.children.filter(x=>x.isGroup); assert.ok(decorative.some(group=>group.visible&&group.children.some(x=>x.name==='brass-latch')),'actual brass hardware must be present'); const identities=frames.map(f=>[f.geometry,f.position,f.scale]); finish.apply('booth');assert.ok(frames.every(f=>f.material===materials));assert.ok(reveal.meshes.every(f=>f.material===revealMaterial));assert.ok(decorative.every(group=>!group.visible)); finish.apply('wood');assert.deepEqual(frames.map(f=>[f.geometry,f.position,f.scale]),identities,'theme switches must not replace or move the primary frame'); let disposed=0;const maps=new Set(frames.flatMap(f=>f.material.flatMap(m=>[m.map,m.bumpMap])));maps.forEach(map=>map.addEventListener('dispose',()=>disposed++));finish.dispose();assert.equal(disposed,maps.size,'owned texture resources dispose exactly once'); reveal.dispose();frameGeometry.dispose();materials.forEach(m=>m.dispose());revealMaterial.dispose(); console.log('Wood finish: lit textured six-face frame, directional grain, UV reveal, brass hardware, booth restoration, geometry preservation and disposal passed (no WebGL pixels)');