feat: 增加plugins,util,graph的单测

This commit is contained in:
baizn 2020-01-09 15:16:20 +08:00 committed by Moyee
parent 631684b7db
commit 523d353ed8
30 changed files with 274 additions and 69 deletions

View File

@ -77,9 +77,9 @@
"@antv/path-util": "^2.0.3",
"@antv/scale": "^0.2.0",
"@antv/util": "~2.0.5",
"lodash": "^4.17.15",
"d3-force": "^2.0.1",
"dagre": "^0.8.5",
"lodash": "^4.17.15",
"numericjs": "^1.2.6",
"tslib": "^1.10.0",
"webpack": "^4.41.4"

View File

@ -271,7 +271,7 @@ export default class ItemController {
* @param {string[]} states
* @memberof ItemController
*/
public clearItemStates(item: Item | string, states: string[]): void {
public clearItemStates(item: Item | string, states: string | string[]): void {
const graph = this.graph;
if (isString(item)) {

View File

@ -133,7 +133,6 @@ export default class StateController {
*/
public updateGraphStates() {
const states = this.graph.get('states')
const cachedStates = this.cachedStates;
each(cachedStates.disabled, (val, key) => {
@ -148,6 +147,8 @@ export default class StateController {
if (!states[key]) {
states[key] = val;
} else {
// TODO 需要测试是否能够走到这个分支
console.log('走到这里没有', states)
const map = {};
states[key].forEach(item => {
if (!item.destroyed) {

View File

@ -378,7 +378,7 @@ export default class Graph extends EventEmitter implements IGraph {
* @param {string|Item} item id或元素实例
* @param {string[]} states
*/
public clearItemStates(item: Item | string, states?: string[]): void {
public clearItemStates(item: Item | string, states?: string[] | string): void {
if(isString(item)) {
item = this.findById(item)
}
@ -725,9 +725,11 @@ export default class Graph extends EventEmitter implements IGraph {
}
if (!nodeItem && isString(item)) {
this.get('customGroupControll').remove(item);
const customGroupControll: CustomGroup = this.get('customGroupControll')
customGroupControll.remove(item);
} else {
this.get('itemController').removeItem(item);
const itemController: ItemController = this.get('itemController')
itemController.removeItem(item);
}
}
@ -783,7 +785,7 @@ export default class Graph extends EventEmitter implements IGraph {
* @param {string} state
* @param {boolean} enabled
*/
public setItemState(item: Item | string | string, state: string, enabled: boolean): void {
public setItemState(item: Item | string, state: string, enabled: boolean): void {
if (isString(item)) {
item = this.findById(item);
}
@ -1380,7 +1382,6 @@ export default class Graph extends EventEmitter implements IGraph {
customGroupControll.expandGroup(groupId);
}
// TODO plugin 机制完善后再补充类型
/**
*
* @param {object} plugin

View File

@ -386,9 +386,9 @@ export interface IGraph extends EventEmitter {
/**
*
* @param {string|Item} item id或元素实例
* @param {String[]} states
* @param {string | string[]} states
*/
clearItemStates(item: Item | string, states?: string[]): void;
clearItemStates(item: Item | string, states?: string | string[]): void;
/**
* keyShape

View File

@ -4,7 +4,7 @@ import Graph from '@g6/graph/graph';
import { GraphData, NodeConfig, NodeMapConfig, EdgeConfig } from '@g6/types';
import { Point } from '@antv/g-base/lib/types';
interface IBundlingConfig extends IPluginBaseConfig {
interface BundlingConfig extends IPluginBaseConfig {
edgeBundles?: Edge[];
edgePoints?: NodeConfig[];
K?: number;
@ -47,10 +47,10 @@ function projectPointToEdge(p: Point, e): Point {
}
export default class Bundling extends Base {
constructor(cfg?: IBundlingConfig) {
constructor(cfg?: BundlingConfig) {
super(cfg)
}
public getDefaultCfgs(): IBundlingConfig {
public getDefaultCfgs(): BundlingConfig {
return {
edgeBundles: [], // |edges| arrays, each one stores the related edges' id
edgePoints: [], // |edges| * divisions edge points

View File

@ -5,12 +5,17 @@ import Base, { IPluginBaseConfig } from '../base'
interface MenuConfig extends IPluginBaseConfig {
createDOM?: boolean;
getContent: (evt?: IG6GraphEvent) => string;
menu?: HTMLDivElement;
getContent?: (evt?: IG6GraphEvent) => string;
onShow: (evt?: IG6GraphEvent) => boolean;
onHide: (evt?: IG6GraphEvent) => boolean;
}
export default class Menu extends Base {
constructor(cfg: MenuConfig) {
super(cfg)
}
public getDefaultCfgs(): MenuConfig {
return {
createDOM: true, // 是否渲染 dom

View File

@ -17,7 +17,7 @@ const DEFAULT_MODE = 'default';
const KEYSHAPE_MODE = 'keyShape';
const DELEGATE_MODE = 'delegate';
interface IMiniMapConfig extends IPluginBaseConfig {
interface MiniMapConfig extends IPluginBaseConfig {
viewportClassName?: string;
type?: 'default' | 'keyShape' | 'delegate';
size: number[];
@ -26,11 +26,11 @@ interface IMiniMapConfig extends IPluginBaseConfig {
}
export default class MiniMap extends Base {
constructor(cfg: IMiniMapConfig) {
constructor(cfg: MiniMapConfig) {
super(cfg)
}
public getDefaultCfgs(): IMiniMapConfig {
public getDefaultCfgs(): MiniMapConfig {
return {
container: null,
className: 'g6-minimap',
@ -70,7 +70,7 @@ export default class MiniMap extends Base {
}
private initViewport() {
const cfgs:IMiniMapConfig = this._cfgs as IMiniMapConfig;
const cfgs:MiniMapConfig = this._cfgs as MiniMapConfig;
const size = cfgs.size;
const graph = cfgs.graph;
const canvas = this.get('canvas');

View File

@ -289,6 +289,9 @@ export const getAdjMatrix = (data: GraphData, directed: boolean): Matrix[] => {
// map node with index in data.nodes
const nodeMap = {};
if(!nodes) {
throw new Error('invalid nodes data!')
}
if (nodes) {
nodes.forEach((node, i) => {
nodeMap[node.id] = i;

View File

@ -28,7 +28,7 @@ export const getSpline = (points: IPoint[]) => {
const data: number[] = [];
if (points.length < 2) {
console.warn(`point length must largn than 2, now it's ${points.length}`);
throw new Error(`point length must largn than 2, now it's ${points.length}`);
}
for (const point of points) {
const { x, y } = point;

View File

@ -28,7 +28,7 @@ G6.registerNode('circleNode', {
}
}, 'circle');
describe.only('signle layer group', () => {
describe('signle layer group', () => {
it('render signle group test', () => {
const graph = new G6.Graph({
@ -605,8 +605,8 @@ describe.only('signle layer group', () => {
expect(groups.length).toBe(4);
// 删除group1
const customGroup = graph.get('customGroupControll');
customGroup.remove('group1');
// const customGroup = graph.get('customGroupControll');
graph.remove('group1');
const groupNodes1 = graph.get('groupNodes');
const keys1 = Object.keys(groupNodes1);
@ -625,7 +625,7 @@ describe.only('signle layer group', () => {
});
describe.only('nesting layer group', () => {
describe('nesting layer group', () => {
it('render nesting layer group', () => {
const data = {
nodes: [
@ -924,7 +924,7 @@ describe.only('nesting layer group', () => {
});
// 手动创建分子
describe.only('create node group', () => {
describe('create node group', () => {
it('use addItem create group', () => {
const data = {
nodes: [

View File

@ -1,15 +1,10 @@
import G6 from '../../../../src'
import { timerOut } from '../../util/timeOut'
const div = document.createElement('div');
div.id = 'state-controller';
document.body.appendChild(div);
function timerGame(callback, time = 50) {
setTimeout(() => {
callback();
}, time);
}
describe('graph state controller', () => {
const graph = new G6.Graph({
container: div,
@ -18,7 +13,7 @@ describe('graph state controller', () => {
});
const data = {
nodes: [
{ id: 'node1', x: 100, y: 100 },
{ id: 'node1', x: 100, y: 100, label: 'node1' },
{ id: 'node2', x: 120, y: 80 },
{ id: 'node3', x: 150, y: 150 }
],
@ -44,7 +39,7 @@ describe('graph state controller', () => {
graph.setItemState('node1', 'selected', true);
timerGame(() => {
timerOut(() => {
expect(itemCount).toBe(1);
expect(graphCount).toBe(0);
@ -52,7 +47,7 @@ describe('graph state controller', () => {
expect(graph.get('states').selected.length).toBe(1);
expect(graph.get('states').selected[0]).toEqual(graph.findById('node1'));
timerGame(() => {
timerOut(() => {
graph.setItemState('node1', 'selected', false);
graph.setItemState('node1', 'selected', true);
graph.setItemState('node2', 'selected', true);
@ -65,7 +60,7 @@ describe('graph state controller', () => {
graph.setItemState('node2', 'selected', false);
graph.setItemState('node3', 'selected', false);
timerGame(() => {
timerOut(() => {
expect(graph.get('states').selected.length).toBe(0);
}, 90)
}, 70)
@ -86,4 +81,21 @@ describe('graph state controller', () => {
expect(Object.keys(modes)).toEqual(['default'])
expect(modes.default).toEqual([])
});
it('updateGraphStates', () => {
const node1 = graph.findById('node1')
graph.setItemState(node1, 'selected', true)
graph.setItemState(node1, 'hover', true)
graph.setItemState('node2', 'hover', true)
graph.on('node:mouseenter', () => {
const states = graph.get('states')
expect(states).not.toBe(undefined)
expect(states.selected.length).toBe(1)
expect(states.hover.length).toBe(2)
})
timerOut(() => {
graph.emit('node:mouseenter', { item: node1 })
}, 20)
})
});

View File

@ -4,6 +4,7 @@ import '../../../src/behavior'
import { scale, translate } from '../../../src/util/math'
import { GraphData, Item } from '../../../types';
import Plugin from '../../../src/plugins'
import { timerOut } from '../util/timeOut'
const div = document.createElement('div');
div.id = 'global-spec';
@ -626,9 +627,13 @@ describe('all node link center', () => {
graph.setItemState(node, 'a', true);
graph.setItemState(node, 'b', true);
debugger
graph.clearItemStates('a', ['a']);
expect(graph.findAllByState('node', 'a').length).toBe(0);
expect(graph.findAllByState('node', 'b').length).toBe(1);
graph.clearItemStates(node, 'b')
expect(graph.findAllByState('node', 'b').length).toBe(0)
});
it('default node & edge style', () => {
@ -1004,4 +1009,57 @@ describe('plugins & layout', () => {
graph.destroy()
expect(graph.destroyed).toBe(true)
})
it('graph animate', () => {
const graph = new G6.Graph({
container: div,
height: 500,
width: 500
})
const data = {
nodes: [
{
id: 'node',
label: 'node',
groupId: 'g1'
},{
id: 'node1',
groupId: 'g2'
}
],
groups: [
{
id: 'g1',
title: 'cokkdl'
},
{
id: 'g2'
}
]
}
graph.data(data)
graph.render()
graph.stopAnimate()
const isAnimating = graph.isAnimating()
expect(isAnimating).toBe(false)
graph.collapseGroup('g1')
let gnode = graph.findById('node')
expect(gnode.get('visible')).toBe(false)
const g2Node = graph.findById('node1')
expect(g2Node.get('visible')).toBe(true)
graph.expandGroup('g1')
timerOut(() => {
gnode = graph.findById('node')
expect(gnode.get('visible')).toBe(true)
}, 500)
})
})

View File

@ -1,16 +1,10 @@
import Hierarchy from '@antv/hierarchy';
import G6 from '../../../src';
import { timerOut } from '../util/timeOut'
const div = document.createElement('div');
div.id = 'tree-spec';
document.body.appendChild(div);
function timerGame(callback, time = 50) {
setTimeout(() => {
callback();
}, time);
}
describe('tree graph without animate', () => {
let graph = new G6.TreeGraph({
container: div,
@ -166,7 +160,7 @@ describe('tree graph without animate', () => {
});
graph.addBehaviors('collapse-expand', 'default');
graph.emit('node:click', { item: parent });
timerGame(() => {
timerOut(() => {
collapsed = false;
graph.emit('node:click', { item: parent });
}, 600);
@ -232,7 +226,7 @@ describe('tree graph without animate', () => {
}
});
graph.emit('node:dblclick', { item: parent });
timerGame(() => {
timerOut(() => {
collapsed = false;
graph.emit('node:dblclick', { item: parent });
}, 600);
@ -446,7 +440,7 @@ describe('tree graph with animate', () => {
graph.addBehaviors('collapse-expand', 'default');
graph.emit('node:click', { item: parent });
timerGame(() => {
timerOut(() => {
collapsed = false;
graph.emit('node:click', { item: parent });
}, 600);
@ -478,7 +472,7 @@ describe('tree graph with animate', () => {
graph.emit('node:dblclick', { item: parent });
timerGame(() => {
timerOut(() => {
collapsed = false;
graph.emit('node:dblclick', { item: parent });
}, 600);

View File

@ -6,7 +6,7 @@ const div = document.createElement('div');
div.id = 'circular-layout';
document.body.appendChild(div);
describe.only('circular layout', () => {
describe('circular layout', () => {
it('circular layout with default configs', () => {
const graph = new G6.Graph({
container: div,

View File

@ -7,7 +7,7 @@ const div = document.createElement('div');
div.id = 'circular-layout-web-worker';
document.body.appendChild(div);
describe.only('circular layout(web worker)', () => {
describe('circular layout(web worker)', () => {
it('circular layout(web worker) with default configs', (done) => {
const graph = new G6.Graph({
container: div,

View File

@ -6,7 +6,7 @@ const div = document.createElement('div');
div.id = 'force-layout-web-worker';
document.body.appendChild(div);
describe.only('force layout(web worker)', function() {
describe('force layout(web worker)', function() {
// this.timeout(10000);
it('force layout(web worker) with default configs', done => {

View File

@ -24,7 +24,7 @@ const div = document.createElement('div');
div.id = 'grid-layout';
document.body.appendChild(div);
describe.only('grid layout', () => {
describe('grid layout', () => {
it('grid layout with default configs', () => {
const graph = new G6.Graph({
container: div,

View File

@ -5,7 +5,7 @@ const div = document.createElement('div');
div.id = 'mds-layout';
document.body.appendChild(div);
describe.only('mds', () => {
describe('mds', () => {
it('mds layout with default configs', () => {
const graph = new G6.Graph({
container: div,

View File

@ -86,7 +86,7 @@ const data2 = {
],
};
describe.only('preset layout', () => {
describe('preset layout', () => {
it('new graph without layout, part of the data has position infor', () => {
const graph = new G6.Graph({
container: div,

View File

@ -16,7 +16,7 @@ const data: {nodes: object, edges: object} = {
edges: [],
};
describe.only('random', () => {
describe('random', () => {
it('new graph without layout, random by default', () => {
const graph = new G6.Graph({
container: div,

View File

@ -9,7 +9,7 @@ document.body.appendChild(div);
// jest.setTimeout(10000)
describe.only('layout using web worker', function() {
describe('layout using web worker', function() {
it('change layout', function(done) {
const node = data.nodes[0];
const graph = new G6.Graph({

View File

@ -7,8 +7,7 @@ const div = document.createElement('div');
div.id = 'force-layout';
document.body.appendChild(div);
describe.skip('edge bundling', () => {
// TODO Wait for layout
describe('edge bundling', () => {
const graph = new G6.Graph({
container: div,
width: 500,
@ -31,6 +30,7 @@ describe.skip('edge bundling', () => {
expect(graphData.edges[0].shape).toEqual('polyline');
expect(graphData.edges[0].controlPoints.length > 2).toEqual(true);
bundle.destroy()
});
it('bundling on circular with fixed bundleThreshold and iterations', () => {
@ -45,6 +45,7 @@ describe.skip('edge bundling', () => {
expect(graphData.edges[0].shape).toEqual('polyline');
expect(graphData.edges[0].controlPoints.length > 2).toEqual(true);
bundle.destroy()
});
it('bundling update', () => {
@ -80,16 +81,27 @@ describe.skip('edge bundling', () => {
expect(data2.edges[0].shape).toEqual('polyline');
expect(data2.edges[0].controlPoints.length > 2).toEqual(true);
bundle.destroy()
});
it('bundling no position info, throw error', () => {
const bundle = new Bundling();
bundle.initPlugin(graph);
const data2: GraphData = {
nodes: [
{ id: 'n0' }, { id: 'n1' }
],
edges: [
{ source: 'n0', target: 'n1' }
]
};
function fn() {
bundle.bundling(data);
bundle.bundling(data2);
}
expect(fn).toThrowError('please layout the graph or assign x and y for nodes first');
bundle.destroy()
graph.destroy();
});
});

View File

@ -18,6 +18,7 @@ describe('menu', () => {
onShow(e) {
expect(isNaN(e.canvasX)).toBe(false);
expect(isNaN(e.canvasY)).toBe(false);
return true
},
onHide() {
count++;
@ -80,13 +81,19 @@ describe('menu', () => {
const menu = new Menu({
createDOM: false,
menu: outDiv,
getContent(e) {
expect(e).not.toBe(undefined);
return 'test menu';
},
onShow(e) {
outDiv.style.left = e.canvasX + 'px';
outDiv.style.top = e.canvasY + 'px';
outDiv.style.visibility = 'visible';
return true
},
onHide() {
outDiv.style.visibility = 'hidden';
return false
}
});

View File

@ -1,6 +1,7 @@
import G6 from '../../../src'
import Minimap from '../../../src/plugins/minimap'
import Simulate from 'event-simulate'
import { timerOut } from '../util/timeOut'
const div = document.createElement('div');
div.id = 'minimap';
@ -8,12 +9,6 @@ document.body.appendChild(div);
const container = document.createElement('div');
div.appendChild(container);
function timerGame(callback, time = 50) {
setTimeout(() => {
callback();
}, time);
}
describe('minimap', () => {
const minimap = new Minimap({ size: [ 200, 200 ] });
const graph = new G6.Graph({
@ -111,7 +106,7 @@ describe('minimap', () => {
clientY: 120
});
timerGame(() => {
timerOut(() => {
expect(viewport.style.left).toEqual('20px');
expect(viewport.style.top).toEqual('20px');
expect(viewport.style.width).toEqual('0px');
@ -134,7 +129,7 @@ describe('minimap', () => {
clientY: 0
});
timerGame(() => {
timerOut(() => {
expect(viewport.style.left).toEqual('0px');
expect(viewport.style.top).toEqual('0px');
expect(viewport.style.width).toEqual('100px');

View File

@ -0,0 +1,34 @@
import { formatPadding, isViewportChanged } from '../../../src/util/base'
describe('base util', () => {
it('formatPadding', () => {
let padding = formatPadding(5)
expect(padding).toEqual([5, 5, 5, 5])
padding = formatPadding('10')
expect(padding).toEqual([10, 10, 10, 10])
padding = formatPadding([5])
expect(padding).toEqual([5, 5, 5, 5])
padding = formatPadding([5, 10])
expect(padding).toEqual([5, 10, 5, 10])
padding = formatPadding([5, 10, 15])
expect(padding).toEqual([5, 10, 15, 10])
padding = formatPadding([5, 10, 15, 20])
expect(padding).toEqual([5, 10, 15, 20])
})
it('isViewportChanged', () => {
let isChanged = isViewportChanged(null)
expect(isChanged).toBe(false)
isChanged = isViewportChanged([1, 0, 0, 0, 1, 0, 0, 0, 1])
expect(isChanged).toBe(false)
isChanged = isViewportChanged([1, 0, 0, 0.5, 1, 0, 0, 0, 1])
expect(isChanged).toBe(true)
})
})

View File

@ -227,6 +227,42 @@ describe('math util test', () => {
expect(directedMatrix[1]).toEqual([])
})
it('getAdjMatrix without nodes', () => {
const data = {
edges: [
{
source: 'node1',
target: 'node2'
},
{
source: 'node1',
target: 'node1'
}
]
}
expect(() => {getAdjMatrix(data, false)}).toThrowError('invalid nodes data!')
})
it('getAdjMatrix without edges', () => {
const data1 = {
nodes: [
{
id: 'node1',
label: 'node1'
},
{
id: 'node2',
label: 'node2'
}
]
}
const directedMatrix = getAdjMatrix(data1, true)
expect(directedMatrix[0]).toEqual([])
expect(directedMatrix[1]).toEqual([])
})
it('floydWarshall', () => {
const matrix = [
[1, 1, 2],

View File

@ -39,6 +39,17 @@ describe('Path Util Test', () => {
expect(three[6]).toEqual(7)
})
it('getSpline thorw new error', () => {
const points = [
{
x: 10,
y: 12
}
]
expect(() => getSpline(points)).toThrowError(`point length must largn than 2, now it's ${points.length}`)
})
it('getControlPoint horizontal', () => {
const start = { x: 0, y: 0 };
const end = { x: 100, y: 0 };
@ -81,6 +92,19 @@ describe('Path Util Test', () => {
expect(point.y).toEqual(50 - sqrt2 * 10 / 2);
})
it('getControlPoint percent is 0', () => {
const start = { x: 100, y: 100 };
const end = { x: 50, y: 20 };
const point = getControlPoint(start, end);
expect(point.x).toEqual(100);
expect(point.y).toEqual(100);
})
it('pointsToPolygon points.length = 0', () => {
const polygonPoint = pointsToPolygon([])
expect(polygonPoint).toEqual('')
})
it('pointsToPolygon z = false', () => {
const points = [
{
@ -112,4 +136,17 @@ describe('Path Util Test', () => {
const polygonPoint = pointsToPolygon(points, true)
expect(polygonPoint).toEqual('M1 2L5 5Z')
})
it('pointsToPolygon substitute', () => {
const points = [
{
x: 1,
y: 2
},
''
]
const polygonPoint = pointsToPolygon(points, true)
expect(polygonPoint).toEqual('M1 2L{x} {y}Z')
})
})

View File

@ -0,0 +1,10 @@
/**
* jest 使 setTimeout
* @param callback
* @param time
*/
export const timerOut = (callback, time = 50) => {
setTimeout(() => {
callback();
}, time);
}

View File

@ -201,8 +201,8 @@ export interface NodeConfig extends ModelConfig {
export interface EdgeConfig extends ModelConfig {
id?: string;
source: string;
target: string;
source?: string;
target?: string;
label?: string;
labelCfg?: {
style?: object;