import test from 'node:test';import assert from 'node:assert/strict';import fs from 'node:fs';import {makeLesson,lessonFrame,lessonKeys} from './src/lessons.mjs';import {violations,doorLatched,doorOpen} from './src/upstream/rules.mjs'; const pack=JSON.parse(fs.readFileSync(new URL('./src/upstream/levels.json',import.meta.url))); for(const l of pack.levels.filter(l=>l.tutorial))test('实际教学关动画:'+l.tutorial,()=>{const lesson=makeLesson(pack,l.tutorial);assert.equal(lesson.level.id,l.id);assert.equal(violations(l,lesson.solution).length,0);for(let i=0;il.cells.some(c=>c.id===id)));if(violations(l,frame.route).length)assert.match(frame.message,/错误示例/);}if(['straight','vertical','corner','parity'].includes(l.tutorial))assert.ok(lesson.beats.some(b=>b.message?.includes('错误示例')));if(l.tutorial==='rhythm-door'){const states=lesson.beats.map(b=>[doorOpen(l.doors[0],b.route),doorLatched(l.doors[0],b.route)]);assert.ok(states.some(([open])=>!open));assert.ok(states.some(([,latched])=>latched));assert.equal(states.at(-1)[1],false);}}); test('200关的组合规则均能找到对应真实教学关',()=>{for(const l of pack.levels)for(const key of lessonKeys(l))assert.ok(makeLesson(pack,key));}); test('整段演示一秒内连续完成,结束后停留并支持从头重播',()=>{for(const level of pack.levels.filter(l=>l.tutorial)){const lesson=makeLesson(pack,level.tutorial);assert.equal(lesson.duration,1000);for(let i=0;i