Added ERD Diagram support with basic table fields, primary key, foreign key, and DDL SQL generation. Fixes #1802

This commit is contained in:
Aditya Toshniwal
2021-01-16 17:06:50 +05:30
committed by Akshay Joshi
parent 065bda37b4
commit 0c8226ff39
78 changed files with 9289 additions and 1472 deletions

View File

@@ -0,0 +1,382 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
import * as createEngineLib from '@projectstorm/react-diagrams';
import TEST_TABLES_DATA from './test_tables';
describe('ERDCore', ()=>{
let eleFactory = jasmine.createSpyObj('nodeFactories', {
'registerFactory': null,
'getFactory': jasmine.createSpyObj('getFactory', ['generateModel', 'calculateRoutingMatrix']),
});
let erdEngine = jasmine.createSpyObj('engine', {
'getNodeFactories': eleFactory,
'getLinkFactories': eleFactory,
'getPortFactories': eleFactory,
'getActionEventBus': jasmine.createSpyObj('actionBus', ['fireAction', 'deregisterAction', 'registerAction']),
'setModel': null,
'getModel': jasmine.createSpyObj('modelObj', {
'addNode': null,
'clearSelection': null,
'getNodesDict': null,
'getLinks': null,
'serialize': ()=>({
'data': 'serialized',
}),
'addLink': null,
'getNodes': null,
'setZoomLevel': null,
'getZoomLevel': null,
'fireEvent': null,
'registerListener': null,
}),
'repaintCanvas': null,
'zoomToFit': null,
'fireEvent': null,
});
beforeAll(()=>{
spyOn(createEngineLib, 'default').and.returnValue(erdEngine);
});
it('initialization', ()=>{
spyOn(ERDCore.prototype, 'initializeEngine').and.callThrough();
spyOn(ERDCore.prototype, 'initializeModel').and.callThrough();
spyOn(ERDCore.prototype, 'computeTableCounter').and.callThrough();
let erdCoreObj = new ERDCore();
expect(erdCoreObj.initializeEngine).toHaveBeenCalled();
expect(erdCoreObj.initializeModel).toHaveBeenCalled();
expect(erdCoreObj.computeTableCounter).toHaveBeenCalled();
});
describe('functions', ()=>{
let erdCoreObj;
beforeAll(()=>{
erdCoreObj = new ERDCore();
});
describe('cache check', ()=>{
it('for single value', ()=>{
erdCoreObj.setCache('key1', 'value1');
expect(erdCoreObj.getCache('key1')).toEqual('value1');
});
it('for multiple value', ()=>{
erdCoreObj.setCache({'key1': 'valuem1', 'key2': 'valuem2'});
expect(erdCoreObj.getCache('key1')).toEqual('valuem1');
expect(erdCoreObj.getCache('key2')).toEqual('valuem2');
});
});
it('registerModelEvent', ()=>{
let fn = ()=>{};
erdCoreObj.registerModelEvent('someEvent', fn);
expect(erdCoreObj.getModel().registerListener).toHaveBeenCalledWith({
'someEvent': fn,
});
});
it('getNextTableName', ()=>{
expect(erdCoreObj.getNextTableName()).toEqual('newtable1');
expect(erdCoreObj.getNextTableName()).toEqual('newtable2');
});
it('getEngine', ()=>{
expect(erdCoreObj.getEngine()).toBe(erdEngine);
});
it('getNewNode', ()=>{
let data = {name: 'table1'};
erdCoreObj.getNewNode(data);
expect(erdEngine.getNodeFactories().getFactory().generateModel).toHaveBeenCalledWith({
initialConfig: {
otherInfo: {
data:data,
},
},
});
});
it('getNewLink', ()=>{
let data = {name: 'link1'};
erdCoreObj.getNewLink('linktype', data);
expect(erdEngine.getLinkFactories().getFactory).toHaveBeenCalledWith('linktype');
expect(erdEngine.getLinkFactories().getFactory().generateModel).toHaveBeenCalledWith({
initialConfig: {
data: data,
},
});
});
it('getNewPort', ()=>{
let data = {name: 'link1'};
let options = {opt1: 'val1'};
erdCoreObj.getNewPort('porttype', data, options);
expect(erdEngine.getPortFactories().getFactory).toHaveBeenCalledWith('porttype');
expect(erdEngine.getPortFactories().getFactory().generateModel).toHaveBeenCalledWith({
initialConfig: {
data:data,
options:options,
},
});
});
it('addNode', ()=>{
let newNode = jasmine.createSpyObj('newNode', ['setPosition']);
spyOn(erdCoreObj, 'getNewNode').and.returnValue(newNode);
spyOn(erdCoreObj, 'clearSelection');
let data = {name: 'link1'};
/* Without position */
erdCoreObj.addNode(data);
expect(erdCoreObj.getNewNode).toHaveBeenCalledWith(data);
expect(erdEngine.getModel().addNode).toHaveBeenCalledWith(newNode);
expect(erdCoreObj.clearSelection).toHaveBeenCalled();
/* With position */
erdCoreObj.addNode(data, [108, 108]);
expect(erdCoreObj.getNewNode().setPosition).toHaveBeenCalledWith(108, 108);
});
it('addLink', ()=>{
let nodesDict = {
'id1': {
serializeData: function(){ return {
'name': 'table1',
};},
getPortName: function(attnum) {
return `port-${attnum}`;
},
getPort: function() {
return null;
},
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
},
'id2': {
serializeData: function(){ return {
'name': 'table2',
};},
getPortName: function(attnum) {
return `port-${attnum}`;
},
getPort: function() {
return null;
},
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
},
};
let link = jasmine.createSpyObj('link', ['setSourcePort', 'setTargetPort']);
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue(nodesDict);
spyOn(erdCoreObj, 'getNewLink').and.callFake(function() {
return link;
});
spyOn(erdCoreObj, 'getNewPort').and.callFake(function(type, initData, options) {
return {
name: options.name,
};
});
erdCoreObj.addLink({
'referenced_column_attnum': 1,
'referenced_table_uid': 'id1',
'local_column_attnum': 3,
'local_table_uid': 'id2',
}, 'onetomany');
expect(nodesDict['id1'].addPort).toHaveBeenCalledWith({name: 'port-1'});
expect(nodesDict['id2'].addPort).toHaveBeenCalledWith({name: 'port-3'});
expect(link.setSourcePort).toHaveBeenCalledWith({name: 'port-1'});
expect(link.setTargetPort).toHaveBeenCalledWith({name: 'port-3'});
});
it('serialize', ()=>{
let retVal = erdCoreObj.serialize();
expect(retVal.hasOwnProperty('version')).toBeTruthy();
expect(retVal.hasOwnProperty('data')).toBeTruthy();
expect(erdEngine.getModel().serialize).toHaveBeenCalled();
});
it('deserialize', ()=>{
let deserialValue = {
'version': 123,
'data': {
'key': 'serialized',
},
};
spyOn(erdCoreObj, 'initializeModel');
erdCoreObj.deserialize(deserialValue);
expect(erdCoreObj.initializeModel).toHaveBeenCalledWith(deserialValue.data);
});
it('serializeData', ()=>{
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue({
'id1': {
serializeData: function(){ return {
'name': 'table1',
};},
},
'id2': {
serializeData: function(){ return {
'name': 'table2',
};},
},
});
spyOn(erdEngine.getModel(), 'getLinks').and.returnValue([
{
serializeData: function(){ return {
'name': 'link1',
};},
getID: function(){ return 'lid1'; },
},
{
serializeData: function(){ return {
'name': 'link2',
};},
getID: function(){ return 'lid2'; },
},
]);
expect(JSON.stringify(erdCoreObj.serializeData())).toEqual(JSON.stringify({
nodes: {
'id1': {'name': 'table1'},
'id2': {'name': 'table2'},
},
links: {
'lid1': {'name': 'link1'},
'lid2': {'name': 'link2'},
},
}));
});
it('deserializeData', (done)=>{
let nodesDict = {};
TEST_TABLES_DATA.forEach((table)=>{
nodesDict[`id-${table.name}`] = {
getColumns: function() {
return table.columns;
},
getPortName: function(attnum) {
return `port-${attnum}`;
},
getPort: function(name) {
return {'name': name};
},
addPort: function() {
},
};
});
spyOn(erdEngine.getModel(), 'getNodesDict').and.returnValue(nodesDict);
spyOn(erdCoreObj, 'getNewLink').and.callFake(function() {
return {
setSourcePort: function() {},
setTargetPort: function() {},
};
});
spyOn(erdCoreObj, 'getNewPort').and.returnValue({id: 'id'});
spyOn(erdCoreObj, 'addNode').and.callFake(function(data) {
return {
getID: function() {
return `id-${data.name}`;
},
};
});
spyOn(erdCoreObj, 'addLink');
spyOn(erdCoreObj, 'dagreDistributeNodes');
erdCoreObj.deserializeData(TEST_TABLES_DATA);
expect(erdCoreObj.addNode).toHaveBeenCalledTimes(TEST_TABLES_DATA.length);
expect(erdCoreObj.addLink).toHaveBeenCalledTimes(1);
setTimeout(()=>{
expect(erdCoreObj.dagreDistributeNodes).toHaveBeenCalled();
done();
}, 10);
});
it('clearSelection', ()=>{
erdCoreObj.clearSelection();
expect(erdEngine.getModel().clearSelection).toHaveBeenCalled();
});
it('repaint', ()=>{
erdCoreObj.repaint();
expect(erdEngine.repaintCanvas).toHaveBeenCalled();
});
it('getNodesData', ()=>{
spyOn(erdEngine.getModel(), 'getNodes').and.returnValue([
{getData: function () {return {name:'node1'};}},
{getData: function () {return {name:'node2'};}},
]);
expect(JSON.stringify(erdCoreObj.getNodesData())).toEqual(JSON.stringify([
{name:'node1'}, {name:'node2'},
]));
});
it('dagreDistributeNodes', ()=>{
spyOn(erdCoreObj.dagre_engine, 'redistribute');
erdCoreObj.dagreDistributeNodes();
expect(erdEngine.getLinkFactories().getFactory().calculateRoutingMatrix).toHaveBeenCalled();
expect(erdCoreObj.dagre_engine.redistribute).toHaveBeenCalledWith(erdEngine.getModel());
});
it('zoomIn', ()=>{
spyOn(erdEngine.getModel(), 'getZoomLevel').and.returnValue(100);
spyOn(erdCoreObj, 'repaint');
erdCoreObj.zoomIn();
expect(erdEngine.getModel().setZoomLevel).toHaveBeenCalledWith(125);
expect(erdCoreObj.repaint).toHaveBeenCalled();
});
it('zoomOut', ()=>{
spyOn(erdEngine.getModel(), 'getZoomLevel').and.returnValue(100);
spyOn(erdCoreObj, 'repaint');
erdCoreObj.zoomOut();
expect(erdEngine.getModel().setZoomLevel).toHaveBeenCalledWith(75);
expect(erdCoreObj.repaint).toHaveBeenCalled();
});
it('zoomToFit', ()=>{
erdCoreObj.zoomToFit();
expect(erdEngine.zoomToFit).toHaveBeenCalled();
});
it('fireAction', ()=>{
erdCoreObj.fireAction({key: 'xyz'});
expect(erdEngine.getActionEventBus().fireAction).toHaveBeenCalled();
});
it('fireEvent', ()=>{
erdCoreObj.fireEvent({key: 'xyz'}, 'someevent', false);
expect(erdEngine.fireEvent).toHaveBeenCalledWith({key: 'xyz'}, 'someevent');
erdCoreObj.fireEvent({key: 'xyz'}, 'someevent', true);
expect(erdEngine.getModel().fireEvent).toHaveBeenCalledWith({key: 'xyz'}, 'someevent');
});
it('registerKeyAction', ()=>{
erdCoreObj.registerKeyAction({key: 'xyz'});
expect(erdEngine.getActionEventBus().registerAction).toHaveBeenCalledWith({key: 'xyz'});
});
it('deregisterKeyAction', ()=>{
let action = {key: 'xyz'};
erdCoreObj.deregisterKeyAction(action);
expect(erdEngine.getActionEventBus().deregisterAction).toHaveBeenCalledWith({key: 'xyz'});
});
});
});

