// 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()])); }); });