diff --git a/.changeset/ode-numeric-not-defined.md b/.changeset/ode-numeric-not-defined.md new file mode 100644 index 0000000000..211f6cd185 --- /dev/null +++ b/.changeset/ode-numeric-not-defined.md @@ -0,0 +1,13 @@ +--- +"@doenet/doenetml": patch +"@doenet/standalone": patch +"@doenet/doenetml-iframe": patch +"@doenet/vscode-extension": patch +"doenet-vscode-extension": patch +--- + +Fix `` failing with "numeric is not defined" in the browser. + +Any document containing an `` rendered as that error banner instead of a document, and any graph of a solution drew no curve. The solver, `dopri`, comes from numeric.js, bundled inside math-expressions. numeric builds most of its helpers at load time with the `Function` constructor, and the generated bodies reference a bare `numeric` — resolvable only if numeric has registered itself on the global object. It did that solely through Node's `global`, which neither a browser main thread nor a web worker has, so every generated helper threw the first time it was called, and `dopri` reaches them immediately. The worker's evaluation and the main-thread renderer's curve sampling both went through that path, so both failed. + +Fixed upstream in math-expressions 2.0.0-alpha95, which registers numeric itself; this bumps to it. diff --git a/package-lock.json b/package-lock.json index ef5b73ef5d..93d5f30bbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,7 +108,7 @@ "katex": "^0.16.47", "lint-staged": "^15.0.0", "lorem-ipsum": "^2.0.8", - "math-expressions": "^2.0.0-alpha94", + "math-expressions": "^2.0.0-alpha95", "micromark": "^4.0.2", "nanoid": "^5.1.16", "nextra": "^3.3.1", @@ -16818,9 +16818,9 @@ } }, "node_modules/math-expressions": { - "version": "2.0.0-alpha94", - "resolved": "https://registry.npmjs.org/math-expressions/-/math-expressions-2.0.0-alpha94.tgz", - "integrity": "sha512-eGHbVE04HGmSYZ6t/eH5cM23+nM5c/oIMwrOBmgfcctJ8aX98DCzNMsobzDZFuXpj/1kNbEhOxfgDPFr5EFJ2g==", + "version": "2.0.0-alpha95", + "resolved": "https://registry.npmjs.org/math-expressions/-/math-expressions-2.0.0-alpha95.tgz", + "integrity": "sha512-PhsgiB52TSVqHZu9oLWfK8ntZ3wdDnYA+VzK1SkuURTBkGw97DDsTGG52220VxCia+bO9vNTCOv9LIeysmuvFA==", "license": "(GPL-3.0 OR Apache-2.0)", "dependencies": { "mathjs": "^15.2.0", @@ -27108,7 +27108,7 @@ "@doenet/i18n": "*", "color-name": "^1.1.4", "colord": "^2.9.3", - "math-expressions": "^2.0.0-alpha93", + "math-expressions": "^2.0.0-alpha95", "micromark": "^4.0.2" }, "devDependencies": { diff --git a/package.json b/package.json index 985f0ec469..c315bf476a 100644 --- a/package.json +++ b/package.json @@ -167,7 +167,7 @@ "lint-staged": "^15.0.0", "katex": "^0.16.47", "lorem-ipsum": "^2.0.8", - "math-expressions": "^2.0.0-alpha94", + "math-expressions": "^2.0.0-alpha95", "micromark": "^4.0.2", "nanoid": "^5.1.16", "nextra": "^3.3.1", diff --git a/packages/test-cypress/cypress/e2e/dynamicalsystem/odesystem.cy.js b/packages/test-cypress/cypress/e2e/dynamicalsystem/odesystem.cy.js new file mode 100644 index 0000000000..580cbaf024 --- /dev/null +++ b/packages/test-cypress/cypress/e2e/dynamicalsystem/odesystem.cy.js @@ -0,0 +1,66 @@ +import { getDiagnosticsByType } from "../../support/diagnostics"; + +describe("ODESystem Tag Tests", { tags: ["@group2"] }, function () { + beforeEach(() => { + cy.clearIndexedDB(); + cy.visit("/"); + }); + + // Regression test for `ReferenceError: numeric is not defined`, which turned + // every `` document into an error banner. The solver comes from + // numeric.js, whose generated helpers reference a bare `numeric` and so need + // numeric registered on the global object; it used to register itself only + // through Node's `global` (fixed in math-expressions 2.0.0-alpha95). + // + // That makes this a Cypress test by necessity: under Vitest `global` exists, + // numeric registers itself, and nothing fails. Both sides of the worker + // boundary have to be exercised — the worker evaluates `$$f(1)`, and the + // main-thread renderer samples the same solution to draw the curve. + it("solves and plots in the browser", () => { + cy.window().then((win) => { + win.postMessage( + { + doenetML: ` + + 0.0000006*x*(x-2000)(500-x) + + + +

value at 1: $$f(1)

+ + + + + `, + }, + "*", + ); + }); + + // Worker side: the solution evaluated at t = 1. + cy.get("#pVal").should("have.text", "value at 1: 518.99"); + + // Main-thread side: JSXGraph samples the solution to draw the curve, so + // a plotted path proves the renderer reached numeric too. The axes and + // their ticks are paths as well, but they are the only ones stroked in + // the axis color, so excluding that color leaves just the curve. + cy.get(`.jxgbox path:not([stroke="var(--canvasText)"])`).should( + ($paths) => { + const segments = $paths + .toArray() + .map((p) => (p.getAttribute("d") || "").split("L").length); + // A sampled curve is hundreds of segments; anything the + // renderer draws without reaching the solver is a handful. + expect( + Math.max(...segments), + "segments in the longest non-axis path", + ).to.be.greaterThan(100); + }, + ); + + cy.window().then((win) => { + const { errors } = getDiagnosticsByType(win.returnDiagnostics1()); + expect(errors.length).eq(0); + }); + }); +}); diff --git a/packages/utils/package.json b/packages/utils/package.json index 674fad130a..c68e4ba859 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -62,7 +62,7 @@ "@doenet/i18n": "*", "color-name": "^1.1.4", "colord": "^2.9.3", - "math-expressions": "^2.0.0-alpha93", + "math-expressions": "^2.0.0-alpha95", "micromark": "^4.0.2" }, "devDependencies": {