View File

@@ -0,0 +1,34 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import ERDModel from 'pgadmin.tools.erd/erd_tool/ERDModel';
describe('ERDModel', ()=>{
it('getNodesDict', ()=>{
let model = new ERDModel();
spyOn(model, 'getNodes').and.returnValue([
{
name: 'test1',
getID: function() {
return 'id1';
},
},
{
name: 'test2',
getID: function() {
return 'id2';
},
},
]);
expect(JSON.stringify(model.getNodesDict())).toBe(JSON.stringify({
'id1': {name: 'test1'},
'id2': {name: 'test2'},
}));
});
});

View File

@@ -0,0 +1,61 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2020, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import {KeyboardShortcutAction} from 'pgadmin.tools.erd/erd_tool/ui_components/BodyWidget';
describe('KeyboardShortcutAction', ()=>{
let keyAction = null;
let key1 = {
alt: true,
control: true,
shift: false,
key: {
key_code: 65,
},
};
let key2 = {
alt: false,
control: true,
shift: false,
key: {
key_code: 66,
},
};
let handler1 = jasmine.createSpy('handler1');
let handler2 = jasmine.createSpy('handler2');
beforeAll(()=>{
spyOn(KeyboardShortcutAction.prototype, 'shortcutKey').and.callThrough();
keyAction = new KeyboardShortcutAction([
[key1, handler1],
[key2, handler2],
]);
});
it('init', ()=>{
expect(Object.keys(keyAction.shortcuts).length).toBe(2);
});
it('shortcutKey', ()=>{
expect(keyAction.shortcutKey(true, true, true, true, 65)).toBe('true:true:true:true:65');
expect(keyAction.shortcutKey(true, false, true, true, 65)).toBe('true:false:true:true:65');
expect(keyAction.shortcutKey(true, true, false, true, 65)).toBe('true:true:false:true:65');
expect(keyAction.shortcutKey(true, true, true, false, 65)).toBe('true:true:true:false:65');
expect(keyAction.shortcutKey(false, true, true, true, 65)).toBe('false:true:true:true:65');
});
it('callHandler', ()=>{
let keyEvent = {altKey: key1.alt, ctrlKey: key1.control, shiftKey: key1.shift, metaKey: false, keyCode:key1.key.key_code};
keyAction.callHandler(keyEvent);
expect(handler1).toHaveBeenCalled();
keyEvent = {altKey: key2.alt, ctrlKey: key2.control, shiftKey: key2.shift, metaKey: false, keyCode:key2.key.key_code};
keyAction.callHandler(keyEvent);
expect(handler2).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,133 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {mount} from 'enzyme';
import '../helper/enzyme.helper';
import {
RightAngleLinkModel,
} from '@projectstorm/react-diagrams';
import OneToManyPortModel from 'pgadmin.tools.erd/erd_tool/ports/OneToManyPort';
import {OneToManyLinkModel, OneToManyLinkWidget, OneToManyLinkFactory} from 'pgadmin.tools.erd/erd_tool/links/OneToManyLink';
describe('ERD OneToManyLinkModel', ()=>{
let modelObj = null;
beforeAll(()=>{
spyOn(RightAngleLinkModel.prototype, 'serialize').and.returnValue({'key': 'value'});
});
beforeEach(()=>{
modelObj = new OneToManyLinkModel({
data: {
local_table_uid: 'id1',
local_column_attnum: 0,
referenced_table_uid: 'id2',
referenced_column_attnum: 1,
},
});
});
it('init', ()=>{
expect(modelObj.getData()).toEqual({
local_table_uid: 'id1',
local_column_attnum: 0,
referenced_table_uid: 'id2',
referenced_column_attnum: 1,
});
});
it('setData', ()=>{
modelObj.setData({
local_column_attnum: 2,
referenced_column_attnum: 4,
});
expect(modelObj.getData()).toEqual({
local_column_attnum: 2,
referenced_column_attnum: 4,
});
});
it('serializeData', ()=>{
let nodesDict = {
'id1': {
getData: function(){ return {
'name': 'table1',
'schema': 'erd1',
'columns': [
{'name': 'col11', attnum: 0},
{'name': 'col12', attnum: 1},
],
};},
},
'id2': {
getData: function(){ return {
'name': 'table2',
'schema': 'erd2',
'columns': [
{'name': 'col21', attnum: 0},
{'name': 'col22', attnum: 1},
],
};},
},
};
expect(modelObj.serializeData(nodesDict)).toEqual({
'schema': 'erd1',
'table': 'table1',
'remote_schema': 'erd2',
'remote_table': 'table2',
'columns': [{
'local_column': 'col11',
'referenced': 'col22',
}],
});
});
it('serialize', ()=>{
let retVal = modelObj.serialize();
expect(RightAngleLinkModel.prototype.serialize).toHaveBeenCalled();
expect(retVal).toEqual({
key: 'value',
data: {
local_table_uid: 'id1',
local_column_attnum: 0,
referenced_table_uid: 'id2',
referenced_column_attnum: 1,
},
});
});
});
describe('ERD OneToManyLinkWidget', ()=>{
let linkFactory = new OneToManyLinkFactory();
let engine = {
getFactoryForLink: ()=>linkFactory,
};
let link = null;
beforeEach(()=>{
jasmineEnzyme();
link = new OneToManyLinkModel({
color: '#000',
data: {
local_table_uid: 'id1',
local_column_attnum: 0,
referenced_table_uid: 'id2',
referenced_column_attnum: 1,
},
});
link.setSourcePort(new OneToManyPortModel({options: {}}));
link.setTargetPort(new OneToManyPortModel({options: {}}));
});
it('render', ()=>{
let linkWidget = mount(
<svg><OneToManyLinkWidget link={link} diagramEngine={engine} factory={linkFactory} /></svg>
);
let paths = linkWidget.find('g g');
expect(paths.at(0).find('polyline').length).toBe(1);
expect(paths.at(paths.length-1).find('polyline').length).toBe(1);
expect(paths.at(paths.length-1).find('circle').length).toBe(1);
});
});

View File

@@ -0,0 +1,21 @@
import { PortModel } from '@projectstorm/react-diagrams-core';
import OneToManyPortModel from 'pgadmin.tools.erd/erd_tool/ports/OneToManyPort';
import {OneToManyLinkModel} from 'pgadmin.tools.erd/erd_tool/links/OneToManyLink';
describe('ERD OneToManyPortModel', ()=>{
it('removeAllLinks', ()=>{
let link1 = jasmine.createSpyObj('link1', ['remove']);
let link2 = jasmine.createSpyObj('link2', ['remove']);
spyOn(PortModel.prototype, 'getLinks').and.returnValue([link1, link2]);
let portObj = new OneToManyPortModel({options: {}});
portObj.removeAllLinks();
expect(link1.remove).toHaveBeenCalled();
expect(link2.remove).toHaveBeenCalled();
});
it('createLinkModel', ()=>{
let portObj = new OneToManyPortModel({options: {}});
expect(portObj.createLinkModel()).toBeInstanceOf(OneToManyLinkModel);
});
});

View File

@@ -0,0 +1,305 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {mount} from 'enzyme';
import '../helper/enzyme.helper';
import { DefaultNodeModel } from '@projectstorm/react-diagrams';
import {TableNodeModel, TableNodeWidget} from 'pgadmin.tools.erd/erd_tool/nodes/TableNode';
import { IconButton, DetailsToggleButton } from 'pgadmin.tools.erd/erd_tool/ui_components/ToolBar';
describe('ERD TableNodeModel', ()=>{
let modelObj = null;
beforeAll(()=>{
spyOn(DefaultNodeModel.prototype, 'serialize').and.returnValue({'key': 'value'});
});
beforeEach(()=>{
modelObj = new TableNodeModel({
color: '#000',
otherInfo: {
note: 'some note',
data: {
name: 'table1',
schema: 'erd',
},
},
});
});
it('init', ()=>{
expect(modelObj.getData()).toEqual({
columns: [],
name: 'table1',
schema: 'erd',
});
expect(modelObj.getNote()).toBe('some note');
expect(modelObj.getColumns()).toEqual([]);
});
it('getPortName', ()=>{
expect(modelObj.getPortName(2)).toBe('coll-port-2');
});
it('setNote', ()=>{
modelObj.setNote('some note to test');
expect(modelObj.getNote()).toBe('some note to test');
});
it('addColumn', ()=>{
modelObj.addColumn({name: 'col1', not_null:false, attnum: 0});
expect(modelObj.getColumns()).toEqual([{name: 'col1', not_null:false, attnum: 0}]);
});
it('getColumnAt', ()=>{
modelObj.addColumn({name: 'col1', not_null:false, attnum: 0});
modelObj.addColumn({name: 'col2', not_null:false, attnum: 1});
expect(modelObj.getColumnAt(0)).toEqual({name: 'col1', not_null:false, attnum: 0});
expect(modelObj.getColumnAt(1)).toEqual({name: 'col2', not_null:false, attnum: 1});
expect(modelObj.getColumnAt(2)).toBeUndefined();
});
it('setName', ()=>{
modelObj.setName('changedName');
expect(modelObj.getData().name).toBe('changedName');
});
it('cloneData', ()=>{
modelObj.addColumn({name: 'col1', not_null:false, attnum: 0});
expect(modelObj.cloneData('clonedNode')).toEqual({
name: 'clonedNode',
schema: 'erd',
columns: [{name: 'col1', not_null:false, attnum: 0}],
});
});
describe('setData', ()=>{
let existPort = jasmine.createSpyObj('port', ['removeAllLinks']);
beforeEach(()=>{
modelObj._data.columns = [
{name: 'col1', not_null:false, attnum: 0},
{name: 'col2', not_null:false, attnum: 1},
{name: 'col3', not_null:false, attnum: 2},
];
spyOn(modelObj, 'getPort').and.callFake((portName)=>{
/* If new port added there will not be any port */
if(portName !== 'coll-port-3') {
return existPort;
}
});
spyOn(modelObj, 'removePort');
spyOn(modelObj, 'getPortName');
});
it('add columns', ()=>{
existPort.removeAllLinks.calls.reset();
modelObj.setData({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col1', not_null:false, attnum: 0},
{name: 'col2', not_null:false, attnum: 1},
{name: 'col3', not_null:false, attnum: 2},
{name: 'col4', not_null:false, attnum: 3},
],
});
expect(modelObj.getData()).toEqual({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col1', not_null:false, attnum: 0},
{name: 'col2', not_null:false, attnum: 1},
{name: 'col3', not_null:false, attnum: 2},
{name: 'col4', not_null:false, attnum: 3},
],
});
expect(existPort.removeAllLinks).not.toHaveBeenCalled();
});
it('update columns', ()=>{
existPort.removeAllLinks.calls.reset();
modelObj.setData({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col1', not_null:false, attnum: 0},
{name: 'col2updated', not_null:false, attnum: 1},
{name: 'col3', not_null:true, attnum: 2},
],
});
expect(modelObj.getData()).toEqual({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col1', not_null:false, attnum: 0},
{name: 'col2updated', not_null:false, attnum: 1},
{name: 'col3', not_null:true, attnum: 2},
],
});
expect(existPort.removeAllLinks).not.toHaveBeenCalled();
});
it('remove columns', ()=>{
existPort.removeAllLinks.calls.reset();
modelObj.setData({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col2', not_null:false, attnum: 1},
{name: 'col3', not_null:false, attnum: 2},
],
});
expect(modelObj.getData()).toEqual({
name: 'noname',
schema: 'erd',
columns: [
{name: 'col2', not_null:false, attnum: 1},
{name: 'col3', not_null:false, attnum: 2},
],
});
expect(modelObj.getPortName).toHaveBeenCalledWith(0);
expect(existPort.removeAllLinks).toHaveBeenCalled();
expect(modelObj.removePort).toHaveBeenCalledWith(existPort);
});
});
it('getSchemaTableName', ()=>{
expect(modelObj.getSchemaTableName()).toEqual(['erd', 'table1']);
});
it('serializeData', ()=>{
modelObj.addColumn({name: 'col1', not_null:false, attnum: 0});
expect(modelObj.serializeData()).toEqual({
name: 'table1',
schema: 'erd',
columns: [{name: 'col1', not_null:false, attnum: 0}],
});
});
it('serialize', ()=>{
let retVal = modelObj.serialize();
expect(DefaultNodeModel.prototype.serialize).toHaveBeenCalled();
expect(retVal).toEqual({
key: 'value',
otherInfo: {
data: {
columns: [],
name: 'table1',
schema: 'erd',
},
note: 'some note',
},
});
});
});
describe('ERD TableNodeWidget', ()=>{
let node = null;
beforeEach(()=>{
jasmineEnzyme();
node = new TableNodeModel({
color: '#000',
otherInfo: {
note: 'some note',
data: {
name: 'table1',
schema: 'erd',
columns: [{
attnum: 0,
is_primary_key: true,
name: 'id',
cltype: 'integer',
attlen: null,
attprecision: null,
}, {
attnum: 1,
is_primary_key: false,
name: 'amount',
cltype: 'number',
attlen: 10,
attprecision: 5,
}, {
attnum: 2,
is_primary_key: false,
name: 'desc',
cltype: 'character varrying',
attlen: 50,
attprecision: null,
}],
},
},
});
});
it('render', ()=>{
let nodeWidget = mount(<TableNodeWidget node={node}/>);
expect(nodeWidget.getDOMNode().className).toBe('table-node ');
expect(nodeWidget.find('.table-node .table-toolbar').length).toBe(1);
expect(nodeWidget.find('.table-node .table-schema').text()).toBe('erd');
expect(nodeWidget.find('.table-node .table-name').text()).toBe('table1');
expect(nodeWidget.find('.table-node .table-cols').length).toBe(1);
expect(nodeWidget.find(DetailsToggleButton).length).toBe(1);
expect(nodeWidget.find(IconButton).findWhere(n => n.prop('title')=='Check note').length).toBe(1);
});
it('node selected', ()=>{
spyOn(node, 'isSelected').and.returnValue(true);
let nodeWidget = mount(<TableNodeWidget node={node}/>);
expect(nodeWidget.getDOMNode().className).toBe('table-node selected');
});
it('remove note', ()=>{
node.setNote('');
let nodeWidget = mount(<TableNodeWidget node={node}/>);
expect(nodeWidget.find(IconButton).findWhere(n => n.prop('title')=='Check note').length).toBe(0);
});
describe('generateColumn', ()=>{
let nodeWidget = null;
beforeEach(()=>{
nodeWidget = mount(<TableNodeWidget node={node}/>);
});
it('count', ()=>{
expect(nodeWidget.find('.table-node .table-cols .col-row').length).toBe(3);
});
it('icons', ()=>{
let cols = nodeWidget.find('.table-node .table-cols .col-row-data');
expect(cols.at(0).find('.wcTabIcon').hasClass('icon-primary_key')).toBeTrue();
expect(cols.at(1).find('.wcTabIcon').hasClass('icon-column')).toBeTrue();
expect(cols.at(2).find('.wcTabIcon').hasClass('icon-column')).toBeTrue();
});
it('column names', ()=>{
let cols = nodeWidget.find('.table-node .table-cols .col-row-data');
expect(cols.at(0).find('.col-name').text()).toBe('id');
expect(cols.at(1).find('.col-name').text()).toBe('amount');
expect(cols.at(2).find('.col-name').text()).toBe('desc');
});
it('data types', ()=>{
let cols = nodeWidget.find('.table-node .table-cols .col-row-data');
expect(cols.at(0).find('.col-datatype').text()).toBe('integer');
expect(cols.at(1).find('.col-datatype').text()).toBe('number(10,5)');
expect(cols.at(2).find('.col-datatype').text()).toBe('character varrying(50)');
});
it('show_details', (done)=>{
nodeWidget.setState({show_details: false});
expect(nodeWidget.find('.table-node .table-cols .col-row-data .col-datatype').length).toBe(0);
nodeWidget.instance().toggleShowDetails(jasmine.createSpyObj('event', ['preventDefault']));
/* Dummy set state to wait for toggleShowDetails -> setState to complete */
nodeWidget.setState({}, ()=>{
expect(nodeWidget.find('.table-node .table-cols .col-row-data .col-datatype').length).toBe(3);
done();
});
});
});
});

