470 lines
24 KiB
JavaScript
470 lines
24 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
withChart, chartBox, at, armTool, drawingIds, assertNoPageErrors,
|
|
} from './helpers.mjs';
|
|
|
|
test('the timeframe visibility setting governs every drawing type',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const ids = await page.evaluate(async () => {
|
|
const bars = (await (await fetch('/api/bars?tf=30m&limit=2')).json()).bars;
|
|
const [first, second] = bars;
|
|
const post = async (url, body) => (await fetch(url, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
})).json();
|
|
const comment = await post('/api/comments', {
|
|
text: `tf comment ${Date.now()}`, tf: '30m', pinned: true,
|
|
anchor_t: first.t, anchor_p: first.c,
|
|
});
|
|
const symbol = await post('/api/comments', {
|
|
text: 'TF symbol', icon: 'skull', tf: '30m', pinned: true,
|
|
anchor_t: first.t, anchor_p: first.c,
|
|
});
|
|
const fib = await post('/api/lines', {
|
|
kind: 'fibonacci', tf: '30m', side: 'support',
|
|
anchor_t: first.t, anchor_p: first.l, end_t: second.t, end_p: second.h,
|
|
});
|
|
const level = await post('/api/lines/price', {
|
|
tf: '30m', price: first.c, note: `tf level ${Date.now()}`,
|
|
});
|
|
return { comment: comment.id, symbol: symbol.id, fib: fib.id, level: level.id };
|
|
});
|
|
|
|
await page.reload({ waitUntil: 'networkidle' });
|
|
await page.waitForFunction(() => window.__chart?.bars?.length > 0);
|
|
const allVisible = expected => page.waitForFunction(([values, visible]) => {
|
|
const chart = window.__chart;
|
|
const comments = chart.comments.some(item => item.id === values.comment)
|
|
&& chart.comments.some(item => item.id === values.symbol);
|
|
const fib = chart.fibs.some(item => item.id === values.fib);
|
|
const level = chart.priceLines.has(values.level);
|
|
return visible ? comments && fib && level : !comments && !fib && !level;
|
|
}, [ids, expected]);
|
|
await allVisible(true);
|
|
|
|
await page.click('.timeframes button:text-is("1d")');
|
|
await page.waitForFunction(() => window.__chart.bars[0]?.tf === '1d');
|
|
await allVisible(false);
|
|
|
|
await page.locator('.config-section summary').click();
|
|
const setting = page.locator('.config-section label:has-text("Hide lower-TF drawings") input');
|
|
assert.equal(await setting.isChecked(), true, 'timeframe filtering does not default on');
|
|
await setting.uncheck();
|
|
await allVisible(true);
|
|
assert.equal(await page.evaluate(() => localStorage.getItem('chart-hide-lower-tf-drawings')),
|
|
'false', 'the Config choice was not persisted');
|
|
await page.reload({ waitUntil: 'networkidle' });
|
|
await page.waitForFunction(() => window.__chart?.bars?.length > 0);
|
|
await page.locator('.config-section summary').click();
|
|
assert.equal(await page.locator(
|
|
'.config-section label:has-text("Hide lower-TF drawings") input').isChecked(), false,
|
|
'the Config choice reset on reload');
|
|
await allVisible(true);
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('the newest checked trendline stays active and movable during bulk selection',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const ids = await page.evaluate(async () => {
|
|
const bars = (await (await fetch('/api/bars?tf=1m&limit=30')).json()).bars;
|
|
const create = async (first, second, suffix) => (await (await fetch('/api/lines', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tf: '1m', side: 'support', anchor_t: first.t, anchor_p: first.l,
|
|
end_t: second.t, end_p: second.l + 0.25, note: `active ${suffix} ${Date.now()}`,
|
|
}),
|
|
})).json()).id;
|
|
return [
|
|
await create(bars.at(-25), bars.at(-18), 'one'),
|
|
await create(bars.at(-15), bars.at(-8), 'two'),
|
|
];
|
|
});
|
|
const row = id => page.locator(`.trendline-row[data-drawing-id="${id}"]`);
|
|
await page.waitForFunction(values => values.every(id =>
|
|
document.querySelector(`.trendline-row[data-drawing-id="${id}"]`)), ids);
|
|
await row(ids[0]).locator('.line-select').check();
|
|
await row(ids[1]).locator('.line-select').check();
|
|
await page.waitForFunction(id => window.__chart.selectedLineId === id
|
|
&& !window.__chart.lineHitTarget.hasAttribute('hidden'), ids[1]);
|
|
|
|
assert.equal(await row(ids[0]).evaluate(node => node.classList.contains('selected')), true);
|
|
assert.equal(await row(ids[1]).evaluate(node => node.classList.contains('selected')), true);
|
|
assert.equal(await row(ids[0]).evaluate(node => node.classList.contains('active')), false);
|
|
assert.equal(await row(ids[1]).evaluate(node => node.classList.contains('active')), true);
|
|
assert.equal(await page.locator('.chart-line-focus').getAttribute('hidden'), null,
|
|
'programmatic selection did not trigger the focus glow');
|
|
|
|
const target = await page.evaluate(() => {
|
|
const chart = window.__chart;
|
|
const hit = chart.lineHitTarget;
|
|
const plot = chart.plotCanvas().getBoundingClientRect();
|
|
return {
|
|
x: plot.left + (Number(hit.getAttribute('x1')) + Number(hit.getAttribute('x2'))) / 2,
|
|
y: plot.top + (Number(hit.getAttribute('y1')) + Number(hit.getAttribute('y2'))) / 2,
|
|
};
|
|
});
|
|
await page.mouse.move(target.x, target.y);
|
|
await page.mouse.down();
|
|
assert.equal(await page.evaluate(() => window.__chart.draggingLine?.id), ids[1],
|
|
'the active selected line did not own the drag target');
|
|
await page.mouse.up();
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('a daily trendline selected from the list can be dragged by its body',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const created = await page.evaluate(async () => {
|
|
const bars = (await (await fetch('/api/bars?tf=1d&limit=30')).json()).bars;
|
|
const first = bars.at(-24);
|
|
const second = bars.at(-14);
|
|
return await (await fetch('/api/lines', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tf: '1d', side: 'support', anchor_t: first.t, anchor_p: first.l,
|
|
end_t: second.t, end_p: second.l + 20, note: `daily drag ${Date.now()}`,
|
|
}),
|
|
})).json();
|
|
});
|
|
await page.click('.timeframes button:text-is("1d")');
|
|
await page.waitForFunction(() => window.__chart.bars[0]?.tf === '1d');
|
|
const row = page.locator(`.trendline-row[data-drawing-id="${created.id}"]`);
|
|
await row.locator('.line-select').check();
|
|
await page.waitForFunction(id => window.__chart.selectedLineId === id
|
|
&& !window.__chart.lineHitTarget.hasAttribute('hidden'), created.id);
|
|
const original = await page.evaluate(id => {
|
|
const chart = window.__chart;
|
|
const line = chart.levels.find(item => item.id === id);
|
|
const hit = chart.lineHitTarget;
|
|
const plot = chart.plotCanvas().getBoundingClientRect();
|
|
const spacing = chart.coordinateAtIndex(chart.bars.length - 1)
|
|
- chart.coordinateAtIndex(chart.bars.length - 2);
|
|
return {
|
|
anchor_t: line.anchor_t,
|
|
anchor_p: line.anchor_p,
|
|
x: plot.left + (Number(hit.getAttribute('x1')) + Number(hit.getAttribute('x2'))) / 2,
|
|
y: plot.top + (Number(hit.getAttribute('y1')) + Number(hit.getAttribute('y2'))) / 2,
|
|
dx: spacing * 2,
|
|
};
|
|
}, created.id);
|
|
|
|
await page.mouse.move(original.x, original.y);
|
|
await page.mouse.down();
|
|
await page.mouse.move(original.x + original.dx, original.y + 20, { steps: 8 });
|
|
await page.mouse.up();
|
|
await page.waitForFunction(([id, before]) => {
|
|
const line = window.__chart.levels.find(item => item.id === id);
|
|
return line && (line.anchor_t !== before.anchor_t || line.anchor_p !== before.anchor_p);
|
|
}, [created.id, original]);
|
|
assert.equal(await page.evaluate(() => window.__chart.selectedLineId), created.id,
|
|
'daily body drag cleared the active selection');
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('editing a drawing name with Backspace or Delete cannot delete the drawing',
|
|
{
|
|
timeout: 180000,
|
|
skip: 'quarantined: optimistic creation races the shared live levels broadcast',
|
|
}, async () => {
|
|
await withChart(async page => {
|
|
const label = `e2e rename ${Date.now()}`;
|
|
const box = await chartBox(page);
|
|
await armTool(page, 'Trendline');
|
|
assert.deepEqual(await page.locator('select[aria-label="Trendline width"] option').allTextContents(),
|
|
['1px', '2px', '3px', '4px', '5px', '6px', '7px', '8px', '9px']);
|
|
await page.selectOption('select[aria-label="Trendline width"]', '9');
|
|
await page.fill('input[aria-label="Trendline label"]', label);
|
|
const first = at(box, 0.42, 0.55);
|
|
const second = at(box, 0.62, 0.40);
|
|
await page.mouse.click(first.x, first.y);
|
|
await page.waitForTimeout(300);
|
|
await page.mouse.click(second.x, second.y);
|
|
|
|
await page.waitForFunction(name =>
|
|
[...document.querySelectorAll('input[aria-label="Drawing name"]')]
|
|
.some(input => input.value === name), label);
|
|
await page.waitForFunction(name =>
|
|
window.__chart.levels.some(level => level.label === name && !level.id.startsWith('tmp_')),
|
|
label);
|
|
const created = await page.evaluate(name => {
|
|
const line = window.__chart.levels.find(
|
|
level => level.label === name && !level.id.startsWith('tmp_'),
|
|
);
|
|
return line?.id || null;
|
|
}, label);
|
|
assert.ok(created, 'the trendline was not created');
|
|
assert.ok((await drawingIds(page)).includes(created), 'the trendline was not persisted');
|
|
assert.equal(await page.evaluate(id =>
|
|
window.__chart.levels.find(level => level.id === id)?.line_width, created), 9,
|
|
'the 9px trendline width was not persisted');
|
|
|
|
await page.fill('input[aria-label="Filter drawings by text"]', label);
|
|
const input = page.locator('input[aria-label="Drawing name"]');
|
|
assert.equal(await input.count(), 1, 'the unique drawing filter did not isolate the test line');
|
|
const row = page.locator('.trendline-row');
|
|
assert.deepEqual(await row.locator('select[aria-label="Drawing width"] option').allTextContents(),
|
|
['1', '2', '3', '4', '5', '6', '7', '8', '9']);
|
|
const controls = await row.locator('.drawing-controls>*').evaluateAll(nodes =>
|
|
nodes.map(node => node.classList.contains('color-picker') ? 'color' : node.tagName.toLowerCase()));
|
|
assert.deepEqual(controls, ['select', 'color'], 'the color picker is not the rightmost control');
|
|
const rowBox = await row.boundingBox();
|
|
assert.ok(rowBox.height <= 48, `drawing row is still ${rowBox.height}px tall`);
|
|
assert.equal(await row.locator('.drawing-state .fa-bell').count(), 1,
|
|
'the armed state is not represented by a bell');
|
|
assert.equal(await row.locator('.color-picker>summary').getAttribute('title'), 'custom (#65B7CF)',
|
|
'the current-color swatch does not name its color');
|
|
const checkbox = row.locator('.line-select');
|
|
if (await checkbox.isChecked()) await checkbox.click();
|
|
await row.locator('.drawing-secondary>span').click();
|
|
assert.equal(await page.evaluate(() => window.__chart.selectedLineId), null,
|
|
'clicking drawing-list content selected the trendline');
|
|
await checkbox.check();
|
|
await page.waitForFunction(id => window.__chart.selectedLineId === id, created);
|
|
assert.equal(await page.locator('.chart-line-focus').isVisible(), true,
|
|
'selecting the drawing did not point out its trendline on the chart');
|
|
await row.locator('.color-picker>summary').click();
|
|
assert.equal(await row.locator('.color-family-swatches>button[aria-label^="Use "]').count(), 24,
|
|
'the preset palette does not contain 24 colors');
|
|
assert.equal(await row.locator('.color-family-swatches>button[title="blue1 (#4699FE)"]').count(), 1,
|
|
'palette colors do not expose readable names');
|
|
assert.equal(await row.locator('.custom-color input[type="color"]').count(), 1,
|
|
'the custom color choice is missing');
|
|
assert.equal(await row.locator('.palette-close').count(), 1,
|
|
'the palette close control is missing');
|
|
await page.keyboard.press('Escape');
|
|
assert.equal(await row.locator('.color-popover').isVisible(), false,
|
|
'Escape did not close the color palette');
|
|
await row.locator('.color-picker>summary').click();
|
|
await row.locator('.palette-close').click();
|
|
assert.equal(await row.locator('.color-popover').isVisible(), false,
|
|
'the close control did not close the color palette');
|
|
await row.locator('.color-picker>summary').click();
|
|
await row.locator('button[aria-label="Use red2 (#F45B78)"]').click();
|
|
await page.waitForFunction(([id, color]) =>
|
|
window.__chart.levels.find(level => level.id === id)?.color === color,
|
|
[created, '#F45B78']);
|
|
await input.focus();
|
|
await input.press('End');
|
|
await input.press('Backspace');
|
|
assert.equal(await input.inputValue(), label.slice(0, -1), 'Backspace did not edit the name');
|
|
await page.waitForTimeout(500);
|
|
assert.ok((await drawingIds(page)).includes(created), 'Backspace deleted the drawing');
|
|
|
|
const beforeDelete = await input.inputValue();
|
|
await input.press('Home');
|
|
await input.press('Delete');
|
|
assert.equal(await input.inputValue(), beforeDelete.slice(1), 'Delete did not edit the name');
|
|
await page.waitForTimeout(500);
|
|
assert.ok((await drawingIds(page)).includes(created), 'Delete deleted the drawing');
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('a price level can be edited from the list and adjusted on the chart',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const label = `e2e level focus ${Date.now()}`;
|
|
const price = await page.evaluate(() => window.__chart.bars.at(-1).c);
|
|
await armTool(page, 'Price level');
|
|
assert.deepEqual(await page.locator('select[aria-label="Level width"] option').allTextContents(),
|
|
['1px', '2px', '3px', '4px', '5px', '6px', '7px', '8px', '9px']);
|
|
await page.fill('input[aria-label="Level label"]', label);
|
|
await page.fill('input[aria-label="Level price"]', String(price));
|
|
await page.locator('.price-row button[type="submit"]').click();
|
|
await page.fill('input[aria-label="Filter drawings by text"]', label);
|
|
const row = page.locator('.trendline-row');
|
|
await row.waitFor();
|
|
await row.locator('.color-picker>summary').click();
|
|
assert.deepEqual(
|
|
await row.locator('.color-family-swatches>button[aria-label^="Use "]').evaluateAll(nodes =>
|
|
nodes.map(node => node.getAttribute('title'))),
|
|
[
|
|
'green1 (#A6D8AA)', 'green2 (#4DB155)', 'green3 (#258F33)', 'green4 (#006D09)',
|
|
'red1 (#FAADBC)', 'red2 (#F45B78)', 'red3 (#D13F62)', 'red4 (#AF2850)',
|
|
'blue1 (#A3CCFF)', 'blue2 (#4699FE)', 'blue3 (#1E76D8)', 'blue4 (#0054B3)',
|
|
'orange1 (#F8C592)', 'orange2 (#F08A24)', 'orange3 (#D66B12)', 'orange4 (#B94E08)',
|
|
'teal1 (#80D8D8)', 'teal2 (#00B0B1)', 'teal3 (#008E8F)', 'teal4 (#006C6E)',
|
|
'purple1 (#DDBCEB)', 'purple2 (#BB79D7)', 'purple3 (#9858B3)', 'purple4 (#763790)',
|
|
],
|
|
);
|
|
assert.equal(await row.locator('.custom-color input[type="color"]').count(), 1,
|
|
'the palette lost its custom color wheel');
|
|
assert.equal(await row.locator('.palette-close[title="Cancel"]').count(), 1,
|
|
'the palette lost its Cancel control');
|
|
assert.equal(await row.locator('input[aria-label="blue row annotation"]').inputValue(), 'blue');
|
|
await row.locator('input[aria-label="blue row annotation"]').fill('primary structure');
|
|
await row.locator('input[aria-label="blue row annotation"]').press('Tab');
|
|
assert.equal(await page.evaluate(() =>
|
|
JSON.parse(localStorage.getItem('chart-color-row-labels')).blue), 'primary structure');
|
|
await row.locator('button[aria-label="Use blue2 (#4699FE)"]').evaluate(node => node.click());
|
|
const id = await page.evaluate(name =>
|
|
window.__chart.levels.find(level => level.label === name)?.id || null, label);
|
|
assert.ok(id, 'the price level was not created');
|
|
await page.waitForFunction(levelId =>
|
|
window.__chart.levels.find(level => level.id === levelId)?.color === '#4699FE', id);
|
|
const priceInput = row.locator('input[aria-label="Level price in drawing list"]');
|
|
const earlyInput = row.locator('input[aria-label="Level alert early points"]');
|
|
await priceInput.fill(String(price + 1));
|
|
await priceInput.press('Enter');
|
|
await page.waitForFunction(([levelId, expectedPrice]) =>
|
|
window.__chart.levels.find(level => level.id === levelId)?.anchor_p === expectedPrice,
|
|
[id, price + 1]);
|
|
await earlyInput.fill('1.5');
|
|
await earlyInput.press('Enter');
|
|
await page.waitForFunction(levelId =>
|
|
window.__chart.levels.find(level => level.id === levelId)?.alert_early_points === 1.5, id);
|
|
const number = await page.evaluate(levelId =>
|
|
window.__chart.levels.find(level => level.id === levelId).number, id);
|
|
assert.equal(await page.evaluate(levelId =>
|
|
window.__chart.priceLines.get(levelId).line.options().title, id), `#${number}`,
|
|
'an unselected price level exposed its full name on the price axis');
|
|
|
|
await row.locator('.drawing-secondary>span').click();
|
|
await page.waitForFunction(levelId => window.__chart.selectedLineId === levelId, id);
|
|
assert.equal(await page.evaluate(levelId =>
|
|
window.__chart.priceLines.get(levelId).line.options().title, id), label,
|
|
'a selected price level did not show its full name on the price axis');
|
|
const focus = page.locator('.chart-line-focus');
|
|
assert.equal(await focus.getAttribute('hidden'), null, 'the selected price level was not highlighted');
|
|
assert.equal(await focus.evaluate(node => getComputedStyle(node).animationName), 'line-focus',
|
|
'the price-level focus animation did not run');
|
|
assert.equal(await focus.getAttribute('y1'), await focus.getAttribute('y2'),
|
|
'the price-level highlight was not horizontal');
|
|
assert.equal(await page.evaluate(levelId => {
|
|
const c = window.__chart;
|
|
const level = c.levels.find(value => value.id === levelId);
|
|
c.updateLineTooltip({ point: {
|
|
x: c.plotCanvas().getBoundingClientRect().width / 2,
|
|
y: c.candles.priceToCoordinate(level.anchor_p),
|
|
} });
|
|
return c.tooltip.textContent;
|
|
}, id), `#${number} ${label}\nblue2 (#4699FE)\nprimary structure`);
|
|
|
|
const target = await page.evaluate(() => {
|
|
const chart = window.__chart;
|
|
const bar = chart.bars[chart.bars.length - 30];
|
|
const plot = chart.plotCanvas().getBoundingClientRect();
|
|
return {
|
|
price: bar.h,
|
|
x: plot.left + chart.chart.timeScale().timeToCoordinate(bar.t),
|
|
y: plot.top + chart.candles.priceToCoordinate(bar.h),
|
|
};
|
|
});
|
|
const handle = page.locator('.chart-anchor[data-anchor="start"]:not([hidden])');
|
|
const handleBox = await handle.boundingBox();
|
|
assert.ok(handleBox, 'a selected price level has no grab handle');
|
|
await page.mouse.move(handleBox.x + handleBox.width / 2, handleBox.y + handleBox.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(target.x, target.y, { steps: 8 });
|
|
await page.mouse.up();
|
|
await page.waitForFunction(([levelId, expected]) =>
|
|
window.__chart.levels.find(level => level.id === levelId)?.anchor_p === expected,
|
|
[id, target.price]);
|
|
|
|
await page.keyboard.press('ArrowUp');
|
|
await page.waitForFunction(([levelId, expected]) =>
|
|
window.__chart.levels.find(level => level.id === levelId)?.anchor_p === expected,
|
|
[id, target.price + 0.25]);
|
|
await page.waitForFunction(expected =>
|
|
Number(document.querySelector('input[aria-label="Level price in drawing list"]')?.value) === expected,
|
|
target.price + 0.25);
|
|
assert.equal(Number(await priceInput.inputValue()), target.price + 0.25,
|
|
'the drawing-list price did not follow the chart nudge');
|
|
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('Delete removes a drawing selected through its checkbox',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const label = `e2e checkbox delete ${Date.now()}`;
|
|
const price = await page.evaluate(() => window.__chart.bars.at(-1).c);
|
|
await armTool(page, 'Price level');
|
|
await page.fill('input[aria-label="Level label"]', label);
|
|
await page.fill('input[aria-label="Level price"]', String(price));
|
|
await page.locator('.price-row button[type="submit"]').click();
|
|
await page.fill('input[aria-label="Filter drawings by text"]', label);
|
|
const row = page.locator('.trendline-row');
|
|
await row.waitFor();
|
|
const id = await page.evaluate(name =>
|
|
window.__chart.levels.find(level => level.label === name)?.id || null, label);
|
|
assert.ok(id, 'the drawing was not created');
|
|
|
|
const checkbox = row.locator('.line-select');
|
|
await checkbox.check();
|
|
assert.equal(await checkbox.evaluate(node => document.activeElement === node), true,
|
|
'the checkbox did not retain keyboard focus');
|
|
await page.keyboard.press('Delete');
|
|
await page.waitForFunction(levelId =>
|
|
!window.__chart.levels.some(level => level.id === levelId), id);
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('filtered selections can hide and show drawings without deleting them',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const label = `bulk visibility ${Date.now()}`;
|
|
const created = await page.evaluate(async name => {
|
|
const chart = window.__chart;
|
|
const start = chart.bars.at(-30);
|
|
const end = chart.bars.at(-10);
|
|
const response = await fetch('/api/lines', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
tf: '1m', side: 'support',
|
|
anchor_t: start.t, anchor_p: start.l,
|
|
end_t: end.t, end_p: end.l,
|
|
note: name,
|
|
}),
|
|
});
|
|
return response.json();
|
|
}, label);
|
|
await page.waitForFunction(id => window.__chart.levels.some(level => level.id === id), created.id);
|
|
|
|
const filter = page.locator('input[aria-label="Filter drawings by text"]');
|
|
await filter.fill('1m');
|
|
assert.equal(await page.locator(`input[aria-label="Drawing name"][value="${label}"]`).count(), 1,
|
|
'the drawing filter did not match the timeframe');
|
|
|
|
await filter.fill(label);
|
|
const row = page.locator('.trendline-row');
|
|
await page.getByRole('button', { name: 'Select shown' }).click();
|
|
assert.equal(await page.locator('.selection-count').textContent(), '1 selected');
|
|
await page.getByRole('button', { name: 'Hide selected drawings' }).click();
|
|
await page.waitForFunction(id => window.__chart.levels.find(level => level.id === id)?.hidden, created.id);
|
|
assert.equal(await row.evaluate(node => node.classList.contains('hidden-drawing')), true);
|
|
assert.equal(await page.evaluate(id => window.__chart.levelSeries.has(id), created.id), false,
|
|
'the hidden drawing remained on the chart');
|
|
|
|
await page.getByRole('button', { name: 'Show selected drawings' }).click();
|
|
await page.waitForFunction(id => !window.__chart.levels.find(level => level.id === id)?.hidden, created.id);
|
|
assert.equal(await row.evaluate(node => node.classList.contains('hidden-drawing')), false);
|
|
assert.equal(await page.evaluate(id => window.__chart.levelSeries.has(id), created.id), true,
|
|
'the shown drawing did not return to the chart');
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|
|
|
|
test('trendline slope in the tooltip is signed points per hour',
|
|
{ timeout: 180000 }, async () => {
|
|
await withChart(async page => {
|
|
const labels = await page.evaluate(() => ({
|
|
up: window.__chart.slopeLabel({ kind: 'manual', slope: 2 / 3600 }),
|
|
down: window.__chart.slopeLabel({ kind: 'manual', slope: -0.5 / 3600 }),
|
|
flat: window.__chart.slopeLabel({ kind: 'manual', slope: 0 }),
|
|
}));
|
|
assert.equal(labels.up, '+2.00 /h');
|
|
assert.equal(labels.down, '-0.50 /h');
|
|
assert.equal(labels.flat, '');
|
|
assertNoPageErrors(page, assert);
|
|
});
|
|
});
|