Files
AtreidesAssetsFree/tests/repo/dashboard.test.mjs
T
2026-10-29 11:57:29 +01:00

245 lines
11 KiB
JavaScript

// Generated, do not edit. sha256:b4d746208e0e2104c1a5250636bc0551b849cdc5c3d7fff3b175f2d7cb20fa75
import { describe, it, beforeEach, mock } from 'node:test';
import assert from 'node:assert/strict';
import { createDbHarness } from '../helpers/db-node.mjs';
const harness = createDbHarness();
mock.module('../../server/db.js', { namedExports: harness.exports });
const {
listProfiles, activeProfile, createProfile, renameProfile, activateProfile, deleteProfile,
listWidgets, saveWidgets,
} = await import('../../server/data/dashboard.js');
const { widgetParams } = await import('../../client/data/dashboard.js');
const { DASHBOARD } = await import('../../shared/config.js');
function systeemprofiel() {
harness.exports.run(
`INSERT INTO DashboardProfile (name, searchName, description, sortOrder, isActive, isSystem, createdAt, updatedAt)
VALUES ('Standaard','standaard',NULL,0,1,1,'2026-01-01T00:00:00Z','2026-01-01T00:00:00Z')`);
return harness.exports.lastId();
}
let btc;
beforeEach(async () => {
harness.recreate();
harness.exports.run(
`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
VALUES ('BTC','Bitcoin','bitcoin','crypto','BTC','sat',8,'subunit')`);
btc = harness.exports.lastId();
const id = systeemprofiel();
await saveWidgets(id, null);
});
const namen = () => listProfiles().map(p => p.name);
const actieveNaam = () => activeProfile().name;
const aantalActief = () => harness.exports.one(`SELECT COUNT(*) AS n FROM DashboardProfile WHERE isActive = 1`).n;
describe('exact één actief profiel', () => {
it('houdt het bij één als je wisselt', async () => {
const id = await createProfile({ name: 'Fiscaal' });
assert.equal(aantalActief(), 1, 'een nieuw profiel komt inactief binnen');
await activateProfile(id);
assert.equal(aantalActief(), 1);
assert.equal(actieveNaam(), 'Fiscaal');
});
it('kan heen en terug wisselen', async () => {
const id = await createProfile({ name: 'Fiscaal' });
const systeem = listProfiles().find(p => p.isSystem).id;
await activateProfile(id);
await activateProfile(systeem);
assert.equal(aantalActief(), 1);
assert.equal(actieveNaam(), 'Standaard');
});
it('wijst een opvolger aan als je het actieve profiel verwijdert', async () => {
const id = await createProfile({ name: 'Fiscaal' });
await activateProfile(id);
await deleteProfile(id);
assert.equal(aantalActief(), 1);
assert.equal(actieveNaam(), 'Standaard');
});
it('zet het systeemprofiel terug op de standaardindeling in plaats van het te verwijderen', async () => {
const systeem = listProfiles().find(p => p.isSystem).id;
await saveWidgets(systeem, [{ instanceKey: 'clocks', type: 'clocks', gridX: 3, gridY: 2, spanWidth: 6, spanHeight: 4, params: { cities: ['Tokyo'] } }]);
await deleteProfile(systeem);
assert.deepEqual(namen(), ['Standaard'], 'de rij blijft bestaan');
const widgets = listWidgets(systeem);
assert.deepEqual(widgets.map(w => w.instanceKey).sort(), DASHBOARD.DEFAULT_LAYOUT.map(w => w.instId).sort());
assert.ok(widgets.every(w => w.spanHeight == null && w.settings == null), 'vers: niet gemeten, geen instellingen');
assert.deepEqual(Object.fromEntries(widgets.map(w => [w.instanceKey, w.gridX])),
Object.fromEntries(DASHBOARD.DEFAULT_LAYOUT.map(w => [w.instId, w.x || 0])));
assert.equal(actieveNaam(), 'Standaard', 'het blijft het actieve profiel');
});
it('laat bij het terugzetten van het systeemprofiel een ander actief profiel en zijn indeling staan', async () => {
const id = await createProfile({ name: 'Fiscaal' });
await saveWidgets(id, [{ instanceKey: 'signal', type: 'signal', gridX: 0, gridY: 0, spanWidth: 4, spanHeight: 3, params: null }]);
await activateProfile(id);
await deleteProfile(listProfiles().find(p => p.isSystem).id);
assert.equal(actieveNaam(), 'Fiscaal');
assert.equal(aantalActief(), 1);
assert.deepEqual(listWidgets(id).map(w => w.instanceKey), ['signal'], 'alleen het systeemprofiel begint opnieuw');
});
it('weigert een onbekend profiel in alle richtingen', async () => {
await assert.rejects(() => activateProfile(9999), (e) => e.key === 'dashboard.unknownProfile');
await assert.rejects(() => deleteProfile(9999), (e) => e.key === 'dashboard.unknownProfile');
await assert.rejects(() => renameProfile(9999, { name: 'X' }), (e) => e.key === 'dashboard.unknownProfile');
await assert.rejects(() => saveWidgets(9999, []), (e) => e.key === 'dashboard.unknownProfile');
});
it('klaagt hoorbaar als er tóch geen actief profiel is', () => {
harness.exports.run(`UPDATE DashboardProfile SET isActive = 0`);
assert.throws(() => activeProfile(), /No active dashboard profile/);
});
});
describe('profielen beheren', () => {
it('geeft een nieuw profiel de standaardindeling en een eigen sortOrder', async () => {
const id = await createProfile({ name: 'Fiscaal', description: 'Voor de aangifte' });
const p = listProfiles().find(x => x.id === id);
assert.equal(p.description, 'Voor de aangifte');
assert.equal(p.isSystem, 0);
assert.ok(p.sortOrder > 0, 'komt achter het systeemprofiel');
assert.equal(listWidgets(id).length, DASHBOARD.DEFAULT_LAYOUT.length);
});
it('leidt searchName af en weigert een dubbele naam', async () => {
const id = await createProfile({ name: 'Fiscaal' });
assert.equal(listProfiles().find(x => x.id === id).searchName, 'fiscaal');
await assert.rejects(() => createProfile({ name: 'fiscaal' }));
});
it('weigert een lege naam bij aanmaken en hernoemen', async () => {
await assert.rejects(() => createProfile({ name: ' ' }), (e) => e.key === 'dashboard.nameRequired');
const systeem = listProfiles().find(p => p.isSystem).id;
await assert.rejects(() => renameProfile(systeem, { name: '' }), (e) => e.key === 'dashboard.nameRequired');
});
it('hernoemt met searchName mee', async () => {
const id = await createProfile({ name: 'Fiscaal' });
await renameProfile(id, { name: 'Belasting' });
const p = listProfiles().find(x => x.id === id);
assert.equal(p.name, 'Belasting');
assert.equal(p.searchName, 'belasting');
});
it('neemt de widgets mee als een profiel verdwijnt', async () => {
const id = await createProfile({ name: 'Fiscaal' });
assert.ok(listWidgets(id).length > 0);
await deleteProfile(id);
assert.equal(harness.exports.one(
`SELECT COUNT(*) AS n FROM DashboardWidget WHERE profileId = ?`, [id]).n, 0);
assert.deepEqual(namen(), ['Standaard']);
});
});
describe('widgets van een profiel', () => {
const indeling = (extra = {}) => [{
instanceKey: 'signal', type: 'signal', gridX: 0, gridY: 0, spanWidth: 4, spanHeight: 3, params: null, ...extra,
}];
it('vervangt de hele set', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling());
assert.deepEqual(listWidgets(id).map(w => w.instanceKey), ['signal'],
'de standaardindeling is vervangen, niet aangevuld');
});
it('slaat een leeg raster op als leeg en niet als de standaard', async () => {
const id = activeProfile().id;
await saveWidgets(id, []);
assert.deepEqual(listWidgets(id), []);
});
it('herstelt de standaardindeling met null', async () => {
const id = activeProfile().id;
await saveWidgets(id, []);
await saveWidgets(id, null);
assert.deepEqual(listWidgets(id).map(w => w.type).sort(), DASHBOARD.DEFAULT_LAYOUT.map(w => w.type).sort());
});
it('bewaart een ongemeten hoogte als NULL', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling({ spanHeight: null }));
assert.equal(listWidgets(id)[0].spanHeight, null);
});
it('weigert een onbekend widgettype zónder iets weg te gooien', async () => {
const id = activeProfile().id;
const voor = listWidgets(id).length;
await assert.rejects(() => saveWidgets(id, indeling({ type: 'kwartaalcijfers' })), /Unknown widget type/);
assert.equal(listWidgets(id).length, voor, 'de bestaande indeling staat er nog');
});
it('splitst assetId naar zijn eigen kolom en de rest naar settings', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling({
instanceKey: 'spot-1', type: 'spot', params: { assetId: btc, cities: ['amsterdam'] },
}));
const rij = listWidgets(id)[0];
assert.equal(rij.assetId, btc, 'een verwijzing hoort een kolom te zijn, want die moet bewaakt worden');
assert.equal(rij.settings, JSON.stringify({ cities: ['amsterdam'] }),
'assetId staat NIET ook in settings: twee plekken die hetzelfde beweren gaan afwijken');
});
it('geeft ze als één object terug', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling({
instanceKey: 'clocks', type: 'clocks', params: { cities: ['tokyo'], showExchanges: true },
}));
assert.deepEqual(widgetParams(listWidgets(id)[0]), { cities: ['tokyo'], showExchanges: true });
});
it('geeft null als er niets te melden is', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling());
assert.equal(listWidgets(id)[0].settings, null, 'geen leeg {} in de kolom');
assert.equal(widgetParams(listWidgets(id)[0]), null);
});
it('overleeft onleesbare JSON in settings', async () => {
const id = activeProfile().id;
await saveWidgets(id, indeling({ instanceKey: 'spot-1', type: 'spot', params: { assetId: btc } }));
harness.exports.run(`UPDATE DashboardWidget SET settings = '{kapot' WHERE profileId = ?`, [id]);
assert.deepEqual(widgetParams(listWidgets(id)[0]), { assetId: btc });
});
it('neemt de koers-widget mee als zijn asset verdwijnt', async () => {
const id = activeProfile().id;
await saveWidgets(id, [
...indeling(),
{ instanceKey: 'spot-1', type: 'spot', gridX: 4, gridY: 0, spanWidth: 2, spanHeight: 2, params: { assetId: btc } },
]);
harness.exports.run(`DELETE FROM Asset WHERE id = ?`, [btc]);
assert.deepEqual(listWidgets(id).map(w => w.instanceKey), ['signal']);
});
it('houdt de instanties per profiel gescheiden', async () => {
const eerste = activeProfile().id;
const tweede = await createProfile({ name: 'Fiscaal' });
await saveWidgets(tweede, indeling());
assert.equal(listWidgets(eerste).length, DASHBOARD.DEFAULT_LAYOUT.length);
assert.deepEqual(listWidgets(tweede).map(w => w.instanceKey), ['signal']);
});
it('geeft de instanties in leesvolgorde terug', async () => {
const id = activeProfile().id;
await saveWidgets(id, [
{ instanceKey: 'c', type: 'signal', gridX: 0, gridY: 5, spanWidth: 4, spanHeight: 1, params: null },
{ instanceKey: 'b', type: 'metrics', gridX: 6, gridY: 0, spanWidth: 3, spanHeight: 1, params: null },
{ instanceKey: 'a', type: 'netshare', gridX: 0, gridY: 0, spanWidth: 3, spanHeight: 1, params: null },
]);
assert.deepEqual(listWidgets(id).map(w => w.instanceKey), ['a', 'b', 'c']);
});
it('weigert twee instanties met dezelfde sleutel in één profiel', async () => {
const id = activeProfile().id;
await assert.rejects(() => saveWidgets(id, [...indeling(), ...indeling()]));
});
});