View File

@@ -0,0 +1,651 @@
export default [
{
'oid': 123456,
'name': 'test1',
'spcoid': 0,
'relacl_str': null,
'spcname': 'pg_default',
'schema': 'schema1',
'relowner': 'postgres',
'relkind': 'r',
'is_partitioned': false,
'relhassubclass': false,
'reltuples': '0',
'description': null,
'conname': null,
'conkey': null,
'isrepl': false,
'triggercount': '0',
'coll_inherits': [],
'inherited_tables_cnt': '0',
'relpersistence': false,
'fillfactor': null,
'parallel_workers': null,
'toast_tuple_target': null,
'autovacuum_enabled': 'x',
'autovacuum_vacuum_threshold': null,
'autovacuum_vacuum_scale_factor': null,
'autovacuum_analyze_threshold': null,
'autovacuum_analyze_scale_factor': null,
'autovacuum_vacuum_cost_delay': null,
'autovacuum_vacuum_cost_limit': null,
'autovacuum_freeze_min_age': null,
'autovacuum_freeze_max_age': null,
'autovacuum_freeze_table_age': null,
'toast_autovacuum_enabled': 'x',
'toast_autovacuum_vacuum_threshold': null,
'toast_autovacuum_vacuum_scale_factor': null,
'toast_autovacuum_analyze_threshold': null,
'toast_autovacuum_analyze_scale_factor': null,
'toast_autovacuum_vacuum_cost_delay': null,
'toast_autovacuum_vacuum_cost_limit': null,
'toast_autovacuum_freeze_min_age': null,
'toast_autovacuum_freeze_max_age': null,
'toast_autovacuum_freeze_table_age': null,
'reloptions': null,
'toast_reloptions': null,
'reloftype': 0,
'typname': null,
'typoid': null,
'rlspolicy': false,
'forcerlspolicy': false,
'hastoasttable': false,
'seclabels': null,
'is_sys_table': false,
'partition_scheme': '',
'autovacuum_custom': false,
'toast_autovacuum': false,
'rows_cnt': 0,
'vacuum_settings_str': '',
'vacuum_table': [
{
'name': 'autovacuum_analyze_scale_factor',
'setting': '0.1',
'label': 'ANALYZE scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_analyze_threshold',
'setting': '50',
'label': 'ANALYZE base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_max_age',
'setting': '200000000',
'label': 'FREEZE maximum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_delay',
'setting': '2',
'label': 'VACUUM cost delay',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_limit',
'setting': '-1',
'label': 'VACUUM cost limit',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_scale_factor',
'setting': '0.2',
'label': 'VACUUM scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_vacuum_threshold',
'setting': '50',
'label': 'VACUUM base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_min_age',
'setting': '50000000',
'label': 'FREEZE minimum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_table_age',
'setting': '150000000',
'label': 'FREEZE table age',
'column_type': 'integer',
},
],
'vacuum_toast': [
{
'name': 'autovacuum_freeze_max_age',
'setting': '200000000',
'label': 'FREEZE maximum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_delay',
'setting': '2',
'label': 'VACUUM cost delay',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_limit',
'setting': '-1',
'label': 'VACUUM cost limit',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_scale_factor',
'setting': '0.2',
'label': 'VACUUM scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_vacuum_threshold',
'setting': '50',
'label': 'VACUUM base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_min_age',
'setting': '50000000',
'label': 'FREEZE minimum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_table_age',
'setting': '150000000',
'label': 'FREEZE table age',
'column_type': 'integer',
},
],
'columns': [{
'name': 'id',
'atttypid': 23,
'attlen': null,
'attnum': 1,
'attndims': 0,
'atttypmod': -1,
'attacl': [],
'attnotnull': true,
'attoptions': null,
'attstattarget': -1,
'attstorage': 'p',
'attidentity': '',
'defval': null,
'typname': 'integer',
'displaytypname': 'integer',
'cltype': 'integer',
'elemoid': 23,
'typnspname': 'pg_catalog',
'defaultstorage': 'p',
'description': null,
'indkey': '1',
'isdup': false,
'collspcname': '',
'is_fk': false,
'seclabels': null,
'is_sys_column': false,
'colconstype': 'n',
'genexpr': null,
'relname': 'tab1',
'is_view_only': false,
'seqrelid': null,
'seqtypid': null,
'seqstart': null,
'seqincrement': null,
'seqmax': null,
'seqmin': null,
'seqcache': null,
'seqcycle': null,
'is_pk': true,
'is_primary_key': true,
'attprecision': null,
'edit_types': [
'bigint',
'double precision',
'information_schema.cardinal_number',
'integer',
'money',
'numeric',
'oid',
'real',
'regclass',
'regconfig',
'regdictionary',
'regnamespace',
'regoper',
'regoperator',
'regproc',
'regprocedure',
'regrole',
'regtype',
'smallint',
],
}],
'primary_key': [],
'unique_constraint': [],
'check_constraint': [],
'index': {},
'rule': {},
'trigger': {},
'row_security_policy': {},
},
{
'oid': 408229,
'name': 'test2',
'spcoid': 0,
'relacl_str': null,
'spcname': 'pg_default',
'schema': 'erd',
'relowner': 'postgres',
'relkind': 'r',
'is_partitioned': false,
'relhassubclass': false,
'reltuples': '0',
'description': null,
'conname': 'tab1_pkey',
'conkey': [
1,
],
'isrepl': false,
'triggercount': '0',
'coll_inherits': [],
'inherited_tables_cnt': '0',
'relpersistence': false,
'fillfactor': null,
'parallel_workers': null,
'toast_tuple_target': null,
'autovacuum_enabled': 'x',
'autovacuum_vacuum_threshold': null,
'autovacuum_vacuum_scale_factor': null,
'autovacuum_analyze_threshold': null,
'autovacuum_analyze_scale_factor': null,
'autovacuum_vacuum_cost_delay': null,
'autovacuum_vacuum_cost_limit': null,
'autovacuum_freeze_min_age': null,
'autovacuum_freeze_max_age': null,
'autovacuum_freeze_table_age': null,
'toast_autovacuum_enabled': 'x',
'toast_autovacuum_vacuum_threshold': null,
'toast_autovacuum_vacuum_scale_factor': null,
'toast_autovacuum_analyze_threshold': null,
'toast_autovacuum_analyze_scale_factor': null,
'toast_autovacuum_vacuum_cost_delay': null,
'toast_autovacuum_vacuum_cost_limit': null,
'toast_autovacuum_freeze_min_age': null,
'toast_autovacuum_freeze_max_age': null,
'toast_autovacuum_freeze_table_age': null,
'reloptions': null,
'toast_reloptions': null,
'reloftype': 0,
'typname': null,
'typoid': null,
'rlspolicy': false,
'forcerlspolicy': false,
'hastoasttable': false,
'seclabels': null,
'is_sys_table': false,
'partition_scheme': '',
'autovacuum_custom': false,
'toast_autovacuum': false,
'rows_cnt': 0,
'vacuum_settings_str': '',
'vacuum_table': [
{
'name': 'autovacuum_analyze_scale_factor',
'setting': '0.1',
'label': 'ANALYZE scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_analyze_threshold',
'setting': '50',
'label': 'ANALYZE base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_max_age',
'setting': '200000000',
'label': 'FREEZE maximum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_delay',
'setting': '2',
'label': 'VACUUM cost delay',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_limit',
'setting': '-1',
'label': 'VACUUM cost limit',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_scale_factor',
'setting': '0.2',
'label': 'VACUUM scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_vacuum_threshold',
'setting': '50',
'label': 'VACUUM base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_min_age',
'setting': '50000000',
'label': 'FREEZE minimum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_table_age',
'setting': '150000000',
'label': 'FREEZE table age',
'column_type': 'integer',
},
],
'vacuum_toast': [
{
'name': 'autovacuum_freeze_max_age',
'setting': '200000000',
'label': 'FREEZE maximum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_delay',
'setting': '2',
'label': 'VACUUM cost delay',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_cost_limit',
'setting': '-1',
'label': 'VACUUM cost limit',
'column_type': 'integer',
},
{
'name': 'autovacuum_vacuum_scale_factor',
'setting': '0.2',
'label': 'VACUUM scale factor',
'column_type': 'number',
},
{
'name': 'autovacuum_vacuum_threshold',
'setting': '50',
'label': 'VACUUM base threshold',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_min_age',
'setting': '50000000',
'label': 'FREEZE minimum age',
'column_type': 'integer',
},
{
'name': 'autovacuum_freeze_table_age',
'setting': '150000000',
'label': 'FREEZE table age',
'column_type': 'integer',
},
],
'columns': [
{
'name': 'id',
'atttypid': 23,
'attlen': null,
'attnum': 1,
'attndims': 0,
'atttypmod': -1,
'attacl': [],
'attnotnull': true,
'attoptions': null,
'attstattarget': -1,
'attstorage': 'p',
'attidentity': '',
'defval': null,
'typname': 'integer',
'displaytypname': 'integer',
'cltype': 'integer',
'elemoid': 23,
'typnspname': 'pg_catalog',
'defaultstorage': 'p',
'description': null,
'indkey': '1',
'isdup': false,
'collspcname': '',
'is_fk': false,
'seclabels': null,
'is_sys_column': false,
'colconstype': 'n',
'genexpr': null,
'relname': 'tab1',
'is_view_only': false,
'seqrelid': null,
'seqtypid': null,
'seqstart': null,
'seqincrement': null,
'seqmax': null,
'seqmin': null,
'seqcache': null,
'seqcycle': null,
'is_pk': true,
'is_primary_key': true,
'attprecision': null,
'edit_types': [
'bigint',
'double precision',
'information_schema.cardinal_number',
'integer',
'money',
'numeric',
'oid',
'real',
'regclass',
'regconfig',
'regdictionary',
'regnamespace',
'regoper',
'regoperator',
'regproc',
'regprocedure',
'regrole',
'regtype',
'smallint',
],
},
{
'name': 'col1col1col1col1col1col1col1col1',
'atttypid': 23,
'attlen': null,
'attnum': 2,
'attndims': 0,
'atttypmod': -1,
'attacl': [],
'attnotnull': true,
'attoptions': null,
'attstattarget': -1,
'attstorage': 'p',
'attidentity': '',
'defval': null,
'typname': 'integer',
'displaytypname': 'integer',
'cltype': 'integer',
'elemoid': 23,
'typnspname': 'pg_catalog',
'defaultstorage': 'p',
'description': null,
'indkey': '1',
'isdup': false,
'collspcname': '',
'is_fk': true,
'seclabels': null,
'is_sys_column': false,
'colconstype': 'n',
'genexpr': null,
'relname': 'tab1',
'is_view_only': false,
'seqrelid': null,
'seqtypid': null,
'seqstart': null,
'seqincrement': null,
'seqmax': null,
'seqmin': null,
'seqcache': null,
'seqcycle': null,
'is_pk': false,
'is_primary_key': false,
'attprecision': null,
'edit_types': [
'bigint',
'double precision',
'information_schema.cardinal_number',
'integer',
'integer',
'money',
'numeric',
'oid',
'real',
'regclass',
'regconfig',
'regdictionary',
'regnamespace',
'regoper',
'regoperator',
'regproc',
'regprocedure',
'regrole',
'regtype',
'smallint',
],
},
{
'name': 'col2',
'atttypid': 23,
'attlen': null,
'attnum': 3,
'attndims': 0,
'atttypmod': -1,
'attacl': [],
'attnotnull': false,
'attoptions': null,
'attstattarget': -1,
'attstorage': 'p',
'attidentity': '',
'defval': null,
'typname': 'integer',
'displaytypname': 'integer',
'cltype': 'integer',
'elemoid': 23,
'typnspname': 'pg_catalog',
'defaultstorage': 'p',
'description': null,
'indkey': '1',
'isdup': false,
'collspcname': '',
'is_fk': false,
'seclabels': null,
'is_sys_column': false,
'colconstype': 'n',
'genexpr': null,
'relname': 'tab1',
'is_view_only': false,
'seqrelid': null,
'seqtypid': null,
'seqstart': null,
'seqincrement': null,
'seqmax': null,
'seqmin': null,
'seqcache': null,
'seqcycle': null,
'is_pk': false,
'is_primary_key': false,
'attprecision': null,
'edit_types': [
'bigint',
'double precision',
'information_schema.cardinal_number',
'integer',
'integer',
'integer',
'money',
'numeric',
'oid',
'real',
'regclass',
'regconfig',
'regdictionary',
'regnamespace',
'regoper',
'regoperator',
'regproc',
'regprocedure',
'regrole',
'regtype',
'smallint',
],
},
],
'primary_key': [
{
'oid': 408232,
'name': 'tab1_pkey',
'col_count': 1,
'spcname': 'pg_default',
'comment': null,
'condeferrable': false,
'condeferred': false,
'fillfactor': null,
'columns': [
{
'column': 'id',
},
],
'include': [],
},
],
'unique_constraint': [],
'foreign_key': [
{
'oid': 408239,
'name': 'tab1_col1_fkey',
'condeferrable': false,
'condeferred': false,
'confupdtype': 'a',
'confdeltype': 'a',
'confmatchtype': false,
'conkey': [
2,
],
'confkey': [
1,
],
'confrelid': 408234,
'fknsp': 'erd',
'fktab': 'tab1',
'refnsp': 'erd',
'reftab': 'tab2',
'comment': null,
'convalidated': false,
'columns': [
{
'local_column': 'col1col1col1col1col1col1col1col1',
'references': 123456,
'referenced': 'id',
'references_table_name': 'schema1.test1',
},
],
'remote_schema': 'schema1',
'remote_table': 'test1',
'coveringindex': null,
'autoindex': true,
'hasindex': false,
},
],
'check_constraint': [],
'index': {},
'rule': {},
'trigger': {},
'row_security_policy': {},
},
];

