feat: use serverside rendering for integration tests (#4793)

* feat: use serverside rendering for integration tests

* chore: extend svg & webgl matchers for jest

* chore: interactive events can be triggerred in test case now

* chore: increase timeout

* chore: skip WebGL snapshot for now

* chore: support animation snapshot in test case

* chore: DOM API can be used in test case

* fix: make graph.getItemById private
This commit is contained in:
xiaoiver 2023-08-10 10:22:51 +08:00 committed by GitHub
parent a06766b440
commit 73bd1fed41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 2139 additions and 165406 deletions

View File

@ -17,8 +17,6 @@ jobs:
- name: Run CI
run: |
npm install
npm run test
cd packages/g6
npm install
npm run ci

4
.gitignore vendored
View File

@ -27,8 +27,8 @@ coverage
stats.html
# Snapshots error images
__tests__/integration/snapshots/**/*-actual.*
__tests__/integration/snapshots/**/*-diff.*
packages/g6/tests/integration/snapshots/**/*-actual.*
packages/g6/tests/integration/snapshots/**/*-diff.*
# Website cache byb dumi
site/.dumi/tmp

165138
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,13 @@
{
"name": "g6",
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"test": "echo passed",
"postinstall": "husky install",
"prepare": "husky install"
},
"devDependencies": {
"@types/jest": "^29.5.1",
"@commitlint/cli": "^17.5.0",
"@commitlint/config-conventional": "^17.4.4",
"husky": "^8.0.3",

View File

@ -0,0 +1,29 @@
// Installing third-party modules by tnpm or cnpm will name modules with underscore as prefix.
// In this case _{module} is also necessary.
const esm = ['internmap', 'd3-*', 'lodash-es']
.map((d) => `_${d}|${d}`)
.join('|');
module.exports = {
testEnvironment: 'jsdom',
testTimeout: 100000,
preset: 'ts-jest/presets/js-with-ts',
globals: {
'ts-jest': {
diagnostics: {
exclude: ['**'],
},
tsconfig: {
target: 'esnext', // Increase test coverage.
allowJs: true,
sourceMap: true,
},
},
},
collectCoverageFrom: ['src/**/*.ts'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
collectCoverage: false,
testRegex: '(/tests/.*\\.(test|spec))\\.(ts|tsx|js)$',
// Transform esm to cjs.
transformIgnorePatterns: [`<rootDir>/node_modules/(?!(${esm}))`],
};

View File

@ -36,7 +36,7 @@
"build": "run-p build:*",
"bundle-vis": "cross-env BUNDLE_VIS=1 run-p build:umd",
"prepublishOnly": "npm run ci",
"ci": "run-s lint build",
"ci": "run-s lint build test:integration",
"clean": "rimraf es lib",
"coverage": "jest --coverage",
"doc": "rimraf apis && typedoc",
@ -44,6 +44,7 @@
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx",
"fix": "eslint ./src ./tests --fix && prettier ./src ./tests --write ",
"test": "jest",
"test:integration": "node --expose-gc --max-old-space-size=4096 --unhandled-rejections=strict node_modules/jest/bin/jest tests/integration/ --config jest.node.config.js --coverage -i --logHeapUsage --detectOpenHandles",
"size": "limit-size",
"test-live": "DEBUG_MODE=1 jest --watch ./tests/unit/edge-spec.ts",
"test-behavior": "DEBUG_MODE=1 jest --watch ./tests/unit/item-3d-spec.ts"
@ -82,20 +83,31 @@
"@antv/g6": "^4.8.13",
"@kayahr/jest-electron-runner": "^5.1.1",
"@rollup/plugin-terser": "^0.4.3",
"@types/gl": "6.0.2",
"@types/jest": "^29.5.1",
"@types/jsdom": "^21.1.1",
"@types/node": "13.11.1",
"@types/pixelmatch": "^5.2.4",
"@types/pngjs": "^6.0.1",
"@types/xmlserializer": "^0.6.3",
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
"@umijs/fabric": "^2.0.0",
"babel-loader": "^8.0.6",
"canvas": "2.11.0",
"eslint": "^7.22.0",
"eslint-plugin-import": "^2.22.1",
"father": "^2.29.1",
"gl": "^6.0.2",
"jest": "^28.1.3",
"jest-environment-jsdom": "",
"jest-extended": "^0.11.2",
"jsdom": "^19.0.0",
"limit-size": "^0.1.4",
"lint-staged": "^10.5.4",
"npm-run-all": "^4.1.5",
"pixelmatch": "5.3.0",
"pngjs": "^6.0.0",
"prettier": "^2.2.1",
"rimraf": "^3.0.0",
"rollup": "^2.79.1",
@ -107,7 +119,8 @@
"ts-jest": "^28.0.8",
"typedoc": "^0.24.0",
"typescript": "^4.9.5",
"vite": "^4.2.2"
"vite": "^4.2.2",
"xmlserializer": "^0.6.1"
},
"limit-size": [
{

View File

@ -144,7 +144,7 @@ export default abstract class Item implements IItem {
}
}
this.renderExt = new RenderExtension({
themeStyles: this.themeStyles.default,
themeStyles: this.themeStyles?.default,
lodStrategy,
device: this.device,
zoom: this.zoom,

View File

@ -238,6 +238,9 @@ export default class Node extends Item {
point,
);
break;
case 'mesh':
intersectPoint = innerPoint;
break;
default: {
const bbox =
this.renderExt.boundsCache?.keyShapeLocal ||

View File

@ -256,11 +256,11 @@ export class InteractionController {
this.handleCanvasEvent,
);
});
const $dom = this.graph.canvas.getContextService().getDomElement();
Object.values(DOM_EVENT_TYPE).forEach((eventName) => {
this.graph.canvas
.getContextService()
.getDomElement()
.addEventListener(eventName, this.handleDOMEvent);
if ($dom && $dom.addEventListener) {
$dom.addEventListener(eventName, this.handleDOMEvent);
}
});
};

View File

@ -694,6 +694,8 @@ export class ItemController {
private onDestroy = () => {
Object.values(this.itemMap).forEach((item) => item.destroy());
// Fix OOM problem, since this map will hold all the refs of items.
this.itemMap = {};
};
private onTransientUpdate(param: {

View File

@ -42,6 +42,7 @@ export class LayoutController {
*/
private tap() {
this.graph.hooks.layout.tap(this.onLayout.bind(this));
this.graph.hooks.destroy.tap(this.onDestroy.bind(this));
}
private async onLayout(params: {
@ -189,7 +190,11 @@ export class LayoutController {
}
}
destroy() {
getCurrentAnimation() {
return this.currentAnimation;
}
onDestroy() {
this.stopLayout();
if (this.currentSupervisor) {

View File

@ -54,10 +54,14 @@ export class ThemeController {
if (this.extension) {
this.solver = new this.extension(this.themeConfig, this.themes);
this.specification = this.solver.specification;
// apply canvas style in theme to the background canvas dom
const { canvas } = this.specification;
const dom = canvases.background.getContextService().getDomElement();
Object.keys(canvas).forEach((key) => (dom.style[key] = canvas[key]));
if (this.specification) {
// apply canvas style in theme to the background canvas dom
const { canvas } = this.specification;
const dom = canvases.background.getContextService().getDomElement();
if (dom && dom.style) {
Object.keys(canvas).forEach((key) => (dom.style[key] = canvas[key]));
}
}
}
}

View File

@ -124,8 +124,17 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
}
private initCanvas() {
const { renderer, container, width, height } = this.specification;
let pixelRatio;
const {
renderer,
container,
canvas,
backgroundCanvas,
transientCanvas,
width,
height,
} = this.specification;
let pixelRatio: number;
if (renderer && !isString(renderer)) {
// @ts-ignore
this.rendererType = renderer.type || 'canvas';
@ -135,55 +144,80 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
// @ts-ignore
this.rendererType = renderer || 'canvas';
}
const containerDOM = isString(container)
? document.getElementById(container as string)
: container;
if (!containerDOM) {
console.error(
`Create graph failed. The container for graph ${containerDOM} is not exist.`,
/**
* These 3 canvases can be passed in by users, e.g. when doing serverside rendering we can't use DOM API.
*/
if (canvas) {
this.canvas = canvas;
this.backgroundCanvas = backgroundCanvas;
this.transientCanvas = transientCanvas;
} else {
const containerDOM = isString(container)
? document.getElementById(container as string)
: (container as HTMLElement);
if (!containerDOM) {
console.error(
`Create graph failed. The container for graph ${containerDOM} is not exist.`,
);
this.destroy();
return;
}
this.container = containerDOM;
const size = [width, height];
if (size[0] === undefined) {
size[0] = containerDOM.scrollWidth;
}
if (size[1] === undefined) {
size[1] = containerDOM.scrollHeight;
}
this.backgroundCanvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
);
this.canvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
);
this.transientCanvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
);
this.destroy();
return;
}
this.container = containerDOM;
const size = [width, height];
if (size[0] === undefined) {
size[0] = containerDOM.scrollWidth;
}
if (size[1] === undefined) {
size[1] = containerDOM.scrollHeight;
}
this.backgroundCanvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
);
this.canvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
);
this.transientCanvas = createCanvas(
this.rendererType,
containerDOM,
size[0],
size[1],
pixelRatio,
true,
{
pointerEvents: 'none',
},
);
Promise.all(
[this.backgroundCanvas, this.canvas, this.transientCanvas].map(
(canvas) => canvas.ready,
),
).then(() => (this.canvasReady = true));
).then(() => {
[this.backgroundCanvas, this.canvas, this.transientCanvas].forEach(
(canvas, i) => {
const $domElement = canvas
.getContextService()
.getDomElement() as unknown as HTMLElement;
if ($domElement && $domElement.style) {
$domElement.style.position = 'fixed';
$domElement.style.outline = 'none';
$domElement.tabIndex = 1; // Enable keyboard events
// Transient canvas should let interactive events go through.
if (i === 2) {
$domElement.style.pointerEvents = 'none';
}
}
},
);
this.canvasReady = true;
});
}
/**
@ -623,7 +657,7 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
public async focusItem(id: ID | ID[], effectTiming?: CameraAnimationOptions) {
let bounds: AABB | null = null;
for (const itemId of !isArray(id) ? [id] : id) {
const item = this.itemController.getItemById(itemId);
const item = this.getItemById(itemId);
if (item) {
const itemBounds = item.group.getBounds();
if (!bounds) {
@ -645,6 +679,15 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
}
}
/**
* Get item by id. We don't want to
* @param id
* @returns Node | Edge | Combo
*/
private getItemById(id: ID) {
return this.itemController.getItemById(id);
}
/**
* Get the size of the graph canvas.
* @returns [width, height]
@ -1368,6 +1411,14 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
this.layoutController.stopLayout();
}
/**
*
* @returns
*/
public getLayoutCurrentAnimation() {
return this.layoutController.getCurrentAnimation();
}
/**
* Switch mode.
* @param mode mode name
@ -1609,12 +1660,12 @@ export default class Graph<B extends BehaviorRegistry, T extends ThemeRegistry>
*/
public destroy(callback?: Function) {
// TODO: call the destroy functions after items' buildOut animations finished
setTimeout(() => {
this.canvas.destroy();
this.backgroundCanvas.destroy();
this.transientCanvas.destroy();
callback?.();
}, 500);
// setTimeout(() => {
this.canvas.destroy();
this.backgroundCanvas.destroy();
this.transientCanvas.destroy();
callback?.();
// }, 500);
this.hooks.destroy.emit({});

View File

@ -58,7 +58,6 @@ const DEFAULT_OPTIONS: ActivateRelationsOptions = {
};
export default class ActivateRelations extends Behavior {
options: ActivateRelationsOptions;
timer: number;
inactiveItems: {};
prevNodeIds: ID[];

View File

@ -99,7 +99,6 @@ const DEFAULT_OPTIONS: BrushSelectOptions = {
};
export default class BrushSelect extends Behavior {
options: BrushSelectOptions;
brush: DisplayObject | undefined;
selectedIds: IDSet | undefined = {
nodes: [],

View File

@ -58,7 +58,6 @@ const DEFAULT_OPTIONS: ClickSelectOptions = {
};
export default class ClickSelect extends Behavior {
options: ClickSelectOptions;
/**
* Cache the ids of items selected by this behavior
*/

View File

@ -28,8 +28,6 @@ const DEFAULT_OPTIONS: Required<CollapseExpandComboOptions> = {
};
export default class CollapseExpandCombo extends Behavior {
options: CollapseExpandComboOptions;
constructor(options: Partial<CollapseExpandComboOptions>) {
const finalOptions = Object.assign({}, DEFAULT_OPTIONS, options);
super(finalOptions);

View File

@ -66,8 +66,6 @@ const DEFAULT_OPTIONS: Required<DragCanvasOptions> = {
};
export default class DragCanvas extends Behavior {
options: DragCanvasOptions;
private pointerDownAt: Point;
private dragging: boolean; // pointerdown + pointermove a distance
private keydown: boolean;

View File

@ -82,8 +82,6 @@ const DEFAULT_OPTIONS: Required<DragComboOptions> = {
};
export default class DragCombo extends Behavior {
options: DragComboOptions;
// Private states
private hiddenEdges: EdgeModel[] = [];
private hiddenComboTreeRoots: (ComboModel | NodeModel)[] = [];

View File

@ -87,8 +87,6 @@ const DEFAULT_OPTIONS: Required<DragNodeOptions> = {
};
export default class DragNode extends Behavior {
options: DragNodeOptions;
// Private states
private hiddenEdges: EdgeModel[] = [];
private hiddenComboTreeItems: (ComboModel | NodeModel)[] = [];

View File

@ -39,7 +39,6 @@ const DEFAULT_OPTIONS: Required<HoverActivateOptions> = {
};
export default class HoverActivate extends Behavior {
options: HoverActivateOptions;
private currentItemInfo: { id: ID; itemType: ITEM_TYPE };
constructor(options: Partial<HoverActivateOptions>) {

View File

@ -39,8 +39,6 @@ const DEFAULT_OPTIONS: Required<OrbitCanvas3DOptions> = {
* Translate the 3d canvas along the plane parallel to the screen.
*/
export default class OrbitCanvas3D extends RotateCanvas3D {
options: OrbitCanvas3DOptions;
private previousType: CameraType;
constructor(options: Partial<OrbitCanvas3DOptions>) {

View File

@ -50,8 +50,6 @@ const MOTION_FACTOR = 10;
* Rotate the 3d canvas with the center of the graph.
*/
export default class RotateCanvas3D extends Behavior {
options: RotateCanvas3DOptions;
public pointStartAt: Point;
public keydown: boolean;
public speedUpKeydown: boolean;

View File

@ -39,8 +39,6 @@ const DEFAULT_OPTIONS: Required<TrackCanvas3DOptions> = {
* Translate the 3d canvas along the plane parallel to the screen.
*/
export default class TrackCanvas3D extends RotateCanvas3D {
options: TrackCanvas3DOptions;
private previousType: CameraType;
constructor(options: Partial<TrackCanvas3DOptions>) {

View File

@ -60,8 +60,6 @@ const DEFAULT_OPTIONS: Required<ZoomCanvas3DOptions> = {
* Zoom the 3d canvas along the ray vertical to the screen.
*/
export default class ZoomCanvas3D extends Behavior {
options: ZoomCanvas3DOptions;
private keydown: boolean;
constructor(options: Partial<ZoomCanvas3DOptions>) {
@ -80,16 +78,16 @@ export default class ZoomCanvas3D extends Behavior {
}
getEvents = () => {
this.graph.canvas
.getContextService()
.getDomElement()
.addEventListener(
const $el = this.graph.canvas.getContextService().getDomElement();
if ($el && $el.addEventListener) {
$el.addEventListener(
'wheel',
(e) => {
e.preventDefault();
},
{ passive: false },
);
}
if (this.options.trigger === 'wheel') {
return {

View File

@ -69,8 +69,6 @@ const DEFAULT_OPTIONS: Required<ZoomCanvasOptions> = {
};
export default class ZoomCanvas extends Behavior {
options: ZoomCanvasOptions;
private zooming: boolean; // pointerdown + pointermove a distance
private keydown: boolean;
private speedupKeydown: boolean;

View File

@ -282,6 +282,7 @@ export abstract class BaseEdge {
...this.defaultStyles.labelShape,
textAlign: positionPreset.textAlign,
wordWrapWidth,
isBillboard: true,
...positionStyle,
...otherStyle,
};
@ -527,7 +528,7 @@ export abstract class BaseEdge {
const { labelShape, labelBackgroundShape } = shapeMap;
const balanceRatio = 1 / zoom || 1;
this.zoomCache.balanceRatio = balanceRatio;
const { labelShape: labelStyle } = this.mergedStyles;
const { labelShape: labelStyle = {} } = this.mergedStyles;
const { position = 'bottom' } = labelStyle;
if (!labelShape) return;

View File

@ -86,10 +86,11 @@ export class LineEdge extends BaseEdge {
...keyShapeStyle,
x1: sourcePoint.x,
y1: sourcePoint.y,
z1: sourcePoint.z,
z1: sourcePoint.z || 0,
x2: targetPoint.x,
y2: targetPoint.y,
z2: targetPoint.z,
z2: targetPoint.z || 0,
isBillboard: true,
},
shapeMap,
model,

View File

@ -300,6 +300,7 @@ export abstract class BaseNode {
const style: any = {
...this.defaultStyles.labelShape,
...positionPreset,
isBillboard: true,
...otherStyle,
};
return this.upsertShape('text', 'labelShape', style, shapeMap, model);

View File

@ -102,8 +102,10 @@ export abstract class BaseNode3D extends BaseNode {
const style: any = {
...this.defaultStyles.labelShape,
...positionPreset,
isBillboard: true,
...otherStyle,
};
return this.upsertShape('text', 'labelShape', style, shapeMap);
}

View File

@ -28,7 +28,7 @@ export default class Grid extends Base {
public getDefaultCfgs(): GridConfig {
return {
img: GRID_PNG,
follow: true
follow: true,
};
}
@ -71,9 +71,9 @@ export default class Grid extends Base {
left: `0px`,
top: `0px`,
});
graphContainer.insertBefore(container, canvas);
graphContainer.insertBefore(container, canvas as Node);
this.container = container;
}
@ -94,8 +94,9 @@ export default class Grid extends Base {
if (!matrix) matrix = [1, 0, 0, 0, 1, 0, 0, 0, 1];
const isFollow = this.options.follow;
const transform = `matrix(${matrix[0]}, ${matrix[1]}, ${matrix[3]}, ${matrix[4]}, ${isFollow ? matrix[6] : '0'
}, ${isFollow ? matrix[7] : '0'})`;
const transform = `matrix(${matrix[0]}, ${matrix[1]}, ${matrix[3]}, ${
matrix[4]
}, ${isFollow ? matrix[6] : '0'}, ${isFollow ? matrix[7] : '0'})`;
modifyCSS(gridContainer, {
transform,

View File

@ -31,9 +31,6 @@ interface SpecThemeSolverOptions {
};
}
export default class SpecThemeSolver extends BaseThemeSolver {
protected specification: ThemeSpecification;
public options: SpecThemeSolverOptions;
public solver(
options: SpecThemeSolverOptions,
themes: ThemeSpecificationMap,

View File

@ -28,9 +28,6 @@ interface SubjectThemeSolverOptions {
}
export default class SubjectThemeSolver extends BaseThemeSolver {
protected specification: ThemeSpecification;
public options: SubjectThemeSolverOptions;
public solver(
options: SubjectThemeSolverOptions,
themes: ThemeSpecificationMap,

View File

@ -1,4 +1,3 @@
import { uniqueId } from '@antv/util';
import { IG6GraphEvent } from './event';
import { IGraph } from './graph';

View File

@ -1,3 +1,4 @@
import { Canvas } from '@antv/g';
import { AnimateCfg } from './animate';
import { Point } from './common';
import {
@ -36,7 +37,10 @@ export interface Specification<
T extends ThemeRegistry,
> {
type: 'graph' | 'tree';
container: string | HTMLElement;
container?: string | HTMLElement;
backgroundCanvas?: Canvas;
canvas?: Canvas;
transientCanvas?: Canvas;
width?: number;
height?: number;
renderer?:

View File

@ -1,4 +1,4 @@
import { Canvas, CanvasLike } from '@antv/g';
import { Canvas, IRenderer } from '@antv/g';
import { Renderer as CanvasRenderer } from '@antv/g-canvas';
import { Renderer as SVGRenderer } from '@antv/g-svg';
import { Renderer as WebGLRenderer } from '@antv/g-webgl';
@ -13,7 +13,6 @@ import { RendererName } from '../types/render';
* @param {number} width
* @param {number} height
* @param {number} pixelRatio optional
* @param {boolean} customCanvasTag whether create a <canvas /> for multiple canvas under the container
* @returns
*/
export const createCanvas = (
@ -22,10 +21,8 @@ export const createCanvas = (
width: number,
height: number,
pixelRatio?: number,
customCanvasTag = true,
style: any = {},
): Canvas => {
let renderer;
let renderer: IRenderer;
switch (rendererType.toLowerCase()) {
case 'svg':
renderer = new SVGRenderer();
@ -48,30 +45,13 @@ export const createCanvas = (
}),
);
if (typeof document !== 'undefined' && customCanvasTag) {
const canvasTag = document.createElement('canvas');
const dpr = pixelRatio || window.devicePixelRatio;
canvasTag.width = dpr * width;
canvasTag.height = dpr * height;
canvasTag.style.width = `${width}px`;
canvasTag.style.height = `${height}px`;
canvasTag.style.position = 'fixed';
canvasTag.style.outline = 'none';
canvasTag.tabIndex = 1; // Enable keyboard events
Object.assign(canvasTag.style, style);
container!.appendChild(canvasTag);
return new Canvas({
canvas: canvasTag as CanvasLike,
devicePixelRatio: pixelRatio,
renderer,
});
}
return new Canvas({
container,
width,
height,
devicePixelRatio: pixelRatio,
renderer,
supportsMutipleCanvasesInOneContainer: true,
});
};
@ -85,7 +65,7 @@ export const changeRenderer = (
rendererType: RendererName,
canvas: Canvas,
): Canvas => {
let renderer;
let renderer: IRenderer;
switch (rendererType.toLowerCase()) {
case 'svg':
renderer = new SVGRenderer();

View File

@ -0,0 +1,35 @@
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const { width, height } = context;
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'circular',
center: [width! / 2, height! / 2],
radius: 200,
},
node: (innerModel) => {
return {
...innerModel,
data: {
...innerModel.data,
animates: {
buildIn: [
{
fields: ['opacity'],
duration: 2000,
fill: 'both',
delay: 0,
},
],
},
},
};
},
});
};

View File

@ -1,15 +1,13 @@
import G6 from '../../../src/index';
import { container, height, width } from '../../datasets/const';
export default () => {
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
return new G6.Graph({
container,
width,
height,
...context,
type: 'graph',
layout: {
type: 'grid',
},
plugins: ['grid'],
node: {
labelShape: {
text: {

View File

@ -2,7 +2,12 @@ import behaviors_activateRelations from './behaviors/activate-relations';
import behaviors_brush_select from './behaviors/brush-select';
import behaviors_click_select from './behaviors/click-select';
import layouts_circular from './layouts/circular';
import layouts_grid from './layouts/grid';
import layouts_dagre from './layouts/dagre';
import layouts_force from './layouts/force';
import layouts_d3force from './layouts/d3force';
import layouts_custom from './layouts/custom';
import user_defined_canvas from './user-defined-canvas/circular';
import layouts_fruchterman_wasm from './layouts/fruchterman-wasm';
import layouts_forceatlas2_wasm from './layouts/forceatlas2-wasm';
import layouts_force_wasm from './layouts/force-wasm';
@ -26,11 +31,17 @@ import cubic_vertical_edge from './item/edge/cubic-vertical-edge';
import fisheye from './plugins/fisheye';
import tooltip from './demo/tooltip';
import comboBasic from './combo/combo-basic';
import animations_node_build_in from './animations/node-build-in';
export {
behaviors_activateRelations,
layouts_circular,
layouts_grid,
layouts_dagre,
layouts_force,
layouts_d3force,
layouts_custom,
user_defined_canvas,
layouts_fruchterman_wasm,
layouts_forceatlas2_wasm,
layouts_force_wasm,
@ -56,4 +67,5 @@ export {
fisheye,
tooltip,
comboBasic,
animations_node_build_in,
};

View File

@ -0,0 +1,12 @@
import { Canvas } from '@antv/g';
import { RendererName } from '../../src/types/render';
export type TestCaseContext = Partial<{
container: HTMLElement;
renderer: RendererName;
canvas: Canvas;
transientCanvas: Canvas;
backgroundCanvas: Canvas;
width: number;
height: number;
}>;

View File

@ -1,4 +1,5 @@
import { Graph, IGraph } from '../../../../src/index';
import { TestCaseContext } from '../../interface';
// @ts-nocheck
let graph: IGraph;
@ -48,16 +49,14 @@ const defaultData = {
};
// create container for controllers
const createCtrlContainer = () => {
const container = document.getElementById('container')!;
const createCtrlContainer = (container: HTMLElement) => {
const ctrlContainer = document.createElement('div');
ctrlContainer.id = 'ctrl-container';
ctrlContainer.style.width = '100%';
ctrlContainer.style.height = '200px';
ctrlContainer.style.backgroundColor = '#eee';
const appElement = document.getElementById('app')!;
appElement.insertBefore(ctrlContainer, container);
container.appendChild(ctrlContainer);
};
// Create options and control buttons (for selecting different features to test)
@ -83,7 +82,6 @@ const createControls = () => {
labelCb.style.zIndex = '100';
labelCb.addEventListener('click', (e) => {
console.log(labelCb.checked);
if (labelCb.checked) {
graph.updateData('edge', {
id: 'edge1',
@ -127,7 +125,6 @@ const createControls = () => {
iconCb.style.zIndex = '100';
iconCb.addEventListener('click', (e) => {
console.log(iconCb.checked);
if (iconCb.checked) {
graph.updateData('edge', {
id: 'edge1',
@ -180,7 +177,6 @@ const createControls = () => {
selectedStyleCb.style.zIndex = '100';
selectedStyleCb.addEventListener('click', (e) => {
console.log(selectedStyleCb.checked);
if (selectedStyleCb.checked) {
graph.setItemState('edge1', 'selected', true);
} else {
@ -210,7 +206,6 @@ const createControls = () => {
highlightStyleCb.style.zIndex = '100';
highlightStyleCb.addEventListener('click', (e) => {
console.log(highlightStyleCb.checked);
if (highlightStyleCb.checked) {
graph.setItemState('edge1', 'highlight', true);
} else {
@ -222,17 +217,16 @@ const createControls = () => {
parentEle.appendChild(highlightStyleCb);
};
export default () => {
export default (context: TestCaseContext) => {
const { container } = context;
// 1.create control container (for control buttons, etc.)
createCtrlContainer();
createCtrlContainer(container!);
createControls();
// 2.create graph
container = document.getElementById('container')!;
graph = new Graph({
container,
width: 500,
height: 500,
...context,
type: 'graph',
data: defaultData,
modes: {

View File

@ -0,0 +1,17 @@
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const { width, height } = context;
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'circular',
center: [width! / 2, height! / 2],
radius: 200,
},
});
};

View File

@ -0,0 +1,40 @@
import { Layout, LayoutMapping } from '@antv/layout';
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
class MyCustomLayout implements Layout<{}> {
async assign(graph, options?: {}): Promise<void> {
throw new Error('Method not implemented.');
}
async execute(graph, options?: {}): Promise<LayoutMapping> {
const nodes = graph.getAllNodes();
return {
nodes: nodes.map((node, i) => ({
id: node.id,
data: {
x: 0 + i * 10,
y: 250,
z: 0,
},
})),
edges: [],
};
}
options: {};
id: 'myCustomLayout';
}
// Register custom layout
G6.stdLib.layouts['myCustomLayout'] = MyCustomLayout;
export default (context: TestCaseContext) => {
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'myCustomLayout',
},
});
};

View File

@ -1,17 +1,17 @@
import G6 from '../../../src/index';
import { container, data, height, width } from '../../datasets/const';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default () => {
export default (context: TestCaseContext) => {
const { width, height } = context;
return new G6.Graph({
container,
width,
height,
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'd3force',
animated: true,
center: [250, 250],
center: [width! / 2, height! / 2],
preventOverlap: true,
nodeSize: 20,
},

View File

@ -0,0 +1,121 @@
import G6 from '../../../src/index';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const data = {
nodes: [
{
id: '1',
data: {
name: 'alps_file1',
},
},
{
id: '2',
data: {
name: 'alps_file2',
},
},
{
id: '3',
data: {
name: 'alps_file3',
},
},
{
id: '4',
data: {
name: 'sql_file1',
},
},
{
id: '5',
data: {
name: 'sql_file2',
},
},
{
id: '6',
data: {
name: 'feature_etl_1',
},
},
{
id: '7',
data: {
name: 'feature_etl_1',
},
},
{
id: '8',
data: {
name: 'feature_extractor',
},
},
],
edges: [
{
id: 'e1',
data: {},
source: '1',
target: '2',
},
{
id: 'e2',
data: {},
source: '1',
target: '3',
},
{
id: 'e3',
data: {},
source: '2',
target: '4',
},
{
id: 'e4',
data: {},
source: '3',
target: '4',
},
{
id: 'e5',
data: {},
source: '4',
target: '5',
},
{
id: 'e6',
data: {},
source: '5',
target: '6',
},
{
id: 'e7',
data: {},
source: '6',
target: '7',
},
{
id: 'e8',
data: {},
source: '6',
target: '8',
},
],
};
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'dagre',
nodeSize: 10,
ranksep: 20,
controlPoints: true,
begin: [20, 20],
align: 'UR',
},
});
};

View File

@ -0,0 +1,56 @@
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const { width, height } = context;
const graph = new G6.Graph({
...context,
type: 'graph',
renderer: 'webgl-3d',
// modes: {
// default: [
// {
// type: 'orbit-canvas-3d',
// trigger: 'drag',
// },
// 'zoom-canvas-3d',
// ],
// },
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'force',
dimensions: 3,
iterations: 100,
center: [width! / 2, height! / 2, 0],
},
edge: {
type: 'line-edge',
keyShape: {
lineWidth: 2,
stroke: 'grey',
},
},
node: {
type: 'sphere-node',
keyShape: {
opacity: 0.6,
r: 10,
},
labelShape: {
text: {
fields: ['id'],
formatter: (model) => model.id,
},
fontSize: 4,
wordWrapWidth: 200,
isSizeAttenuation: true,
},
// iconShape: {
// img: 'https://gw.alipayobjects.com/zos/basement_prod/012bcf4f-423b-4922-8c24-32a89f8c41ce.svg',
// },
},
});
return graph;
};

View File

@ -0,0 +1,18 @@
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const { width, height } = context;
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'force',
center: [width! / 2, height! / 2, 0],
preventOverlap: true,
nodeSize: 20,
},
});
};

View File

@ -0,0 +1,16 @@
import G6 from '../../../src/index';
import { data } from '../../datasets/dataset1';
import { TestCaseContext } from '../interface';
export default (context: TestCaseContext) => {
const { width, height } = context;
return new G6.Graph({
...context,
type: 'graph',
data: JSON.parse(JSON.stringify(data)),
layout: {
type: 'grid',
center: [width! / 2, height! / 2],
},
});
};

View File

@ -0,0 +1,75 @@
import { Canvas } from '@antv/g';
import { Renderer } from '@antv/g-canvas';
import G6 from '../../../src/index';
import { RendererName } from '../../../src/types/render';
import { data } from '../../datasets/dataset1';
export default ({
container,
renderer,
width,
height,
}: {
container: HTMLElement;
renderer: RendererName;
canvas: Canvas;
transientCanvas: Canvas;
backgroundCanvas: Canvas;
width: number;
height: number;
}) => {
const $backgroundCanvas = document.createElement('canvas');
const $canvas = document.createElement('canvas');
const $transientCanvas = document.createElement('canvas');
container?.appendChild($backgroundCanvas);
container?.appendChild($canvas);
container?.appendChild($transientCanvas);
[$backgroundCanvas, $canvas, $transientCanvas].forEach(($domElement, i) => {
$domElement.width = 2 * 500;
$domElement.height = 2 * 500;
$domElement.style.width = '500px';
$domElement.style.height = '500px';
$domElement.style.position = 'fixed';
$domElement.style.outline = 'none';
$domElement.tabIndex = 1; // Enable keyboard events
// Transient canvas should let interactive events go through.
if (i === 2) {
$domElement.style.pointerEvents = 'none';
}
});
const backgroundCanvas = new Canvas({
canvas: $backgroundCanvas,
width: 500,
height: 500,
renderer: new Renderer(),
});
const canvas = new Canvas({
canvas: $canvas,
width: 500,
height: 500,
renderer: new Renderer(),
});
const transientCanvas = new Canvas({
canvas: $transientCanvas,
width: 500,
height: 500,
renderer: new Renderer(),
});
return new G6.Graph({
canvas,
transientCanvas,
backgroundCanvas,
width,
height,
type: 'graph',
data,
layout: {
type: 'circular',
center: [250, 250],
radius: 200,
},
});
};

View File

@ -8,8 +8,14 @@
<body>
<div id="app">
<label for="">选择要测试的DEMO </label>
<select id="select" style="height: 40px; cursor: pointer"></select>
<label for="demo-select">选择 DEMO </label>
<select id="demo-select" style="height: 40px; cursor: pointer"></select>
<label for="renderer-select">选择 Renderer </label>
<select id="renderer-select" style="height: 40px; cursor: pointer">
<option value="canvas" selected>canvas</option>
<option value="svg">svg</option>
<option value="webgl">webgl</option>
</select>
<div id="container" style="height: 500px; width: 100%"></div>
</div>
<script type="module" src="./main.ts"></script>

View File

@ -0,0 +1,170 @@
import { resetEntityCounter } from '@antv/g';
import nodeBuildIn from '../demo/animations/node-build-in';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Animation node buildIn', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = nodeBuildIn({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
const nodes = graph.getAllNodesData();
/**
* Time: 0
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.currentTime = 0;
animation.pause();
});
});
await expect(canvas).toMatchCanvasSnapshot(
dir,
'animations-node-build-in-ready',
);
/**
* Time: 200
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.currentTime = 200;
animation.pause();
});
});
await expect(canvas).toMatchCanvasSnapshot(
dir,
'animations-node-build-in-running',
);
/**
* Resume all animations.
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.play();
});
});
/**
* Time: finished
*/
await Promise.all(
nodes.map(async ({ id }) => {
const node = graph['getItemById'](id);
await Promise.all(
node.animations.map((animation) => animation.finished),
);
}),
);
await expect(canvas).toMatchCanvasSnapshot(
dir,
'animations-node-build-in-finished',
);
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = nodeBuildIn({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
const nodes = graph.getAllNodesData();
/**
* Time: 0
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.currentTime = 0;
animation.pause();
});
});
await expect(canvas).toMatchSVGSnapshot(
dir,
'animations-node-build-in-ready',
);
/**
* Time: 200
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.currentTime = 200;
animation.pause();
});
});
await expect(canvas).toMatchSVGSnapshot(
dir,
'animations-node-build-in-running',
);
/**
* Resume all animations.
*/
nodes.forEach(({ id }) => {
const node = graph['getItemById'](id);
node.animations.forEach((animation) => {
animation.play();
});
});
/**
* Time: finished
*/
await Promise.all(
nodes.map(async ({ id }) => {
const node = graph['getItemById'](id);
await Promise.all(
node.animations.map((animation) => animation.finished),
);
}),
);
await expect(canvas).toMatchSVGSnapshot(
dir,
'animations-node-build-in-finished',
);
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,82 @@
import { resetEntityCounter } from '@antv/g';
import activateRelations from '../demo/behaviors/activate-relations';
import './utils/useSnapshotMatchers';
import { createContext, triggerEvent } from './utils';
describe('Activate relations behavior', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = activateRelations({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(
dir,
'behaviors-activate-relations',
);
// @ts-ignore
// mouseEvent.target = canvas.getContextService().getDomElement();
triggerEvent(graph, 'mousedown', 81, 50);
triggerEvent(graph, 'mouseup', 81, 50);
await expect(canvas).toMatchCanvasSnapshot(
dir,
'behaviors-activate-relations-activate-node1',
);
/**
* Click document to clear active state.
*/
triggerEvent(graph, 'mousedown', 0, 0);
triggerEvent(graph, 'mouseup', 0, 0);
await expect(canvas).toMatchCanvasSnapshot(
dir,
'behaviors-activate-relations-deactivate-node1',
);
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = activateRelations({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(
dir,
'behaviors-activate-relations',
);
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,136 @@
import { resetEntityCounter } from '@antv/g';
import lineEdge from '../demo/item/edge/line-edge';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Items edge line', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = lineEdge({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'items-edge-line');
/**
* Click the checkbox to show label.
*/
const $showLabel = document.querySelectorAll(
'input',
)[0] as HTMLInputElement;
$showLabel.click();
await expect(canvas).toMatchCanvasSnapshot(
dir,
'items-edge-line-show-label',
);
$showLabel.click();
/**
* Click the checkbox to display selected style.
*/
const $selected = document.querySelectorAll(
'input',
)[2] as HTMLInputElement;
$selected.click();
await expect(canvas).toMatchCanvasSnapshot(
dir,
'items-edge-line-selected-style',
);
$selected.click();
/**
* Click the checkbox to highlight.
*/
const $highlight = document.querySelectorAll(
'input',
)[3] as HTMLInputElement;
$highlight.click();
await expect(canvas).toMatchCanvasSnapshot(
dir,
'items-edge-line-highlight-style',
);
$highlight.click();
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = lineEdge({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'items-edge-line');
/**
* Click the checkbox to show label.
*/
const $showLabel = document.querySelectorAll(
'input',
)[0] as HTMLInputElement;
$showLabel.click();
await expect(canvas).toMatchSVGSnapshot(
dir,
'items-edge-line-show-label',
);
$showLabel.click();
/**
* Click the checkbox to display selected style.
*/
const $selected = document.querySelectorAll(
'input',
)[2] as HTMLInputElement;
$selected.click();
await expect(canvas).toMatchSVGSnapshot(
dir,
'items-edge-line-selected-style',
);
$selected.click();
/**
* Click the checkbox to highlight.
*/
const $highlight = document.querySelectorAll(
'input',
)[3] as HTMLInputElement;
$highlight.click();
await expect(canvas).toMatchSVGSnapshot(
dir,
'items-edge-line-highlight-style',
);
$highlight.click();
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,76 @@
import { resetEntityCounter } from '@antv/g';
import circular from '../demo/layouts/circular';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Circular layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = circular({
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-circular');
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = circular({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'layouts-circular');
graph.destroy();
done();
});
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = circular({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-circular');
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,83 @@
import { resetEntityCounter } from '@antv/g';
import d3force from '../demo/layouts/d3force';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('D3Force layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = d3force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-d3force');
// const layoutAnimation = graph.getLayoutCurrentAnimation()!;
// layoutAnimation.
// layoutAnimation.currentTime = 0;
// await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-d3force-0');
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = d3force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'layouts-d3force');
graph.destroy();
done();
});
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = d3force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-d3force');
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,77 @@
import { resetEntityCounter } from '@antv/g';
import dagre from '../demo/layouts/dagre';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Dagre layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = dagre({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-dagre');
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = dagre({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'layouts-dagre');
graph.destroy();
done();
});
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = dagre({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-dagre');
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,77 @@
import { resetEntityCounter } from '@antv/g';
import force from '../demo/layouts/force';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe.skip('Force layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-force');
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'layouts-force');
graph.destroy();
done();
});
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = force({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-force');
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,36 @@
import { resetEntityCounter } from '@antv/g';
import force3D from '../demo/layouts/force-3d';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Force3D layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = force3D({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
renderer: 'webgl-3d',
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-force3d');
graph.destroy();
done();
});
});
});

View File

@ -0,0 +1,77 @@
import { resetEntityCounter } from '@antv/g';
import grid from '../demo/layouts/grid';
import './utils/useSnapshotMatchers';
import { createContext } from './utils';
describe('Grid layout', () => {
beforeEach(() => {
/**
* SVG Snapshot testing will generate a unique id for each element.
* Reset to 0 to keep snapshot consistent.
*/
resetEntityCounter();
});
it('should be rendered correctly with Canvas2D', (done) => {
const dir = `${__dirname}/snapshots/canvas`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('canvas', 500, 500);
const graph = grid({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchCanvasSnapshot(dir, 'layouts-grid');
graph.destroy();
done();
});
});
it('should be rendered correctly with SVG', (done) => {
const dir = `${__dirname}/snapshots/svg`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('svg', 500, 500);
const graph = grid({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchSVGSnapshot(dir, 'layouts-grid');
graph.destroy();
done();
});
});
it.skip('should be rendered correctly with WebGL', (done) => {
const dir = `${__dirname}/snapshots/webgl`;
const { backgroundCanvas, canvas, transientCanvas, container } =
createContext('webgl', 500, 500);
const graph = grid({
container,
backgroundCanvas,
canvas,
transientCanvas,
width: 500,
height: 500,
});
graph.on('afterlayout', async () => {
await expect(canvas).toMatchWebGLSnapshot(dir, 'layouts-grid');
graph.destroy();
done();
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

Some files were not shown because too many files have changed in this diff Show More