Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,244 @@
|
||||
// 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()]));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user