View File

@@ -0,0 +1,514 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {mount} from 'enzyme';
import '../../helper/enzyme.helper';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios/index';
import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
import * as erdModule from 'pgadmin.tools.erd/erd_module';
import erdPref from './erd_preferences';
import BodyWidget from 'pgadmin.tools.erd/erd_tool/ui_components/BodyWidget';
import * as ERDSqlTool from 'tools/datagrid/static/js/show_query_tool';
let pgAdmin = {
Browser: {
Events: {
on: jasmine.createSpy('on'),
},
get_preferences_for_module: function() {
return erdPref;
},
docker: {
findPanels: function() {
return [
{
isVisible: function() {
return true;
},
},
];
},
},
onPreferencesChange: ()=>{},
utils: {
app_version_int: 1234,
},
},
FileManager: {
init: jasmine.createSpy(),
show_dialog: jasmine.createSpy(),
},
};
let alertify = jasmine.createSpyObj('alertify', {
'success': null,
'error': null,
'confirm': null,
'alert': {
'set': ()=>{},
},
});
let tableDialog = jasmine.createSpyObj('TableDialog', ['show']);
let otmDialog = jasmine.createSpyObj('otmDialog', ['show']);
let mtmDialog = jasmine.createSpyObj('mtmDialog', ['show']);
let getDialog = (dialogName)=>{
switch(dialogName) {
case 'entity_dialog': return tableDialog;
case 'onetomany_dialog': return otmDialog;
case 'manytomany_dialog': return mtmDialog;
}
};
describe('ERD BodyWidget', ()=>{
let body = null;
let bodyInstance = null;
let networkMock = null;
let serverVersion = 120000;
let colTypes = [
{'label': 'integer', 'value': 'integer'},
{'label': 'character varrying', 'value': 'character varrying'},
];
let schemas = [
{'oid': 111, 'name': 'erd1'},
{'oid': 222, 'name': 'erd2'},
];
let params = {
bgcolor: null,
client_platform: 'macos',
did: '13637',
fgcolor: null,
gen: true,
is_desktop_mode: true,
is_linux: false,
server_type: 'pg',
sgid: '1',
sid: '5',
title: 'postgres/postgres@PostgreSQL 12',
trans_id: 110008,
};
beforeAll(()=>{
spyOn(erdModule, 'setPanelTitle');
spyOn(ERDCore.prototype, 'repaint');
spyOn(ERDCore.prototype, 'deserializeData');
spyOn(ERDCore.prototype, 'addNode').and.returnValue({
setSelected: ()=>{},
getColumns: ()=>([{attnum: 0}, {attnum: 1}]),
getID: ()=>'newid1',
});
spyOn(ERDCore.prototype, 'addLink').and.returnValue({
setSelected: ()=>{},
});
spyOn(alertify, 'confirm').and.callFake((arg1, arg2, okCallback)=>{
okCallback();
});
networkMock = new MockAdapter(axios);
networkMock.onPost('/erd/initialize/110008/1/5/13637').reply(200, {'data': {
serverVersion: serverVersion,
}});
networkMock.onGet('/erd/prequisite/110008/1/5/13637').reply(200, {'data': {
'col_types': colTypes,
'schemas': schemas,
}});
networkMock.onGet('/erd/tables/110008/1/5/13637').reply(200, {'data': []});
networkMock.onPost('/erd/sql/110008/1/5/13637').reply(200, {'data': 'SELECT 1;'});
networkMock.onPost('/sqleditor/load_file/').reply(200, {'data': 'data'});
networkMock.onPost('/sqleditor/save_file/').reply(200, {'data': 'data'});
});
beforeEach(()=>{
jasmineEnzyme();
body = mount(<BodyWidget params={params} pgAdmin={pgAdmin} getDialog={getDialog} transformToSupported={()=>{}} alertify={alertify}/>);
bodyInstance = body.instance();
});
afterAll(() => {
networkMock.restore();
if(body) {
body.unmount();
}
});
it('constructor', (done)=>{
expect(body.find('ToolBar').length).toBe(1);
expect(body.find('ConnectionBar').length).toBe(1);
expect(body.find('FloatingNote').length).toBe(1);
expect(body.find('.diagram-container Loader').length).toBe(1);
expect(body.find('.diagram-container CanvasWidget').length).toBe(1);
body.instance().setState({}, ()=>{
let instance = body.instance();
setTimeout(()=>{
expect(body.state()).toEqual(jasmine.objectContaining({
server_version: serverVersion,
preferences: erdPref,
}));
expect(instance.diagram.getCache('colTypes')).toEqual(colTypes);
expect(instance.diagram.getCache('schemas')).toEqual(schemas);
done();
});
});
});
it('event offsetUpdated', (done)=>{
bodyInstance.diagram.fireEvent({offsetX: 4, offsetY: 5}, 'offsetUpdated', true);
setTimeout(()=>{
expect(bodyInstance.canvasEle.style.backgroundPosition).toBe('4px 5px');
done();
});
});
it('event zoomUpdated', (done)=>{
spyOn(bodyInstance.diagram.getModel(), 'getOptions').and.returnValue({gridSize: 15});
bodyInstance.diagram.fireEvent({zoom: 20}, 'zoomUpdated', true);
setTimeout(()=>{
expect(bodyInstance.canvasEle.style.backgroundSize).toBe('9px 9px');
done();
});
});
it('event nodesSelectionChanged', (done)=>{
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([{key:'value'}]);
bodyInstance.diagram.fireEvent({}, 'nodesSelectionChanged', true);
setTimeout(()=>{
expect(body.state().single_node_selected).toBe(true);
expect(body.state().any_item_selected).toBe(true);
done();
});
});
it('event linksSelectionChanged', (done)=>{
spyOn(bodyInstance.diagram, 'getSelectedLinks').and.returnValue([{key:'value'}]);
bodyInstance.diagram.fireEvent({}, 'linksSelectionChanged', true);
setTimeout(()=>{
expect(body.state().single_link_selected).toBe(true);
expect(body.state().any_item_selected).toBe(true);
done();
});
});
it('event linksUpdated', (done)=>{
bodyInstance.diagram.fireEvent({}, 'linksUpdated', true);
setTimeout(()=>{
expect(body.state().dirty).toBe(true);
done();
});
});
it('event nodesUpdated', (done)=>{
bodyInstance.diagram.fireEvent({}, 'nodesUpdated', true);
setTimeout(()=>{
expect(body.state().dirty).toBe(true);
done();
});
});
it('event showNote', (done)=>{
let noteNode = {key: 'value', getNote: ()=>'a note'};
spyOn(bodyInstance, 'showNote');
bodyInstance.diagram.fireEvent({node: noteNode}, 'showNote', true);
setTimeout(()=>{
expect(bodyInstance.showNote).toHaveBeenCalledWith(noteNode);
done();
});
});
it('event editNode', (done)=>{
let node = {key: 'value', getNote: ()=>'a note'};
spyOn(bodyInstance, 'addEditNode');
bodyInstance.diagram.fireEvent({node: node}, 'editNode', true);
setTimeout(()=>{
expect(bodyInstance.addEditNode).toHaveBeenCalledWith(node);
done();
});
});
it('getDialog', ()=>{
bodyInstance.getDialog('entity_dialog')();
expect(tableDialog.show).toHaveBeenCalled();
bodyInstance.getDialog('onetomany_dialog')();
expect(otmDialog.show).toHaveBeenCalled();
bodyInstance.getDialog('manytomany_dialog')();
expect(mtmDialog.show).toHaveBeenCalled();
});
it('addEditNode', ()=>{
/* New */
tableDialog.show.calls.reset();
bodyInstance.addEditNode();
expect(tableDialog.show).toHaveBeenCalled();
let saveCallback = tableDialog.show.calls.mostRecent().args[5];
let newData = {key: 'value'};
saveCallback(newData);
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith(newData);
/* Existing */
tableDialog.show.calls.reset();
let node = jasmine.createSpyObj('node',{
getSchemaTableName: ['erd1', 'table1'],
setData: null,
getData: null,
});
bodyInstance.addEditNode(node);
expect(tableDialog.show).toHaveBeenCalled();
saveCallback = tableDialog.show.calls.mostRecent().args[5];
newData = {key: 'value'};
saveCallback(newData);
expect(node.setData).toHaveBeenCalledWith(newData);
});
it('onEditNode', ()=>{
let node = {key: 'value'};
spyOn(bodyInstance, 'addEditNode');
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
bodyInstance.onEditNode();
expect(bodyInstance.addEditNode).toHaveBeenCalledWith(node);
});
it('onAddNewNode', ()=>{
spyOn(bodyInstance, 'addEditNode');
bodyInstance.onAddNewNode();
expect(bodyInstance.addEditNode).toHaveBeenCalled();
});
it('onCloneNode', ()=>{
let node = jasmine.createSpyObj('node',{
getSchemaTableName: ['erd1', 'table1'],
setData: null,
getData: null,
cloneData: {key: 'value'},
getPosition: {x: 30, y: 30},
});
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
spyOn(bodyInstance.diagram, 'getNextTableName').and.returnValue('newtable1');
bodyInstance.onCloneNode();
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith({key: 'value'}, [50, 50]);
});
it('onDeleteNode', (done)=>{
let node = jasmine.createSpyObj('node',{
getSchemaTableName: ['erd1', 'table1'],
setData: null,
getData: null,
cloneData: {key: 'value'},
getPosition: {x: 30, y: 30},
remove: null,
setSelected: null,
});
let link = jasmine.createSpyObj('link', {
remove: null,
setSelected: null,
getTargetPort: jasmine.createSpyObj('port', ['remove']),
getSourcePort: jasmine.createSpyObj('port', ['remove']),
});
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
spyOn(bodyInstance.diagram, 'getSelectedLinks').and.returnValue([link]);
bodyInstance.onDeleteNode();
setTimeout(()=>{
expect(node.remove).toHaveBeenCalled();
expect(link.remove).toHaveBeenCalled();
done();
});
});
it('onAutoDistribute', ()=>{
spyOn(bodyInstance.diagram, 'dagreDistributeNodes');
bodyInstance.onAutoDistribute();
expect(bodyInstance.diagram.dagreDistributeNodes).toHaveBeenCalled();
});
it('onDetailsToggle', (done)=>{
let node = jasmine.createSpyObj('node',['fireEvent']);
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
'getNodes': ()=>[node],
});
let show_details = body.state().show_details;
bodyInstance.onDetailsToggle();
body.setState({}, ()=>{
expect(body.state().show_details).toBe(!show_details);
expect(node.fireEvent).toHaveBeenCalledWith({show_details: !show_details}, 'toggleDetails');
done();
});
});
it('onLoadDiagram', ()=>{
bodyInstance.onLoadDiagram();
expect(pgAdmin.FileManager.show_dialog).toHaveBeenCalled();
});
it('openFile', (done)=>{
spyOn(bodyInstance.diagram, 'deserialize');
bodyInstance.openFile('test.pgerd');
setTimeout(()=>{
expect(body.state()).toEqual(jasmine.objectContaining({
current_file: 'test.pgerd',
dirty: false,
}));
expect(bodyInstance.diagram.deserialize).toHaveBeenCalledWith({data: 'data'});
done();
});
});
it('onSaveDiagram', (done)=>{
body.setState({
current_file: 'newfile.pgerd',
});
bodyInstance.onSaveDiagram();
setTimeout(()=>{
expect(body.state()).toEqual(jasmine.objectContaining({
current_file: 'newfile.pgerd',
dirty: false,
}));
done();
});
bodyInstance.onSaveDiagram(true);
expect(pgAdmin.FileManager.show_dialog).toHaveBeenCalledWith({
'supported_types': ['pgerd'],
'dialog_type': 'create_file',
'dialog_title': 'Save File',
'btn_primary': 'Save',
});
});
it('onSaveAsDiagram', ()=>{
spyOn(bodyInstance, 'onSaveDiagram');
bodyInstance.onSaveAsDiagram();
expect(bodyInstance.onSaveDiagram).toHaveBeenCalledWith(true);
});
it('onSQLClick', (done)=>{
spyOn(bodyInstance.diagram, 'serializeData').and.returnValue({key: 'value'});
spyOn(ERDSqlTool, 'showERDSqlTool');
spyOn(localStorage, 'setItem');
bodyInstance.onSQLClick();
setTimeout(()=>{
let sql = '-- This script was generated by a beta version of the ERD tool in pgAdmin 4.\n'
+ '-- Please log an issue at https://redmine.postgresql.org/projects/pgadmin4/issues/new if you find any bugs, including reproduction steps.\n'
+ 'BEGIN;\nSELECT 1;\nEND;';
expect(localStorage.setItem).toHaveBeenCalledWith('erd'+params.trans_id, sql);
expect(ERDSqlTool.showERDSqlTool).toHaveBeenCalled();
done();
});
});
it('onOneToManyClick', ()=>{
let node = jasmine.createSpyObj('node',{
getID: 'id1',
});
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
otmDialog.show.calls.reset();
bodyInstance.onOneToManyClick();
expect(otmDialog.show).toHaveBeenCalled();
let saveCallback = otmDialog.show.calls.mostRecent().args[4];
let newData = {key: 'value'};
saveCallback(newData);
expect(bodyInstance.diagram.addLink).toHaveBeenCalledWith(newData, 'onetomany');
});
it('onManyToManyClick', ()=>{
let node = jasmine.createSpyObj('node',{
getID: 'id1',
});
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
mtmDialog.show.calls.reset();
bodyInstance.onManyToManyClick();
expect(mtmDialog.show).toHaveBeenCalled();
/* onSave */
let nodesDict = {
'id1': {
getID: ()=>'id1',
getData: ()=>({name: 'table1', schema: 'erd1'}),
getColumnAt: ()=>({name: 'col1', type: 'type1', attnum: 0}),
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
},
'id2': {
getID: ()=>'id2',
getData: ()=>({name: 'table2', schema: 'erd2'}),
getColumnAt: ()=>({name: 'col2', type: 'type2', attnum: 1}),
addPort: jasmine.createSpy('addPort').and.callFake((obj)=>obj),
},
};
spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
'getNodesDict': ()=>nodesDict,
});
spyOn(bodyInstance.diagram, 'addLink');
let saveCallback = mtmDialog.show.calls.mostRecent().args[4];
let newData = {
left_table_uid: 'id1',
left_table_column_attnum: 1,
right_table_uid: 'id2',
right_table_column_attnum: 2,
};
bodyInstance.diagram.addNode.calls.reset();
bodyInstance.diagram.addLink.calls.reset();
saveCallback(newData);
expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith({
name: 'table1_table2',
schema: 'erd1',
columns: [
{
type: 'type1',
name: 'table1_col1',
is_primary_key: false,
attnum: 0,
},
{
type: 'type2',
name: 'table2_col2',
is_primary_key: false,
attnum: 1,
},
],
});
let linkData = {
local_table_uid: 'newid1',
local_column_attnum: 0,
referenced_table_uid: 'id1',
referenced_column_attnum : 1,
};
expect(bodyInstance.diagram.addLink.calls.argsFor(0)).toEqual([linkData, 'onetomany']);
linkData = {
local_table_uid: 'newid1',
local_column_attnum: 1,
referenced_table_uid: 'id2',
referenced_column_attnum : 2,
};
expect(bodyInstance.diagram.addLink.calls.argsFor(1)).toEqual([linkData, 'onetomany']);
});
it('onNoteClick', ()=>{
let noteNode = {key: 'value', getNote: ()=>'a note'};
spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([noteNode]);
spyOn(bodyInstance.diagram.getEngine(), 'getNodeElement').and.returnValue(null);
spyOn(bodyInstance.diagram.getEngine(), 'getNodeElement').and.returnValue(null);
spyOn(bodyInstance, 'setState');
bodyInstance.onNoteClick();
expect(bodyInstance.setState).toHaveBeenCalledWith({
note_node: noteNode,
note_open: true,
});
});
});

