Files
Learn_System/backend/tests/math6-page.test.js
T
Maxim Dolgolyov 09c61d8eed feat(math6): Глава 5 — Координатная плоскость (§1–§5, на Math6.plane)
§1 чтение координат + определение четверти (плоскость с точкой);
§2 чтение графиков реальных процессов + изменение величины (polyline);
§3 слайдер y=kx + классификатор прямая/обратная пропорциональность;
§5 прикладной (путь–время); финал — 5 боссов (координаты, четверти,
график, k для y=kx и y=k/x). Math6.plane получил поддержку polyline.
Тесты math6: 13/13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:42:48 +03:00

166 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/*
* Phase 0 jsdom-каркас «Математика 6»: хаб и 6 глав выполняются на движке
* math6_engine.js без ошибок скриптов; para-selector строится с нужным числом
* карточек; активен первый §; движок рендерит секции и заглушку/контент с кнопкой
* прочтения. Содержание §§ наполняется по главам — здесь проверяется каркас.
*/
const test = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const { JSDOM, VirtualConsole } = require('jsdom');
const ROOT = path.join(__dirname, '..', '..');
const readF = p => fs.readFileSync(path.join(ROOT, p), 'utf8');
const wait = ms => new Promise(r => setTimeout(r, ms));
/* Инлайним внешние скрипты (CDN убираем, api/xp заменяем заглушками). */
function buildPage(file) {
let html = readF('frontend/textbooks/' + file);
const inl = {
'/js/math6_svg.js': readF('frontend/js/math6_svg.js'),
'/js/math6_engine.js': readF('frontend/js/math6_engine.js')
};
html = html
.replace(/<script defer src="https:\/\/cdn[^"]*"[^>]*><\/script>/g, '')
.replace(/<script src="\/js\/api\.js" defer><\/script>/, '<script>window.renderMathInElement=function(){};</script>')
.replace(/<script src="\/js\/xp\.js" defer><\/script>/, '');
Object.keys(inl).forEach(src => {
html = html.replace(new RegExp('<script src="' + src + '" defer><\\/script>'), () => '<script>\n' + inl[src] + '\n</script>');
});
return html;
}
async function loadDom(file) {
const errors = [];
const vc = new VirtualConsole();
vc.on('jsdomError', e => errors.push(e.message));
const dom = new JSDOM(buildPage(file), {
runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/',
beforeParse(w) { w.scrollTo = function () {}; }
});
await wait(160);
return { dom, errors, doc: dom.window.document };
}
const CHAPTERS = [
{ file: 'math_6_ch1.html', cards: 12 },
{ file: 'math_6_ch2.html', cards: 9 },
{ file: 'math_6_ch3.html', cards: 5 },
{ file: 'math_6_ch4.html', cards: 11 },
{ file: 'math_6_ch5.html', cards: 5 },
{ file: 'math_6_ch6.html', cards: 6 }
];
for (const ch of CHAPTERS) {
test(`${ch.file}: SPA без ошибок, ${ch.cards} карточек, активен § 1`, async () => {
const { doc, errors } = await loadDom(ch.file);
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, ch.cards, ch.cards + ' карточек');
const active = doc.querySelector('.sec.active');
assert.ok(active && active.id === 'sec-p1', 'активен sec-p1');
const body = doc.querySelector('#p1-body');
assert.ok(body && body.children.length > 0, 'тело § 1 заполнено');
assert.ok(doc.querySelector('#p1-body [data-read]'), 'кнопка прочтения § 1');
/* финал помечен и присутствует */
assert.ok(doc.querySelector('#psel-grid .psel-card.final'), 'есть карточка финала');
});
}
test('ch5: координатная плоскость — интерактивы §1–§3 + финал', async () => {
const { doc, errors } = await loadDom('math_6_ch5.html');
const win = doc.defaultView;
assert.ok(doc.querySelector('#p1-fig svg'), 'плоскость с точкой §1');
assert.ok(doc.querySelectorAll('#p1-iv2 [data-q]').length === 4, 'кнопки четвертей §1');
win.goTo('p2'); await wait(80);
assert.ok(doc.querySelector('#p2-fig svg polyline'), 'график процесса §2');
win.goTo('p3'); await wait(80);
assert.ok(doc.querySelector('#p3-k'), 'слайдер k §3');
assert.ok(doc.querySelector('#p3-fig svg path'), 'график y=kx §3');
win.goTo('final'); await wait(80);
assert.ok(doc.querySelector('#fin-go'), 'арена боссов §5');
win.bumpProgress('final', 100); await wait(20);
assert.ok(win.M6STATE.achievements.has('ch5_done'), 'достижение «Глава 5 пройдена»');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('hub: 6 карточек глав', async () => {
const { doc, errors } = await loadDom('math_6_hub.html');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
assert.equal(doc.querySelectorAll('.ch-grid .ch-card').length, 6, '6 глав');
});
test('ch1 Волна 1: интерактивы §1–§3 монтируются без ошибок', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
// §1 строится при загрузке
assert.ok(doc.querySelector('#p1-iv1 #p1-c'), 'разрядный конструктор §1');
assert.ok(doc.querySelector('#p1-iv2 #p1-qq'), 'разряд-квиз §1');
win.goTo('p2'); await wait(80);
assert.ok(doc.querySelector('#p2-cfig svg'), 'числовая прямая сравнения §2');
assert.ok(doc.querySelector('#p2-iv2 #p2-rq'), 'тренажёр округления §2');
win.goTo('p3'); await wait(80);
assert.ok(doc.querySelector('#p3-afig svg'), 'координатный луч §3');
assert.ok(doc.querySelectorAll('#p3-iv2 [data-pt]').length === 4, 'выбор точки AD §3');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1 Волна 2: интерактивы §4–§6 монтируются без ошибок', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
win.goTo('p4'); await wait(80);
assert.ok(doc.querySelector('#p4-fig'), 'столбик §4');
assert.ok(doc.querySelectorAll('#p4-eopts').length === 1, 'варианты §4');
win.goTo('p5'); await wait(80);
assert.ok(doc.querySelector('#p5-iv1 [data-op]'), 'кнопки сдвига запятой §5');
assert.ok(doc.querySelector('#p5-out').textContent.length > 0 || doc.querySelector('#p5-out'), 'демонстратор §5');
win.goTo('p6'); await wait(80);
assert.ok(doc.querySelector('#p6-asl') && doc.querySelector('#p6-bsl'), 'ползунки множителей §6');
assert.ok(doc.querySelector('#p6-q'), 'тренажёр умножения §6');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1 Волна 3: интерактивы §7–§10 монтируются без ошибок', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
win.goTo('p7'); await wait(80);
assert.ok(doc.querySelector('#p7-q') && doc.querySelector('#p7-rq'), 'тренажёры деления §7');
win.goTo('p8'); await wait(80);
assert.ok(doc.querySelector('#p8-pick [data-k]'), 'выбор примеров §8');
assert.ok(doc.querySelector('#p8-out').textContent.indexOf('=') >= 0, 'демонстратор переноса запятой §8');
win.goTo('p9'); await wait(80);
assert.ok(doc.querySelector('#p9-iv1 [data-fin]'), 'классификатор §9');
assert.ok(doc.querySelectorAll('#p9-dopts [data-o]').length === 3, 'варианты десятичной §9');
win.goTo('p10'); await wait(80);
assert.ok(doc.querySelectorAll('#p10-pool .dnd-chip').length === 5, 'сопоставление дробей §10');
assert.ok(doc.querySelector('#p10-q'), 'выражения §10');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('ch1 Волна 4: §12 прикладной и финал-боссы', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
win.goTo('app'); await wait(80);
assert.ok(doc.querySelector('#app-q') && doc.querySelector('#app-aq'), 'задачи §12');
win.goTo('final'); await wait(80);
assert.ok(doc.querySelector('#fin-q') && doc.querySelector('#fin-go'), 'арена боссов');
// финал на 100% → достижение ch1_done (finalAch)
win.bumpProgress('final', 100); await wait(20);
assert.ok(win.M6STATE.achievements.has('ch1_done'), 'достижение «Глава 1 пройдена» при финале');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});
test('навигация и прогресс: переход на § и отметка прочтения', async () => {
const { doc, errors } = await loadDom('math_6_ch1.html');
const win = doc.defaultView;
win.goTo('p4'); await wait(60);
assert.ok(doc.querySelector('#sec-p4.active'), 'перешли на § 4');
/* отметка прочтения начисляет XP */
const btn = doc.querySelector('#p4-body [data-read]');
assert.ok(btn, 'кнопка прочтения § 4');
btn.click(); await wait(20);
assert.ok((win.M6STATE.progress.p4 || 0) >= 30, 'прогресс § 4 вырос после прочтения');
assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | '));
});