109 lines
4.5 KiB
JavaScript
109 lines
4.5 KiB
JavaScript
/**
|
|
* 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('data freshness and deployment metadata share the existing status row',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
await page.waitForFunction(() => {
|
|
const freshness = document.querySelector('.data-freshness b');
|
|
const deployment = document.querySelector('.app-meta b');
|
|
return freshness?.textContent.trim() !== 'WAITING'
|
|
&& deployment?.textContent.trim() !== '—';
|
|
});
|
|
const state = await page.evaluate(() => {
|
|
const freshness = document.querySelector('.data-freshness');
|
|
const deployment = document.querySelector('.app-meta');
|
|
return {
|
|
freshness: freshness.textContent.trim(),
|
|
deployment: deployment.textContent.trim(),
|
|
title: deployment.title,
|
|
barHeight: document.querySelector('.statusbar').getBoundingClientRect().height,
|
|
};
|
|
});
|
|
assert.match(state.freshness, /^(LOADED|UPDATED)\s+(JUST NOW|\d+[smh] AGO)$/);
|
|
assert.match(state.deployment, /^DEPLOY\s+(dev|[0-9a-f]{8}) · \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('a daily crosshair shows the session date, not the previous evening',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
await selectTimeframe(page, '1d');
|
|
const state = await page.evaluate(() => {
|
|
const c = window.__chart;
|
|
const time = c.bars[c.bars.length - 1].t;
|
|
return {
|
|
actual: c.chart.options().localization.timeFormatter(time),
|
|
expected: new Date(time * 1000).toLocaleString(undefined, {
|
|
timeZone: 'UTC', year: 'numeric', month: 'short', day: 'numeric',
|
|
}),
|
|
};
|
|
});
|
|
assert.equal(state.actual, state.expected);
|
|
assert.doesNotMatch(state.actual, /\d:\d/);
|
|
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);
|
|
});
|
|
});
|