Files
vela-snake/tests/page.cjs

90 lines
3.7 KiB
JavaScript

// Page wiring test: drives the VM methods extracted from index.ux.
const fs = require('node:fs');
const vm = require('node:vm');
const assert = require('node:assert/strict');
const snake = require('../src/common/snake.js');
const ux = fs.readFileSync('src/pages/index/index.ux', 'utf8');
const body = ux.match(/<script>([\s\S]*?)<\/script>/)[1]
.replace(/import\s+\w+\s+from\s+'[^']*';?/g, '')
.replace('export default', 'globalThis.spec =');
const sandbox = { best: require('../src/common/best.js'), storage: {get() {}, set() {}}, snake, Date, Math, String, Number, console, setInterval: () => 0, clearInterval: () => {},
};
vm.createContext(sandbox);
vm.runInContext(body, sandbox);
const spec = sandbox.spec;
function mk() {
const o = Object.assign({}, spec, JSON.parse(JSON.stringify(spec.private)));
o.$element = () => null;
spec.onInit.call(o);
return o;
}
let c = mk();
assert.equal(c.model.length, 3, 'initialized to length 3');
assert.equal(c.snake.length, 3, 'rendered 3 segments');
assert.match(c.snake[0].src, /head_e\.png$/, 'head texture');
assert.match(c.snake[1].src, /body_h\.png$/, 'body texture');
assert.match(c.snake[2].src, /tail_w\.png$/, 'tail texture');
assert.equal(c.scoreText, 'SCORE 0');
assert.equal(c.snake[0].left, (snake.ARENA.offset + 6 * snake.ARENA.cell) + 'px', 'head x mapped to px');
for (let i = 0; i < 1; i++) c.tick();
assert.equal(c.model[0].x, 7, 'head moved right one step from x=6');
assert.equal(c.over, false);
c.onSwipe({ direction: 'down' });
c.tick();
assert.equal(c.dir.x, 0, 'dir x');
assert.equal(c.dir.y, 1, 'direction turned down');
assert.equal(c.model[0].y, 8, 'head moved down');
assert.match(c.snake[0].src, /head_s\.png$/, 'head faces south after turn');
c.onSwipe({ direction: 'up' });
c.tick();
assert.equal(c.dir.x, 0, 'reversal keeps x');
assert.equal(c.dir.y, 1, 'reversal blocked');
// Hit the actual circular boundary.
c.model = [{ x: 10, y: 5 }, { x: 9, y: 5 }, { x: 8, y: 5 }];
c.dir = { x: 1, y: 0 };
c.pending = null;
c.tick();
assert.equal(c.over, true, 'game over on wall hit');
c.restart();
assert.equal(c.over, false);
assert.equal(c.model.length, 3);
assert.equal(c.scoreText, 'SCORE 0');
// template uses image components for textures + documented directives/events
assert.match(ux, /<image class="board" src="\/common\/tex\/board\.png"[^>]*>/);
assert.match(ux, /<image class="seg" src="\{\{ seg\.src \}\}"/);
assert.match(ux, /for="\{\{ seg in snake \}\}"/);
assert.match(ux, /show="\{\{ over \}\}"/);
assert.match(ux, /onscroll="onCrownScroll"/, 'scroll container wired for crown deltas');
assert.match(ux, /<scroll id="scroller"/, 'scroller element present for re-centering');
assert.doesNotMatch(ux, /crownrotationchanged/i);
assert.doesNotMatch(ux, /transform\s*:/);
// crown path: scroll deltas become queued 90° turns, board stays centered
c = mk();
c.visible = true;
c.crownAnchor = 233;
c.onCrownScroll({ scrollTop: 233 + 40 }); // one notch "down"
c.onCrownScroll({ scrollTop: 233 });
assert.equal(c.queue.length, 1, 'one turn queued after one crown notch');
assert.equal(c.crownAnchor, 233, 'scroller re-centered');
assert.equal(JSON.stringify(c.queue[0]), JSON.stringify({ x: 0, y: 1 }), 'right-facing snake turned clockwise = down');
c.onCrownScroll({ scrollTop: 233 - 120 }); // three notches back
assert.equal(c.queue.length, 2, 'queue capped at 2');
assert.equal(c.dir.x, 1, 'no reversal emitted, x unchanged');
assert.equal(c.dir.y, 0, 'no reversal emitted, y unchanged');
c.onCrownScroll({ scrollTop: 233 });
c.tick();
assert.equal(JSON.stringify(c.dir), JSON.stringify({ x: 0, y: 1 }), 'first queued turn consumed');
console.log('PAGE WIRING PASS (textures, movement, turn, reversal, game over, restart, directives, crown scroll)');