From a07c945cfd7c8b062f79b8c11e13e7dff51475c4 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Sat, 30 May 2026 13:26:03 +0300 Subject: [PATCH] =?UTF-8?q?test(biochem):=20=D1=80=D0=B5=D0=B3=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D1=81-=D1=82=D0=B5=D1=81=D1=82=D1=8B=20=D1=85=D0=B8?= =?UTF-8?q?=D0=BC=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=BE=D0=B3=D0=BE=20=D1=8F?= =?UTF-8?q?=D0=B4=D1=80=D0=B0=20(node=20--test)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backend/tests/biochem-core.test.js — 8 тестов BIO (window-shim): формулы, VSEPR-геометрия (вода/метан/CO2 + углы), частичные заряды, полярность, балансировщик, SMILES-парсер, analyze. Все проходят. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/tests/biochem-core.test.js | 84 ++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 backend/tests/biochem-core.test.js diff --git a/backend/tests/biochem-core.test.js b/backend/tests/biochem-core.test.js new file mode 100644 index 0000000..1cb8e66 --- /dev/null +++ b/backend/tests/biochem-core.test.js @@ -0,0 +1,84 @@ +'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); +});