120 lines
5.2 KiB
JavaScript
120 lines
5.2 KiB
JavaScript
/**
|
|
* 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);
|
|
await page.fill('input[aria-label="Filter drawings by text"]', TEXT);
|
|
const row = page.locator('.trendline-row');
|
|
await row.locator('.drawing-secondary>span').click();
|
|
assert.equal(await page.locator(`.chart-comment[title="${TEXT}"]`).evaluate(node => node.classList.contains('focus')), true,
|
|
'selecting the comment row did not point out the comment on the chart');
|
|
await page.keyboard.press('Escape');
|
|
assert.equal(await row.evaluate(node => node.classList.contains('selected')), false,
|
|
'Escape did not clear comment selection');
|
|
// 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}"]`);
|
|
assert.equal(await row.evaluate(node => node.classList.contains('selected')), true,
|
|
'clicking the chart comment did not select its drawing row');
|
|
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);
|
|
});
|
|
});
|