added interactions e2e tests

Added new e2e tests for chart interactions such as mouse and touch interactions.
This commit is contained in:
Mark Silverwood 2022-09-14 16:13:05 +01:00
parent 3992003e0a
commit 05ea046da1
19 changed files with 831 additions and 178 deletions

View File

@ -220,6 +220,19 @@ jobs:
- store_test_results:
path: test-results/
interactions:
executor: node16-browsers-executor
environment:
NO_SANDBOX: "true"
TESTS_REPORT_FILE: "test-results/interactions/results.xml"
steps:
- checkout-with-deps
- attach_workspace:
at: ./
- run: scripts/run-interactions-tests.sh
- store_test_results:
path: test-results/
size-limit:
executor: node16-executor
steps:
@ -336,6 +349,10 @@ workflows:
filters: *default-filters
requires:
- build
- interactions:
filters: *default-filters
requires:
- build
- lint-dts:
filters: *default-filters
requires:

10
.vscode/launch.json vendored
View File

@ -41,6 +41,16 @@
"${input:testStandalonePath}"
],
"internalConsoleOptions": "openOnSessionStart"
},
{
"type": "node",
"request": "launch",
"name": "Interaction tests",
"program": "${workspaceFolder}/tests/e2e/interactions/runner.js",
"args": [
"${input:testStandalonePath}"
],
"internalConsoleOptions": "openOnSessionStart"
}
],
"inputs": [

View File

@ -0,0 +1,8 @@
#!/bin/bash
set -e
echo "Preparing"
npm run build
echo "Interactions tests"
node ./tests/e2e/interactions/runner.js ./dist/lightweight-charts.standalone.development.js

View File

@ -107,3 +107,21 @@ Alternatively, you can run the test on a specific file like this:
```bash
node ./tests/e2e/memleaks/runner.js ./dist/lightweight-charts.standalone.development.js
```
### Interactions
The interactions tests check whether the library is correctly handling user interaction on the chart. Interactions include: mouse scrolling, mouse dragging, and touches.
#### Running the Interactions tests
You can run the interactions tests with the following command:
```bash
./scripts/run-interactions-tests.sh
```
Alternatively, you can run the tests on a specific file like this:
```bash
node ./tests/e2e/interactions/runner.js ./dist/lightweight-charts.standalone.development.js
```

View File

@ -13,9 +13,12 @@ import puppeteer, {
HTTPResponse,
launch as launchPuppeteer,
Page,
type CDPSession,
} from 'puppeteer';
import { doHorizontalDrag, doKineticAnimation, doVerticalDrag } from '../helpers/mouse-drag-actions';
import { doMouseScrolls } from '../helpers/mouse-scroll-actions';
import { doLongTouch, doPinchZoomTouch, doSwipeTouch } from '../helpers/touch-actions';
import { expectedCoverage, threshold } from './coverage-config';
const coverageScript = fs.readFileSync(path.join(__dirname, 'coverage-script.js'), { encoding: 'utf-8' });
@ -24,31 +27,6 @@ const testStandalonePathEnvKey = 'TEST_STANDALONE_PATH';
const testStandalonePath: string = process.env[testStandalonePathEnvKey] || '';
async function doMouseScrolls(page: Page, element: ElementHandle): Promise<void> {
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error('Unable to get boundingBox for element.');
}
// move mouse to center of element
await page.mouse.move(
boundingBox.x + boundingBox.width / 2,
boundingBox.y + boundingBox.height / 2
);
await page.mouse.wheel({ deltaX: 10.0 });
await page.mouse.wheel({ deltaY: 10.0 });
await page.mouse.wheel({ deltaX: -10.0 });
await page.mouse.wheel({ deltaY: -10.0 });
await page.mouse.wheel({ deltaX: 10.0, deltaY: 10.0 });
await page.mouse.wheel({ deltaX: -10.0, deltaY: -10.0 });
}
async function doZoomInZoomOut(page: Page): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const prevViewport = page.viewport()!;
@ -60,157 +38,6 @@ async function doZoomInZoomOut(page: Page): Promise<void> {
await page.setViewport(prevViewport);
}
async function doVerticalDrag(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX, elMiddleY - 20);
await page.mouse.move(elMiddleX, elMiddleY + 40);
await page.mouse.up({ button: 'left' });
}
async function doHorizontalDrag(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX - 20, elMiddleY);
await page.mouse.move(elMiddleX + 40, elMiddleY);
await page.mouse.up({ button: 'left' });
}
// await a setTimeout delay evaluated within page context
async function pageTimeout(page: Page, delay: number): Promise<void> {
return page.evaluate(
(ms: number) => new Promise<void>(
(resolve: () => void) => setTimeout(resolve, ms)
),
delay
);
}
async function doKineticAnimation(page: Page, element: ElementHandle): Promise<void> {
const elBox = await element.boundingBox() as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await pageTimeout(page, 50);
await page.mouse.move(elMiddleX - 40, elMiddleY);
await page.mouse.move(elMiddleX - 55, elMiddleY);
await page.mouse.move(elMiddleX - 105, elMiddleY);
await page.mouse.move(elMiddleX - 155, elMiddleY);
await page.mouse.move(elMiddleX - 205, elMiddleY);
await page.mouse.move(elMiddleX - 255, elMiddleY);
await page.mouse.up({ button: 'left' });
await pageTimeout(page, 200);
// stop animation
await page.mouse.down({ button: 'left' });
await page.mouse.up({ button: 'left' });
}
// Simulate a long touch action in a single position
async function doLongTouch(page: Page, element: ElementHandle, duration: number): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const client = await page.target().createCDPSession();
await client.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
await pageTimeout(page, duration);
return client.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
}
// Simulate a touch swipe gesture
async function doSwipeTouch(
devToolsSession: CDPSession,
element: ElementHandle,
{
horizontal = false,
vertical = false,
}: { horizontal?: boolean; vertical?: boolean }
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = horizontal ? elBox.width / 8 : 0;
const yStep = vertical ? elBox.height / 8 : 0;
for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [{ x: elCenterX - i * xStep, y: elCenterY - i * yStep }],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [{ x: elCenterX - xStep, y: elCenterY - yStep }],
});
}
// Perform a pinch or zoom touch gesture within the specified element.
async function doPinchZoomTouch(
devToolsSession: CDPSession,
element: ElementHandle,
zoom?: boolean
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const sign = zoom ? -1 : 1;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = (sign * elBox.width) / 8;
const yStep = (sign * elBox.height) / 8;
for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [
{ x: elCenterX - i * xStep, y: elCenterY - i * yStep },
{ x: elCenterX + i * xStep, y: elCenterY + i * xStep },
],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX - xStep, y: elCenterY - yStep },
{ x: elCenterX + xStep, y: elCenterY + xStep },
],
});
}
async function doUserInteractions(page: Page): Promise<void> {
const chartContainer = await page.$('#container') as ElementHandle<Element>;
const chartBox = await chartContainer.boundingBox() as BoundingBox;

View File

@ -0,0 +1,67 @@
import { BoundingBox, ElementHandle, Page } from 'puppeteer';
import { pageTimeout } from './page-timeout';
export async function doVerticalDrag(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX, elMiddleY - 20);
await page.mouse.move(elMiddleX, elMiddleY + 40);
await page.mouse.up({ button: 'left' });
}
export async function doHorizontalDrag(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await page.mouse.move(elMiddleX - 20, elMiddleY);
await page.mouse.move(elMiddleX + 40, elMiddleY);
await page.mouse.up({ button: 'left' });
}
export async function doKineticAnimation(
page: Page,
element: ElementHandle
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elMiddleX = elBox.x + elBox.width / 2;
const elMiddleY = elBox.y + elBox.height / 2;
// move mouse to the middle of element
await page.mouse.move(elMiddleX, elMiddleY);
await page.mouse.down({ button: 'left' });
await pageTimeout(page, 50);
await page.mouse.move(elMiddleX - 40, elMiddleY);
await page.mouse.move(elMiddleX - 55, elMiddleY);
await page.mouse.move(elMiddleX - 105, elMiddleY);
await page.mouse.move(elMiddleX - 155, elMiddleY);
await page.mouse.move(elMiddleX - 205, elMiddleY);
await page.mouse.move(elMiddleX - 255, elMiddleY);
await page.mouse.up({ button: 'left' });
await pageTimeout(page, 200);
// stop animation
await page.mouse.down({ button: 'left' });
await page.mouse.up({ button: 'left' });
}

