'use strict'; /* * Полностраничная jsdom-проверка глав «Химия 8» (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(file, widgetsSrc) { let html = readF('frontend/textbooks/' + file); const inl = { '/js/biochem-core.js': readF('frontend/js/biochem-core.js'), '/js/chem8_svg.js': readF('frontend/js/chem8_svg.js'), [widgetsSrc]: readF('frontend/js' + widgetsSrc.replace('/js', '')), '/js/chem8_engine.js': readF('frontend/js/chem8_engine.js') }; html = html .replace(/') .replace(/'); }); return html; } async function loadDom(file, widgetsSrc) { const errors = []; const vc = new VirtualConsole(); vc.on('jsdomError', e => errors.push(e.message)); const dom = new JSDOM(buildPage(file, widgetsSrc), { runScripts: 'dangerously', pretendToBeVisual: true, virtualConsole: vc, url: 'http://localhost/', beforeParse(w) { w.scrollTo = function () {}; } }); await wait(180); return { dom, errors, doc: dom.window.document }; } /* ── Вводный раздел ── */ test('intro: SPA без ошибок, 11 карточек, §1 активен, виджеты', async () => { const { doc, errors } = await loadDom('chemistry_8_intro.html', '/js/chem8_intro_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 11, '11 карточек'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p1', '§1 активен'); assert.ok(doc.querySelectorAll('#p1-el .el-cell').length > 10, 'карта элементов'); doc.defaultView.goTo('p6'); await wait(120); assert.ok(doc.querySelector('#p6-mount .mtri'), 'треугольник §6'); }); /* ── Глава 1 ── */ test('ch1: SPA без ошибок, 15 карточек, §10 активен', async () => { const { doc, errors } = await loadDom('chemistry_8_ch1.html', '/js/chem8_ch1_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 15, '14 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p10', '§10 активен'); assert.ok(doc.querySelector('#p10-body .para-hero'), 'para-hero §10'); }); test('ch1: флагман-виджеты монтируются (классификатор, растворимость, ряд активности)', async () => { const { doc } = await loadDom('chemistry_8_ch1.html', '/js/chem8_ch1_widgets.js'); doc.defaultView.goTo('p10'); await wait(120); assert.ok(doc.querySelector('#c-ox-cls .cls-chip'), 'классификатор оксидов §10'); doc.defaultView.goTo('p13'); await wait(120); assert.ok(doc.querySelector('#c-acid-ind .ind-strip'), 'индикатор §13'); doc.defaultView.goTo('p19'); await wait(120); assert.ok(doc.querySelector('#c-salt-sol .sol-tab'), 'таблица растворимости §19'); doc.defaultView.goTo('p14'); await wait(120); assert.ok(doc.querySelector('#c-acid-act .act-cell'), 'ряд активности §14'); }); test('ch1: тренажёр задач отрисован для §10', async () => { const { doc } = await loadDom('chemistry_8_ch1.html', '/js/chem8_ch1_widgets.js'); await wait(150); assert.ok(doc.querySelectorAll('#navDotsp10 .nav-dot').length >= 4, 'навигация по задачам §10'); }); /* ── Глава 2 ── */ test('ch2: SPA без ошибок, 6 карточек, §24 активен, ПСХЭ', async () => { const { doc, errors } = await loadDom('chemistry_8_ch2.html', '/js/chem8_ch2_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 6, '5 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p24', '§24 активен'); await wait(120); assert.ok(doc.querySelectorAll('#c-pt-metals .pt-cell').length > 80, 'ПСХЭ §24 (90 элементов)'); }); test('ch2: амфотерность §25 и семейства §26 монтируются', async () => { const { doc } = await loadDom('chemistry_8_ch2.html', '/js/chem8_ch2_widgets.js'); doc.defaultView.goTo('p25'); await wait(120); assert.ok(doc.querySelector('#c-amph .amph-btn'), 'амфотерность §25'); doc.defaultView.goTo('p26'); await wait(120); assert.ok(doc.querySelectorAll('#c-pt-fam .pt-cell').length > 80, 'ПСХЭ семейства §26'); }); /* ── Глава 3 ── */ test('ch3: SPA без ошибок, 8 карточек, §29 активен, модель атома', async () => { const { doc, errors } = await loadDom('chemistry_8_ch3.html', '/js/chem8_ch3_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 8, '7 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p29', '§29 активен'); await wait(120); assert.ok(doc.querySelector('#c-atom .as-svg'), 'модель атома §29'); }); test('ch3: нуклид §30 и паспорт §35 монтируются', async () => { const { doc } = await loadDom('chemistry_8_ch3.html', '/js/chem8_ch3_widgets.js'); doc.defaultView.goTo('p30'); await wait(120); assert.ok(doc.querySelector('#c-nuclide #nz'), 'калькулятор нуклида §30'); doc.defaultView.goTo('p35'); await wait(120); assert.ok(doc.querySelectorAll('#c-passport .pt-cell').length > 80, 'ПСХЭ паспорта §35'); }); /* ── Глава 4 ── */ test('ch4: SPA без ошибок, 7 карточек, §36 активен, тип связи', async () => { const { doc, errors } = await loadDom('chemistry_8_ch4.html', '/js/chem8_ch4_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 7, '6 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p36', '§36 активен'); doc.defaultView.goTo('p37'); await wait(120); assert.ok(doc.querySelector('#c-bond1 .bt-svg'), 'виджет типа связи §37'); doc.defaultView.goTo('p38'); await wait(120); assert.ok(doc.querySelector('#c-bond2 .bt-out'), 'виджет полярности §38'); }); /* ── Глава 5 ── */ test('ch5: SPA без ошибок, 5 карточек, §42 активен, с.о. и баланс', async () => { const { doc, errors } = await loadDom('chemistry_8_ch5.html', '/js/chem8_ch5_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 5, '4 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p42', '§42 активен'); await wait(120); assert.ok(doc.querySelector('#c-ox .ox-out'), 'калькулятор с.о. §42'); doc.defaultView.goTo('p44'); await wait(120); assert.ok(doc.querySelector('#c-redox-pick option'), 'электронный баланс §44'); }); /* ── Глава 6 ── */ test('ch6: SPA без ошибок, 8 карточек, §46 активен, w/c калькуляторы', async () => { const { doc, errors } = await loadDom('chemistry_8_ch6.html', '/js/chem8_ch6_widgets.js'); assert.deepEqual(errors, [], 'нет ошибок: ' + errors.join(' | ')); assert.equal(doc.querySelectorAll('#psel-grid .psel-card').length, 8, '7 § + финал'); assert.ok(doc.querySelector('.sec.active') && doc.querySelector('.sec.active').id === 'sec-p46', '§46 активен'); await wait(120); assert.ok(doc.querySelector('#c-mix .cls-chip'), 'классификатор смесей §46'); doc.defaultView.goTo('p50'); await wait(120); assert.ok(doc.querySelector('#c-wcalc #w-go'), 'калькулятор w §50'); doc.defaultView.goTo('p51'); await wait(120); assert.ok(doc.querySelector('#c-ccalc #c-go'), 'калькулятор c §51'); });