'use strict'; /* * Полностраничная jsdom-проверка chemistry_8_intro.html (SPA на chem8_engine.js): * выполняем реальный HTML + движок + виджеты, даём таймерам отработать, проверяем, * что 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)); function buildPage() { let html = readF('frontend/textbooks/chemistry_8_intro.html'); const inl = { '/js/biochem-core.js': readF('frontend/js/biochem-core.js'), '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'), '/js/chem8_intro_widgets.js': readF('frontend/js/chem8_intro_widgets.js'), '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js') }; // CDN katex → удалить; api/xp → стабы (LS отсутствует, renderMathInElement — no-op) html = html .replace(/') .replace(/'); }); return html; } async function loadDom() { const errors = []; const vc = new VirtualConsole(); vc.on('jsdomError', e => errors.push(e.message)); const dom = new JSDOM(buildPage(), { runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/', beforeParse(w) { w.scrollTo = function () {}; } // jsdom не реализует scrollTo (в браузере есть) }); await wait(180); // дать отработать таймерам сборки § и монтажа виджетов (40–50 мс) return { dom, errors, doc: dom.window.document }; } test('страница SPA выполняется без ошибок скриптов', async () => { const { errors } = await loadDom(); assert.deepEqual(errors, [], 'нет jsdomError: ' + errors.join(' | ')); }); test('para-selector построен (11 карточек) и первый § активен', async () => { const { doc } = await loadDom(); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 11, '11 карточек §'); const active = doc.querySelector('.sec.active'); assert.ok(active && active.id === 'sec-p1', 'активен §1'); assert.ok(doc.querySelector('#p1-body .para-hero'), 'para-hero §1 построен'); }); test('виджеты § смонтированы движком', async () => { const { doc } = await loadDom(); assert.ok(doc.querySelectorAll('#p1-el .el-cell').length > 10, 'карта элементов §1'); // перейдём на §6 и §8 через goTo, дождёмся монтажа флагманов doc.defaultView.goTo('p6'); await wait(120); assert.ok(doc.querySelector('#p6-mount .mtri'), 'треугольник §6'); doc.defaultView.goTo('p8'); await wait(120); assert.ok(doc.querySelector('#p8-mount .ceqb'), 'балансировщик §8'); }); test('тренажёр задач отрисован для §2 (POOLS)', async () => { const { doc } = await loadDom(); doc.defaultView.goTo('p2'); await wait(150); assert.ok(doc.querySelector('#taskArea p2, #taskAreap2'), 'область задач §2'); assert.ok(doc.querySelectorAll('#navDotsp2 .nav-dot').length >= 4, 'навигация по задачам §2'); }); test('Chem8 доступен и считает Mr', async () => { const { dom } = await loadDom(); assert.ok(dom.window.Chem8, 'window.Chem8 определён'); assert.equal(dom.window.Chem8.molarMass('CaCO3'), 100); });