runtime.test.js (3306B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import { readFile } from 'node:fs/promises'; 4 5 const src = await readFile('src/pytml-runtime.js', 'utf8'); 6 7 8 test('Pytml pins Pyodide 0.314.0.7', () => { 9 assert.match(src, /PYODIDE_VERSION = '0\.314\.0\.7'/); 10 assert.match(src, /cdn\.jsdelivr\.net\/pyodide\/v\$\{PYODIDE_VERSION\}\/full/); 11 }); 12 13 test('the requested fast boot preloads Pyodide before DOMContentLoaded', () => { 14 assert.match(src, /preconnect\(\);/); 15 assert.match(src, /preloadPyodideScript\(\);/); 16 assert.match(src, /pyodide\.asm\.wasm/); 17 assert.match(src, /script\.async = true/); 18 }); 19 20 test('boot locks interactive controls while Python loads', () => { 21 assert.match(src, /html\.pytml-booting button/); 22 assert.match(src, /html\.pytml-booting input/); 23 assert.match(src, /pytml-boot-pill/); 24 assert.match(src, /Python failed to load\. Refresh to retry/); 25 }); 26 27 test('numbered Python blocks and inline UI preprocessor are present', () => { 28 assert.match(src, /const PY_BLOCK = \/\^py\(\?:\\d\+\)\?\$\/i;/); 29 assert.match(src, /const PY_INLINE_UI = \/\^\(btn\|input\|txt\)\(\\d\+\)\$\/i;/); 30 assert.match(src, /preprocessPythonBlock\(el\)/); 31 assert.match(src, /createElement\(kind === 'btn' \? 'button'/); 32 assert.match(src, /insertBefore\(fragment, el\)/); 33 assert.match(src, /node\.remove\(\)/); 34 }); 35 36 test('inline UI supports btn1 through btn99 and beyond', () => { 37 const match = src.match(/const PY_INLINE_UI = (\/[^;]+;)/); 38 assert.ok(match); 39 assert.match(match[1], /\\d\+/); 40 }); 41 42 test('the UI preprocessor does not use textContent as the source parser', () => { 43 assert.match(src, /if \(child\.nodeType === Node\.TEXT_NODE\)/); 44 assert.match(src, /code \+= child\.nodeValue \|\| ''/); 45 assert.match(src, /code: processed\.code/); 46 }); 47 48 test('normal DOM bindings, events, input(), joystick, and micropip remain available', () => { 49 for (const token of [ 50 '_bind_all_dom', 'builtins.input = _pytml_input', 'addEventListener', 51 'normalizeEvent', 'joystick', 'gamepads()', 'micropip.install' 52 ]) assert.match(src, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); 53 }); 54 55 test('Python AST input transform compiles the AST directly', () => { 56 assert.match(src, /ast\.fix_missing_locations\(tree\)/); 57 assert.match(src, /compile\(tree, "<pytml>", "exec", flags=ast\.PyCF_ALLOW_TOP_LEVEL_AWAIT\)/); 58 assert.doesNotMatch(src, /ast\.unparse\(tree\)/); 59 }); 60 61 test('visible output is lazy and no persistent status blob is styled', () => { 62 assert.match(src, /createOutputContainer\(\)/); 63 assert.doesNotMatch(src, /\.pytml-status\{/); 64 assert.doesNotMatch(src, /bottom:16px/); 65 }); 66 67 test('calculator uses inline numbered buttons and not ordinary external calculator buttons', async () => { 68 const html = await readFile('examples/calculator.html', 'utf8'); 69 assert.match(html, /<py1>/); 70 assert.equal((html.match(/<btn\d+>/g) || []).length, 14); 71 assert.doesNotMatch(html, /<button\b/); 72 assert.match(html, /btn1\.on\("click"/); 73 }); 74 75 test('demo gallery links to multiple runnable demos', async () => { 76 const html = await readFile('demo.html', 'utf8'); 77 for (const page of ['calculator.html', 'counter.html', 'greeting.html', 'joystick.html', 'packages.html']) { 78 assert.match(html, new RegExp(`examples/${page}`)); 79 } 80 });