// Generated, do not edit. sha256:05fac95c720d81fea977fcda4544cbb3cf0d29e4d449a955de2c4a1154d3e9ab import { describe, it, mock } from 'node:test'; import assert from 'node:assert/strict'; mock.module('../../client/app-settings.js', { namedExports: { appSetting: () => 1, baseCurrency: () => 'EUR' }, }); let ASSETS = []; let BEREIK = null; mock.module('../../client/data/assets.js', { namedExports: { listAssets: async () => ASSETS } }); mock.module('../../client/data/preferences.js', { namedExports: { getPref: async (_s, _k, d) => BEREIK ?? d, setPref: async () => {} }, }); let RIJEN = []; let SPOT = null; mock.module('../../client/data/price-history.js', { namedExports: { listPriceHistory: async () => RIJEN } }); const KOERS = { USD: 0.8 }; mock.module('../../client/price/providers.js', { namedExports: { liveSpotFor: async () => (SPOT == null ? null : { price: SPOT }), basePriceOf: s => (s && Number.isFinite(s.price) ? s.price : null), currentRate: async code => KOERS[code] ?? null, lastSpotSource: () => null, }, }); mock.module('../../client/data/providers.js', { namedExports: { listProviders: async () => [{ id: 7, name: 'CoinGecko' }, { id: 9, name: 'Atreides Services' }] }, }); let GETEKEND = null; let HINT = null; let STAART = null; let FIJN = null; mock.module('../../client/ui/sparkline.js', { namedExports: { mountSparkline: (_host, punten, opts = {}) => { GETEKEND = punten; HINT = opts.formatValue || null; }, withSpot: (punten, prijs) => (prijs == null ? punten : [...punten, [Date.now(), prijs]]), sliceTone: () => 'up', changePctHtml: (_punten, staart = '', opts = {}) => { STAART = staart; FIJN = !!opts.fine; return ''; }, }, }); globalThis.document = { addEventListener() {} }; const { renderSpotWidget } = await import('../../client/widgets/spot.js'); const { fmtMoney } = await import('../../client/ui/format.js'); const { CONFIG } = await import('../../shared/config.js'); const { recordSpot, clearMomentum } = await import('../../client/price/momentum.js'); const OZT = CONFIG.METALS.UNITS.ozt; function nepHost() { const perSelector = new Map(); return { innerHTML: '', isConnected: true, querySelector(sel) { if (!perSelector.has(sel)) perSelector.set(sel, { classList: { remove() {}, toggle() {} } }); return perSelector.get(sel); }, el(sel) { return perSelector.get(sel); }, }; } async function teken(asset, { spot = null, rijen = [], params = {} } = {}) { ASSETS = [asset]; SPOT = spot; RIJEN = rijen; GETEKEND = null; const host = nepHost(); await renderSpotWidget(host, { params: { assetId: asset.id, ...params } }); await new Promise(r => setImmediate(r)); return host; } const vandaag = new Date().toISOString().slice(0, 10); const gisteren = new Date(Date.now() - 86400000).toISOString().slice(0, 10); const geschiedenis = (a, b) => [ { observedAt: `${gisteren}T00:00:00Z`, fiatPrice: a }, { observedAt: `${vandaag}T00:00:00Z`, fiatPrice: b }, ]; const GOUD = { id: 7, symbol: 'XAU', name: 'Goud', kind: 'metal', unit: 'ozt', subunit: 'g', decimals: 7, displayUnit: 'unit' }; const BTC = { id: 1, symbol: 'BTC', name: 'Bitcoin', kind: 'crypto', unit: 'BTC', subunit: 'sat', decimals: 8, displayUnit: 'unit' }; describe('spotwidget: de koers in de weergave-eenheid van het asset', () => { it('goud in ozt: de spot per troy ounce, zonder eenheid erachter', async () => { const host = await teken(GOUD, { spot: 100, rijen: geschiedenis(90, 95) }); assert.equal(host.el('#spot-price').textContent, fmtMoney(100 * OZT, 'EUR', { decimals: 0 })); assert.doesNotMatch(host.innerHTML, /\/ozt/); }); it('goud in ozt: de sparklijn in dezelfde eenheid als het getal', async () => { await teken(GOUD, { spot: 100, rijen: geschiedenis(90, 95) }); const waarden = GETEKEND.map(p => p[1]); assert.ok(Math.abs(waarden[0] - 90 * OZT) < 1e-6, `eerste punt ${waarden[0]}`); assert.ok(Math.abs(waarden[waarden.length - 1] - 100 * OZT) < 1e-6, 'het laatste punt is de spot'); }); it('zonder spot: de laatste slotkoers, ook in ozt', async () => { const host = await teken(GOUD, { spot: null, rijen: geschiedenis(90, 95) }); assert.equal(host.el('#spot-price').textContent, fmtMoney(95 * OZT, 'EUR', { decimals: 0 })); }); it('goud in gram als weergave-eenheid: geen omrekening', async () => { const host = await teken({ ...GOUD, displayUnit: 'subunit' }, { spot: 100 }); assert.equal(host.el('#spot-price').textContent, fmtMoney(100, 'EUR', { decimals: 2 })); }); it('tweede valuta: de getoonde koers (per ozt) gedeeld door de wisselkoers', async () => { const host = await teken(GOUD, { spot: 100, params: { second: 'USD' } }); assert.equal(host.el('#spot-second').textContent, fmtMoney(100 * OZT / 0.8, 'USD', { decimals: 0 })); }); it('tweede valuta: zonder keuze blijft hij leeg', async () => { const host = await teken(GOUD, { spot: 100 }); assert.equal(host.el('#spot-second')?.textContent, undefined); }); it('crypto: per munt, zonder omrekening', async () => { const host = await teken(BTC, { spot: 60000 }); assert.equal(host.el('#spot-price').textContent, fmtMoney(60000, 'EUR', { decimals: 0 })); }); }); describe('spotwidget: het tijdvak dag', () => { const dagen = n => new Date(Date.now() - n * 86400000).toISOString().slice(0, 10); const maandTotGisteren = () => Array.from({ length: 30 }, (_, i) => ({ observedAt: `${dagen(30 - i)}T00:00:00Z`, fiatPrice: 100 + i, })); it('tekent precies twee punten: de slotkoers van gisteren en de spot', async () => { BEREIK = 'day'; try { await teken(BTC, { spot: 200, rijen: maandTotGisteren() }); assert.equal(GETEKEND.length, 2); assert.deepEqual(GETEKEND.map(p => p[1]), [129, 200]); assert.equal(FIJN, true); } finally { BEREIK = null; } }); it('een week blijft een week, zonder fijne decimaal', async () => { BEREIK = 'week'; try { await teken(BTC, { spot: 200, rijen: maandTotGisteren() }); assert.ok(GETEKEND.length >= 7, `${GETEKEND.length} punten`); assert.equal(FIJN, false); } finally { BEREIK = null; } }); }); describe('spotwidget: de bron in de hint van een punt', () => { const dag = n => new Date(Date.now() - n * 86400000).toISOString().slice(0, 10); const ms = n => Date.parse(`${dag(n)}T00:00:00Z`); it('noemt de provider van een eigen dag, en bij een doorgetrokken dag de dag van de koers', async () => { BEREIK = 'week'; try { await teken(BTC, { spot: null, rijen: [ { observedAt: `${dag(5)}T00:00:00Z`, fiatPrice: 100, providerId: 7 }, { observedAt: `${dag(2)}T00:00:00Z`, fiatPrice: 110, providerId: 9 }, ] }); assert.match(HINT(100, ms(5)), /\nCoinGecko$/); assert.match(HINT(110, ms(2)), /\nAtreides Services$/); assert.match(HINT(100, ms(4)), /\nCoinGecko, koers van /); } finally { BEREIK = null; } }); it('een rij van een onbekende provider: geen bron in plaats van een verkeerde', async () => { BEREIK = 'week'; try { await teken(BTC, { spot: null, rijen: [ { observedAt: `${dag(3)}T00:00:00Z`, fiatPrice: 100, providerId: 42 }, { observedAt: `${dag(2)}T00:00:00Z`, fiatPrice: 110, providerId: 7 }, ] }); assert.doesNotMatch(HINT(100, ms(3)), /CoinGecko|Atreides/); } finally { BEREIK = null; } }); }); describe('spotwidget: de stip van de korte trend', () => { async function dalend(assetId) { clearMomentum(); const nu = Date.now(); for (let i = 0; i <= 3; i++) await recordSpot(assetId, 100 - i, nu - (3 - i) * 60000); } it('rood in de pil na dalingen', async () => { await dalend(BTC.id); await teken(BTC, { spot: 60000, rijen: geschiedenis(59000, 59500) }); assert.match(STAART, /class="momentum-dot is-down"/); }); it('de hint noemt het aandeel van de kant van de kleur', async () => { await dalend(BTC.id); await teken(BTC, { spot: 60000, rijen: geschiedenis(59000, 59500) }); assert.match(STAART, /title="100% van de laatste 3 koersbewegingen omlaag"/); }); it('twee op en één neer: 67% omhoog', async () => { clearMomentum(); const nu = Date.now(); for (const [i, p] of [100, 101, 102, 101].entries()) await recordSpot(BTC.id, p, nu - (3 - i) * 60000); await teken(BTC, { spot: 60000, rijen: geschiedenis(59000, 59500) }); assert.match(STAART, /class="momentum-dot is-up" title="67% van de laatste 3 koersbewegingen omhoog"/); }); it('niet als de schakelaar uit staat', async () => { await dalend(BTC.id); await teken(BTC, { spot: 60000, rijen: geschiedenis(59000, 59500), params: { momentum: false } }); assert.equal(STAART, ''); }); it('niet zonder live spot: de lijn eindigt dan op een slotkoers', async () => { await dalend(BTC.id); await teken(BTC, { spot: null, rijen: geschiedenis(59000, 59500) }); assert.equal(STAART, ''); }); it('niet bij een ander asset', async () => { await dalend(GOUD.id); await teken(BTC, { spot: 60000, rijen: geschiedenis(59000, 59500) }); assert.equal(STAART, ''); }); });