import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import createEngine from '../build_web_blender6/bin/web_engine.js'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const stages = []; const engine = await createEngine({ wasmBinary: fs.readFileSync(path.join(root, 'build_web_blender6/bin/web_engine.wasm')) }); const handle = engine._web_engine_create(); const slots = engine._malloc(8); const read = () => { const pointer = engine.HEAPU32[slots / 4], length = engine.HEAPU32[slots / 4 + 1]; return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(pointer, pointer + length))); }; const snapshot = () => { assert.equal(engine._web_engine_get_scene_snapshot(handle, slots, slots + 4), 0); return read(); }; const apply = (sceneId, timeline, expected = true) => { const start = performance.now(); const bytes = Buffer.from(JSON.stringify({ cmd: 'applyCommand', params: { command: { type: 'setSequencerStrips', sceneId, timeline }, } })); const pointer = engine._malloc(bytes.length); try { engine.HEAPU8.set(bytes, pointer); assert.equal(engine._web_engine_call_json(handle, pointer, bytes.length, slots, slots + 4), 0); const result = read(); assert.equal(result.ok, expected, result.error); return result; } finally { engine._free(pointer); stages.push({ stage: expected ? 'edit' : 'reject', seconds: (performance.now() - start) / 1000 }); } }; try { const start = performance.now(); const bytes = fs.readFileSync(path.join(root, 'tests/files/web/sequencer_scene_source.blend')); const pointer = engine._malloc(bytes.length); try { engine.HEAPU8.set(bytes, pointer); assert.equal(engine._web_engine_open_blend(handle, pointer, bytes.length), 0); } finally { engine._free(pointer); } stages.push({ stage: 'open', seconds: (performance.now() - start) / 1000 }); const initial = snapshot(); const scene = initial.scenes.find(scene => scene.name === 'SequencerScene'); const source = initial.scenes.find(scene => scene.name === 'SourceScene'); assert(scene && source); const timeline = structuredClone(scene.sequencerTimeline); timeline.strips.push({ id: 'strip:Web SCENE', name: 'Web SCENE', type: 'SCENE', sourceId: source.id, frameStart: 1, frameEnd: 25, sourceStart: 0, sourceEnd: 24, channel: 5, speed: 1, muted: false, locked: false }); apply(scene.id, timeline); const accepted = snapshot(); const strips = accepted.scenes.find(item => item.id === scene.id).sequencerTimeline.strips; assert.equal(strips.find(strip => strip.name === 'Web SCENE').sourceId, source.id); assert.deepEqual(accepted.scenes.find(item => item.id === source.id), source); for (const sourceId of [scene.id, 'scene:MissingLocalScene']) { const invalid = structuredClone(timeline); invalid.strips.at(-1).sourceId = sourceId; const result = apply(scene.id, invalid, false); assert.match(result.error, /own scene|missing scene/); assert.deepEqual(snapshot().scenes, accepted.scenes); } apply(scene.id, scene.sequencerTimeline); assert.deepEqual(snapshot().scenes.find(item => item.id === scene.id).sequencerTimeline.strips, scene.sequencerTimeline.strips); console.log(JSON.stringify({ status: 'PASS', stages })); } catch (error) { console.log(JSON.stringify({ status: 'FAIL', error: error.message, stages, frames: String(error.stack).split('\n').filter(line => /wasm-function|wasm:/.test(line)).slice(0, 8) })); process.exitCode = 1; } finally { engine._free(slots); engine._web_engine_destroy(handle); }