Skip to content

Validation and Testing

matejhron edited this page May 10, 2026 · 4 revisions

Running the suite

npm test

Runs node tests/run-tests.mjs. No external test framework — tests/run-tests.mjs implements describe/test/expect inline (lines 10–140) with matchers .toBe, .toEqual, .toBeCloseTo, .toBeGreaterThan, .toBeLessThan, .toHaveProperty, .toHaveLength, .toBeDefined. Output is one line per test, then a pass/fail summary.

208 tests pass in under 5 seconds. The Jest configuration in package.json is vestigial — test:jest and test:watch still work but are not the canonical runner; the CI gate is npm test.

npm test is required to pass before every commit per CLAUDE.md.

Test files

tests/decoModel.test.js (~1300 lines, ~105 tests)

The algorithm suite. Directly imports from js/decoModel.js and js/tissueCompartments.js.

  • Constants & pressure. Checks SURFACE_PRESSURE, WATER_VAPOR_PRESSURE, N2_FRACTION, PRESSURE_PER_METER, getAmbientPressure, getAlveolarN2Pressure.
  • Haldane and Schreiner equations. Verifies the closed-form output at known half-times (1, 5, 10, 20 min) and compares Schreiner against a time-stepped Haldane reference.
  • M-values, ceilings, GF interpolation. Exercises getMValue, getAdjustedMValue, getCompartmentCeiling, getDiveCeiling, interpolateGF, and the calculateInstantGF/calculateMaxGF pair.
  • GF-low anchor and first stop. Covers findFirstStopAtGFLow across a range of depths and GF settings, including edge cases where the unrounded ceiling coincides with a 3 m grid depth and where gas switches occur en route.
  • Deco schedule. Full generateDecoSchedule runs for air, EAN50, and O₂ deco; asserts stop count, per-stop time bounds, total-deco bounds, and the DecoCapExceededError path.
  • Tissue loading end-to-end. calculateTissueLoading on simple profiles; verifies monotonicity on descent, exponential shape, and surface equilibration.
  • Variant switching. setZHL16Variant('A'|'B'|'C') followed by re-checking a reference dive.

tests/diveSetup.test.js (~650 lines, ~49 tests)

Configuration and gas management.

  • Setup loading. getDefaultSetup, extendDiveSetup, localStorage round-trip via saveDiveSetup/loadSavedSetup.
  • Profile generation. generateSimpleProfile descent/ascent rates, bottom-time semantics (measured from dive start, not depth arrival), safety stop insertion.
  • Gas helpers. calculateMOD, getGases, getGasAtWaypoint, getGasAtTime, getGasSwitchEvents, insertGasSwitchWaypoints with 3 m MOD rounding.
  • computeGasConsumption. SAC accounting at switch depths — explicitly tests that the switch-stop window bills at the bottom SAC rate, not the deco SAC rate.

tests/diveProfile.test.js (~290 lines, ~29 tests)

Waypoint-array validation.

  • Structure (first waypoint at surface, monotonic time, non-negative depth).
  • parseProfileInput on tab-separated and comma-separated text.
  • calculateRates classification into descent / ascent / level.
  • getDiveStats maxima and totals.

tests/decotengu-comparison.test.mjs (~180 lines)

The cross-implementation comparison script. Runs as part of npm test (included by run-tests.mjs) but also runnable standalone: node tests/decotengu-comparison.test.mjs.

Procedural style rather than describe/test — loops over every scenario in tests/decotengu-reference.json, reports pass/fail counts, and exits non-zero on regression.

tests/gasSwitchTime-zero-regression.test.mjs (~130 lines)

Pinned regression: passing {gasSwitchTime: 0} to generateDecoSchedule must produce byte-identical schedules to the baseline captured before the gasSwitchTime feature was added. Baseline file: tests/decojs-baseline.json. Tolerance is 0 — exact match required across all 3900 scenarios.

decotengu cross-check

This is the primary numerical-correctness evidence for the DecoJS algorithm.

