clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / tests / title.test.js
title.test.js 6.1K
1import test from 'node:test';2import assert from 'node:assert/strict';3import { createTitleScene, TITLE_LEVEL } from '../src/scenes/title.js';45test('title level is a 9x4 engine board with decorative TRUE only', () => {6 const scene = createTitleScene();7 assert.equal(TITLE_LEVEL.width, 9);8 assert.equal(TITLE_LEVEL.height, 4);9 assert.equal(scene.state.rules.some(rule => rule.text.includes('TRUE')), false);10 assert.equal(scene.state.rules.some(rule => rule.text === 'MIAU IS YOU'), true);11 assert.equal(scene.state.rules.some(rule => rule.text === 'DOOR IS WIN'), true);12});1314test('title controller exposes movement, undo, reset, skip, and solved callback', () => {15 let solved = 0;16 let skipped = 0;17 const scene = createTitleScene({ onSolved: () => { solved++; }, onSkip: () => { skipped++; } });18 for (let index = 0; index < 6; index++) assert.equal(scene.input('right').accepted, true);19 assert.equal(scene.state.won, true);20 assert.equal(solved, 1);21 scene.input('z');22 assert.equal(scene.state.won, false);23 scene.input('r');24 assert.equal(scene.state.objects.find(object => object.id === 'title-miau').x, 1);25 scene.input('Enter');26 assert.equal(skipped, 1);27});2829test('title MIAU keeps accepted horizontal facing through idle and blocked input', () => {30 const scene = createTitleScene();31 assert.equal(scene.facing, 'left');32 assert.equal(scene.input('right').accepted, true);33 assert.equal(scene.facing, 'right');34 assert.equal(scene.input('up').accepted, true);35 assert.equal(scene.facing, 'right');36 assert.equal(scene.input('up').accepted, false);37 assert.equal(scene.facing, 'right');38 scene.reset();39 scene.input('right');40 scene.input('up');41 scene.undo();42 assert.equal(scene.facing, 'right');43 scene.undo();44 assert.equal(scene.facing, 'left');45 scene.input('right');46 const scales = [];47 const ctx = {48 canvas: { width: 800, height: 600 },49 save() {}, restore() {}, beginPath() {}, roundRect() {}, rect() {}, fill() {}, stroke() {}, arc() {}, fillRect() {},50 moveTo() {}, lineTo() {}, closePath() {}, clip() {}, ellipse() {}, translate() {}, scale(x, y) { scales.push([x, y]); },51 fillText() {}, measureText(value) { return { width: value.length * 8 }; },52 };53 scene.draw(ctx);54 assert.ok(scales.some(([x, y]) => x < 0 && y === 1));5556 const animating = createTitleScene({ startTime: 1000 });57 animating.input('right');58 const squashScales = [];59 const squashContext = { ...ctx, scale(x, y) { squashScales.push([x, y]); } };60 animating.draw(squashContext, { now: 1035 });61 assert.ok(squashScales.some(([x, y]) => Math.abs(Math.abs(x) - 0.2) < 0.001 && y === 1));62});6364test('title exposes Workshop in the footer and through keyboard and pointer input', () => {65 let opened = 0;66 const scene = createTitleScene({ onWorkshop: () => { opened++; } });67 const ctx = {68 canvas: { width: 390, height: 844 },69 save() {}, restore() {}, beginPath() {}, roundRect() {}, rect() {}, fill() {}, stroke() {}, arc() {}, fillRect() {},70 moveTo() {}, lineTo() {}, closePath() {}, clip() {}, ellipse() {}, translate() {}, scale() {},71 fillText() {}, measureText(value) { return { width: value.length * 6 }; },72 };73 const result = scene.draw(ctx, { width: 390, height: 844, reducedMotion: true });74 assert.equal(scene.input('space').kind, 'workshop');75 const target = result.footer.find(item => item.action === 'workshop');76 assert.ok(target);77 assert.equal(scene.hit(target.x + 2, target.y + 2).kind, 'workshop');78 assert.equal(opened, 2);79});8081test('title footer menu key remains available to the controller', () => {82 let opened = 0;83 const scene = createTitleScene({ onMenu: () => { opened++; } });84 assert.equal(scene.input('escape').kind, 'menu');85 assert.equal(opened, 1);86});8788test('title draw renders decorative header and delegates board geometry', () => {89 const labels = [];90 const ctx = {91 canvas: { width: 800, height: 600 },92 save() {}, restore() {}, beginPath() {}, roundRect() {}, rect() {}, fill() {}, stroke() {}, arc() {}, fillRect() {},93 moveTo() {}, lineTo() {}, closePath() {}, clip() {}, ellipse() {},94 translate() {}, scale() {},95 fillText(text) { labels.push(text); }, measureText(text) { return { width: text.length * 8 }; },96 };97 const scene = createTitleScene();98 const result = scene.draw(ctx);99 assert.equal(result.geometry.cell > 0, true);100 assert.equal(labels.includes('TRUE'), true);101});102103test('title uses hard-edged title tiles, keycap footer, and centered mobile board', () => {104 const labels = [];105 const ctx = {106 canvas: { width: 390, height: 844 },107 save() {}, restore() {}, beginPath() {}, roundRect() {}, rect() {}, fill() {}, stroke() {}, arc() {}, fillRect() {},108 moveTo() {}, lineTo() {}, closePath() {}, clip() {}, ellipse() {}, translate() {}, scale() {},109 fillText(value) { labels.push(value); }, measureText(value) { return { width: value.length * 6 }; },110 };111 const scene = createTitleScene({ reducedMotion: true });112 const result = scene.draw(ctx, { width: 390, height: 844, now: 0, reducedMotion: true });113 assert.ok(labels.includes('MIAU'));114 assert.ok(labels.includes('IS'));115 assert.ok(labels.includes('TRUE'));116 assert.ok(labels.includes('walk'));117 assert.ok(labels.includes('walk the cat to the door'));118 assert.ok(labels.includes('skip to index'));119 assert.ok(labels.includes('menu'));120 assert.ok(labels.includes('🐈'));121 assert.ok(result.geometry.y > 150);122 assert.ok(result.geometry.y + result.geometry.height < result.header.footerY - 12);123});124125test('title drop starts from its scene epoch and advances across frames', () => {126 const ctx = {127 canvas: { width: 800, height: 600 },128 save() {}, restore() {}, beginPath() {}, roundRect() {}, rect() {}, fill() {}, stroke() {}, arc() {}, fillRect() {},129 moveTo() {}, lineTo() {}, closePath() {}, clip() {}, ellipse() {}, translate() {}, scale() {},130 fillText() {}, measureText(value) { return { width: value.length * 8 }; },131 };132 const scene = createTitleScene({ startTime: 1000 });133 assert.deepEqual(scene.draw(ctx, { now: 1000 }).header.progresses, [0, 0, 0]);134 assert.ok(scene.draw(ctx, { now: 1500 }).header.progresses.every(progress => progress > 0));135});