From 9ecd4d30d1ae01a7ceb4edf4effe85a3eed2b619 Mon Sep 17 00:00:00 2001 From: etowahadams Date: Mon, 29 Jan 2024 18:20:34 -0500 Subject: [PATCH] test(core): add Playwright component testing (#1037) * feat: playwright ct * feat: playwright ct testing * perf: persist GoslingTrackModels * fix: gitignore * fix: remove persisting gosling-track * docs: add comments --- .gitignore | 3 +- e2e/assets/example-spec-expected.png | Bin 34409 -> 0 bytes e2e/assets/example-spec.json | 82611 ---------------- e2e/performance.spec.ts | 58 - e2e/utils.ts | 72 - editor/example/index.ts | 2 +- package.json | 322 +- ...right.config.ts => playwright-ct.config.ts | 54 +- playwright/index.html | 12 + playwright/index.tsx | 2 + pnpm-lock.yaml | 87 +- src/core/gosling-component.test.tsx | 71 + tsconfig.json | 2 +- vite.config.js | 2 +- 14 files changed, 341 insertions(+), 82957 deletions(-) delete mode 100644 e2e/assets/example-spec-expected.png delete mode 100644 e2e/assets/example-spec.json delete mode 100644 e2e/performance.spec.ts delete mode 100644 e2e/utils.ts rename playwright.config.ts => playwright-ct.config.ts (52%) create mode 100644 playwright/index.html create mode 100644 playwright/index.tsx create mode 100644 src/core/gosling-component.test.tsx diff --git a/.gitignore b/.gitignore index e8a96926..fb4862e0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,5 @@ report.* *~ /test-results/ /playwright-report/ -/playwright/.cache/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/e2e/assets/example-spec-expected.png b/e2e/assets/example-spec-expected.png deleted file mode 100644 index 5309d29a0d90b76d4743458cdcf0db5d92f2bff4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34409 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 g|NqGU*Bra@*(VJN { - // Enable clipboard permissions. This is needed to copy the spec to the clipboard in the chromium browser. - await context.grantPermissions(['clipboard-read', 'clipboard-write']); - await page.goto('/'); -}); - -test('Measure zoom time', async ({ page, browser }, testInfo) => { - // This test can take a while to run, so we set the timeout to 60 seconds - test.setTimeout(60000); // 60 seconds - - // Get the spec we want to test and paste it into the editor - const jsonString = fs.readFileSync('./e2e/assets/example-spec.json', 'utf-8'); - await changeEditorSpec(page, jsonString); - - // Wait for the visualization to render - const gosComponent = page.getByLabel('Gosling visualization'); - - // Uncomment this to see the screenshot of the component in the report - // const screenshot = await gosComponent.screenshot(); - // await testInfo.attach('gosComponentScreenshot', { - // body: screenshot, - // contentType: 'image/png', - // }); - - await checkScreenshotUntilMatches( - gosComponent, - 'e2e/assets/example-spec-expected.png', - 10000 - ); - - // Hover over a track - await delay(1000); - const centerTrack: Locator = page.locator('.center-track').first(); - await centerTrack.hover(); - - // Start timer and zoom in - const startTime = Date.now(); - const zoomSteps = 15; // Trigger zoomSteps number of zooms - for (let i = 0; i < zoomSteps; i++) { - await page.mouse.wheel(0, -1); - } - const endTime = Date.now(); - const zoomTime = endTime - startTime; - console.log(`Zoom time: ${zoomTime}ms`); - - const screenshot = await gosComponent.screenshot(); - await testInfo.attach('gosComponentScreenshot', { - body: screenshot, - contentType: 'image/png', - }); - - // Just make sure the zoom time is less than 9 seconds. This is how long it in CI - expect(zoomTime).toBeLessThan(9000); -}); diff --git a/e2e/utils.ts b/e2e/utils.ts deleted file mode 100644 index a884316d..00000000 --- a/e2e/utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { type Page, type Locator } from '@playwright/test'; -import { PNG } from 'pngjs'; -import pixelmatch from 'pixelmatch'; -import * as fs from 'fs'; - -export function delay(time: number) { - return new Promise(resolve => { - setTimeout(resolve, time); - }); -} - -/** - * Compares two PNG files and returns true if they are the same. - */ -export function isPngSame(newImg: Buffer, oldImg: Buffer) { - const img1 = PNG.sync.read(newImg); - const img2 = PNG.sync.read(oldImg); - // check if the images have the same dimensions - if (img1.width !== img2.width || img1.height !== img2.height) return false; - - const { width, height } = img1; - const diff = new PNG({ width, height }); - const pixeldifference = pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 }); - // only write to file if there is a difference in the images - return pixeldifference === 0; -} - -/** - * This function changes the editor spec by pasting the given JSON string. - * Make sure to use context.grantPermissions(['clipboard-read', 'clipboard-write']); before calling this function. - */ -export async function changeEditorSpec(page: Page, jsonString: string) { - // Copy the spec to the keyboard using the clipboard API - await page.evaluate(jsonString => { - navigator.clipboard.writeText(jsonString); - }, jsonString); - // click into the text editor - await page.mouse.click(200, 200); - // Control+A to select all - await page - .getByRole('textbox', { name: 'Editor content;Press Alt+F1 for Accessibility Options.' }) - .press('Control+KeyA'); - // Backspace to delete what is in the text editor - await page.keyboard.press('Backspace'); - - await delay(100); - // Right click to pull up menu - await page.mouse.click(200, 200, { button: 'right' }); - await delay(100); // this is needed to wait for the context menu to appear - // Click on the paste button - await page.getByRole('menuitem', { name: 'Paste' }).click(); -} - -/** - * This function polls until the screenshot of the given component matches the expected screenshot. - */ -export async function checkScreenshotUntilMatches(component: Locator, expectedScreenshotPath: string, timeout: number) { - let screenshotMatchesExpected = false; - let timeElapsed = 0; - const compImgBuffer = fs.readFileSync(expectedScreenshotPath); - - while (!screenshotMatchesExpected && timeElapsed < timeout) { - const screenshot = await component.screenshot(); - - screenshotMatchesExpected = isPngSame(screenshot, compImgBuffer); - - if (!screenshotMatchesExpected) { - await delay(50); // wait 10ms before polling again - timeElapsed += 50; - } - } -} \ No newline at end of file diff --git a/editor/example/index.ts b/editor/example/index.ts index dd914725..aa7a6778 100644 --- a/editor/example/index.ts +++ b/editor/example/index.ts @@ -93,7 +93,7 @@ export const ExampleGroups: { export interface Example { group: ExampleGroup; name: string; - spec: GoslingSpec | string; + spec: GoslingSpec; id?: string; description?: string; underDevelopment?: boolean; diff --git a/package.json b/package.json index 5fd596b7..0043a6fa 100644 --- a/package.json +++ b/package.json @@ -1,165 +1,165 @@ { - "name": "gosling.js", - "author": "Sehi L'Yi", - "version": "0.15.0", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/gosling-lang/gosling.js" + "name": "gosling.js", + "author": "Sehi L'Yi", + "version": "0.15.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/gosling-lang/gosling.js" + }, + "homepage": "https://gosling-lang.github.io/gosling.js/", + "main": "dist/gosling.js", + "module": "dist/gosling.js", + "types": "dist/src/index.d.ts", + "packageManager": "pnpm@8.6.11", + "files": [ + "dist" + ], + "type": "module", + "exports": { + ".": { + "types": "./dist/src/index.d.ts", + "import": "./dist/gosling.js" }, - "homepage": "https://gosling-lang.github.io/gosling.js/", - "main": "dist/gosling.js", - "module": "dist/gosling.js", - "types": "dist/src/index.d.ts", - "packageManager": "pnpm@8.6.11", - "files": [ - "dist" - ], - "type": "module", - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/gosling.js" - }, - "./utils": { - "types": "./dist/src/exported-utils.d.ts", - "import": "./dist/utils.js" - } - }, - "scripts": { - "start": "vite --mode editor", - "start-embed": "vite", - "build": "run-s build-clear build-types build-lib", - "build-lib": "vite build --mode lib", - "build-types": "tsc --emitDeclarationOnly -p tsconfig.build.json", - "build-editor": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build", - "build-clear": "rm -rf ./dist", - "preview": "vite preview", - "check": "tsc --noEmit", - "test": "vitest", - "coverage": "vitest run --coverage", - "format": "eslint src/ editor/ --fix && prettier 'editor/**/*.css' --write", - "schema": "node scripts/generate-schemas.mjs", - "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", - "knip": "knip --config knip.config.json", - "e2e": "playwright test", - "preinstall": "npx only-allow pnpm" - }, - "peerDependencies": { - "pixi.js": "^6.3.0", - "react": "^16.6.3 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.6.3 || ^17.0.0 || ^18.0.0" - }, - "dependencies": { - "@gmod/bam": "^1.1.18", - "@gmod/bbi": "^3.0.1", - "@gmod/bed": "^2.1.2", - "@gmod/gff": "^1.3.0", - "@gmod/tabix": "^1.5.6", - "@gmod/vcf": "^5.0.10", - "allotment": "^1.19.0", - "bezier-js": "4.0.3", - "buffer": "^6.0.3", - "css-element-queries": "^1.2.3", - "d3-array": "^2.5.1", - "d3-color": "^2.0.0", - "d3-dsv": "^2.0.0", - "d3-format": "^3.1.0", - "d3-scale": "^3.2.1", - "d3-scale-chromatic": "^2.0.0", - "d3-shape": "^2.0.0", - "events": "^3.3.0", - "fflate": "^0.7.1", - "generic-filehandle": "^3.0.1", - "higlass": "^1.13.4", - "higlass-register": "^0.3.0", - "higlass-text": "^0.1.1", - "json-stringify-pretty-compact": "^2.0.0", - "jspdf": "^2.3.1", - "lodash-es": "^4.17.21", - "monaco-editor": "^0.27.0", - "nanoevents": "^7.0.1", - "pubsub-js": "^1.9.3", - "quick-lru": "^6.1.1", - "rbush": "^3.0.1", - "stream-browserify": "^3.0.0", - "threads": "^1.6.4" - }, - "devDependencies": { - "@playwright/test": "^1.39.0", - "@types/bezier-js": "^4.1.0", - "@types/d3": "^7.4.3", - "@types/d3-array": "^3.2.1", - "@types/d3-color": "^3.1.3", - "@types/d3-drag": "^2.0.0", - "@types/d3-dsv": "^3.0.1", - "@types/d3-format": "^3.0.4", - "@types/d3-scale": "^4.0.8", - "@types/d3-scale-chromatic": "^3.0.3", - "@types/d3-selection": "^2.0.0", - "@types/d3-shape": "^3.1.6", - "@types/lodash-es": "^4.17.5", - "@types/node": "^18.6.2", - "@types/pixelmatch": "^5.2.5", - "@types/pngjs": "^6.0.3", - "@types/pubsub-js": "^1.8.2", - "@types/rbush": "^3.0.0", - "@types/react": "^18.2.0", - "@types/react-dom": "^18.2.0", - "@types/react-resize-detector": "^4.2.0", - "@types/react-router-dom": "^5.1.6", - "@typescript-eslint/eslint-plugin": "^5.56.0", - "@typescript-eslint/parser": "^5.56.0", - "@vitejs/plugin-react": "^4.1.0", - "@vitest/coverage-v8": "^0.34.6", - "ajv": "^6.12.2", - "c8": "^7.11.2", - "conventional-changelog-cli": "^2.1.1", - "d3-drag": "^2.0.0", - "d3-selection": "^2.0.0", - "esbuild": "^0.12.25", - "eslint": "^8.19.0", - "eslint-config-prettier": "^8.5.0", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.30.1", - "fetch-jsonp": "^1.1.3", - "jsdom": "^19.0.0", - "jsoncrush": "^1.1.6", - "knip": "^2.30.0", - "npm-run-all": "^4.1.5", - "pixelmatch": "^5.3.0", - "pixi.js": "^6.3.0", - "pngjs": "^7.0.0", - "prettier": "^2.0.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-markdown": "^5.0.3", - "react-monaco-editor": "^0.45.0", - "react-resize-detector": "^4.2.3", - "react-router-dom": "^5.2.0", - "remark-gfm": "^1.0.0", - "safe-stable-stringify": "^2.4.3", - "strip-json-comments": "^3.1.1", - "ts-json-schema-generator": "^1.0.0", - "typescript": "^5.0.2", - "vite": "^4.4.11", - "vitest": "^0.34.6", - "vitest-canvas-mock": "^0.3.3" - }, - "resolutions": { - "slugid": "^3.0.0" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] + "./utils": { + "types": "./dist/src/exported-utils.d.ts", + "import": "./dist/utils.js" } + }, + "scripts": { + "start": "vite --mode editor", + "start-embed": "vite", + "build": "run-s build-clear build-types build-lib", + "build-lib": "vite build --mode lib", + "build-types": "tsc --emitDeclarationOnly -p tsconfig.build.json", + "build-editor": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build", + "build-clear": "rm -rf ./dist", + "preview": "vite preview", + "check": "tsc --noEmit", + "test": "vitest", + "coverage": "vitest run --coverage", + "format": "eslint src/ editor/ --fix && prettier 'editor/**/*.css' --write", + "schema": "node scripts/generate-schemas.mjs", + "version": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", + "knip": "knip --config knip.config.json", + "preinstall": "npx only-allow pnpm", + "test-ct": "playwright test -c playwright-ct.config.ts" + }, + "peerDependencies": { + "pixi.js": "^6.3.0", + "react": "^16.6.3 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.6.3 || ^17.0.0 || ^18.0.0" + }, + "dependencies": { + "@gmod/bam": "^1.1.18", + "@gmod/bbi": "^3.0.1", + "@gmod/bed": "^2.1.2", + "@gmod/gff": "^1.3.0", + "@gmod/tabix": "^1.5.6", + "@gmod/vcf": "^5.0.10", + "allotment": "^1.19.0", + "bezier-js": "4.0.3", + "buffer": "^6.0.3", + "css-element-queries": "^1.2.3", + "d3-array": "^2.5.1", + "d3-color": "^2.0.0", + "d3-dsv": "^2.0.0", + "d3-format": "^3.1.0", + "d3-scale": "^3.2.1", + "d3-scale-chromatic": "^2.0.0", + "d3-shape": "^2.0.0", + "events": "^3.3.0", + "fflate": "^0.7.1", + "generic-filehandle": "^3.0.1", + "higlass": "^1.13.4", + "higlass-register": "^0.3.0", + "higlass-text": "^0.1.1", + "json-stringify-pretty-compact": "^2.0.0", + "jspdf": "^2.3.1", + "lodash-es": "^4.17.21", + "monaco-editor": "^0.27.0", + "nanoevents": "^7.0.1", + "pubsub-js": "^1.9.3", + "quick-lru": "^6.1.1", + "rbush": "^3.0.1", + "stream-browserify": "^3.0.0", + "threads": "^1.6.4" + }, + "devDependencies": { + "@playwright/experimental-ct-react": "^1.41.1", + "@types/bezier-js": "^4.1.0", + "@types/d3": "^7.4.3", + "@types/d3-array": "^3.2.1", + "@types/d3-color": "^3.1.3", + "@types/d3-drag": "^2.0.0", + "@types/d3-dsv": "^3.0.1", + "@types/d3-format": "^3.0.4", + "@types/d3-scale": "^4.0.8", + "@types/d3-scale-chromatic": "^3.0.3", + "@types/d3-selection": "^2.0.0", + "@types/d3-shape": "^3.1.6", + "@types/lodash-es": "^4.17.5", + "@types/node": "^18.6.2", + "@types/pixelmatch": "^5.2.5", + "@types/pngjs": "^6.0.3", + "@types/pubsub-js": "^1.8.2", + "@types/rbush": "^3.0.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@types/react-resize-detector": "^4.2.0", + "@types/react-router-dom": "^5.1.6", + "@typescript-eslint/eslint-plugin": "^5.56.0", + "@typescript-eslint/parser": "^5.56.0", + "@vitejs/plugin-react": "^4.1.0", + "@vitest/coverage-v8": "^0.34.6", + "ajv": "^6.12.2", + "c8": "^7.11.2", + "conventional-changelog-cli": "^2.1.1", + "d3-drag": "^2.0.0", + "d3-selection": "^2.0.0", + "esbuild": "^0.12.25", + "eslint": "^8.19.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.30.1", + "fetch-jsonp": "^1.1.3", + "jsdom": "^19.0.0", + "jsoncrush": "^1.1.6", + "knip": "^2.30.0", + "npm-run-all": "^4.1.5", + "pixelmatch": "^5.3.0", + "pixi.js": "^6.3.0", + "pngjs": "^7.0.0", + "prettier": "^2.0.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-markdown": "^5.0.3", + "react-monaco-editor": "^0.45.0", + "react-resize-detector": "^4.2.3", + "react-router-dom": "^5.2.0", + "remark-gfm": "^1.0.0", + "safe-stable-stringify": "^2.4.3", + "strip-json-comments": "^3.1.1", + "ts-json-schema-generator": "^1.0.0", + "typescript": "^5.0.2", + "vite": "^4.4.11", + "vitest": "^0.34.6", + "vitest-canvas-mock": "^0.3.3" + }, + "resolutions": { + "slugid": "^3.0.0" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } } diff --git a/playwright.config.ts b/playwright-ct.config.ts similarity index 52% rename from playwright.config.ts rename to playwright-ct.config.ts index 7f920fd6..1fbc116b 100644 --- a/playwright.config.ts +++ b/playwright-ct.config.ts @@ -1,16 +1,15 @@ -import { defineConfig, devices } from '@playwright/test'; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); +import { defineConfig, devices } from '@playwright/experimental-ct-react'; /** * See https://playwright.dev/docs/test-configuration. */ export default defineConfig({ - testDir: './e2e', + testDir: './src/core/', + testMatch: '*.test.tsx', + /* The base directory, relative to the config file, for snapshot files created with toMatchSnapshot and toHaveScreenshot. */ + snapshotDir: './__snapshots__', + /* Maximum time one test can run for. */ + timeout: 10 * 1000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ @@ -23,11 +22,15 @@ export default defineConfig({ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: 'http://localhost:5173', - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + /* Port to use for Playwright component endpoint. */ + ctPort: 3100, + ctViteConfig: { + /* Vite config to use for the component server. */ + configFile: './vite.config.js', + }, }, /* Configure projects for major browsers */ @@ -36,42 +39,13 @@ export default defineConfig({ name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, - // { // name: 'firefox', // use: { ...devices['Desktop Firefox'] }, // }, - // { // name: 'webkit', // use: { ...devices['Desktop Safari'] }, // }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, ], - - /* Run your local dev server before starting the tests */ - webServer: { - command: 'pnpm start', - url: 'http://localhost:5173/', - reuseExistingServer: !process.env.CI, - }, }); diff --git a/playwright/index.html b/playwright/index.html new file mode 100644 index 00000000..610ddf8a --- /dev/null +++ b/playwright/index.html @@ -0,0 +1,12 @@ + + + + + + Testing Page + + +
+ + + diff --git a/playwright/index.tsx b/playwright/index.tsx new file mode 100644 index 00000000..ac6de14b --- /dev/null +++ b/playwright/index.tsx @@ -0,0 +1,2 @@ +// Import styles, initialize component theme here. +// import '../src/common.css'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 653e9d9f..0cfcca04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -109,9 +109,9 @@ dependencies: version: 1.7.0 devDependencies: - '@playwright/test': - specifier: ^1.39.0 - version: 1.40.1 + '@playwright/experimental-ct-react': + specifier: ^1.41.1 + version: 1.41.1(@types/node@18.6.2)(vite@4.4.11) '@types/bezier-js': specifier: ^4.1.0 version: 4.1.0 @@ -1403,12 +1403,41 @@ packages: dev: true optional: true - /@playwright/test@1.40.1: - resolution: {integrity: sha512-EaaawMTOeEItCRvfmkI9v6rBkF1svM8wjl/YPRrg2N2Wmp+4qJYkWtJsbew1szfKKDm6fPLy4YAanBhIlf9dWw==} + /@playwright/experimental-ct-core@1.41.1(@types/node@18.6.2): + resolution: {integrity: sha512-d7PxESV29x6W9RYs0mhkXmxr+6FfTbg2Tm/WJZlhgbIP+OLv79uJ8hl8ERsiBBFtH88sR+WmxHBMiZRpfpa6Fw==} engines: {node: '>=16'} hasBin: true dependencies: - playwright: 1.40.1 + playwright: 1.41.1 + playwright-core: 1.41.1 + vite: 4.5.2(@types/node@18.6.2) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - terser + dev: true + + /@playwright/experimental-ct-react@1.41.1(@types/node@18.6.2)(vite@4.4.11): + resolution: {integrity: sha512-Ht04RKD/4J69EPHOR4iAWtsOkkqswxonkcEEhniTNflGn30SoPyNww72LJECDrls+7AJayflJf4qe/cK1Ao/ug==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@playwright/experimental-ct-core': 1.41.1(@types/node@18.6.2) + '@vitejs/plugin-react': 4.1.0(vite@4.4.11) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + - vite dev: true /@sinclair/typebox@0.27.8: @@ -5691,18 +5720,18 @@ packages: pathe: 1.1.1 dev: true - /playwright-core@1.40.1: - resolution: {integrity: sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==} + /playwright-core@1.41.1: + resolution: {integrity: sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==} engines: {node: '>=16'} hasBin: true dev: true - /playwright@1.40.1: - resolution: {integrity: sha512-2eHI7IioIpQ0bS1Ovg/HszsN/XKNwEG1kbzSDDmADpclKc7CyqkHw7Mg2JCz/bbCxg25QUPcjksoMW7JcIFQmw==} + /playwright@1.41.1: + resolution: {integrity: sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==} engines: {node: '>=16'} hasBin: true dependencies: - playwright-core: 1.40.1 + playwright-core: 1.41.1 optionalDependencies: fsevents: 2.3.2 dev: true @@ -7208,6 +7237,42 @@ packages: fsevents: 2.3.3 dev: true + /vite@4.5.2(@types/node@18.6.2): + resolution: {integrity: sha512-tBCZBNSBbHQkaGyhGCDUGqeo2ph8Fstyp6FMSvTtsXeZSPpSMGlviAOav2hxVTqFcx8Hj/twtWKsMJXNY0xI8w==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.6.2 + esbuild: 0.18.20 + postcss: 8.4.32 + rollup: 3.29.4 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /vitest-canvas-mock@0.3.3(vitest@0.34.6): resolution: {integrity: sha512-3P968tYBpqYyzzOaVtqnmYjqbe13576/fkjbDEJSfQAkHtC5/UjuRHOhFEN/ZV5HVZIkaROBUWgazDKJ+Ibw+Q==} peerDependencies: diff --git a/src/core/gosling-component.test.tsx b/src/core/gosling-component.test.tsx new file mode 100644 index 00000000..d0da78bd --- /dev/null +++ b/src/core/gosling-component.test.tsx @@ -0,0 +1,71 @@ +import { test, expect } from '@playwright/experimental-ct-react'; +import { GoslingComponent } from './gosling-component'; +import { spec as JSON_SPEC_VISUAL_ENCODING } from '../../editor/example/spec/visual-encoding'; +import { JsonExampleSpecs } from 'editor/example/json-spec'; +import React from 'react'; + +test.use({ viewport: { width: 1000, height: 1000 } }); + +async function zoom(direction: 'in' | 'out', page: any, steps = 15) { + const zoomDirection = direction === 'in' ? -1 : 1; // Zoom in or out + for (let i = 0; i < steps; i++) { + await page.mouse.wheel(0, zoomDirection * 50); + } +} +/** + * This tests the zooming performance of Gosling. It zooms in and out 15 times and records the time it takes to zoom. + */ +test('Zoom visual encoding', async ({ mount, page }) => { + test.setTimeout(60000); // 60 seconds + await mount(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(5000); + + const centerTrack = page.locator('.center-track').first(); + await centerTrack.hover(); + + // Start timer and zoom in + const zoomTimes: number[] = []; + for (let i = 0; i < 15; i++) { + const startTime = Date.now(); + await zoom('in', page); + const endTime = Date.now(); + const zoomTime = endTime - startTime; + zoomTimes.push(zoomTime); + console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); + await zoom('out', page); + } + console.warn('Minimum:', Math.min(...zoomTimes)); + expect(Math.min(...zoomTimes)).toBeLessThan(330); + + // const screenshot = await component.screenshot(); + // await testInfo.attach('gosComponentScreenshot', { + // body: screenshot, + // contentType: 'image/png' + // }); +}); + +test('Zoom multiple sequence alignment', async ({ mount, page }) => { + test.setTimeout(60000); // 60 seconds + await mount(); + await page.waitForLoadState('networkidle'); + await page.waitForTimeout(5000); + + // Hover over the third track + const centerTracks = page.locator('.center-track'); + const thirdCenterTrack = centerTracks.nth(2); + await thirdCenterTrack.hover(); + + const zoomTimes: number[] = []; + for (let i = 0; i < 10; i++) { + const startTime = Date.now(); + await zoom('in', page, 5); + const endTime = Date.now(); + const zoomTime = endTime - startTime; + zoomTimes.push(zoomTime); + console.warn(`Zoom time ${i + 1}: ${zoomTime}ms`); + await zoom('out', page); + } + console.warn('Minimum:', Math.min(...zoomTimes)); + expect(Math.min(...zoomTimes)).toBeLessThan(500); +}); diff --git a/tsconfig.json b/tsconfig.json index 593ccc3f..a99a47df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -41,7 +41,7 @@ "src/index.ts", "editor/index.tsx", ], - "include": ["src", "src/**/*.d.ts", "editor"], + "include": ["src", "src/**/*.d.ts", "editor", "e2e"], "exclude": [ "node_modules", "public", diff --git a/vite.config.js b/vite.config.js index 8693651a..a33be702 100644 --- a/vite.config.js +++ b/vite.config.js @@ -116,7 +116,7 @@ const dev = defineConfig({ const testing = defineConfig({ resolve: { alias }, test: { - exclude: ['./node_modules/**', './dist/**', './e2e/**'], + exclude: ['./node_modules/**', './dist/**', '**/*.test.tsx'], globals: true, setupFiles: [path.resolve(__dirname, './scripts/setup-vitest.js')], environment: 'jsdom',