Skip to content

Commit

Permalink
chore: use jest & playwright testing 3d plots
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoiver committed Nov 17, 2023
1 parent eaf7b8c commit 2e3f049
Show file tree
Hide file tree
Showing 36 changed files with 344 additions and 133 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/test-3d.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: build

on: [push]

jobs:
build:
runs-on: macOS-latest
defaults:
run:
working-directory: ./3d

steps:
- name: Checkout
uses: actions/[email protected]

- name: Setup Node.js environment
uses: actions/[email protected]
with:
node-version: "16"

- name: Install Playwright browsers
run: npx playwright install --with-deps

- name: Run CI
run: |
npm install
npm run ci
- name: Upload snapshots to GitHub Actions Artifacts
if: ${{ failure() }}
uses: actions/upload-artifact@v3
with:
name: snapshots
path: |
__tests__/integration/snapshots/**/*-actual.*
__tests__/integration/snapshots/**/*-diff.*
retention-days: 1
19 changes: 0 additions & 19 deletions .github/workflows/test-ava.yml

This file was deleted.

4 changes: 4 additions & 0 deletions 3d/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ node_modules
esm
lib
dist

playwright-report
*-actual.png
*-diff.png
8 changes: 8 additions & 0 deletions 3d/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,11 @@ chart
palette: "spectral",
});
```

## Run Test Cases

To run playwright, you must download new browsers first:

```bash
$npx playwright install
```
39 changes: 39 additions & 0 deletions 3d/__tests__/e2e/snapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { chromium, devices } from "playwright";
import * as tests from "../plots";
import "./utils/useSnapshotMatchers";
import { sleep } from "./utils/sleep";

describe("Charts", () => {
Object.keys(tests).forEach((key) => {
it(key, async () => {
// Setup
const browser = await chromium.launch({
args: ["--headless", "--no-sandbox"],
});
const context = await browser.newContext(devices["Desktop Chrome"]);
const page = await context.newPage();

await page.addInitScript(() => {
window["USE_PLAYWRIGHT"] = 1;
});

// Go to test page served by vite devServer.
const url = `http://localhost:${globalThis.PORT}/?name=${key}`;
await page.goto(url);

await sleep(300);

// Chart already rendered, capture into buffer.
const buffer = await page.locator("canvas").screenshot();

const dir = `${__dirname}/snapshots/static`;
try {
const maxError = 0;
await expect(buffer).toMatchCanvasSnapshot(dir, key, { maxError });
} finally {
await context.close();
await browser.close();
}
});
});
});
Binary file added 3d/__tests__/e2e/snapshots/static/Bar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/static/Line.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/static/Scatter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added 3d/__tests__/e2e/snapshots/static/Surface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions 3d/__tests__/e2e/utils/sleep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function sleep(n: number) {
return new Promise((resolve) => {
setTimeout(resolve, n);
});
}
89 changes: 89 additions & 0 deletions 3d/__tests__/e2e/utils/toMatchCanvasSnapshot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as fs from "fs";
import * as path from "path";
import pixelmatch from "pixelmatch";
import { PNG } from "pngjs";

export type ToMatchCanvasSnapshotOptions = {
maxError?: number;
};

function writePNG(buffer: Buffer, path: string) {
const png = PNG.sync.read(buffer);
fs.writeFileSync(path, PNG.sync.write(png));
}

/**
* diff between PNGs
*/
function diff(src: string, target: string, diff: string, maxError = 0, showMismatchedPixels = true) {
const img1 = PNG.sync.read(fs.readFileSync(src));
const img2 = PNG.sync.read(fs.readFileSync(target));
const { width, height } = img1;

let diffPNG: PNG | null = null;
let output: Buffer | null = null;
if (showMismatchedPixels) {
diffPNG = new PNG({ width, height });
output = diffPNG.data;
}

// @see https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options
const mismatch = pixelmatch(img1.data, img2.data, output, width, height, {
threshold: 0.1,
});

if (showMismatchedPixels && mismatch > maxError && diffPNG) {
fs.writeFileSync(diff, PNG.sync.write(diffPNG));
}

return mismatch;
}