View File

@ -0,0 +1,48 @@
import { ElementHandle, Page } from 'puppeteer';
export async function centerMouseOnElement(
page: Page,
element: ElementHandle
): Promise<void> {
const boundingBox = await element.boundingBox();
if (!boundingBox) {
throw new Error('Unable to get boundingBox for element.');
}
// move mouse to center of element
await page.mouse.move(
boundingBox.x + boundingBox.width / 2,
boundingBox.y + boundingBox.height / 2
);
}
interface MouseScrollDelta {
x?: number;
y?: number;
}
export async function doMouseScroll(
deltas: MouseScrollDelta,
page: Page
): Promise<void> {
await page.mouse.wheel({ deltaX: deltas.x || 0, deltaY: deltas.y || 0 });
}
export async function doMouseScrolls(
page: Page,
element: ElementHandle
): Promise<void> {
await centerMouseOnElement(page, element);
await doMouseScroll({ x: 10.0 }, page);
await doMouseScroll({ y: 10.0 }, page);
await doMouseScroll({ x: -10.0 }, page);
await doMouseScroll({ y: -10.0 }, page);
await doMouseScroll({ x: 10.0, y: 10.0 }, page);
await doMouseScroll({ x: -10.0, y: -10.0 }, page);
}

View File

@ -0,0 +1,11 @@
import { Page } from 'puppeteer';
// await a setTimeout delay evaluated within page context
export async function pageTimeout(page: Page, delay: number): Promise<void> {
return page.evaluate(
(ms: number) => new Promise<void>(
(resolve: () => void) => setTimeout(resolve, ms)
),
delay
);
}

