// Generated, do not edit. sha256:9ad90176a0537a5dae796d00139a843d9f56aa6cd2de67fc75215c16300b5802 import { describe, it, mock } from 'node:test'; import assert from 'node:assert/strict'; mock.module('../../client/app-settings.js', { namedExports: { appSetting: () => 1, baseCurrency: () => 'EUR', timerSetting: () => 60, loadAppSettings: async () => ({}), }, }); let VOORSTELLEN = []; let ACTIEF = true; const GEDAAN = []; let UITKOMST = []; mock.module('../../client/data/ai-proposals.js', { namedExports: { aiProposalsStatus: async () => ({ enabled: ACTIEF, pending: VOORSTELLEN }), listPendingAiProposals: async () => VOORSTELLEN, acceptAiProposal: async (id) => { GEDAAN.push(['accept', id]); }, acceptAiProposals: async (ids) => { GEDAAN.push(['acceptAll', ids]); return UITKOMST; }, rejectAiProposal: async (id) => { GEDAAN.push(['reject', id]); }, rejectAiProposals: async (ids) => { GEDAAN.push(['rejectAll', ids]); return UITKOMST; }, }, }); let ANTWOORD = true; const GEVRAAGD = []; mock.module('../../client/ui/dialog.js', { namedExports: { confirm: async (o) => { GEVRAAGD.push(o); return ANTWOORD; }, alert: async (o) => { GEVRAAGD.push(o); }, form: async () => null, }, }); const LUISTERAARS = []; globalThis.document = { addEventListener(ev, fn) { LUISTERAARS.push([ev, fn]); }, }; const luisteraar = ev => (LUISTERAARS.find(([naam]) => naam === ev) || [])[1]; const { renderAiProposals } = await import('../../client/actions/ai-proposals-widget.js'); const { CONFIG } = await import('../../shared/config.js'); function nepHost() { const perSelector = new Map(); return { innerHTML: '', isConnected: true, querySelector(sel) { if (!perSelector.has(sel)) perSelector.set(sel, {}); return perSelector.get(sel); }, querySelectorAll() { return []; }, knop(sel) { return perSelector.get(sel); }, }; } async function teken(rijen, actief = true) { return (await tekenOp(rijen, actief)).innerHTML; } async function tekenOp(rijen, actief = true) { VOORSTELLEN = rijen; ACTIEF = actief; GEDAAN.length = 0; GEVRAAGD.length = 0; ANTWOORD = true; UITKOMST = []; const host = nepHost(); await renderAiProposals(host, () => {}); return host; } const BTC = { asset_name: 'Bitcoin', asset_symbol: 'BTC', asset_kind: 'crypto', asset_decimals: 8, asset_unit: 'BTC', asset_subunit: 'sat', asset_display_unit: 'unit', }; const LEEG_ASSET = { asset_name: null, asset_symbol: null, asset_kind: null, asset_decimals: null, asset_unit: null, asset_subunit: null, asset_display_unit: null, }; const BASIS = { id: 1, note: null, detail: null, from_name: null, to_name: null, accountId: null, account_name: null, account_kind: null, name: null, accountKind: null, isOwnCustody: null, eventType: null, amount: null, fiatPrice: null, observedAt: null, fromAccountId: null, toAccountId: null, assetId: null, }; const koerspunt = (extra = {}) => ({ ...BASIS, ...BTC, kind: 'price_point', fiatPrice: 50000, observedAt: '2026-03-01T00:00:00Z', ...extra }); const rekening = (extra = {}) => ({ ...BASIS, ...LEEG_ASSET, kind: 'account', name: 'Kraken', accountKind: 'exchange', isOwnCustody: 0, ...extra }); const boeking = (extra = {}) => ({ ...BASIS, ...BTC, kind: 'event', eventType: 'buy', amount: 25_000_000, fiatPrice: 50000, observedAt: '2026-03-01T00:00:00Z', to_name: 'Kraken', ...extra }); const verwijdering = (extra = {}) => ({ ...BASIS, ...LEEG_ASSET, kind: 'account_delete', account_name: 'Oude beurs', account_kind: 'exchange', ...extra }); const balans = (extra = {}) => ({ ...BASIS, ...BTC, kind: 'valuation', balance: 50_000_000, observedAt: '2026-03-01T00:00:00Z', account_name: 'Kraken', ledger_balance: 40_000_000, events_before: 1, ...extra, }); const portefeuille = (extra = {}) => ({ ...BASIS, ...LEEG_ASSET, kind: 'portfolio', name: 'AI-demonstratie', currency: 'EUR', ...extra }); describe('de lege stand, en dat zijn er TWEE', () => { it('tekent een kader met een vinkje als de koppeling aan staat en er niets openstaat', async () => { const html = await teken([], true); assert.match(html, /task-card-empty/, 'hetzelfde lege kader als de drie widgets ernaast'); assert.match(html, /Voorstellen van de AI/, 'met de kop erboven, anders is het een leeg vlak'); }); it('tekent helemaal niets als de koppeling uit staat', async () => { assert.equal(await teken([], false), ''); }); it('tekent ook niets als de koppeling uit staat terwijl er iets openstaat', async () => { assert.equal(await teken([koerspunt()], false), ''); }); }); describe('de drie soorten in mensentaal', () => { it('toont een koerspunt met de asset-naam en het bedrag in euro', async () => { const html = await teken([koerspunt()]); assert.match(html, /Bitcoin/, 'de asset-naam, niet alleen het symbool'); assert.match(html, /BTC/); assert.match(html, /50\.000,00/, 'het bedrag hoort in euro-opmaak te staan'); }); it('houdt de asset-naam ook heel bij een boeking', async () => { const html = await teken([boeking()]); assert.match(html, /Bitcoin/, 'de asset-naam, niet alleen het symbool'); }); it('toont een rekening-voorstel met de naam, de soort en de custody-stand', async () => { const html = await teken([rekening()]); assert.match(html, /Kraken/); assert.match(html, /Exchange/, 'de soort komt uit de enum-vertaling'); assert.match(html, /derde partij/); }); it('toont bij een boeking de hoeveelheid, de asset en de rekening', async () => { const html = await teken([boeking()]); assert.match(html, /Aankoop/, 'de soort komt uit de enum-vertaling'); assert.match(html, /0,25 BTC/, 'de hoeveelheid hoort in de hoofdeenheid te staan, niet in sats'); assert.match(html, /Kraken/); }); it('zet de eenheid ÉÉN keer neer en niet twee', async () => { const labelSymbool = /class="t-muted-cell">BTCBitcoinBitcoin { const satBtc = { ...BTC, asset_display_unit: 'subunit' }; const html = await teken([balans({ ...satBtc, balance: 25_000_000, ledger_balance: 25_000_000 })]); assert.match(html, /25\.000\.000 sat/, html); assert.match(html, /class="t-muted-cell">BTC { const goud = { asset_name: 'Proefgoud', asset_symbol: 'XAU', asset_kind: 'metal', asset_decimals: 4, asset_unit: 'g', asset_subunit: null, asset_display_unit: 'unit', }; const html = await teken([balans({ ...goud, balance: 3_000_000, ledger_balance: 3_000_000 })]); assert.match(html, /XAU/, html); assert.match(html, /Proefgoud/); }); it('rekent een metaalkoers terug naar de eenheid waarin hij is voorgesteld', async () => { const GOUD = { asset_name: 'Goud', asset_symbol: 'XAU', asset_kind: 'metal', asset_decimals: 7, asset_unit: 'ozt', asset_subunit: 'g', asset_display_unit: 'unit', }; const perGram = 2150 / CONFIG.METALS.UNITS.ozt; const html = await teken([koerspunt({ ...GOUD, fiatPrice: perGram })]); assert.match(html, /2\.150,00/, 'de koers hoort per ozt te staan, niet per gram'); assert.match(html, /per ozt/, 'zonder eenheid is een metaalkoers niet te beoordelen'); assert.ok(!/69,12/.test(html), 'de opslagwaarde per gram hoort hier niet te staan'); }); it('laat een crypto-koers ongemoeid en noemt de eenheid erbij', async () => { const html = await teken([koerspunt()]); assert.match(html, /50\.000,00/); assert.match(html, /per BTC/, 'ook hier hoort de eenheid erbij, en dat is de HOOFDeenheid'); }); it('noemt bij een asset dat in sats getoond wordt nog steeds de koers per BTC', async () => { const html = await teken([koerspunt({ asset_display_unit: 'subunit' })]); assert.match(html, /per BTC/); }); it('zet bij een verplaatsing allebei de rekeningen met een pijl ertussen', async () => { const html = await teken([boeking({ eventType: 'transfer', fiatPrice: null, from_name: 'Kraken', to_name: 'Ledger' })]); assert.match(html, /Kraken → Ledger/); assert.match(html, /Verplaatsing/); }); it('noemt een onbekende soort in plaats van een lege regel te tonen', async () => { const html = await teken([{ ...BASIS, ...LEEG_ASSET, kind: 'iets_nieuws' }]); assert.match(html, /Onbekend soort voorstel/); assert.match(html, /iets_nieuws/); }); }); describe('een balans en een portefeuille op de kaart', () => { it('zet het grootboek en het verschil naast de voorgestelde stand', async () => { const html = await teken([balans()]); assert.match(html, /Balans/); assert.match(html, /0,5 BTC/, 'de voorgestelde stand'); assert.match(html, /grootboek.*0,4 BTC/s, 'en wat het grootboek op dat moment zegt'); assert.match(html, /verschil \+]*>0,1 BTC/); assert.match(html, /Kraken/); }); it('laat de verschil-regel WEG als er geen verschil is', async () => { const html = await teken([balans({ ledger_balance: 50_000_000 })]); assert.match(html, /grootboek/); assert.ok(!/verschil/.test(html), html); }); it('WAARSCHUWT als er geen boeking vóór de peildatum staat', async () => { const html = await teken([balans({ events_before: 0, ledger_balance: 0 })]); assert.match(html, /OPENINGSBALANS/i); assert.match(html, /t-neg/, 'en die regel hoort op te vallen, net als een mislukte poging'); }); it('waarschuwt NIET als er wel een boeking vóór de peildatum staat', async () => { const html = await teken([balans({ events_before: 2 })]); assert.ok(!/OPENINGSBALANS/i.test(html), html); }); it('toont een portefeuille-voorstel met de naam en wat het wél en niet doet', async () => { const html = await teken([portefeuille()]); assert.match(html, /Nieuw portfolio/); assert.match(html, /AI-demonstratie/); assert.match(html, /Leeg/); assert.match(html, /wisselen|niet geopend/i, 'dat hij niet geopend wordt'); }); it('noemt de rekenvaluta, want die ligt na goedkeuring vast', async () => { const html = await teken([portefeuille({ currency: 'USD' })]); assert.match(html, /Amerikaanse dollar \(USD\)/); }); it('escapet de naam van een portefeuille', async () => { const html = await teken([portefeuille({ name: '' })]); assert.ok(!html.includes(' { const voorkeur = (extra = {}) => ({ ...BASIS, ...LEEG_ASSET, kind: 'preference', scope: 'app', key: 'currency', value: '"usd"', ...extra, }); it('toont de sleutel, de nieuwe waarde EN de huidige', async () => { const html = await teken([voorkeur()]); assert.match(html, /Instelling wijzigen/); assert.match(html, /app\.currency/); assert.match(html, /usd/); assert.match(html, /\(nu /, 'de huidige waarde hoort ernaast te staan'); }); it('leest een schakelaar als aan of uit en niet als 1 of 0', async () => { const html = await teken([voorkeur({ key: 'blur', value: '1' })]); assert.match(html, /aan/); assert.ok(!/→ 1/.test(html), html); }); it('valt terug op de kale tekst als de waarde geen JSON is', async () => { const html = await teken([voorkeur({ value: 'kapot' })]); assert.match(html, /kapot/); }); it('escapet de sleutel en de waarde', async () => { const html = await teken([voorkeur({ key: '', value: '"vet"' })]); assert.ok(!html.includes('vet'), html); }); }); describe('de toelichting en een mislukte poging', () => { it('toont de toelichting van de AI onder de regel', async () => { const html = await teken([koerspunt({ note: 'koers volgens de muntwinkel' })]); assert.match(html, /koers volgens de muntwinkel/); }); it('toont de reden waarom een eerdere poging afketste', async () => { const html = await teken([koerspunt({ detail: 'Onbekende koersbron: manual.' })]); assert.match(html, /Vorige poging mislukt/); assert.match(html, /Onbekende koersbron/); }); }); describe('escapen', () => { it('escapet de voorgestelde rekeningnaam', async () => { const html = await teken([rekening({ name: '' })]); assert.ok(!html.includes(' { const html = await teken([koerspunt({ note: '', detail: 'stuk' })]); assert.ok(!html.includes('