eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// 🤖 Tests for the web BlurHash codec (web/static/blurhash.js) — including THE
2// cross-implementation vector: the Swift twin (Blurhash.swift) encodes the
3// same deterministic pixel grid in BlurhashTests.swift and must produce the
4// SAME hash. If either side changes output, both tests fail together.
5// node test/blurhash.test.mjs
6import assert from "node:assert/strict";
7import { encodeBlurhash, decodeBlurhash } from "../web/static/blurhash.js";
8
9let failures = 0;
10async function test(name, fn) {
11 try {
12 await fn();
13 console.log("PASS " + name);
14 } catch (e) {
15 console.log("FAIL " + name + " — " + (e && e.message));
16 failures++;
17 }
18}
19
20// The shared vector grid: 8×6, RGBA, deterministic bytes — red ramps left to
21// right, green top to bottom, blue fixed. Keep IDENTICAL to vectorImage() in
22// BlurhashTests.swift.
23function vectorGrid() {
24 const w = 8, h = 6;
25 const px = new Uint8ClampedArray(w * h * 4);
26 for (let y = 0; y < h; y++) {
27 for (let x = 0; x < w; x++) {
28 const o = (y * w + x) * 4;
29 px[o] = Math.round((x * 255) / (w - 1));
30 px[o + 1] = Math.round((y * 255) / (h - 1));
31 px[o + 2] = 64;
32 px[o + 3] = 255;
33 }
34 }
35 return { px, w, h };
36}
37
38await test("cross-implementation vector (pinned against Blurhash.swift)", () => {
39 const { px, w, h } = vectorGrid();
40 // The one true hash for the vector grid — BlurhashTests.swift pins the same
41 // string. A change here without the twin is a cross-platform drift.
42 assert.equal(encodeBlurhash(px, w, h), "LyI5d=39a|%0z2NKfQnSeXf7fQf7");
43});
44
45await test("solid colour round-trips through encode/decode", () => {
46 const w = 16, h = 12;
47 const px = new Uint8ClampedArray(w * h * 4);
48 for (let i = 0; i < px.length; i += 4) {
49 px[i] = 200; px[i + 1] = 60; px[i + 2] = 40; px[i + 3] = 255;
50 }
51 const hash = encodeBlurhash(px, w, h);
52 const out = decodeBlurhash(hash, 16, 12);
53 let r = 0, g = 0, b = 0;
54 for (let i = 0; i < out.length; i += 4) { r += out[i]; g += out[i + 1]; b += out[i + 2]; }
55 const n = w * h;
56 assert.ok(Math.abs(r / n - 200) < 12, `red ${r / n}`);
57 assert.ok(Math.abs(g / n - 60) < 12, `green ${g / n}`);
58 assert.ok(Math.abs(b / n - 40) < 12, `blue ${b / n}`);
59});
60
61await test("gradient keeps its direction", () => {
62 const w = 16, h = 12;
63 const px = new Uint8ClampedArray(w * h * 4);
64 for (let y = 0; y < h; y++) {
65 for (let x = 0; x < w; x++) {
66 const o = (y * w + x) * 4;
67 px[o] = x < w / 2 ? 255 : 0;
68 px[o + 2] = x < w / 2 ? 0 : 255;
69 px[o + 3] = 255;
70 }
71 }
72 const out = decodeBlurhash(encodeBlurhash(px, w, h), 16, 12);
73 const at = (x, y) => out.slice((y * 16 + x) * 4, (y * 16 + x) * 4 + 4);
74 const left = at(1, 6), right = at(14, 6);
75 assert.ok(left[0] > left[2], "left reads red");
76 assert.ok(right[2] > right[0], "right reads blue");
77});
78
79await test("malformed hashes are rejected", () => {
80 assert.equal(decodeBlurhash(""), null);
81 assert.equal(decodeBlurhash("LEHV6"), null);
82 assert.equal(decodeBlurhash("LEHV6nWB2yk8pyo0adR*.7kCMdnj!!"), null);
83});
84
85console.log(failures ? `\n${failures} FAILED` : "\nALL PASSED");
86process.exit(failures ? 1 : 0);
87