View File

@ -0,0 +1,89 @@
import { BoundingBox, ElementHandle, Page, type CDPSession } from 'puppeteer';
import { pageTimeout } from './page-timeout';
// Simulate a long touch action in a single position
export async function doLongTouch(page: Page, element: ElementHandle, duration: number): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const client = await page.target().createCDPSession();
await client.send('Input.dispatchTouchEvent', {
type: 'touchStart',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
await pageTimeout(page, duration);
return client.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX, y: elCenterY },
],
});
}
// Simulate a touch swipe gesture
export async function doSwipeTouch(
devToolsSession: CDPSession,
element: ElementHandle,
{
horizontal = false,
vertical = false,
}: { horizontal?: boolean; vertical?: boolean }
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = horizontal ? elBox.width / 8 : 0;
const yStep = vertical ? elBox.height / 8 : 0;
for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [{ x: elCenterX - i * xStep, y: elCenterY - i * yStep }],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [{ x: elCenterX - xStep, y: elCenterY - yStep }],
});
}
// Perform a pinch or zoom touch gesture within the specified element.
export async function doPinchZoomTouch(
devToolsSession: CDPSession,
element: ElementHandle,
zoom?: boolean
): Promise<void> {
const elBox = (await element.boundingBox()) as BoundingBox;
const sign = zoom ? -1 : 1;
const elCenterX = elBox.x + elBox.width / 2;
const elCenterY = elBox.y + elBox.height / 2;
const xStep = (sign * elBox.width) / 8;
const yStep = (sign * elBox.height) / 8;
for (let i = 2; i > 0; i--) {
const type = i === 2 ? 'touchStart' : 'touchMove';
await devToolsSession.send('Input.dispatchTouchEvent', {
type,
touchPoints: [
{ x: elCenterX - i * xStep, y: elCenterY - i * yStep },
{ x: elCenterX + i * xStep, y: elCenterY + i * xStep },
],
});
}
return devToolsSession.send('Input.dispatchTouchEvent', {
type: 'touchEnd',
touchPoints: [
{ x: elCenterX - xStep, y: elCenterY - yStep },
{ x: elCenterX + xStep, y: elCenterY + xStep },
],
});
}

View File