Reference data. tests/decotengu-reference.json holds 3900 pre-generated deco scenarios produced by decotengu v0.14.1 (see References). Reference data is regenerated with python3 scripts/generate_decotengu_reference.py > tests/decotengu-reference.json.

Scenario coverage.

  • Depths: 15 m, 18 m, 21 m, … 60 m (step 3 m, 16 values)
  • Bottom times: NDL+3, NDL+6, … NDL+30 min (step 3 min, 10 values per depth)
  • Gas configs: air, air+EAN50, air+O2, air+EAN50+O2
  • GF presets: 100/100 (Bühlmann), 90/95, 80/85, 70/80, 50/80, 40/80, 30/80, 20/80 (Deco Planner-style)
  • Algorithm: Bühlmann ZH-L16C for both sides

Tolerances. Per-scenario assertion is |total_deco_diff| ≤ max(5 min, 20% of reference_total_deco). Per-stop tolerance is ±1 min.

These tolerances cover stop-time discretization noise — both implementations round stop times to whole minutes, and the continuous ceiling crossing can land on either side of a minute boundary, giving ±1 min per stop.

Match statistics across all 3900 scenarios:

Pass rate 100 % (3900/3900)
Exact match on total deco 56 % (2196 scenarios)
Within ±2 min 95 % (P95)
Mean |diff| 0.6 min
Median |diff| 0 min
Max |diff| 5 min
DecoJS gives less 41 %
DecoJS gives more 3 %

The largest residuals are concentrated at GF 20/80 (the most aggressive setting in the matrix) where stop-time discretization between the two implementations occasionally lands a stop on different minute boundaries.

Bühlmann constants (SURFACE_PRESSURE=1.01325, N2_FRACTION=0.7902, TC1 b-coefficient variant-specific) match decotengu — which itself cross-references the HeinrichsWeikamp OSTC firmware. See References for the provenance chain.

Adding tests

Tests live in tests/ with filenames *.test.js (ES modules via import) or *.test.mjs. Structure is:

// tests/myThing.test.js
import { myFn } from '../js/decoModel.js';

describe('myFn', () => {
    test('returns the expected value at surface', () => {
        expect(myFn(0)).toBeCloseTo(1.01325, 4);
    });
});

For a new algorithm feature:

  1. Add unit tests covering the equation at a few hand-calculated points and the equation's boundary behaviour (surface, max depth, zero time).
  2. If the feature changes deco output, regenerate tests/decojs-baseline.json deliberately and commit the diff in a separate commit so the regression test stays meaningful.
  3. If the feature could diverge from decotengu, note the divergence in tests/decotengu-comparison.test.mjs comment block and widen the tolerance locally for the affected scenarios rather than globally.

Bug fixes should include a regression test that fails without the fix (per CLAUDE.md convention).

Test gaps

What is currently not covered — honest inventory so callers know where to be careful:

  • UI components. No render or interaction tests for DiveSetupEditor, DiveProfileChart, MValueChart, or GFChart. Chart.js output is not asserted.
  • i18n. No tests for translation loading, data-i18n substitution, or the languagechange event fan-out to components.
  • Keyboard shortcuts. MValueChart and GFChart expose arrow-key / space / home / end playback; none of this is tested.
  • Helium. COMPARTMENTS carries He coefficients but the algorithm lumps He into N₂ via n2Fraction. Full trimix (separate He kinetics) is not implemented and not tested. Gas definitions accept he > 0 but no decotengu-reference scenarios exercise it.
  • SAC / gas consumption edge cases. computeGasConsumption has basic coverage but not realistic multi-dive or bail-out scenarios.
  • Altitude / salinity. DEFAULT_ENVIRONMENT in chartTypes.js exists but the algorithm uses fixed SURFACE_PRESSURE = 1.01325; altitude adjustment is not tested because it is not implemented.

Cross-link: see the individual algorithm chapters (Algo-02-NDL-Calculation, Algo-03-First-Stop-Ramped-GF, Algo-04-Deco-Stop-Loop, Algo-05-Multi-Gas-Switching) for which algorithm each test file exercises.

Clone this wiki locally