Add verification: tests/verification.test.mjs
This commit is contained in:
1 parent
2fa7d13e94
commit
a027c4b095
1 file changed
+86
@@ -0,0 +1,86 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import {readFile} from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import vm from 'node:vm';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
|
||||
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
||||
const sandbox={setTimeout,URL:{createObjectURL(){return 'blob:test'},revokeObjectURL(){}},document:{createElement(){return {click(){}}}}};
|
||||
sandbox.window=sandbox;
|
||||
vm.createContext(sandbox);
|
||||
for(const file of ['src/dxf.js','src/model.js'])vm.runInContext(await readFile(path.join(root,file),'utf8'),sandbox,{filename:file});
|
||||
|
||||
function pair(code,value){return `${code}\n${value}`}
|
||||
function entity(type,fields){return [pair(0,type),...fields.map(([code,value])=>pair(code,value))].join('\n')}
|
||||
function polyline(layer,points){return entity('LWPOLYLINE',[[8,layer],[90,points.length],[70,1],...points.flatMap(p=>[[10,p[0]],[20,p[1]]])])}
|
||||
function line(layer,a,b){return entity('LINE',[[8,layer],[10,a[0]],[20,a[1]],[11,b[0]],[21,b[1]]])}
|
||||
function text(value,x,y,rotation){return entity('TEXT',[[8,'DIM'],[10,x],[20,y],[50,rotation],[1,value]])}
|
||||
function makeDxf({withInner=true,withDims=true}={}){
|
||||
const items=[polyline('LN_out',[[1000,2000],[5000,2000],[5000,5000],[1000,5000]])];
|
||||
if(withInner)items.push(polyline('LN_out',[[1500,2500],[4500,2500],[4500,4500],[1500,4500]]));
|
||||
if(withDims)items.push(line('DIM',[1000,1600],[5000,1600]),line('DIM',[5400,2000],[5400,5000]),text('4000',3000,1600,0),text('3000',5400,3500,90),text('1200',6000,3500,90),text('150',6500,3500,90));
|
||||
return [pair(0,'SECTION'),pair(2,'ENTITIES'),...items,pair(0,'ENDSEC'),pair(0,'EOF')].join('\n')+'\n';
|
||||
}
|
||||
|
||||
test('DXF parsing detects plan loops and dimension labels',()=>{
|
||||
const parsed=sandbox.window.CatchpitDxf.parseDxf(makeDxf());
|
||||
assert.ok(parsed.layers.includes('LN_out'));
|
||||
assert.equal(parsed.dimensionTexts.length,4);
|
||||
assert.equal(sandbox.window.CatchpitDxf.closedLoops(parsed.entities,'LN_out').length,2);
|
||||
});
|
||||
|
||||
test('model uses drawing dimensions and places the lower-left point at the origin',()=>{
|
||||
const parsed=sandbox.window.CatchpitDxf.parseDxf(makeDxf());
|
||||
const {modelData,heightSource}=sandbox.window.CatchpitModel.createCatchpitModel({
|
||||
entities:parsed.entities,dimensionTexts:parsed.dimensionTexts,outlineLayer:'LN_out',heightMm:900,baseThicknessMm:100,autoHeight:true
|
||||
});
|
||||
assert.equal(modelData.dimX,4000);
|
||||
assert.equal(modelData.dimY,3000);
|
||||
assert.equal(modelData.minX,1000);
|
||||
assert.equal(modelData.minY,2000);
|
||||
assert.equal(modelData.parts[0].footprint[0].x,0);
|
||||
assert.equal(modelData.parts[0].footprint[0].y,0);
|
||||
assert.equal(modelData.height,1.2);
|
||||
assert.equal(modelData.baseThickness,.15);
|
||||
assert.equal(heightSource,'section DIM 1200 mm');
|
||||
assert.deepEqual(Array.from(modelData.parts,p=>p.type),['base','wall']);
|
||||
assert.equal(modelData.parts[1].edgePairs.length,4);
|
||||
});
|
||||
|
||||
test('model rejects missing dimensions and missing inner boundary',()=>{
|
||||
const noDimensions=sandbox.window.CatchpitDxf.parseDxf(makeDxf({withDims:false}));
|
||||
assert.throws(()=>sandbox.window.CatchpitModel.createCatchpitModel({entities:noDimensions.entities,dimensionTexts:[],outlineLayer:'LN_out',heightMm:1200,baseThicknessMm:150,autoHeight:false}),/DIM 치수/);
|
||||
const noInner=sandbox.window.CatchpitDxf.parseDxf(makeDxf({withInner:false}));
|
||||
assert.throws(()=>sandbox.window.CatchpitModel.createCatchpitModel({entities:noInner.entities,dimensionTexts:noInner.dimensionTexts,outlineLayer:'LN_out',heightMm:1200,baseThicknessMm:150,autoHeight:false}),/안쪽 경계선/);
|
||||
});
|
||||
|
||||
test('malformed DXF without supported geometry reports an error',()=>{
|
||||
assert.throws(()=>sandbox.window.CatchpitDxf.parseDxf('0\nSECTION\n2\nENTITIES\n0\nENDSEC\n0\nEOF\n'),/지원되는 선 도형/);
|
||||
});
|
||||
|
||||
test('OBJ export includes closed slab and wall meshes',async()=>{
|
||||
class TestBlob{constructor(parts){this.parts=parts}}
|
||||
let objText='';
|
||||
sandbox.Blob=TestBlob;
|
||||
sandbox.URL.createObjectURL=blob=>{objText=blob.parts.join('');return 'blob:test'};
|
||||
vm.runInContext(await readFile(path.join(root,'src/obj-export.js'),'utf8'),sandbox,{filename:'src/obj-export.js'});
|
||||
const parsed=sandbox.window.CatchpitDxf.parseDxf(makeDxf());
|
||||
const {modelData}=sandbox.window.CatchpitModel.createCatchpitModel({entities:parsed.entities,dimensionTexts:parsed.dimensionTexts,outlineLayer:'LN_out',heightMm:1200,baseThicknessMm:150,autoHeight:true});
|
||||
sandbox.window.CatchpitObjExporter.download(modelData,'verification.dxf');
|
||||
const meshes=new Map();let current='';
|
||||
for(const row of objText.split(/\r?\n/)){
|
||||
if(row.startsWith('o ')){current=row.slice(2);meshes.set(current,[])}
|
||||
else if(row.startsWith('f '))meshes.get(current).push(row.slice(2).split(/\s+/).map(Number));
|
||||
}
|
||||
assert.deepEqual(Array.from(meshes.keys()),['바닥판','벽체']);
|
||||
for(const [name,faces] of meshes){
|
||||
const edges=new Map();
|
||||
for(const face of faces)for(let i=0;i<face.length;i++){
|
||||
const a=face[i],b=face[(i+1)%face.length],key=a<b?`${a}:${b}`:`${b}:${a}`;
|
||||
edges.set(key,(edges.get(key)||0)+1);
|
||||
}
|
||||
assert.ok(faces.length>0,`${name} faces should be exported`);
|
||||
assert.ok([...edges.values()].every(count=>count===2),`${name} mesh has an open or duplicated edge`);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user