From d17d168d9ff80dea3bea22c3aff8616178ebbbe6 Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Sun, 14 Jun 2026 08:50:39 +0200 Subject: [PATCH 1/9] introducting regression tests --- .../HeatConduction1DWall/REGRESSION.md | 61 +++++++++++++++ .../HeatConduction1DWall/regression.test.js | 61 +++++++++++++++ .../HeatConduction2DFin/REGRESSION.md | 67 ++++++++++++++++ .../HeatConduction2DFin/regression.test.js | 77 +++++++++++++++++++ tests/run-all-tests.js | 53 +++++++++++++ tests/unit/README.md | 9 +++ 6 files changed, 328 insertions(+) create mode 100644 tests/regression/HeatConduction1DWall/REGRESSION.md create mode 100644 tests/regression/HeatConduction1DWall/regression.test.js create mode 100644 tests/regression/HeatConduction2DFin/REGRESSION.md create mode 100644 tests/regression/HeatConduction2DFin/regression.test.js create mode 100644 tests/run-all-tests.js create mode 100644 tests/unit/README.md diff --git a/tests/regression/HeatConduction1DWall/REGRESSION.md b/tests/regression/HeatConduction1DWall/REGRESSION.md new file mode 100644 index 0000000..579ffac --- /dev/null +++ b/tests/regression/HeatConduction1DWall/REGRESSION.md @@ -0,0 +1,61 @@ +# Regression Test — HeatConduction1DWall + +## Purpose + +This test guards the numerical output of the 1D heat-conduction-through-a-wall example +against unintended changes to the solver, assembler, or mesh-generation logic. + +It replicates exactly the problem set up in +[`HeatConduction1DWall.html`](../../../examples/solidHeatTransferScript/HeatConduction1DWall/HeatConduction1DWall.html) +and asserts a known good value. + +## Problem setup + +| Parameter | Value | +|-----------|-------| +| Domain | 1D, 0 – 0.15 m | +| Mesh | 10 linear elements | +| Boundary 0 (x = 0) | Convection, h = 1, T∞ = 25 °C | +| Boundary 1 (x = 0.15) | Constant temperature, T = 5 °C | +| Solver | LU decomposition (`lusolve`) | + +## Expected value + +| Quantity | Value | +|----------|-------| +| Temperature at node 0 (x = 0) | **10.29412 °C** | + +Tolerance used in the assertion: `1e-4`. + +## How to run + +From the repository root: + +```bash +node tests/regression/HeatConduction1DWall/regression.test.js +``` + +A passing run prints: + +``` +PASS: T(x=0) = 10.29412 (expected 10.29412) +``` + +A failing run prints a `FAIL:` message and exits with code 1. + +The `test` script in `package.json` also runs this file, so `npm test` works too. + +## After modifying the code + +| Situation | Action | +|-----------|--------| +| Bug fix that should not change results | Run the test — it must still pass. | +| Intentional algorithm change (new element type, new integration rule, etc.) | Re-derive the expected value, update `EXPECTED_T0` in `regression.test.js`, and document the reason here. | +| New boundary condition API | Update both the test and the reference HTML example together. | +| Adding a new solver method | Add a separate assertion block for the new method; keep the `lusolve` block untouched as the baseline. | + +## Change log + +| Date | Change | New expected value | +|------|--------|--------------------| +| 2026-06-14 | Initial regression baseline | 10.29412 | diff --git a/tests/regression/HeatConduction1DWall/regression.test.js b/tests/regression/HeatConduction1DWall/regression.test.js new file mode 100644 index 0000000..fbd5f2b --- /dev/null +++ b/tests/regression/HeatConduction1DWall/regression.test.js @@ -0,0 +1,61 @@ +/** + * Regression test for HeatConduction1DWall + * + * Replicates the exact setup from HeatConduction1DWall.html and asserts + * that the temperature at node 0 (convection boundary) remains 10.29412. + * + * Run: node tests/regression/HeatConduction1DWall/regression.test.js + */ + +import * as mathjs from "mathjs"; +import { FEAScriptModel } from "../../../src/FEAScript.js"; +import { basicLog } from "../../../src/utilities/loggingScript.js"; + +// FEAScript.js references `math` as a global (loaded via CDN in browser). +// Set it here before any solve() call. +globalThis.math = mathjs; + +const EXPECTED_T0 = 10.29412; +const TOLERANCE = 1e-4; + +function runSimulation() { + const model = new FEAScriptModel(); + + basicLog("") + basicLog("================================") + basicLog("Starting test in solid heat transfer 1D wall...") + + model.setSolverConfig("solidHeatTransferScript"); + model.setMeshConfig({ + meshDimension: "1D", + elementOrder: "linear", + numElementsX: 10, + maxX: 0.15, + }); + + model.addBoundaryCondition("0", ["convection", 1, 25]); + model.addBoundaryCondition("1", ["constantTemp", 5]); + model.setSolverMethod("lusolve"); + + return model.solve(); +} + +function assert(condition, message) { + if (!condition) { + console.error(`FAIL: ${message}`); + process.exit(1); + } +} + +const { solutionVector } = runSimulation(); + +// solutionVector from math.lusolve is a nested array: [[T0], [T1], ...] +const T0 = Array.isArray(solutionVector[0]) ? solutionVector[0][0] : solutionVector[0]; + +assert( + Math.abs(T0 - EXPECTED_T0) < TOLERANCE, + `Temperature at node 0: expected ${EXPECTED_T0}, got ${T0} (tolerance ${TOLERANCE})` +); + +console.log(`PASS: T(x=0) = ${T0.toFixed(5)} (expected ${EXPECTED_T0})`); +basicLog("================================") diff --git a/tests/regression/HeatConduction2DFin/REGRESSION.md b/tests/regression/HeatConduction2DFin/REGRESSION.md new file mode 100644 index 0000000..5bea510 --- /dev/null +++ b/tests/regression/HeatConduction2DFin/REGRESSION.md @@ -0,0 +1,67 @@ +# Regression Test — HeatConduction2DFin + +## Purpose + +This test guards the numerical output of the 2D heat-conduction-in-a-fin example +against unintended changes to the solver, assembler, or mesh-generation logic. + +It replicates exactly the problem set up in +[`HeatConduction2DFin.html`](../../../examples/solidHeatTransferScript/HeatConduction2DFin/HeatConduction2DFin.html) +and asserts a known good value at a representative interior point. + +## Problem setup + +| Parameter | Value | +|-----------|-------| +| Domain | 2D, x ∈ [0, 4] m, y ∈ [0, 2] m | +| Mesh | 8 × 4 quadratic elements | +| Boundary 0 (bottom, y = 0) | Constant temperature, T = 200 °C | +| Boundary 1 (left, x = 0) | Symmetry (zero flux) | +| Boundary 2 (top, y = 2) | Convection, h = 1, T∞ = 20 °C | +| Boundary 3 (right, x = 4) | Constant temperature, T = 200 °C | +| Solver | LU decomposition (`lusolve`) | + +## Expected value + +| Quantity | Value | +|----------|-------| +| Temperature at node (x = 0, y = 2) | **81.31873 °C** | + +This point sits at the top-left corner of the fin — on the symmetry boundary and +the convection boundary — and is sensitive to both the heat transfer coefficient +and the thermal gradient across the domain. + +Tolerance used in the assertion: `1e-4`. + +## How to run + +From the repository root: + +```bash +node tests/regression/HeatConduction2DFin/regression.test.js +``` + +A passing run prints: + +``` +PASS: T(x=0, y=2) = 81.31873 (expected 81.31873) +``` + +A failing run prints a `FAIL:` message and exits with code 1. + +Running `npm test` executes all regression tests, including this one. + +## After modifying the code + +| Situation | Action | +|-----------|--------| +| Bug fix that should not change results | Run the test — it must still pass. | +| Intentional algorithm change (new element type, new integration rule, etc.) | Re-derive the expected value, update `EXPECTED_T` in `regression.test.js`, and document the reason here. | +| New boundary condition API | Update both the test and the reference HTML example together. | +| Mesh refinement study | Add a separate assertion block for the refined mesh; keep the current block as the coarse-mesh baseline. | + +## Change log + +| Date | Change | New expected value | +|------|--------|--------------------| +| 2026-06-14 | Initial regression baseline | 81.31873 | diff --git a/tests/regression/HeatConduction2DFin/regression.test.js b/tests/regression/HeatConduction2DFin/regression.test.js new file mode 100644 index 0000000..b57d48e --- /dev/null +++ b/tests/regression/HeatConduction2DFin/regression.test.js @@ -0,0 +1,77 @@ +/** + * Regression test for HeatConduction2DFin + * + * Replicates the exact setup from HeatConduction2DFin.html and asserts + * that the temperature at (x=0, y=2) remains 81.31873. + * + * Run: node tests/regression/HeatConduction2DFin/regression.test.js + */ + +import * as mathjs from "mathjs"; +import { FEAScriptModel } from "../../../src/FEAScript.js"; +import { basicLog } from "../../../src/utilities/loggingScript.js"; + +basicLog("") +basicLog("================================") +basicLog("Starting test in solid heat transfer 2D fin...") + +// FEAScript.js references `math` as a global (loaded via CDN in browser). +// Set it here before any solve() call. +globalThis.math = mathjs; + +const EXPECTED_X = 0; +const EXPECTED_Y = 2; +const EXPECTED_T = 81.31873; +const TOLERANCE = 1e-4; + +function runSimulation() { + const model = new FEAScriptModel(); + + model.setSolverConfig("solidHeatTransferScript"); + model.setMeshConfig({ + meshDimension: "2D", + elementOrder: "quadratic", + numElementsX: 8, + numElementsY: 4, + maxX: 4, + maxY: 2, + }); + + model.addBoundaryCondition("0", ["constantTemp", 200]); + model.addBoundaryCondition("1", ["symmetry"]); + model.addBoundaryCondition("2", ["convection", 1, 20]); + model.addBoundaryCondition("3", ["constantTemp", 200]); + model.setSolverMethod("lusolve"); + + return model.solve(); +} + +function assert(condition, message) { + if (!condition) { + console.error(`FAIL: ${message}`); + process.exit(1); + } +} + +const { solutionVector, nodesCoordinates } = runSimulation(); +const { nodesXCoordinates, nodesYCoordinates } = nodesCoordinates; + +// Locate the node at (x=0, y=2) by searching coordinates. +// This is robust against changes in mesh ordering conventions. +const nodeIndex = nodesXCoordinates.findIndex( + (x, i) => Math.abs(x - EXPECTED_X) < 1e-10 && Math.abs(nodesYCoordinates[i] - EXPECTED_Y) < 1e-10 +); + +assert(nodeIndex !== -1, `No node found at (x=${EXPECTED_X}, y=${EXPECTED_Y})`); + +// solutionVector from math.lusolve is a nested array: [[T0], [T1], ...] +const T = Array.isArray(solutionVector[nodeIndex]) ? solutionVector[nodeIndex][0] : solutionVector[nodeIndex]; + +assert( + Math.abs(T - EXPECTED_T) < TOLERANCE, + `Temperature at (x=${EXPECTED_X}, y=${EXPECTED_Y}): expected ${EXPECTED_T}, got ${T} (tolerance ${TOLERANCE})` +); + +console.log(`PASS: T(x=${EXPECTED_X}, y=${EXPECTED_Y}) = ${T.toFixed(5)} (expected ${EXPECTED_T})`); + +basicLog("================================") \ No newline at end of file diff --git a/tests/run-all-tests.js b/tests/run-all-tests.js new file mode 100644 index 0000000..8901fcc --- /dev/null +++ b/tests/run-all-tests.js @@ -0,0 +1,53 @@ +/** + * Test runner — discovers and executes every *.test.js file under tests/. + * Add a new test file anywhere in this tree and it runs automatically. + * + * Usage: node tests/run-all-tests.js + */ + +import { readdirSync, statSync } from "fs"; +import { join, relative } from "path"; +import { spawnSync } from "child_process"; +import { fileURLToPath } from "url"; + +const __dirname = fileURLToPath(new URL(".", import.meta.url)); + +function collectTestFiles(dir) { + const entries = readdirSync(dir); + const files = []; + for (const entry of entries) { + const fullPath = join(dir, entry); + if (statSync(fullPath).isDirectory()) { + files.push(...collectTestFiles(fullPath)); + } else if (entry.endsWith(".test.js")) { + files.push(fullPath); + } + } + return files; +} + +const testFiles = collectTestFiles(__dirname); + +if (testFiles.length === 0) { + console.log("No test files found."); + process.exit(0); +} + +console.log(`Found ${testFiles.length} test file(s).\n`); + +let passed = 0; +let failed = 0; + +for (const file of testFiles) { + const label = relative(__dirname, file); + const result = spawnSync(process.execPath, [file], { stdio: "inherit" }); + if (result.status === 0) { + passed++; + } else { + console.error(`\nFAILED: ${label}\n`); + failed++; + } +} + +console.log(`\n${passed} passed, ${failed} failed.`); +process.exit(failed > 0 ? 1 : 0); diff --git a/tests/unit/README.md b/tests/unit/README.md new file mode 100644 index 0000000..dee4e93 --- /dev/null +++ b/tests/unit/README.md @@ -0,0 +1,9 @@ +# Unit Tests + +This folder will contain unit tests for individual FEAScript modules (solvers, assemblers, utilities, etc.). + +Each test file should target a single module and be runnable with: + +```bash +node tests/unit/.js +``` From bbe2fd09f9bb5aed4131f125a381cbb656f89516 Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Sun, 14 Jun 2026 08:52:47 +0200 Subject: [PATCH 2/9] Adding sanity check on the jacobi method --- src/methods/jacobiMethodScript.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/methods/jacobiMethodScript.js b/src/methods/jacobiMethodScript.js index 1a3e3df..bebe702 100644 --- a/src/methods/jacobiMethodScript.js +++ b/src/methods/jacobiMethodScript.js @@ -22,6 +22,36 @@ */ export function jacobiMethod(A, b, x0, maxIterations = 100, tolerance = 1e-7) { const n = A.length; // Size of the square matrix + + // Sanity checks for input dimensions + if (!Array.isArray(A) || n === 0) { + throw new Error("Matrix A must be a non-empty array"); + } + + // Verify A is square + for (let i = 0; i < n; i++) { + if (!Array.isArray(A[i]) || A[i].length !== n) { + throw new Error(`Matrix A must be square. Row ${i} has length ${A[i].length}, expected ${n}`); + } + } + + // Verify b is a vector of correct length + if (!Array.isArray(b) || b.length !== n) { + throw new Error(`Vector b must have length ${n}, got ${b.length}`); + } + + // Verify x0 is a vector of correct length + if (!Array.isArray(x0) || x0.length !== n) { + throw new Error(`Initial guess x0 must have length ${n}, got ${x0.length}`); + } + + // Verify no zero diagonal elements (required for Jacobi method) + for (let i = 0; i < n; i++) { + if (A[i][i] === 0) { + throw new Error(`Diagonal element A[${i}][${i}] is zero; Jacobi method requires non-zero diagonal elements`); + } + } + let x = [...x0]; // Current solution (starts with initial guess) let xNew = new Array(n); // Next iteration's solution From 774815abd2c74174426e0147f2926b3b7562c537 Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Wed, 8 Jul 2026 12:39:31 +0200 Subject: [PATCH 3/9] Adding the unit tests --- src/methods/jacobiMethodScript.js | 12 +-- tests/unit/jacobiMethod.test.js | 147 ++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 tests/unit/jacobiMethod.test.js diff --git a/src/methods/jacobiMethodScript.js b/src/methods/jacobiMethodScript.js index bebe702..e383087 100644 --- a/src/methods/jacobiMethodScript.js +++ b/src/methods/jacobiMethodScript.js @@ -21,13 +21,13 @@ * - converged: Boolean indicating whether the method converged */ export function jacobiMethod(A, b, x0, maxIterations = 100, tolerance = 1e-7) { - const n = A.length; // Size of the square matrix - - // Sanity checks for input dimensions - if (!Array.isArray(A) || n === 0) { + // Sanity checks — Array.isArray guard must precede .length access + if (!Array.isArray(A) || A.length === 0) { throw new Error("Matrix A must be a non-empty array"); } + const n = A.length; // Size of the square matrix + // Verify A is square for (let i = 0; i < n; i++) { if (!Array.isArray(A[i]) || A[i].length !== n) { @@ -37,12 +37,12 @@ export function jacobiMethod(A, b, x0, maxIterations = 100, tolerance = 1e-7) { // Verify b is a vector of correct length if (!Array.isArray(b) || b.length !== n) { - throw new Error(`Vector b must have length ${n}, got ${b.length}`); + throw new Error(`Vector b must have length ${n}, got ${Array.isArray(b) ? b.length : "undefined"}`); } // Verify x0 is a vector of correct length if (!Array.isArray(x0) || x0.length !== n) { - throw new Error(`Initial guess x0 must have length ${n}, got ${x0.length}`); + throw new Error(`Initial guess x0 must have length ${n}, got ${Array.isArray(x0) ? x0.length : "undefined"}`); } // Verify no zero diagonal elements (required for Jacobi method) diff --git a/tests/unit/jacobiMethod.test.js b/tests/unit/jacobiMethod.test.js new file mode 100644 index 0000000..b8e1799 --- /dev/null +++ b/tests/unit/jacobiMethod.test.js @@ -0,0 +1,147 @@ +/** + * Unit tests for jacobiMethod + * + * Covers: + * - No data : called with no arguments + * - Missing data : required arguments omitted (b, x0) + * - Invalid data : wrong types / non-square matrix / empty matrix + * - Out of boundaries: zero diagonal element, mismatched vector lengths + * - Success case : diagonally dominant 2×2 system with known solution + * + * Run: node tests/unit/jacobiMethod.test.js + */ + +import { jacobiMethod } from "../../src/methods/jacobiMethodScript.js"; + +let passed = 0; +let failed = 0; + +function assert(condition, message) { + if (!condition) { + console.error(` FAIL: ${message}`); + failed++; + } else { + console.log(` PASS: ${message}`); + passed++; + } +} + +function assertThrows(fn, expectedFragment, label) { + try { + fn(); + console.error(` FAIL: ${label} — expected an error but none was thrown`); + failed++; + } catch (err) { + if (err.message.includes(expectedFragment)) { + console.log(` PASS: ${label}`); + passed++; + } else { + console.error(` FAIL: ${label} — wrong error message.\n Expected fragment: "${expectedFragment}"\n Got: "${err.message}"`); + failed++; + } + } +} + +// --------------------------------------------------------------------------- +// 1. NO DATA — called with no arguments at all +// --------------------------------------------------------------------------- +console.log("\n[1] No data"); + +assertThrows( + () => jacobiMethod(), + "Matrix A must be a non-empty array", + "jacobiMethod() with no arguments throws on missing A" +); + +// --------------------------------------------------------------------------- +// 2. MISSING DATA — required arguments omitted +// --------------------------------------------------------------------------- +console.log("\n[2] Missing data"); + +assertThrows( + () => jacobiMethod([[4, 1], [2, 3]]), + "Vector b must have length", + "Missing b argument throws dimension error" +); + +assertThrows( + () => jacobiMethod([[4, 1], [2, 3]], [9, 8]), + "Initial guess x0 must have length", + "Missing x0 argument throws dimension error" +); + +// --------------------------------------------------------------------------- +// 3. INVALID DATA — wrong types or non-square matrix +// --------------------------------------------------------------------------- +console.log("\n[3] Invalid data"); + +assertThrows( + () => jacobiMethod("not-an-array", [1, 2], [0, 0]), + "Matrix A must be a non-empty array", + "String passed as A throws type error" +); + +assertThrows( + () => jacobiMethod([], [], []), + "Matrix A must be a non-empty array", + "Empty matrix A throws non-empty error" +); + +assertThrows( + () => jacobiMethod([[1, 2, 3], [4, 5, 6]], [1, 2], [0, 0]), + "Matrix A must be square", + "Non-square matrix (2x3) throws square error" +); + +// --------------------------------------------------------------------------- +// 4. DATA OUT OF BOUNDARIES — mathematical constraints violated +// --------------------------------------------------------------------------- +console.log("\n[4] Data out of boundaries"); + +assertThrows( + () => jacobiMethod([[0, 1], [1, 3]], [1, 2], [0, 0]), + "Diagonal element A[0][0] is zero", + "Zero diagonal element throws divide-by-zero guard" +); + +assertThrows( + () => jacobiMethod([[4, 1], [2, 3]], [9, 8, 7], [0, 0]), + "Vector b must have length 2", + "b longer than n throws dimension mismatch" +); + +assertThrows( + () => jacobiMethod([[4, 1], [2, 3]], [9, 8], [0, 0, 0]), + "Initial guess x0 must have length 2", + "x0 longer than n throws dimension mismatch" +); + +// --------------------------------------------------------------------------- +// 5. SUCCESS CASE — diagonally dominant 2×2 system +// +// 10x + 2y = 12 exact solution: x = 1, y = 1 +// 1x + 5y = 6 +// +// Diagonal dominance: |10| > |2| and |5| > |1| — guaranteed convergence. +// --------------------------------------------------------------------------- +console.log("\n[5] Success case"); + +const A = [[10, 2], [1, 5]]; +const b = [12, 6]; +const x0 = [0, 0]; + +const result = jacobiMethod(A, b, x0); + +assert(result.converged === true, "Method converges"); +assert(result.iterations > 0, "At least one iteration was performed"); +assert(result.iterations <= 100, "Converges within the default 100-iteration limit"); + +const tolerance = 1e-6; +assert(Math.abs(result.solution[0] - 1) < tolerance, `x ≈ 1 (got ${result.solution[0]})`); +assert(Math.abs(result.solution[1] - 1) < tolerance, `y ≈ 1 (got ${result.solution[1]})`); + +// --------------------------------------------------------------------------- +// Summary +// --------------------------------------------------------------------------- +console.log(`\n${passed + failed} tests — ${passed} passed, ${failed} failed.\n`); +if (failed > 0) process.exit(1); From 6474c3a93f8232530dcd05455f67430efef20da6 Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Wed, 8 Jul 2026 12:41:40 +0200 Subject: [PATCH 4/9] adding the test and format commands --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 94de51e..f1de80c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "scripts": { "build": "rollup -c", "prepublishOnly": "npm run build", - "test": "echo \"No tests configured\" && exit 0" + "test": "node tests/run-all-tests.js", + "format": "prettier --write ." }, "repository": { "type": "git", @@ -64,6 +65,7 @@ "rollup": "^2.79.2", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "prettier": "^2.8.8" } } From 0692117603ed0643b059c0440364fc668fdb56d3 Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Wed, 8 Jul 2026 12:48:39 +0200 Subject: [PATCH 5/9] Renaming the jacobiMethodScript.js file to jacobiSolver.js --- src/FEAScript.js | 2 +- src/methods/{jacobiMethodScript.js => jacobiSolver.js} | 0 tests/unit/jacobiMethod.test.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/methods/{jacobiMethodScript.js => jacobiSolver.js} (100%) diff --git a/src/FEAScript.js b/src/FEAScript.js index e7a5dda..5034ef1 100644 --- a/src/FEAScript.js +++ b/src/FEAScript.js @@ -9,7 +9,7 @@ // Website: https://feascript.com/ \__| // // Internal imports -import { jacobiMethod } from "./methods/jacobiMethodScript.js"; +import { jacobiMethod } from "./methods/jacobiSolver.js"; import { assembleSolidHeatTransferMat } from "./solvers/solidHeatTransferScript.js"; import { basicLog, debugLog, errorLog } from "./utilities/loggingScript.js"; diff --git a/src/methods/jacobiMethodScript.js b/src/methods/jacobiSolver.js similarity index 100% rename from src/methods/jacobiMethodScript.js rename to src/methods/jacobiSolver.js diff --git a/tests/unit/jacobiMethod.test.js b/tests/unit/jacobiMethod.test.js index b8e1799..52c8c38 100644 --- a/tests/unit/jacobiMethod.test.js +++ b/tests/unit/jacobiMethod.test.js @@ -11,7 +11,7 @@ * Run: node tests/unit/jacobiMethod.test.js */ -import { jacobiMethod } from "../../src/methods/jacobiMethodScript.js"; +import { jacobiMethod } from "../../src/methods/jacobiSolver.js"; let passed = 0; let failed = 0; From ff2e1526c74b359586a1dd79a8f359431801a7cd Mon Sep 17 00:00:00 2001 From: ferrari212 Date: Wed, 8 Jul 2026 13:57:52 +0200 Subject: [PATCH 6/9] Adding the title for the unit tests --- tests/unit/jacobiMethod.test.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/jacobiMethod.test.js b/tests/unit/jacobiMethod.test.js index 52c8c38..d12dfb8 100644 --- a/tests/unit/jacobiMethod.test.js +++ b/tests/unit/jacobiMethod.test.js @@ -13,6 +13,11 @@ import { jacobiMethod } from "../../src/methods/jacobiSolver.js"; +console.log(""); +console.log("================================"); +console.log("Unit tests: jacobiMethod"); +console.log("================================"); + let passed = 0; let failed = 0; From daa5167edec70837ce3177e019c9923cf3087922 Mon Sep 17 00:00:00 2001 From: Nikos Chamakos Date: Thu, 9 Jul 2026 08:36:54 +0300 Subject: [PATCH 7/9] Fix formatting issue in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 653b205..e4ae545 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "typescript": "^5.8.3", - "prettier": "^2.8.8" + "prettier": "^2.8.8", "rollup-plugin-wasm": "^3.0.0", "taichi.js": "^0.0.36" } From 3f031db37e44616e49619059ebd0e475604ba412 Mon Sep 17 00:00:00 2001 From: Nikos Chamakos Date: Thu, 9 Jul 2026 08:41:43 +0300 Subject: [PATCH 8/9] Remove prettier from dependencies I believe it is not required --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e4ae545..60f7e0e 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,6 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "typescript": "^5.8.3", - "prettier": "^2.8.8", "rollup-plugin-wasm": "^3.0.0", "taichi.js": "^0.0.36" } From 8e069965a7048e97de37658066838f0286b0a2e2 Mon Sep 17 00:00:00 2001 From: nikoscham Date: Thu, 9 Jul 2026 09:59:36 +0300 Subject: [PATCH 9/9] Update directory names for the tests, fix bugs and update readme --- CONTRIBUTING.md | 46 +- README.md | 8 + package-lock.json | 569 +++++++++--------- package.json | 1 + .../HeatConduction1DWall/REGRESSION.md | 32 +- .../HeatConduction1DWall/regression.test.js | 19 +- .../HeatConduction2DFin/REGRESSION.md | 36 +- .../HeatConduction2DFin/regression.test.js | 20 +- tests/run-all-tests.js | 15 +- tests/unit/jacobiMethod.test.js | 162 ++--- 10 files changed, 396 insertions(+), 512 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ea6cd8..97e5221 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,6 +10,7 @@ Thank you for your interest in contributing! FEAScript is in early development, - [Branching & Workflow](#branching--workflow) - [Local Testing](#local-testing) - [Running the Node.js Examples](#running-the-nodejs-examples) +- [Verifying Your Changes](#verifying-your-changes) ## Development Environment & Coding Style @@ -120,49 +121,10 @@ http://127.0.0.1:8000/FEAScript-website/tutorials/solidification-front-2d-worker Static file server npm packages like [serve](https://github.com/vercel/serve#readme) and [Vite](https://vite.dev/) can also be used. -## Running the Node.js Examples - -All examples under `examples/` can be run directly with Node.js to verify the library works in a non-browser environment. Run them from the `FEAScript-core/` directory (so that the `feascript` package resolves via `node_modules`): +Testing can be also performed at the Node.js environment. In this case you can also run predefined tests to make sure nothing is broken. This performed as follows: ```bash -cd /path/to/FEAScript-workspace/FEAScript-core - -# Heat conduction — 1D wall -node examples/heatConductionScript/heatConduction1DWall/heatConduction1DWall.js - -# Heat conduction — 2D fin (structured mesh) -node examples/heatConductionScript/heatConduction2DFin/heatConduction2DFin.js - -# Heat conduction — 2D fin (rectangular Gmsh mesh) -node examples/heatConductionScript/heatConduction2DFin/heatConduction2DFinGmsh.js - -# Heat conduction — 2D rhomboid fin (Gmsh mesh) -node examples/heatConductionScript/heatConduction2DFin/heatConduction2DRhomFinGmsh.js - -# Advection-diffusion — 1D with Gaussian source -node examples/generalFormPDEScript/advectionDiffusion1D/advectionDiffusion1D.js - -# Solidification front propagation — 2D -node examples/frontPropagationScript/solidificationFront2D/solidificationFront2D.js - -# Lid-driven cavity — 2D creeping flow -node examples/creepingFlowScript/lidDrivenCavity2DCreepingFlow/lidDrivenCavity2DCreepingFlow.js +npm test ``` -Each script prints its computed solution array to the console. A successful run exits with code 0 and produces numerical output; any import or runtime error indicates a problem. - -To run all examples in one go and check for failures: - -```bash -for f in \ - examples/heatConductionScript/heatConduction1DWall/heatConduction1DWall.js \ - examples/heatConductionScript/heatConduction2DFin/heatConduction2DFin.js \ - examples/heatConductionScript/heatConduction2DFin/heatConduction2DFinGmsh.js \ - examples/heatConductionScript/heatConduction2DFin/heatConduction2DRhomFinGmsh.js \ - examples/generalFormPDEScript/advectionDiffusion1D/advectionDiffusion1D.js \ - examples/frontPropagationScript/solidificationFront2D/solidificationFront2D.js \ - examples/creepingFlowScript/lidDrivenCavity2DCreepingFlow/lidDrivenCavity2DCreepingFlow.js; do - echo -n " $f ... " - node "$f" > /dev/null 2>&1 && echo "OK" || echo "FAILED" -done -``` +These tests compare the numerical results against stored reference solutions at selected points. diff --git a/README.md b/README.md index e5b4e7c..57c0fb2 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,14 @@ Here is a minimal browser-based example using the FEAScript API. Adapt paths, ph - "conditionType" should be replaced with an actual boundary condition type such as "constantTemp" - "boundaryIndex" should be replaced with a string identifying the boundary +Furthermore, the scripts under `examples/` contain Node.js examples. You can run them from the `FEAScript-core/` directory as follows: + +```bash +node examples/heatConductionScript/heatConduction1DWall/heatConduction1DWall.js +``` + +Each script prints its computed solution to the console. + ## Support FEAScript > 💖 **If you find FEAScript useful, please consider supporting its development through a donation:** diff --git a/package-lock.json b/package-lock.json index 43fe012..defc303 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,11 +19,13 @@ "@kitware/vtk.js": "^32.12.0", "@rollup/plugin-commonjs": "^29.0.2", "@rollup/plugin-node-resolve": "^16.0.3", + "prettier": "^2.8.8", "rollup": "^2.79.2", "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "rollup-plugin-wasm": "^3.0.0", - "taichi.js": "^0.0.36" + "taichi.js": "^0.0.36", + "typescript": "^5.8.3" }, "peerDependencies": { "@kitware/vtk.js": "^32.12.0", @@ -39,13 +41,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -54,9 +56,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "peer": true, @@ -65,22 +67,22 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -127,15 +129,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -159,15 +161,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -289,9 +291,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "peer": true, @@ -315,31 +317,31 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -363,9 +365,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "peer": true, @@ -427,9 +429,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "peer": true, @@ -438,9 +440,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -448,9 +450,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "peer": true, @@ -475,29 +477,29 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -1108,17 +1110,17 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", + "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.29.0" + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1691,35 +1693,35 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -1746,15 +1748,15 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -6518,30 +6520,6 @@ "url": "https://opencollective.com/turf" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6845,10 +6823,11 @@ "peer": true }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "devOptional": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -6871,9 +6850,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "devOptional": true, "license": "MIT", "peer": true, @@ -7158,9 +7137,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -7986,15 +7965,15 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", "devOptional": true, "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -8021,9 +8000,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", "devOptional": true, "license": "MIT", "peer": true @@ -8316,9 +8295,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "devOptional": true, "funding": [ { @@ -8615,14 +8594,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true, - "license": "BSD-2-Clause", - "peer": true - }, "node_modules/global-prefix": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-4.0.0.tgz", @@ -9300,14 +9271,6 @@ "node": ">=6" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true, - "license": "MIT", - "peer": true - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -9377,9 +9340,9 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "devOptional": true, "license": "MIT", "peer": true, @@ -9717,9 +9680,9 @@ "license": "MIT" }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -9727,20 +9690,6 @@ "node": ">= 0.6" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -9764,6 +9713,101 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimizer-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/minimizer-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/mouse-change": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/mouse-change/-/mouse-change-1.4.0.tgz", @@ -9833,9 +9877,9 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "devOptional": true, "funding": [ { @@ -10102,9 +10146,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -10206,9 +10250,9 @@ "peer": true }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "devOptional": true, "funding": [ { @@ -10227,7 +10271,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10333,6 +10377,22 @@ "optional": true, "peer": true }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/probe-image-size": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", @@ -10355,9 +10415,9 @@ "peer": true }, "node_modules/protocol-buffers-schema": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz", - "integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", + "integrity": "sha512-VG2K63Igkiv9p76tk1lilczEK1cT+kCjKtkdhw1dQZV3k3IXJbd3o6Ho8b9zJZaHSnT2hKe4I+ObmX9w6m5SmQ==", "license": "MIT", "optional": true, "peer": true @@ -10395,7 +10455,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -10657,9 +10717,9 @@ "peer": true }, "node_modules/rollup": { - "version": "2.79.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", - "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "version": "2.80.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz", + "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==", "dev": true, "license": "MIT", "bin": { @@ -10722,9 +10782,9 @@ } }, "node_modules/rollup-plugin-typescript2/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -11179,9 +11239,9 @@ } }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "devOptional": true, "license": "MIT", "peer": true, @@ -11212,86 +11272,6 @@ "node": ">=10" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "devOptional": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/texture-compressor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/texture-compressor/-/texture-compressor-1.0.2.tgz", @@ -11423,7 +11403,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11586,14 +11565,13 @@ } }, "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", "devOptional": true, "license": "MIT", "peer": true, "dependencies": { - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { @@ -11620,38 +11598,35 @@ } }, "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", "devOptional": true, "license": "MIT", "peer": true, "dependencies": { - "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.22.2", + "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" + "watchpack": "^2.5.2", + "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" @@ -11670,9 +11645,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", "devOptional": true, "license": "MIT", "peer": true, diff --git a/package.json b/package.json index 60f7e0e..e4ae545 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "rollup-plugin-terser": "^7.0.2", "rollup-plugin-typescript2": "^0.36.0", "typescript": "^5.8.3", + "prettier": "^2.8.8", "rollup-plugin-wasm": "^3.0.0", "taichi.js": "^0.0.36" } diff --git a/tests/regression/HeatConduction1DWall/REGRESSION.md b/tests/regression/HeatConduction1DWall/REGRESSION.md index 579ffac..6359a5b 100644 --- a/tests/regression/HeatConduction1DWall/REGRESSION.md +++ b/tests/regression/HeatConduction1DWall/REGRESSION.md @@ -11,18 +11,18 @@ and asserts a known good value. ## Problem setup -| Parameter | Value | -|-----------|-------| -| Domain | 1D, 0 – 0.15 m | -| Mesh | 10 linear elements | -| Boundary 0 (x = 0) | Convection, h = 1, T∞ = 25 °C | +| Parameter | Value | +| --------------------- | ------------------------------ | +| Domain | 1D, 0 – 0.15 m | +| Mesh | 10 linear elements | +| Boundary 0 (x = 0) | Convection, h = 1, T∞ = 25 °C | | Boundary 1 (x = 0.15) | Constant temperature, T = 5 °C | -| Solver | LU decomposition (`lusolve`) | +| Solver | LU decomposition (`lusolve`) | ## Expected value -| Quantity | Value | -|----------|-------| +| Quantity | Value | +| ----------------------------- | --------------- | | Temperature at node 0 (x = 0) | **10.29412 °C** | Tolerance used in the assertion: `1e-4`. @@ -47,15 +47,15 @@ The `test` script in `package.json` also runs this file, so `npm test` works too ## After modifying the code -| Situation | Action | -|-----------|--------| -| Bug fix that should not change results | Run the test — it must still pass. | +| Situation | Action | +| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| Bug fix that should not change results | Run the test — it must still pass. | | Intentional algorithm change (new element type, new integration rule, etc.) | Re-derive the expected value, update `EXPECTED_T0` in `regression.test.js`, and document the reason here. | -| New boundary condition API | Update both the test and the reference HTML example together. | -| Adding a new solver method | Add a separate assertion block for the new method; keep the `lusolve` block untouched as the baseline. | +| New boundary condition API | Update both the test and the reference HTML example together. | +| Adding a new solver method | Add a separate assertion block for the new method; keep the `lusolve` block untouched as the baseline. | ## Change log -| Date | Change | New expected value | -|------|--------|--------------------| -| 2026-06-14 | Initial regression baseline | 10.29412 | +| Date | Change | New expected value | +| ---------- | --------------------------- | ------------------ | +| 2026-06-14 | Initial regression baseline | 10.29412 | diff --git a/tests/regression/HeatConduction1DWall/regression.test.js b/tests/regression/HeatConduction1DWall/regression.test.js index fbd5f2b..faf7858 100644 --- a/tests/regression/HeatConduction1DWall/regression.test.js +++ b/tests/regression/HeatConduction1DWall/regression.test.js @@ -9,7 +9,7 @@ import * as mathjs from "mathjs"; import { FEAScriptModel } from "../../../src/FEAScript.js"; -import { basicLog } from "../../../src/utilities/loggingScript.js"; +import { basicLog, errorLog } from "../../../src/utilities/logging.js"; // FEAScript.js references `math` as a global (loaded via CDN in browser). // Set it here before any solve() call. @@ -21,11 +21,7 @@ const TOLERANCE = 1e-4; function runSimulation() { const model = new FEAScriptModel(); - basicLog("") - basicLog("================================") - basicLog("Starting test in solid heat transfer 1D wall...") - - model.setSolverConfig("solidHeatTransferScript"); + model.setModelConfig("heatConductionScript"); model.setMeshConfig({ meshDimension: "1D", elementOrder: "linear", @@ -42,11 +38,14 @@ function runSimulation() { function assert(condition, message) { if (!condition) { - console.error(`FAIL: ${message}`); + errorLog(`FAIL: ${message}`); process.exit(1); } } +basicLog(""); +basicLog("================================"); +basicLog("Starting test in solid heat transfer 1D wall..."); const { solutionVector } = runSimulation(); // solutionVector from math.lusolve is a nested array: [[T0], [T1], ...] @@ -54,8 +53,8 @@ const T0 = Array.isArray(solutionVector[0]) ? solutionVector[0][0] : solutionVec assert( Math.abs(T0 - EXPECTED_T0) < TOLERANCE, - `Temperature at node 0: expected ${EXPECTED_T0}, got ${T0} (tolerance ${TOLERANCE})` + `Temperature at node 0: expected ${EXPECTED_T0}, got ${T0} (tolerance ${TOLERANCE})`, ); -console.log(`PASS: T(x=0) = ${T0.toFixed(5)} (expected ${EXPECTED_T0})`); -basicLog("================================") +basicLog(`PASS: T(x=0) = ${T0.toFixed(5)} (expected ${EXPECTED_T0})`); +basicLog("================================"); diff --git a/tests/regression/HeatConduction2DFin/REGRESSION.md b/tests/regression/HeatConduction2DFin/REGRESSION.md index 5bea510..4c27f0d 100644 --- a/tests/regression/HeatConduction2DFin/REGRESSION.md +++ b/tests/regression/HeatConduction2DFin/REGRESSION.md @@ -11,20 +11,20 @@ and asserts a known good value at a representative interior point. ## Problem setup -| Parameter | Value | -|-----------|-------| -| Domain | 2D, x ∈ [0, 4] m, y ∈ [0, 2] m | -| Mesh | 8 × 4 quadratic elements | +| Parameter | Value | +| -------------------------- | -------------------------------- | +| Domain | 2D, x ∈ [0, 4] m, y ∈ [0, 2] m | +| Mesh | 8 × 4 quadratic elements | | Boundary 0 (bottom, y = 0) | Constant temperature, T = 200 °C | -| Boundary 1 (left, x = 0) | Symmetry (zero flux) | -| Boundary 2 (top, y = 2) | Convection, h = 1, T∞ = 20 °C | -| Boundary 3 (right, x = 4) | Constant temperature, T = 200 °C | -| Solver | LU decomposition (`lusolve`) | +| Boundary 1 (left, x = 0) | Symmetry (zero flux) | +| Boundary 2 (top, y = 2) | Convection, h = 1, T∞ = 20 °C | +| Boundary 3 (right, x = 4) | Constant temperature, T = 200 °C | +| Solver | LU decomposition (`lusolve`) | ## Expected value -| Quantity | Value | -|----------|-------| +| Quantity | Value | +| ---------------------------------- | --------------- | | Temperature at node (x = 0, y = 2) | **81.31873 °C** | This point sits at the top-left corner of the fin — on the symmetry boundary and @@ -53,15 +53,15 @@ Running `npm test` executes all regression tests, including this one. ## After modifying the code -| Situation | Action | -|-----------|--------| -| Bug fix that should not change results | Run the test — it must still pass. | +| Situation | Action | +| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| Bug fix that should not change results | Run the test — it must still pass. | | Intentional algorithm change (new element type, new integration rule, etc.) | Re-derive the expected value, update `EXPECTED_T` in `regression.test.js`, and document the reason here. | -| New boundary condition API | Update both the test and the reference HTML example together. | -| Mesh refinement study | Add a separate assertion block for the refined mesh; keep the current block as the coarse-mesh baseline. | +| New boundary condition API | Update both the test and the reference HTML example together. | +| Mesh refinement study | Add a separate assertion block for the refined mesh; keep the current block as the coarse-mesh baseline. | ## Change log -| Date | Change | New expected value | -|------|--------|--------------------| -| 2026-06-14 | Initial regression baseline | 81.31873 | +| Date | Change | New expected value | +| ---------- | --------------------------- | ------------------ | +| 2026-06-14 | Initial regression baseline | 81.31873 | diff --git a/tests/regression/HeatConduction2DFin/regression.test.js b/tests/regression/HeatConduction2DFin/regression.test.js index b57d48e..334d3cb 100644 --- a/tests/regression/HeatConduction2DFin/regression.test.js +++ b/tests/regression/HeatConduction2DFin/regression.test.js @@ -9,11 +9,11 @@ import * as mathjs from "mathjs"; import { FEAScriptModel } from "../../../src/FEAScript.js"; -import { basicLog } from "../../../src/utilities/loggingScript.js"; +import { basicLog, errorLog } from "../../../src/utilities/logging.js"; -basicLog("") -basicLog("================================") -basicLog("Starting test in solid heat transfer 2D fin...") +basicLog(""); +basicLog("================================"); +basicLog("Starting test in solid heat transfer 2D fin..."); // FEAScript.js references `math` as a global (loaded via CDN in browser). // Set it here before any solve() call. @@ -27,7 +27,7 @@ const TOLERANCE = 1e-4; function runSimulation() { const model = new FEAScriptModel(); - model.setSolverConfig("solidHeatTransferScript"); + model.setModelConfig("heatConductionScript"); model.setMeshConfig({ meshDimension: "2D", elementOrder: "quadratic", @@ -48,7 +48,7 @@ function runSimulation() { function assert(condition, message) { if (!condition) { - console.error(`FAIL: ${message}`); + errorLog(`FAIL: ${message}`); process.exit(1); } } @@ -59,7 +59,7 @@ const { nodesXCoordinates, nodesYCoordinates } = nodesCoordinates; // Locate the node at (x=0, y=2) by searching coordinates. // This is robust against changes in mesh ordering conventions. const nodeIndex = nodesXCoordinates.findIndex( - (x, i) => Math.abs(x - EXPECTED_X) < 1e-10 && Math.abs(nodesYCoordinates[i] - EXPECTED_Y) < 1e-10 + (x, i) => Math.abs(x - EXPECTED_X) < 1e-10 && Math.abs(nodesYCoordinates[i] - EXPECTED_Y) < 1e-10, ); assert(nodeIndex !== -1, `No node found at (x=${EXPECTED_X}, y=${EXPECTED_Y})`); @@ -69,9 +69,9 @@ const T = Array.isArray(solutionVector[nodeIndex]) ? solutionVector[nodeIndex][0 assert( Math.abs(T - EXPECTED_T) < TOLERANCE, - `Temperature at (x=${EXPECTED_X}, y=${EXPECTED_Y}): expected ${EXPECTED_T}, got ${T} (tolerance ${TOLERANCE})` + `Temperature at (x=${EXPECTED_X}, y=${EXPECTED_Y}): expected ${EXPECTED_T}, got ${T} (tolerance ${TOLERANCE})`, ); -console.log(`PASS: T(x=${EXPECTED_X}, y=${EXPECTED_Y}) = ${T.toFixed(5)} (expected ${EXPECTED_T})`); +basicLog(`PASS: T(x=${EXPECTED_X}, y=${EXPECTED_Y}) = ${T.toFixed(5)} (expected ${EXPECTED_T})`); -basicLog("================================") \ No newline at end of file +basicLog("================================"); diff --git a/tests/run-all-tests.js b/tests/run-all-tests.js index 8901fcc..e1f848d 100644 --- a/tests/run-all-tests.js +++ b/tests/run-all-tests.js @@ -9,6 +9,7 @@ import { readdirSync, statSync } from "fs"; import { join, relative } from "path"; import { spawnSync } from "child_process"; import { fileURLToPath } from "url"; +import { basicLog, errorLog, warnLog } from "../src/utilities/logging.js"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); @@ -29,11 +30,12 @@ function collectTestFiles(dir) { const testFiles = collectTestFiles(__dirname); if (testFiles.length === 0) { - console.log("No test files found."); + warnLog("No test files found."); process.exit(0); } -console.log(`Found ${testFiles.length} test file(s).\n`); +basicLog(`Found ${testFiles.length} test file(s).`); +basicLog(""); let passed = 0; let failed = 0; @@ -44,10 +46,15 @@ for (const file of testFiles) { if (result.status === 0) { passed++; } else { - console.error(`\nFAILED: ${label}\n`); + errorLog(`FAILED: ${label}`); + basicLog(""); failed++; } } -console.log(`\n${passed} passed, ${failed} failed.`); +if (failed > 0) { + errorLog(`${passed} passed, ${failed} failed.`); +} else { + basicLog(`${passed} passed, ${failed} failed.`); +} process.exit(failed > 0 ? 1 : 0); diff --git a/tests/unit/jacobiMethod.test.js b/tests/unit/jacobiMethod.test.js index d12dfb8..070ce5f 100644 --- a/tests/unit/jacobiMethod.test.js +++ b/tests/unit/jacobiMethod.test.js @@ -1,152 +1,84 @@ /** - * Unit tests for jacobiMethod + * Unit tests for jacobiSolver * * Covers: - * - No data : called with no arguments - * - Missing data : required arguments omitted (b, x0) - * - Invalid data : wrong types / non-square matrix / empty matrix - * - Out of boundaries: zero diagonal element, mismatched vector lengths - * - Success case : diagonally dominant 2×2 system with known solution + * - Success case on a diagonally dominant 2x2 system + * - Non-convergence when maxIterations is too small * * Run: node tests/unit/jacobiMethod.test.js */ -import { jacobiMethod } from "../../src/methods/jacobiSolver.js"; +import { jacobiSolver } from "../../src/methods/jacobiSolver.js"; +import { basicLog, errorLog } from "../../src/utilities/logging.js"; -console.log(""); -console.log("================================"); -console.log("Unit tests: jacobiMethod"); -console.log("================================"); +basicLog(""); +basicLog("================================"); +basicLog("Unit tests: jacobiSolver"); let passed = 0; let failed = 0; function assert(condition, message) { if (!condition) { - console.error(` FAIL: ${message}`); + errorLog(`FAIL: ${message}`); failed++; } else { - console.log(` PASS: ${message}`); + basicLog(`PASS: ${message}`); passed++; } } -function assertThrows(fn, expectedFragment, label) { - try { - fn(); - console.error(` FAIL: ${label} — expected an error but none was thrown`); - failed++; - } catch (err) { - if (err.message.includes(expectedFragment)) { - console.log(` PASS: ${label}`); - passed++; - } else { - console.error(` FAIL: ${label} — wrong error message.\n Expected fragment: "${expectedFragment}"\n Got: "${err.message}"`); - failed++; - } - } -} - -// --------------------------------------------------------------------------- -// 1. NO DATA — called with no arguments at all -// --------------------------------------------------------------------------- -console.log("\n[1] No data"); - -assertThrows( - () => jacobiMethod(), - "Matrix A must be a non-empty array", - "jacobiMethod() with no arguments throws on missing A" -); - -// --------------------------------------------------------------------------- -// 2. MISSING DATA — required arguments omitted // --------------------------------------------------------------------------- -console.log("\n[2] Missing data"); - -assertThrows( - () => jacobiMethod([[4, 1], [2, 3]]), - "Vector b must have length", - "Missing b argument throws dimension error" -); - -assertThrows( - () => jacobiMethod([[4, 1], [2, 3]], [9, 8]), - "Initial guess x0 must have length", - "Missing x0 argument throws dimension error" -); - -// --------------------------------------------------------------------------- -// 3. INVALID DATA — wrong types or non-square matrix -// --------------------------------------------------------------------------- -console.log("\n[3] Invalid data"); - -assertThrows( - () => jacobiMethod("not-an-array", [1, 2], [0, 0]), - "Matrix A must be a non-empty array", - "String passed as A throws type error" -); - -assertThrows( - () => jacobiMethod([], [], []), - "Matrix A must be a non-empty array", - "Empty matrix A throws non-empty error" -); - -assertThrows( - () => jacobiMethod([[1, 2, 3], [4, 5, 6]], [1, 2], [0, 0]), - "Matrix A must be square", - "Non-square matrix (2x3) throws square error" -); - -// --------------------------------------------------------------------------- -// 4. DATA OUT OF BOUNDARIES — mathematical constraints violated -// --------------------------------------------------------------------------- -console.log("\n[4] Data out of boundaries"); - -assertThrows( - () => jacobiMethod([[0, 1], [1, 3]], [1, 2], [0, 0]), - "Diagonal element A[0][0] is zero", - "Zero diagonal element throws divide-by-zero guard" -); - -assertThrows( - () => jacobiMethod([[4, 1], [2, 3]], [9, 8, 7], [0, 0]), - "Vector b must have length 2", - "b longer than n throws dimension mismatch" -); - -assertThrows( - () => jacobiMethod([[4, 1], [2, 3]], [9, 8], [0, 0, 0]), - "Initial guess x0 must have length 2", - "x0 longer than n throws dimension mismatch" -); - -// --------------------------------------------------------------------------- -// 5. SUCCESS CASE — diagonally dominant 2×2 system +// 1. SUCCESS CASE — diagonally dominant 2x2 system // // 10x + 2y = 12 exact solution: x = 1, y = 1 // 1x + 5y = 6 -// -// Diagonal dominance: |10| > |2| and |5| > |1| — guaranteed convergence. // --------------------------------------------------------------------------- -console.log("\n[5] Success case"); - -const A = [[10, 2], [1, 5]]; -const b = [12, 6]; +basicLog(""); +basicLog("[1] Success case"); + +const A = [ + [10, 2], + [1, 5], +]; +const b = [12, 6]; const x0 = [0, 0]; -const result = jacobiMethod(A, b, x0); +const result = jacobiSolver(A, b, x0, { + maxIterations: 500, + tolerance: 1e-10, +}); assert(result.converged === true, "Method converges"); assert(result.iterations > 0, "At least one iteration was performed"); -assert(result.iterations <= 100, "Converges within the default 100-iteration limit"); +assert(result.iterations <= 500, "Converges within configured iteration limit"); const tolerance = 1e-6; -assert(Math.abs(result.solution[0] - 1) < tolerance, `x ≈ 1 (got ${result.solution[0]})`); -assert(Math.abs(result.solution[1] - 1) < tolerance, `y ≈ 1 (got ${result.solution[1]})`); +assert(Math.abs(result.solutionVector[0] - 1) < tolerance, `x ~= 1 (got ${result.solutionVector[0]})`); +assert(Math.abs(result.solutionVector[1] - 1) < tolerance, `y ~= 1 (got ${result.solutionVector[1]})`); + +// --------------------------------------------------------------------------- +// 2. NON-CONVERGENCE CASE — force early stop +// --------------------------------------------------------------------------- +basicLog(""); +basicLog("[2] Non-convergence case"); + +const hardResult = jacobiSolver(A, b, x0, { + maxIterations: 1, + tolerance: 1e-20, +}); + +assert(hardResult.converged === false, "Method reports non-convergence with too few iterations"); +assert(hardResult.iterations === 1, "Method reports the configured iteration cap"); +assert(hardResult.solutionVector.length === 2, "Returns a solution vector of expected size"); // --------------------------------------------------------------------------- // Summary // --------------------------------------------------------------------------- -console.log(`\n${passed + failed} tests — ${passed} passed, ${failed} failed.\n`); +if (failed > 0) { + errorLog(`${passed + failed} assertions — ${passed} passed, ${failed} failed.`); +} else { + basicLog(`${passed + failed} assertions — ${passed} passed, ${failed} failed.`); +} +basicLog("================================"); if (failed > 0) process.exit(1);