/** * Comments stay where they were put, across zoom, scroll and timeframe. * * A comment is stored as a drawing but must never behave like a level, so the * last test here guards the invariant that matters most: a note on a chart can * never end up in a confluence cluster or fire a push notification. */ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { withChart, chartBox, at, armTool, selectTimeframe, assertNoPageErrors, } from './helpers.mjs'; const TEXT = 'e2e placement check'; /** Place a comment at a point on the chart. */ async function placeComment(page, box, fx, fy, { floating = false } = {}) { await armTool(page, 'Comment'); await page.fill('textarea[aria-label="Comment text"]', TEXT); if (floating) { await page.evaluate(() => { const body = [...document.querySelectorAll('.tool-body')].find(b => b.querySelector('textarea')); const box_ = body.querySelector('input[type=checkbox]'); if (!box_.checked) box_.click(); }); await page.waitForTimeout(300); } const point = at(box, fx, fy); await page.mouse.move(point.x, point.y); await page.mouse.down(); await page.mouse.up(); await page.waitForTimeout(1800); } const commentNode = page => page.evaluate(text => { const node = [...document.querySelectorAll('.chart-comment')].find(n => n.title === text); if (!node) return null; return { left: Math.round(node.getBoundingClientRect().left), collapsed: node.classList.contains('collapsed'), parked: node.classList.contains('off-left') || node.classList.contains('off-right'), }; }, TEXT); test('a pinned comment keeps its bar across timeframes', { timeout: 300000 }, async () => { await withChart(async page => { const box = await chartBox(page); await selectTimeframe(page, '30m'); await placeComment(page, box, 0.55, 0.40); const anchor = await page.evaluate(t => { const c = window.__chart.comments.find(x => x.note === t); return c ? c.anchor_t : null; }, TEXT); assert.ok(anchor, 'the comment was not created'); // The bug: timeToCoordinate answers only for exact data points, so a 30m // bucket returned null on 15m and null was read as "off the left edge". for (const tf of ['15m', '1h', '30m']) { await selectTimeframe(page, tf); const node = await commentNode(page); assert.ok(node, `the comment vanished on ${tf}`); assert.equal(node.parked, false, `the comment was edge-parked on ${tf} despite being in view`); } assertNoPageErrors(page, assert); }); }); test('clicking a comment collapses it', { timeout: 180000 }, async () => { await withChart(async page => { const box = await chartBox(page); await placeComment(page, box, 0.5, 0.35); assert.equal((await commentNode(page)).collapsed, false); // By title, not by class: the dev store is shared, so other people's // comments are on the chart too and `.chart-comment` matches them first. await page.click(`.chart-comment[title="${TEXT}"]`); await page.waitForTimeout(1200); assert.equal((await commentNode(page)).collapsed, true, 'clicking did not collapse it'); assertNoPageErrors(page, assert); }); }); test('a floating comment holds its position while the chart scrolls', { timeout: 180000 }, async () => { await withChart(async page => { const box = await chartBox(page); await placeComment(page, box, 0.4, 0.3, { floating: true }); const before = await commentNode(page); assert.ok(before, 'the floating comment was not created'); await page.evaluate(() => window.__chart.chart.timeScale().scrollToPosition(-300, false)); await page.waitForTimeout(1200); const after = await commentNode(page); assert.equal(after.left, before.left, 'a floating comment moved with the chart'); assertNoPageErrors(page, assert); }); }); test('a comment is never a level', { timeout: 180000 }, async () => { await withChart(async page => { const box = await chartBox(page); await placeComment(page, box, 0.5, 0.5); // Levels cluster and alert. A comment reaching that list would push a // phone notification about a piece of text. const leaked = await page.evaluate(t => { const note = window.__chart.comments.find(x => x.note === t); if (!note) return 'comment missing'; return window.__chart.levels.some(level => level.id === note.id); }, TEXT); assert.equal(leaked, false, 'a comment appeared in the level list'); assertNoPageErrors(page, assert); }); });