View File

@@ -0,0 +1,25 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {mount} from 'enzyme';
import '../../helper/enzyme.helper';
import ConnectionBar, {STATUS} from 'pgadmin.tools.erd/erd_tool/ui_components/ConnectionBar';
describe('ERD ConnectionBar', ()=>{
beforeEach(()=>{
jasmineEnzyme();
});
it('<ConnectionBar /> comp', ()=>{
let connBar = mount(<ConnectionBar statusId="conn-bar" status={STATUS.DISCONNECTED} title="test title"/>);
expect(connBar.find('.editor-title').text()).toBe('test title');
connBar.setProps({status: STATUS.CONNECTING});
expect(connBar.find('.editor-title').text()).toBe('(Obtaining connection...) test title');
connBar.setProps({bgcolor: '#000', fgcolor: '#fff'});
expect(connBar.find('.editor-title').prop('style').backgroundColor).toBe('#000');
expect(connBar.find('.editor-title').prop('style').color).toBe('#fff');
});
});

View File

@@ -0,0 +1,147 @@
export default {
'erd_new_browser_tab': false,
'open_project': {
'alt': false,
'shift': false,
'control': true,
'key': {
'key_code': 79,
'char': 'o',
},
},
'save_project': {
'alt': false,
'shift': false,
'control': true,
'key': {
'key_code': 83,
'char': 's',
},
},
'save_project_as': {
'alt': false,
'shift': true,
'control': true,
'key': {
'key_code': 83,
'char': 's',
},
},
'generate_sql': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 83,
'char': 's',
},
},
'download_image': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 73,
'char': 'i',
},
},
'add_table': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 65,
'char': 'a',
},
},
'edit_table': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 69,
'char': 'e',
},
},
'clone_table': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 67,
'char': 'c',
},
},
'drop_table': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 68,
'char': 'd',
},
},
'add_edit_note': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 78,
'char': 'n',
},
},
'one_to_many': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 79,
'char': 'o',
},
},
'many_to_many': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 77,
'char': 'm',
},
},
'auto_align': {
'alt': true,
'shift': false,
'control': true,
'key': {
'key_code': 76,
'char': 'l',
},
},
'zoom_to_fit': {
'alt': true,
'shift': true,
'control': false,
'key': {
'key_code': 70,
'char': 'f',
},
},
'zoom_in': {
'alt': true,
'shift': true,
'control': false,
'key': {
'key_code': 187,
'char': '+',
},
},
'zoom_out': {
'alt': true,
'shift': true,
'control': false,
'key': {
'key_code': 189,
'char': '-',
},
},
};

