'use strict'; /* * Регресс-тесты химического ядра frontend/js/biochem-core.js (window.BIO). * Чистые функции (формулы, VSEPR, заряды, диполь, баланс, SMILES) — без DOM. */ const test = require('node:test'); const assert = require('node:assert'); const path = require('node:path'); // shim browser global, then load the frontend module global.window = global; require(path.join(__dirname, '..', '..', 'frontend', 'js', 'biochem-core.js')); const B = global.BIO; const m = (atoms, bonds) => ({ atoms, bonds }); const water = m([{ id: 1, s: 'O' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }], [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }]); const co2 = m([{ id: 1, s: 'C' }, { id: 2, s: 'O' }, { id: 3, s: 'O' }], [{ f: 1, t: 2, o: 2 }, { f: 1, t: 3, o: 2 }]); const methane = m([{ id: 1, s: 'C' }, { id: 2, s: 'H' }, { id: 3, s: 'H' }, { id: 4, s: 'H' }, { id: 5, s: 'H' }], [{ f: 1, t: 2, o: 1 }, { f: 1, t: 3, o: 1 }, { f: 1, t: 4, o: 1 }, { f: 1, t: 5, o: 1 }]); function bondAngle(a3, c, n1, n2) { const C = a3.find(a => a.id === c), P = a3.find(a => a.id === n1), Q = a3.find(a => a.id === n2); const v1 = [P.x - C.x, P.y - C.y, P.z - C.z], v2 = [Q.x - C.x, Q.y - C.y, Q.z - C.z]; const d = v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; return Math.acos(d / (Math.hypot(...v1) * Math.hypot(...v2))) * 180 / Math.PI; } test('hillFormula & molarMass', () => { assert.equal(B.hillFormula(methane.atoms), 'CH4'); assert.ok(Math.abs(B.molarMass(methane.atoms) - 16.04) < 0.05); }); test('parseFormula handles parentheses', () => { assert.deepEqual(B.parseFormula('Ca(OH)2'), { Ca: 1, O: 2, H: 2 }); }); test('VSEPR geometry: water bent, methane tetrahedral, CO2 linear', () => { const gw = B.vsepr(water.atoms, water.bonds); assert.equal(gw.shape, 'угловая'); assert.equal(gw.hybridization, 'sp³'); const gm = B.vsepr(methane.atoms, methane.bonds); assert.equal(gm.shape, 'тетраэдрическая'); assert.ok(Math.abs(bondAngle(gm.atoms3d, 1, 2, 3) - 109.5) < 1.5); const gc = B.vsepr(co2.atoms, co2.bonds); assert.equal(gc.shape, 'линейная'); assert.ok(Math.abs(bondAngle(gc.atoms3d, 1, 2, 3) - 180) < 1); }); test('partial charges: water O negative, H positive', () => { const q = B.partialCharges(water.atoms, water.bonds); assert.ok(q[1] < -0.3, 'O should be δ−'); assert.ok(q[2] > 0.1 && q[3] > 0.1, 'H should be δ+'); }); test('polarity: symmetric molecules nonpolar, water polar', () => { assert.equal(B.polarity(co2.atoms, co2.bonds).label, 'Неполярная'); assert.equal(B.polarity(methane.atoms, methane.bonds).label, 'Неполярная'); assert.ok(B.polarity(water.atoms, water.bonds).dipole > 0.3); }); test('balance: classic equations', () => { assert.deepEqual(B.balance(['H2', 'O2'], ['H2O']).coefficients, [2, 1, 2]); assert.deepEqual(B.balance(['CH4', 'O2'], ['CO2', 'H2O']).coefficients, [1, 2, 1, 2]); assert.deepEqual(B.balance(['Fe', 'O2'], ['Fe2O3']).coefficients, [4, 3, 2]); assert.deepEqual(B.balance(['Ca(OH)2', 'HCl'], ['CaCl2', 'H2O']).coefficients, [1, 2, 1, 2]); }); test('parseSmiles: skeleton + implicit H', () => { assert.equal(B.hillFormula(B.parseSmiles('CCO').atoms), 'C2H6O'); assert.equal(B.hillFormula(B.parseSmiles('CC(=O)O').atoms), 'C2H4O2'); assert.equal(B.hillFormula(B.parseSmiles('C1=CC=CC=C1').atoms), 'C6H6'); assert.equal(B.hillFormula(B.parseSmiles('ClC(Cl)(Cl)Cl').atoms), 'CCl4'); assert.equal(B.parseSmiles('bad[x]'), null); }); test('analyze returns a complete report', () => { const a = B.analyze(water.atoms, water.bonds); assert.equal(a.formula, 'H2O'); assert.ok(a.geometry && a.geometry.shape === 'угловая'); assert.ok(a.dipole > 0); assert.ok(a.massFractions.O > 80); });