// @see https://jestjs.io/docs/26.x/expect#expectextendmatchers
export async function toMatchCanvasSnapshot(
buffer: Buffer,
dir: string,
name: string,
options: ToMatchCanvasSnapshotOptions = {},
): Promise<{ message: () => string; pass: boolean }> {
const { maxError = 0 } = options;
const namePath = path.join(dir, name);
const actualPath = path.join(dir, `${name}-actual.png`);
const expectedPath = path.join(dir, `${name}.png`);
const diffPath = path.join(dir, `${name}-diff.png`);

try {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
if (!fs.existsSync(expectedPath)) {
if (process.env.CI === "true") {
throw new Error(`Please generate golden image for ${namePath}`);
}
console.warn(`! generate ${namePath}`);
writePNG(buffer, expectedPath);
return {
message: () => `generate ${namePath}`,
pass: true,
};
} else {
writePNG(buffer, actualPath);
const error = diff(actualPath, expectedPath, diffPath, maxError);
if (error <= maxError) {
if (fs.existsSync(diffPath)) fs.unlinkSync(diffPath);
fs.unlinkSync(actualPath);
return {
message: () => `match ${namePath}`,
pass: true,
};
}
return {
message: () => `mismatch ${namePath} (error: ${error}) `,
pass: false,
};
}
} catch (e) {
return {
message: () => `${e}`,
pass: false,
};
}
}
14 changes: 14 additions & 0 deletions 3d/__tests__/e2e/utils/useSnapshotMatchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { toMatchCanvasSnapshot, ToMatchCanvasSnapshotOptions } from "./toMatchCanvasSnapshot";

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace jest {
interface Matchers<R> {
toMatchCanvasSnapshot(dir: string, name: string, options?: ToMatchCanvasSnapshotOptions): R;
}
}
}

expect.extend({
toMatchCanvasSnapshot,
});
11 changes: 10 additions & 1 deletion 3d/__tests__/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
</head>
<script type="module">
import * as plots from "./plots/index.ts";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";

const select = createSelect(() => {
const { value } = select;
Expand Down Expand Up @@ -39,7 +42,13 @@
async function render() {
// if (typeof preClear === "function") preClear();
const fn = plots[select.value];
const { finished, destroy } = await fn({ container: "container" });
// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
if (!window.USE_PLAYWRIGHT) {
renderer.registerPlugin(new ControlPlugin());
}
const { finished, destroy } = await fn({ container: "container", renderer });
preClear = destroy;
}
</script>
Expand Down
13 changes: 3 additions & 10 deletions 3d/__tests__/plots/bar-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function BarPerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand All @@ -27,7 +20,7 @@ export function BarPerspective(context) {
x: `x-${x}`,
z: `z-${z}`,
y: 10 - x - z,
color: Math.random() < 0.33 ? 0 : Math.random() < 0.67 ? 1 : 2,
color: x > 2 ? 1 : 2,
});
}
}
Expand Down
13 changes: 3 additions & 10 deletions 3d/__tests__/plots/bar.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function Bar(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand All @@ -28,7 +21,7 @@ export function Bar(context) {
x: `x-${x}`,
z: `z-${z}`,
y: 10 - x - z,
color: Math.random() < 0.33 ? 0 : Math.random() < 0.67 ? 1 : 2,
color: x > 2 ? 1 : 2,
});
}
}
Expand Down
10 changes: 1 addition & 9 deletions 3d/__tests__/plots/line-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function LinePerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
10 changes: 1 addition & 9 deletions 3d/__tests__/plots/line.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function Line(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
11 changes: 2 additions & 9 deletions 3d/__tests__/plots/scatter-legend.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

Expand Down Expand Up @@ -67,12 +65,7 @@ function legendColor(chart) {
}

export function ScatterLegend(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
11 changes: 2 additions & 9 deletions 3d/__tests__/plots/scatter-perspective.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
import { CameraType } from "@antv/g";
import { Renderer as WebGLRenderer } from "@antv/g-webgl";
import { Plugin as ThreeDPlugin, DirectionalLight } from "@antv/g-plugin-3d";
import { Plugin as ControlPlugin } from "@antv/g-plugin-control";
import { DirectionalLight } from "@antv/g-plugin-3d";
import { Runtime, extend, corelib } from "@antv/g2";
import { threedlib } from "../../src";

export function ScatterPerspective(context) {
const { container } = context;

// Create a WebGL renderer.
const renderer = new WebGLRenderer();
renderer.registerPlugin(new ThreeDPlugin());
renderer.registerPlugin(new ControlPlugin());
const { container, renderer } = context;

const Chart = extend(Runtime, { ...corelib(), ...threedlib() });
const chart = new Chart({
Expand Down
Loading

0 comments on commit 2e3f049

Please sign in to comment.