/** * The chart shows the right window, at the right density, without throwing. * * Every assertion here failed at some point on 2026-08-10 while all 96 Python * tests passed, which is the whole argument for this file existing. */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { withChart, chartState, selectTimeframe, assertNoPageErrors } from './helpers.mjs'; const TIMEFRAMES = ['1m', '5m', '15m', '30m', '1h', '1d']; test('the viewport opens on the live edge, not in the past', { timeout: 180000 }, async () => { await withChart(async page => { const state = await chartState(page); assert.ok(state.bars > 0, 'the chart loaded no bars at all'); // The bug: setBars placed the viewport by logical index, and the daily MA // series then prepended hundreds of points, sliding the view ~10 hours back // while the data itself was perfectly current. assert.equal( state.visibleTo, state.lastTime, 'the newest bar is not the right-hand edge of the viewport', ); assertNoPageErrors(page, assert); }); }); test('the running deployment time is visible without making the status bar taller', { timeout: 180000 }, async () => { await withChart(async page => { await page.waitForFunction(() => { const node = document.querySelector('.app-updated b'); return node && node.textContent.trim() !== '—'; }); const state = await page.evaluate(() => { const node = document.querySelector('.app-updated'); return { text: node.textContent.trim(), title: node.title, barHeight: document.querySelector('.statusbar').getBoundingClientRect().height, }; }); assert.match(state.text, /^UPDATED\s+\S/); assert.match(state.title, /^Running since .+ · (dev|[0-9a-f]{7,40})$/); assert.ok(state.barHeight <= 40, `status bar grew to ${state.barHeight}px`); assertNoPageErrors(page, assert); }); }); test('every timeframe gives one slot per candle', { timeout: 300000 }, async () => { await withChart(async page => { for (const tf of TIMEFRAMES) { await selectTimeframe(page, tf); const state = await chartState(page); // The bug: a minute-resolution VWAP put its own points on the shared time // scale, so an hourly chart spread 160 candles over 908 slots and drew // them as slivers. Anything much above 1.0 means the scale is polluted. const ratio = state.slots / Math.max(state.inView, 1); assert.ok( ratio > 0.8 && ratio < 1.3, `${tf}: ${state.inView} candles spread over ${state.slots} slots (ratio ${ratio.toFixed(2)})`, ); } assertNoPageErrors(page, assert); }); }); test('switching timeframes rapidly never throws', { timeout: 300000 }, async () => { await withChart(async page => { // The bug: bar events for the timeframe just left arrived after the new // snapshot, and Lightweight Charts throws "Cannot update oldest data" // rather than ignoring a stale bar, which took the whole app down. for (let round = 0; round < 3; round += 1) { for (const tf of ['1m', '1h', '15m', '1d', '5m']) { await page.click(`.timeframes button:text-is("${tf}")`); await page.waitForTimeout(700); } } await page.waitForTimeout(2000); const state = await chartState(page); assert.ok(state.bars > 0, 'the chart lost its data while switching'); assertNoPageErrors(page, assert); }); });