Files
Learn_System/backend/tests/math6-page.test.js
T
Maxim Dolgolyov 653d3564df feat(math6): Глава 1, волна 1 — §1–§3 (разряды, сравнение/округление, координатный луч)
§1 разрядный конструктор (ползунки) + квиз «цифра в разряде»;
§2 сравнение на числовой прямой + тренажёр округления;
§3 чтение координаты и поиск точки A–D на координатном луче (Math6.numberLine).
Теория-карточки, шпаргалки, подсказки, глоссарий. Тесты: 9/9.

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

104 lines
5.2 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('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('навигация и прогресс: переход на § и отметка прочтения', 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(' | '));
});