@ -0,0 +1,60 @@
/// <reference types="node" />
import * as fs from 'fs';
import * as path from 'path';
export interface TestCase {
name: string;
caseContent: string;
}
const testCasesDir = path.join(__dirname, '..', 'test-cases');
function extractTestCaseName(fileName: string): string | null {
const match = /^([^.].+)\.js$/.exec(path.basename(fileName));
return match && match[1];
}
function isTestCaseFile(filePath: string): boolean {
return fs.lstatSync(filePath).isFile() && extractTestCaseName(filePath) !== null;
}
interface TestCasesGroupInfo {
name: string;
path: string;
}
function getTestCaseGroups(): TestCasesGroupInfo[] {
return [
{
name: '',
path: testCasesDir,
},
...fs.readdirSync(testCasesDir)
.filter((filePath: string) => fs.lstatSync(path.join(testCasesDir, filePath)).isDirectory())
.map((filePath: string) => {
return {
name: filePath,
path: path.join(testCasesDir, filePath),
};
}),
];
}
export function getTestCases(): Record<string, TestCase[]> {
const result: Record<string, TestCase[]> = {};
for (const group of getTestCaseGroups()) {
result[group.name] = fs.readdirSync(group.path)
.map((filePath: string) => path.join(group.path, filePath))
.filter(isTestCaseFile)
.map((testCaseFile: string) => {
return {
name: extractTestCaseName(testCaseFile) as string,
caseContent: fs.readFileSync(testCaseFile, { encoding: 'utf-8' }),
};
});
}
return result;
}

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
<title>Test case page</title>
</head>
<body style="padding: 0; margin: 0;">
<div id="container" style="position: absolute; width: 100%; height: 100%;"></div>
<script type="text/javascript" src="PATH_TO_STANDALONE_MODULE"></script>
<script type="text/javascript">
TEST_CASE_SCRIPT
</script>
<script type="text/javascript">
window.interactions = interactionsToPerform();
window.finishedSetup = beforeInteractions(document.getElementById('container'));
</script>
</body>
</html>

View File

@ -0,0 +1,171 @@
/// <reference types="node" />
import * as fs from 'fs';
import * as path from 'path';
import { expect } from 'chai';
import { describe, it } from 'mocha';
import puppeteer, {
Browser,
HTTPResponse,
launch as launchPuppeteer,
} from 'puppeteer';
import { doMouseScroll } from '../helpers/mouse-scroll-actions';
import { getTestCases, TestCase } from './helpers/get-test-cases';
const dummyContent = fs.readFileSync(
path.join(__dirname, 'helpers', 'test-page-dummy.html'),
{ encoding: 'utf-8' }
);
function generatePageContent(
standaloneBundlePath: string,
testCaseCode: string
): string {
return dummyContent
.replace('PATH_TO_STANDALONE_MODULE', standaloneBundlePath)
.replace('TEST_CASE_SCRIPT', testCaseCode);
}
const testStandalonePathEnvKey = 'TEST_STANDALONE_PATH';
const testStandalonePath: string = process.env[testStandalonePathEnvKey] || '';
type Interaction = 'scrollLeft' | 'scrollRight' | 'scrollUp' | 'scrollDown';
interface InternalWindow {
interactions: Interaction[];
finishedSetup: Promise<() => void>;
afterInteractions: () => void;
}
describe('Interactions tests', function(): void {
// this tests are unstable sometimes.
this.retries(5);
const puppeteerOptions: Parameters<typeof launchPuppeteer>[0] = {};
if (process.env.NO_SANDBOX) {
puppeteerOptions.args = ['--no-sandbox', '--disable-setuid-sandbox'];
}
let browser: Browser;
before(async () => {
expect(
testStandalonePath,
`path to test standalone module must be passed via ${testStandalonePathEnvKey} env var`
).to.have.length.greaterThan(0);
// note that we cannot use launchPuppeteer here as soon it wrong typing in puppeteer
// see https://github.com/puppeteer/puppeteer/issues/7529
const browserPromise = puppeteer.launch(puppeteerOptions);
browser = await browserPromise;
});
let testCaseCount = 0;
const runTestCase = (testCase: TestCase) => {
testCaseCount += 1;
it(testCase.name, async () => {
const pageContent = generatePageContent(
testStandalonePath,
testCase.caseContent
);
const page = await browser.newPage();
await page.setViewport({ width: 600, height: 600 });
const errors: string[] = [];
page.on('pageerror', (error: Error) => {
errors.push(error.message);
});
page.on('response', (response: HTTPResponse) => {
if (!response.ok()) {
errors.push(
`Network error: ${response.url()} status=${response.status()}`
);
}
});
await page.setContent(pageContent, { waitUntil: 'load' });
await page.evaluate(() => {
return (window as unknown as InternalWindow).finishedSetup;
});
const interactionsToPerform = await page.evaluate(() => {
return (window as unknown as InternalWindow).interactions;
});
for (const interactionName of interactionsToPerform) {
switch (interactionName) {
case 'scrollLeft':
await doMouseScroll({ x: -10.0 }, page);
break;
case 'scrollRight':
await doMouseScroll({ x: 10.0 }, page);
break;
case 'scrollDown':
await doMouseScroll({ y: 10.0 }, page);
break;
case 'scrollUp':
await doMouseScroll({ y: -10.0 }, page);
break;
default:
// eslint-disable-next-line no-case-declarations
const exhaustiveCheck: never = interactionName;
throw new Error(exhaustiveCheck);
}
}
await page.evaluate(() => {
return new Promise<void>((resolve: () => void) => {
(window as unknown as InternalWindow).afterInteractions();
window.requestAnimationFrame(() => {
setTimeout(resolve, 50);
});
});
});
if (errors.length !== 0) {
throw new Error(`Page has errors:\n${errors.join('\n')}`);
}
expect(errors.length).to.be.equal(
0,
'There should not be any errors thrown within the test page.'
);
});
};
const testCaseGroups = getTestCases();
for (const groupName of Object.keys(testCaseGroups)) {
if (groupName.length === 0) {
for (const testCase of testCaseGroups[groupName]) {
runTestCase(testCase);
}
} else {
describe(groupName, () => {
for (const testCase of testCaseGroups[groupName]) {
runTestCase(testCase);
}
});
}
}
it('number of test cases', () => {
// we need to have at least 1 test to check it
expect(testCaseCount).to.be.greaterThan(
0,
'there should be at least 1 test case'
);
});
after(async () => {
await browser.close();
});
});

