Files
2026-10-29 11:57:29 +01:00

260 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Generated, do not edit. sha256:81c29b0b17dff467d076992c914cba7f0090a65799888be3dc9347a850d7de68
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { trapPunten, magnetischeIndex, withSpot, mountSparkline, changePctHtml, smoothPath, labelLeft } from '../../client/ui/sparkline.js';
const p = (px, py) => ({ px, py });
describe('trapPunten', () => {
it('laat een vlakke reeks ongemoeid', () => {
const in_ = [p(0, 50), p(10, 50), p(20, 50)];
assert.deepEqual(trapPunten(in_), in_);
});
it('zet bij een stap een hoekpunt op de x van de NIEUWE waarde', () => {
const uit = trapPunten([p(0, 80), p(10, 40)]);
assert.deepEqual(uit, [p(0, 80), p(10, 80), p(10, 40)]);
});
it('werkt net zo omlaag, want een verkoop verlaagt de inleg', () => {
const uit = trapPunten([p(0, 40), p(10, 80)]);
assert.deepEqual(uit, [p(0, 40), p(10, 40), p(10, 80)]);
});
it('doet niets bij een verschil onder de drempel', () => {
const in_ = [p(0, 50), p(10, 50.005)];
assert.deepEqual(trapPunten(in_), in_);
});
it('houdt de eerste en de laatste waarde op hun plek', () => {
const uit = trapPunten([p(0, 90), p(5, 60), p(10, 20)]);
assert.deepEqual(uit[0], p(0, 90));
assert.deepEqual(uit[uit.length - 1], p(10, 20));
assert.equal(uit.length, 5, 'drie punten met twee stappen worden vijf hoekpunten');
});
it('valt niet om op een lege of eenpuntige reeks', () => {
assert.deepEqual(trapPunten([]), []);
assert.deepEqual(trapPunten([p(0, 1)]), [p(0, 1)]);
});
});
describe('labelLeft: het label blijft binnen de grafiek', () => {
it('staat op het punt als er ruimte is', () => {
assert.equal(labelLeft(115, 100, 230), 115);
});
it('schuift naar binnen aan beide randen, met 4 px afstand', () => {
assert.equal(labelLeft(5, 200, 230), 104);
assert.equal(labelLeft(225, 200, 230), 126);
});
it('staat in het midden als het label breder is dan de grafiek', () => {
assert.equal(labelLeft(10, 260, 230), 115);
});
});
describe('magnetischeIndex', () => {
const reeks = Array.from({ length: 11 }, (_, i) => ({ fx: i / 10 }));
it('geeft het dichtstbijzijnde punt als er geen markering in de buurt is', () => {
assert.equal(magnetischeIndex(reeks, [8], 0.22, 0.03), 2);
});
it('klikt op de markering zodra die binnen de tolerantie ligt', () => {
assert.equal(magnetischeIndex(reeks, [5], 0.52, 0.03), 5);
assert.equal(magnetischeIndex(reeks, [5], 0.48, 0.03), 5);
});
it('laat de markering NIET winnen buiten de tolerantie', () => {
assert.equal(magnetischeIndex(reeks, [5], 0.30, 0.03), 3);
});
it('kiest de dichtstbijzijnde markering als er twee in de buurt liggen', () => {
assert.equal(magnetischeIndex(reeks, [4, 5], 0.49, 0.05), 5);
});
it('wint binnen de tolerantie ook van een punt dat NÓG dichterbij ligt', () => {
assert.equal(magnetischeIndex(reeks, [5], 0.401, 0.11), 5);
});
it('valt terug op het dichtstbijzijnde punt zonder markeringen', () => {
assert.equal(magnetischeIndex(reeks, [], 0.71, 0.03), 7);
});
it('negeert een markering die buiten de reeks wijst', () => {
assert.equal(magnetischeIndex(reeks, [99], 0.31, 0.5), 3);
});
});
describe('withSpot', () => {
const DAG = 86400000;
const dag = iso => Date.parse(`${iso}T00:00:00Z`);
const nu = dag('2026-09-04') + 13 * 3600000;
const week = [
[dag('2026-09-01'), 100],
[dag('2026-09-02'), 110],
[dag('2026-09-03'), 120],
[dag('2026-09-04'), 130],
];
it('vervangt de waarde van vandaag en maakt de reeks niet langer', () => {
const uit = withSpot(week, 140, nu);
assert.equal(uit.length, week.length);
assert.deepEqual(uit[uit.length - 1], [dag('2026-09-04'), 140]);
assert.deepEqual(uit.slice(0, -1), week.slice(0, -1));
});
it('houdt het punt op middernacht van die dag en zet er geen tijdstip in', () => {
assert.equal(withSpot(week, 140, nu).at(-1)[0] % DAG, 0);
});
it('voegt vandaag toe als de reeks bij gisteren ophoudt', () => {
const totGisteren = week.slice(0, -1);
const uit = withSpot(totGisteren, 140, nu);
assert.equal(uit.length, totGisteren.length + 1);
assert.deepEqual(uit.at(-1), [dag('2026-09-04'), 140]);
});
it('laat een reeks die voorbij vandaag loopt ongemoeid', () => {
const toekomst = [...week, [dag('2026-09-05'), 150]];
assert.equal(withSpot(toekomst, 140, nu), toekomst);
});
it('geeft de reeks onveranderd terug zonder bruikbare prijs', () => {
for (const prijs of [null, undefined, NaN, 0, -1, '140']) {
assert.equal(withSpot(week, prijs, nu), week);
}
});
it('geeft een lege of ontbrekende reeks onveranderd terug', () => {
assert.deepEqual(withSpot([], 140, nu), []);
assert.equal(withSpot(null, 140, nu), null);
});
});
describe('mountSparkline', () => {
const DAG = 86400000;
const punten = [[0, 100], [DAG, 110], [2 * DAG, 105]];
function bouw(opties = {}) {
const leeg = () => ({ className: '', innerHTML: '', appendChild() {}, querySelector: () => ({}), addEventListener() {} });
globalThis.document = { createElement: leeg };
const host = { innerHTML: '', appendChild(kind) { this.kind = kind; } };
mountSparkline(host, punten, opties);
return host.kind.innerHTML;
}
it('zet de markeringen, de hover-lijn en het puntje ín de kliplaag', () => {
const html = bouw({ markers: [{ x: 0, variant: 'buy' }] });
const clip = html.indexOf('<div class="spark-mini-clip">');
const label = html.indexOf('<div class="spark-mini-label"');
assert.ok(clip >= 0 && label > clip, 'de kliplaag hoort te bestaan en vóór het label te staan');
const binnen = html.slice(clip, label);
for (const deel of ['spark-mini-mark', 'spark-mini-vline', 'spark-mini-dot']) {
assert.ok(binnen.includes(deel), `${deel} hoort in de kliplaag te zitten`);
}
assert.equal((binnen.match(/<div/g) || []).length, (binnen.match(/<\/div>/g) || []).length,
'de kliplaag hoort gesloten te zijn vóór het prijslabel');
});
it('houdt het prijslabel BUITEN de kliplaag', () => {
const html = bouw();
const label = html.indexOf('<div class="spark-mini-label"');
assert.ok(label > 0);
assert.equal(html.slice(label).includes('spark-mini-dot'), false);
assert.equal(html.slice(label).includes('spark-mini-clip'), false);
});
it('geeft een markering op het eerste punt zijn echte x en schuift hem niet naar binnen', () => {
const html = bouw({ markers: [{ x: 0, variant: 'buy' }] });
assert.match(html, /spark-mini-mark is-buy[^>]*--spark-x:0\.00%/);
});
it('draagt de sleutel van een markering mee als `data-mark`', () => {
const html = bouw({ markers: [{ x: DAG, variant: 'sell', key: 't42' }] });
assert.match(html, /data-mark="t42"/);
});
it('maakt het vak leeg bij te weinig punten', () => {
const leeg = () => ({ className: '', innerHTML: '', appendChild() {}, querySelector: () => ({}), addEventListener() {} });
globalThis.document = { createElement: leeg };
const host = { innerHTML: 'oud', appendChild() { throw new Error('er hoort niets gemount te worden'); } };
mountSparkline(host, [[0, 100]]);
assert.equal(host.innerHTML, '');
});
});
describe('changePctHtml: de verandering als pil', () => {
it('geeft een stijging een groene pil, met het teken uit de instelling', () => {
assert.equal(changePctHtml([[0, 100], [1, 110]]), '<span class="spark-change delta t-pos">10,0%</span>');
assert.match(changePctHtml([[0, 100], [1, 110]], '', { signs: 'both' }), />\+10,0%</);
assert.match(changePctHtml([[0, 100], [1, 90]], '', { signs: 'none' }), /class="spark-change delta t-neg">10,0%</);
});
it('geeft een daling een rode pil, met komma en minteken zoals elk ander percentage', () => {
assert.match(changePctHtml([[0, 100], [1, 90]]), /class="spark-change delta t-neg">−10,0%</);
});
it('geeft niets bij te weinig punten of een ongeldig begin', () => {
assert.equal(changePctHtml([[0, 100]]), '');
assert.equal(changePctHtml([[0, 0], [1, 5]]), '');
});
it('zet een staart ín de pil, achter het getal', () => {
assert.equal(changePctHtml([[0, 100], [1, 110]], '<i></i>'), '<span class="spark-change delta t-pos">10,0%<i></i></span>');
});
it('fine: een tweede decimaal als één decimaal de beweging verbergt', () => {
assert.match(changePctHtml([[0, 10000], [1, 10003]], '', { fine: true }), />0,03%</);
assert.match(changePctHtml([[0, 10000], [1, 10003]]), />0,0%</);
});
});
describe('smoothPath', () => {
const p = ys => ys.map((py, i) => ({ px: i * 10, py }));
const segmenten = d => [...d.matchAll(/C[\d.-]+,([\d.-]+) [\d.-]+,([\d.-]+) [\d.-]+,([\d.-]+)/g)]
.map(m => m.slice(1).map(Number));
it('loopt door elk punt', () => {
const ys = [50, 20, 80, 10, 60];
const d = smoothPath(p(ys));
assert.match(d, /^M0\.00,50\.00 /);
assert.deepEqual(segmenten(d).map(s => s[2]), ys.slice(1));
});
it('schiet nergens door: de controlepunten blijven binnen hun segment', () => {
const ys = [50, 20, 80, 10, 60, 61, 95, 94, 3];
segmenten(smoothPath(p(ys))).forEach(([c1, c2], i) => {
const lo = Math.min(ys[i], ys[i + 1]) - 1e-9, hi = Math.max(ys[i], ys[i + 1]) + 1e-9;
assert.ok(c1 >= lo && c1 <= hi && c2 >= lo && c2 <= hi, `segment ${i}: ${c1}, ${c2} buiten [${lo}, ${hi}]`);
});
});
it('een top of dal krijgt een vlakke raaklijn', () => {
const [s0, s1] = segmenten(smoothPath(p([50, 20, 80])));
assert.equal(s0[1], 20);
assert.equal(s1[0], 20);
});
it('twee punten zijn een rechte lijn, en geen punten geen pad', () => {
assert.equal(smoothPath(p([10, 20])), 'M0.00,10.00 L10.00,20.00');
assert.equal(smoothPath([]), '');
});
it('mountSparkline tekent standaard rond (overal, gebruiker 08-10-2026), en recht met `smooth: false`', () => {
const leeg = () => ({ className: '', innerHTML: '', appendChild() {}, querySelector: () => ({}), addEventListener() {} });
globalThis.document = { createElement: leeg };
const teken = opties => {
const host = { innerHTML: '', appendChild(kind) { this.kind = kind; } };
mountSparkline(host, [[0, 100], [1, 110], [2, 105]], opties);
return host.kind.innerHTML;
};
assert.match(teken({}), /<path class="spark-mini-line" d="M[^"]* C/);
assert.match(teken({}), /<path class="spark-mini-area" d="M[^"]* C[^"]*Z"/);
assert.match(teken({ smooth: false }), /<polyline class="spark-mini-line"/);
assert.match(teken({ vergelijk: [[0, 90], [1, 90], [2, 95]] }), /<polyline class="spark-mini-cmp"/);
});
});