import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import assert from 'node:assert/strict'; import createEngine from '../build_web_blender6/bin/web_engine.js'; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const stages = []; const started = performance.now(); const engine = await createEngine({ wasmBinary: fs.readFileSync(path.join(root, 'build_web_blender6/bin/web_engine.wasm')), }); stages.push({ stage: 'instantiate', seconds: (performance.now() - started) / 1000 }); const handle = engine._web_engine_create(); const pointers = engine._malloc(8); const call = (name, data) => { const start = performance.now(); const pointer = data ? engine._malloc(data.length) : 0; try { if (data) engine.HEAPU8.set(data, pointer); const code = name === 'snapshot' ? engine._web_engine_get_scene_snapshot(handle, pointers, pointers + 4) : name === 'open' ? engine._web_engine_open_blend(handle, pointer, data.length) : engine._web_engine_call_json(handle, pointer, data.length, pointers, pointers + 4); if (code !== 0) throw new Error(engine.UTF8ToString(engine._web_engine_last_error_message(handle))); if (name === 'open') return null; const output = engine.HEAPU32[pointers / 4]; const length = engine.HEAPU32[pointers / 4 + 1]; return JSON.parse(new TextDecoder().decode(engine.HEAPU8.slice(output, output + length))); } finally { if (pointer) engine._free(pointer); stages.push({ stage: name, seconds: (performance.now() - start) / 1000 }); } }; try { call('open', fs.readFileSync(path.join(root, 'tests/files/web/sequencer_scene.blend'))); const snapshot = call('snapshot'); const scene = snapshot.scenes.find(scene => scene.name === 'SequencerScene'); const timeline = structuredClone(scene.sequencerTimeline); if (process.argv.includes('--inspect')) { console.log(JSON.stringify({ fixtureStrips: timeline.strips.map(strip => ({ id: strip.id, type: strip.type, sourceId: strip.sourceId, sourcePath: strip.sourcePath, })) })); } const image = timeline.strips.find(strip => strip.type === 'IMAGE'); image.frameStart += 1; image.frameEnd += 1; const result = call('edit', Buffer.from(JSON.stringify({ cmd: 'applyCommand', params: { command: { type: 'setSequencerStrips', sceneId: scene.id, timeline }, } }))); if (!result.ok) throw new Error(result.error); const updated = call('snapshot').scenes.find(item => item.id === scene.id).sequencerTimeline; const updatedImage = updated.strips.find(strip => strip.name === image.name); assert.equal(updatedImage.frameStart, image.frameStart); assert.equal(updatedImage.frameEnd, image.frameEnd); assert.equal(updated.strips.length, timeline.strips.length); const invalid = structuredClone(updated); invalid.strips.push({ id: 'strip:MissingScene', name: 'Missing Scene', type: 'SCENE', channel: 8, frameStart: 1, frameEnd: 25, sourceStart: 0, sourceEnd: 24, speed: 1, muted: false, locked: false, sourceId: 'scene:MissingLocalTestScene' }); const rejected = call('reject-missing-scene', Buffer.from(JSON.stringify({ cmd: 'applyCommand', params: { command: { type: 'setSequencerStrips', sceneId: scene.id, timeline: invalid }, } }))); assert.equal(rejected.ok, false); assert.match(rejected.error, /missing scene/); assert.deepEqual(call('snapshot').scenes.find(item => item.id === scene.id).sequencerTimeline.strips, updated.strips); console.log(JSON.stringify({ status: 'PASS', stages })); } catch (error) { console.log(JSON.stringify({ status: 'FAIL', error: error.message, frames: String(error.stack).split('\n').filter(line => /wasm-function|wasm:/.test(line)).slice(0,12), stages })); process.exitCode = 1; } finally { engine._free(pointers); engine._web_engine_destroy(handle); }