View File

@ -0,0 +1,71 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const Mocha = require('mocha');
const serveLocalFiles = require('../serve-local-files').serveLocalFiles;
const mochaConfig = require('../../../.mocharc.js');
// override tsconfig
process.env.TS_NODE_PROJECT = path.resolve(__dirname, '../tsconfig.composite.json');
mochaConfig.require.forEach(module => {
require(module);
});
if (process.argv.length !== 3) {
console.log('Usage: runner PATH_TO_TEST_STANDALONE_MODULE');
process.exit(1);
}
const startTime = Date.now();
let testStandalonePath = process.argv[2];
const hostname = 'localhost';
const port = 34567;
const httpServerPrefix = `http://${hostname}:${port}/`;
const filesToServe = new Map();
if (fs.existsSync(testStandalonePath)) {
const fileNameToServe = 'test.js';
filesToServe.set(fileNameToServe, path.resolve(testStandalonePath));
testStandalonePath = `${httpServerPrefix}${fileNameToServe}`;
}
process.env.TEST_STANDALONE_PATH = testStandalonePath;
function runMocha(closeServer) {
console.log('Running tests...');
const mocha = new Mocha({
timeout: 20000,
slow: 10000,
reporter: mochaConfig.reporter,
reporterOptions: mochaConfig._reporterOptions,
});
if (mochaConfig.checkLeaks) {
mocha.checkLeaks();
}
mocha.diff(mochaConfig.diff);
mocha.addFile(path.resolve(__dirname, './interactions-test-cases.ts'));
mocha.run(failures => {
if (closeServer !== null) {
closeServer();
}
const timeInSecs = (Date.now() - startTime) / 1000;
console.log(`Done in ${timeInSecs.toFixed(2)}s with ${failures} error(s)`);
process.exitCode = failures !== 0 ? 1 : 0;
});
}
serveLocalFiles(filesToServe, port, hostname)
.then(runMocha);

View File

@ -0,0 +1,14 @@
/* eslint-env node */
module.exports = {
env: {
browser: true,
node: false,
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^(beforeInteractions|afterInteractions|interactionsToPerform)$', args: 'none' }],
},
globals: {
LightweightCharts: false,
},
};

View File

@ -0,0 +1,47 @@
function generateData() {
const res = [];
const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
for (let i = 0; i < 500; ++i) {
res.push({
time: time.getTime() / 1000,
value: i,
});
time.setUTCDate(time.getUTCDate() + 1);
}
return res;
}
function interactionsToPerform() {
return ['scrollLeft', 'scrollDown'];
}
let chart;
let startRange;
function beforeInteractions(container) {
chart = LightweightCharts.createChart(container);
const mainSeries = chart.addLineSeries();
mainSeries.setData(generateData());
return new Promise(resolve => {
requestAnimationFrame(() => {
startRange = chart.timeScale().getVisibleLogicalRange();
resolve();
});
});
}
function afterInteractions() {
const endRange = chart.timeScale().getVisibleLogicalRange();
const pass = Boolean(startRange.from !== endRange.from && startRange.to !== endRange.to);
if (!pass) {
throw new Error('Expected visible logical range to have changed.');
}
return Promise.resolve();
}

