Rebuild printable roast plans and remove machine-based adjustments
Test and deploy / test-and-deploy (push) Successful in 2m28s

This commit is contained in:
snowspeeder committed 2026-09-05 14:56:11 -04:00
1 parent e9f2393c96
commit 824a964f9d
30 files changed
+1721 -908

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
import test from "node:test";
import assert from "node:assert/strict";
import { blankPlan } from "../shared/fields.js";
import { computeLedger } from "../shared/ledger.js";
import {
buildPlanCurve,
curveSegments,
curveWarnings,
} from "../shared/curve.js";
import { renderReportHtml } from "../shared/report.js";
import { parseDuration } from "../shared/time.js";
function plan() {
const p = blankPlan();
Object.assign(p.fields, {
0.1: "Burundi",
0.4: "500",
1.4: "8:45",
4.1: "light",
4.2: "12",
4.3: "1:38",
3.2: "-0:30",
1.7: "-0:05",
});
return p;
}
test("light development does not silently collapse to 1:03; explicit override wins", () => {
const p = plan();
let l = computeLedger(p);
assert.equal(l.rawC, 63);
assert.equal(l.C, 75);
assert.ok(l.warnings.some((w) => w.includes("below 1:15")));
p.fields["4.4"] = "1:20";
assert.equal(computeLedger(p).C, 80);
});
test("machine profiles cannot modify plan timing or temperature defaults", () => {
const p = plan(),
l = computeLedger(p);
const machine = {
pace: { value: 1.8 },
n: 99,
medians: { firstCrackTempC: 140, turningPointS: 10 },
};
assert.deepEqual(computeLedger(p, machine), l);
assert.deepEqual(buildPlanCurve(p, l, machine), buildPlanCurve(p, l));
assert.equal(buildPlanCurve(p, l)[3].tempC, 200);
});
test("default development average rise declines and cubic segments do not overshoot", () => {
const p = plan(),
l = computeLedger(p),
points = buildPlanCurve(p, l);
assert.deepEqual(curveWarnings(points), []);
for (const s of curveSegments(points).segments) {
for (let t = 0; t <= 1; t += 0.01) {
const v =
(1 - t) ** 3 * s.a.tempC +
3 * (1 - t) ** 2 * t * s.c1.tempC +
3 * (1 - t) * t * t * s.c2.tempC +
t ** 3 * s.b.tempC;
assert.ok(
v >= Math.min(s.a.tempC, s.b.tempC) - 1e-8 &&
v <= Math.max(s.a.tempC, s.b.tempC) + 1e-8,
);
}
}
});
test("explicit flick-producing targets are preserved and flagged", () => {
const p = plan();
p.temps.drop.tempC = "230";
const points = buildPlanCurve(p, computeLedger(p));
assert.equal(points.at(-1).tempC, 230);
assert.ok(curveWarnings(points).length);
});
test("negative timings and implausible density are surfaced", () => {
const p = plan();
p.fields["1.6"] = "-10:00";
p.fields["5.2"] = "76.14";
const l = computeLedger(p);
assert.equal(l.A, null);
assert.ok(l.warnings.some((w) => w.includes("761.4")));
assert.equal(parseDuration("1:99"), null);
assert.equal(parseDuration("9".repeat(400)), null);
});
test("standalone report has Fahrenheit, grams, graph annotations and escapes untrusted content", () => {
const p = plan();
p.fields["0.1"] = "</title><script>alert(1)</script>";
p.afterRoast.cupNotes = "<img src=x onerror=alert(1)>";
const html = renderReportHtml(p);
assert.ok(html.startsWith("<!doctype html>"));
assert.ok(html.includes("440 g"));
assert.ok(html.includes("500 g"));
assert.ok(html.includes("392°F"));
assert.ok(html.includes("Development · 1:15"));
assert.ok(html.includes("8:45"));
assert.ok(html.includes("Maillard"));
assert.ok(html.includes("@media print"));
assert.ok(!html.includes("<script>"));
assert.ok(!html.includes("<img"));
assert.ok(!html.includes("°C"));
assert.ok(!html.includes("Hottop"));
assert.ok(!html.includes("<script src"));
});
test("incomplete plans export without NaN, Infinity or invented output weight", () => {
const html = renderReportHtml(blankPlan());
assert.ok(!/NaN|Infinity/.test(html));
assert.ok(!html.includes("0 g"));
});