// Headless check of the wasm build: same contract as the native library. // node tool/test_wasm.mjs [out.bin] // With an argument, writes the raw RGBA frame there so it can be diffed // against the native kernel (tool/test_wasm_parity.sh). import { createRequire } from 'node:module'; import { writeFileSync } from 'node:fs'; const require = createRequire(import.meta.url); const createNimFlutterModule = require('../example/web/nimflutter.js'); const M = await createNimFlutterModule(); let failures = 0; const check = (name, ok, detail = '') => { console.log(`${ok ? 'ok ' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`); if (!ok) failures++; }; M.ccall('nf_init', null, [], []); check('nf_add', M.ccall('nf_add', 'number', ['number', 'number'], [20, 22]) === 42); // nf_greet returns a Nim-allocated string the caller must release. const namePtr = M._malloc(16); M.stringToUTF8('wasm', namePtr, 16); const resPtr = M.ccall('nf_greet', 'number', ['number'], [namePtr]); const greeting = M.UTF8ToString(resPtr); M.ccall('nf_free', null, ['number'], [resPtr]); M._free(namePtr); check('nf_greet', greeting === 'Hello, wasm, from Nim!', greeting); const W = 240, H = 180, ITER = 300, BYTES = W * H * 4; const buf = M._malloc(BYTES); const fill = (len, y0, y1) => { M.HEAPU8.fill(0, buf, buf + BYTES); M.ccall( 'nf_mandelbrot', null, ['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'], [buf, len, W, H, -0.5, 0.0, 3.0, ITER, y0, y1], ); return M.HEAPU8.slice(buf, buf + BYTES); }; const frame = fill(BYTES, 0, H); check('every pixel has alpha', frame.every((v, i) => i % 4 !== 3 || v === 255)); check('frame is a fractal, not a flat fill', new Set(frame).size > 8); // size_t is 32-bit on wasm32 — the guard has to hold there too. check('undersized buf_len writes nothing', fill(BYTES - 1, 0, H).every((v) => v === 0)); check('zero buf_len writes nothing', fill(0, 0, H).every((v) => v === 0)); check('inverted band writes nothing', fill(BYTES, 10, 2).every((v) => v === 0)); // Band split must reassemble into the identical frame. const top = fill(BYTES, 0, H / 2).slice(0, (BYTES / 2)); const bottom = fill(BYTES, H / 2, H).slice(0, (BYTES / 2)); const joined = new Uint8Array(BYTES); joined.set(top, 0); joined.set(bottom, BYTES / 2); check('band split matches a single-shot render', joined.every((v, i) => v === frame[i])); M._free(buf); if (process.argv[2]) { writeFileSync(process.argv[2], Buffer.from(frame)); console.log(`wrote ${process.argv[2]} (${frame.length} bytes)`); } process.exit(failures === 0 ? 0 : 1);