The rain read only the OS reduced-motion query, once, at mount. The settings menu's motion override (data-motion on the document) wins over the OS in either direction everywhere else, and the shell applies it from an effect that runs after this one. Whether the rain may move is now rainAllowed(), and the component re-evaluates it on mount, on every document attribute change and on the media query's change, starting or stopping the interval and clearing the canvas.
86 lines
2.9 KiB
JavaScript
86 lines
2.9 KiB
JavaScript
// The digital rain's arithmetic: columns, how they start and step, the glyph
|
|
// drawn, and the fade colour. The random source is passed in, so each case is
|
|
// exact. Run by `npm test`.
|
|
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import {
|
|
CELL,
|
|
GLYPHS,
|
|
columnsFor,
|
|
fadeColorOf,
|
|
glyphFor,
|
|
rainAllowed,
|
|
startDrops,
|
|
stepDrops,
|
|
} from "../src/shell/rain.ts";
|
|
|
|
const always = (n) => () => n;
|
|
|
|
test("the app's reduce setting stops the rain whatever the OS says", () => {
|
|
assert.equal(rainAllowed("reduce", false), false);
|
|
assert.equal(rainAllowed("reduce", true), false);
|
|
});
|
|
|
|
test("the app's allow setting runs the rain even on a reduce-motion OS", () => {
|
|
assert.equal(rainAllowed("allow", true), true);
|
|
assert.equal(rainAllowed("allow", false), true);
|
|
});
|
|
|
|
test("with no app setting the rain follows the OS", () => {
|
|
assert.equal(rainAllowed(undefined, false), true);
|
|
assert.equal(rainAllowed(undefined, true), false);
|
|
assert.equal(rainAllowed("", true), false);
|
|
});
|
|
|
|
test("a canvas gets one column per cell, rounded up, and at least one", () => {
|
|
assert.equal(columnsFor(CELL * 10), 10);
|
|
assert.equal(columnsFor(CELL * 10 + 1), 11);
|
|
assert.equal(columnsFor(0), 1);
|
|
});
|
|
|
|
test("columns start above the top, spread over 40 cells", () => {
|
|
assert.deepEqual(
|
|
startDrops(3, always(0)).map((row) => row === 0),
|
|
[true, true, true],
|
|
);
|
|
assert.deepEqual(startDrops(2, always(0.5)), [-20, -20]);
|
|
for (const row of startDrops(200, Math.random)) {
|
|
assert.ok(row <= 0 && row >= -40, String(row));
|
|
}
|
|
});
|
|
|
|
test("a step moves every column down one row", () => {
|
|
assert.deepEqual(stepDrops([-3, 0, 5], 10_000, always(0)), [-2, 1, 6]);
|
|
});
|
|
|
|
test("a column past the bottom restarts only on a high enough roll", () => {
|
|
const past = [Math.ceil(100 / CELL) + 1];
|
|
assert.deepEqual(stepDrops(past, 100, always(0.99)), [1]);
|
|
assert.deepEqual(stepDrops(past, 100, always(0.5)), [past[0] + 1]);
|
|
});
|
|
|
|
test("a column still on the canvas never restarts", () => {
|
|
assert.deepEqual(stepDrops([2], 100, always(0.99)), [3]);
|
|
});
|
|
|
|
test("a step does not change the array it was given", () => {
|
|
const drops = [1, 2];
|
|
stepDrops(drops, 100, always(0.99));
|
|
assert.deepEqual(drops, [1, 2]);
|
|
});
|
|
|
|
test("the glyph comes from the set, first and last at the ends of the roll", () => {
|
|
assert.equal(glyphFor(always(0)), GLYPHS[0]);
|
|
assert.equal(glyphFor(always(0.999999)), GLYPHS[GLYPHS.length - 1]);
|
|
});
|
|
|
|
test("the fade colour keeps the background's channels at a low alpha", () => {
|
|
assert.equal(fadeColorOf("rgb(30, 30, 46)"), "rgba(30, 30, 46, 0.09)");
|
|
assert.equal(fadeColorOf("rgb(239,241,245)"), "rgba(239, 241, 245, 0.09)");
|
|
assert.equal(fadeColorOf("rgba(1, 2, 3, 0.5)"), "rgba(1, 2, 3, 0.09)");
|
|
});
|
|
|
|
test("a colour with no channels fades from the default background", () => {
|
|
assert.equal(fadeColorOf("transparent"), "rgba(30, 30, 46, 0.09)");
|
|
assert.equal(fadeColorOf(""), "rgba(30, 30, 46, 0.09)");
|
|
});
|