View File

@ -0,0 +1,54 @@
function generateData() {
const res = [];
const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
for (let i = 0; i < 500; ++i) {
res.push({
time: time.getTime() / 1000,
value: i,
});
time.setUTCDate(time.getUTCDate() + 1);
}
return res;
}
function interactionsToPerform() {
return ['scrollLeft', 'scrollDown'];
}
let chart;
let startRange;
function beforeInteractions(container) {
chart = LightweightCharts.createChart(container, {
handleScroll: {
mouseWheel: false,
},
handleScale: {
mouseWheel: false,
},
});
const mainSeries = chart.addLineSeries();
mainSeries.setData(generateData());
return new Promise(resolve => {
requestAnimationFrame(() => {
startRange = chart.timeScale().getVisibleLogicalRange();
resolve();
});
});
}
function afterInteractions() {
const endRange = chart.timeScale().getVisibleLogicalRange();
const pass = Boolean(startRange.from === endRange.from && startRange.to === endRange.to);
if (!pass) {
throw new Error('Expected visible logical range to be unchanged.');
}
return Promise.resolve();
}

View File

@ -0,0 +1,54 @@
function generateData() {
const res = [];
const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
for (let i = 0; i < 500; ++i) {
res.push({
time: time.getTime() / 1000,
value: i,
});
time.setUTCDate(time.getUTCDate() + 1);
}
return res;
}
function interactionsToPerform() {
return ['scrollLeft', 'scrollDown'];
}
let chart;
let startRange;
function beforeInteractions(container) {
chart = LightweightCharts.createChart(container, {
handleScroll: {
mouseWheel: true,
},
handleScale: {
mouseWheel: true,
},
});
const mainSeries = chart.addLineSeries();
mainSeries.setData(generateData());
return new Promise(resolve => {
requestAnimationFrame(() => {
startRange = chart.timeScale().getVisibleLogicalRange();
resolve();
});
});
}
function afterInteractions() {
const endRange = chart.timeScale().getVisibleLogicalRange();
const pass = Boolean(startRange.from !== endRange.from && startRange.to !== endRange.to);
if (!pass) {
throw new Error('Expected visible logical range to have changed.');
}
return Promise.resolve();
}

View File

@ -0,0 +1,62 @@
function generateData() {
const res = [];
const time = new Date(Date.UTC(2018, 0, 1, 0, 0, 0, 0));
for (let i = 0; i < 500; ++i) {
res.push({
time: time.getTime() / 1000,
value: i,
});
time.setUTCDate(time.getUTCDate() + 1);
}
return res;
}
function interactionsToPerform() {
return ['scrollLeft', 'scrollDown'];
}
let chart;
let startRange;
function beforeInteractions(container) {
chart = LightweightCharts.createChart(container, {
handleScroll: {
mouseWheel: false,
},
handleScale: {
mouseWheel: false,
},
});
const mainSeries = chart.addLineSeries();
mainSeries.setData(generateData());
return new Promise(resolve => {
requestAnimationFrame(() => {
chart.applyOptions({
handleScroll: {
mouseWheel: true,
},
handleScale: {
mouseWheel: true,
},
});
startRange = chart.timeScale().getVisibleLogicalRange();
resolve();
});
});
}
function afterInteractions() {
const endRange = chart.timeScale().getVisibleLogicalRange();
const pass = Boolean(startRange.from !== endRange.from && startRange.to !== endRange.to);
if (!pass) {
throw new Error('Expected visible logical range to have changed.');
}
return Promise.resolve();
}

View File

@ -13,6 +13,8 @@
"./coverage/**/*.ts",
"./graphics/graphics-test-cases.ts",
"./graphics/helpers/**/*.ts",
"./memleaks/**/*.ts"
"./memleaks/**/*.ts",
"./interactions/**/*.ts",
"./helpers/**/*.ts"
]
}