35 lines
2.5 KiB
TypeScript
35 lines
2.5 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { zipSync, strToU8 } from 'fflate';
|
|
import { portableModel, modelComponents, modelResourcePath } from '../engine/model-import.ts';
|
|
import { inspectModel, modelDocument } from '../engine/model.ts';
|
|
import { sceneAnimationGlb } from './fixtures.ts';
|
|
|
|
test('model metadata exposes scene contents and material animation',()=>{
|
|
const m=inspectModel(sceneAnimationGlb(),'scene.glb');
|
|
assert.deepEqual(m.clips,['Scene']);assert.equal(m.lights,1);assert.equal(m.morphTargets,1);assert.equal(m.cameras.length,2);
|
|
assert.ok(m.animatedProperties.includes('/materials/0/pbrMetallicRoughness/roughnessFactor'));
|
|
assert.equal(modelComponents('a',m).animator!.autoplay,'Scene');
|
|
});
|
|
test('zipped glTF bundles embed relative buffers and textures for offline reload',async()=>{
|
|
const gltf={asset:{version:'2.0'},buffers:[{uri:'../geometry.bin',byteLength:4}],images:[{uri:'textures/color%20map.png'}]};
|
|
const zip=zipSync({'scene/model.gltf':strToU8(JSON.stringify(gltf)),'geometry.bin':new Uint8Array([1,2,3,4]),'scene/textures/color map.png':new Uint8Array([9,8,7])});
|
|
const model=await portableModel(zip,'scene.zip'),doc=modelDocument(model.bytes,model.name);
|
|
assert.equal(model.name,'model.gltf');assert.match(doc.buffers[0].uri,/^data:application\/octet-stream;base64,AQIDBA==$/);
|
|
assert.match(doc.images[0].uri,/^data:image\/png;base64,CQgH$/);
|
|
});
|
|
test('model import rejects missing resources, remote URLs, traversal and ambiguous bundles',async()=>{
|
|
for(const uri of ['../../outside.bin','https://example.test/a.bin','%2fetc/passwd','C:/secret','textures\\a.png']) assert.throws(()=>modelResourcePath('scene/a.gltf',uri));
|
|
await assert.rejects(()=>portableModel(strToU8(JSON.stringify({asset:{version:'2.0'},buffers:[{uri:'a.bin'}]})),'a.gltf'),/связанные/);
|
|
await assert.rejects(()=>portableModel(zipSync({'a.glb':sceneAnimationGlb(),'b.glb':sceneAnimationGlb()}),'model.zip'),/ровно одну/);
|
|
});
|
|
|
|
import {modelProject} from '../engine/model-import.ts';
|
|
import {validateProject,activeScene} from '../engine/schema.ts';
|
|
test('opening an imported scene uses its camera, timeline and lighting',async()=>{
|
|
const p=modelProject(await portableModel(sceneAnimationGlb(),'scene.glb'));
|
|
validateProject(p);const n=activeScene(p).entities[0];
|
|
assert.equal(n.components.camera.mode,'imported');assert.equal(n.components.animator.autoplay,'Scene');
|
|
assert.equal(p.settings.rendering!.defaultLights,false);assert.equal(p.assets.length,1);
|
|
});
|