View File

@@ -0,0 +1,39 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {mount} from 'enzyme';
import '../../helper/enzyme.helper';
import FloatingNote from 'pgadmin.tools.erd/erd_tool/ui_components/FloatingNote';
describe('ERD FloatingNote', ()=>{
beforeEach(()=>{
jasmineEnzyme();
});
it('<FloatingNote /> on OK click', ()=>{
let floatNote = null;
let onClose = jasmine.createSpy('onClose');
let noteNode = {
getNote: function() {
return 'some note';
},
setNote: jasmine.createSpy('setNote'),
getSchemaTableName: function() {
return ['schema1', 'table1'];
},
};
floatNote = mount(<FloatingNote open={false} onClose={onClose}
reference={null} noteNode={noteNode} appendTo={document.body} rows={8}/>);
floatNote.find('textarea').simulate('change', {
target: {
value: 'the new note',
},
});
floatNote.find('button[data-label="OK"]').simulate('click');
expect(noteNode.setNote).toHaveBeenCalledWith('the new note');
expect(onClose).toHaveBeenCalled();
});
});

View File

@@ -0,0 +1,23 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import {shallow} from 'enzyme';
import '../../helper/enzyme.helper';
import Loader from 'pgadmin.tools.erd/erd_tool/ui_components/Loader';
describe('ERD Loader', ()=>{
beforeEach(()=>{
jasmineEnzyme();
});
it('<Loader /> comp', ()=>{
let loaderComp = shallow(<Loader />);
expect(loaderComp.isEmptyRender()).toBeTrue();
loaderComp.setProps({message: 'test message'});
expect(loaderComp.find('.pg-sp-text').text()).toBe('test message');
loaderComp.setProps({autoEllipsis: true});
expect(loaderComp.find('.pg-sp-text').text()).toBe('test message...');
});
});

View File

@@ -0,0 +1,76 @@
import jasmineEnzyme from 'jasmine-enzyme';
import React from 'react';
import Tippy from '@tippyjs/react';
import {mount, shallow} from 'enzyme';
import '../../helper/enzyme.helper';
import ToolBar, {ButtonGroup, DetailsToggleButton, IconButton, Shortcut} from 'pgadmin.tools.erd/erd_tool/ui_components/ToolBar';
describe('ERD Toolbar', ()=>{
beforeEach(()=>{
jasmineEnzyme();
});
it('<Toolbar /> comp', ()=>{
let toolBar = mount(<ToolBar id="id1"><div className="test"></div></ToolBar>);
expect(toolBar.getDOMNode().id).toBe('id1');
expect(toolBar.find('.test').length).toBe(1);
});
it('<ButtonGroup /> comp', ()=>{
let btnGrp = mount(<ButtonGroup><div className="test"></div></ButtonGroup>);
expect(btnGrp.getDOMNode().className).toBe('btn-group mr-1 ');
expect(btnGrp.find('.test').length).toBe(1);
btnGrp.unmount();
btnGrp = mount(<ButtonGroup className="someclass"></ButtonGroup>);
expect(btnGrp.getDOMNode().className).toBe('btn-group mr-1 someclass');
});
it('<DetailsToggleButton /> comp', ()=>{
let toggle = shallow(<DetailsToggleButton showDetails={true} />);
let btn = toggle.find(IconButton);
expect(btn.prop('icon')).toBe('far fa-eye');
expect(btn.prop('title')).toBe('Show fewer details');
toggle.setProps({showDetails: false});
btn = toggle.find(IconButton);
expect(btn.prop('icon')).toBe('fas fa-low-vision');
expect(btn.prop('title')).toBe('Show more details');
});
it('<IconButton /> comp', ()=>{
let btn = mount(<IconButton />);
let tippy = btn.find(Tippy);
expect(tippy.length).toBe(0);
btn.setProps({title: 'test title'});
tippy = btn.find(Tippy);
expect(tippy.length).toBe(1);
expect(btn.find('button').getDOMNode().className).toBe('btn btn-sm btn-primary-icon ');
btn.setProps({icon: 'fa fa-icon'});
expect(btn.find('button .sql-icon-lg').getDOMNode().className).toBe('fa fa-icon sql-icon-lg');
});
it('<Shortcut /> comp', ()=>{
let key = {
alt: true,
control: true,
shift: false,
key: {
key_code: 65,
char: 'a',
},
};
let shortcutComp = mount(<Shortcut shortcut={key}/>);
expect(shortcutComp.find('.shortcut-key').length).toBe(3);
key.alt = false;
shortcutComp.setProps({shortcut: key});
expect(shortcutComp.find('.shortcut-key').length).toBe(2);
});
});