mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-09 23:15:58 -06:00
Initial version of the new tree implementation.
This is the first version of our Tree implementation. At this point is a very simple tree without no abstractions and with code that eventually is not very performant, but this is only the first iteration and we are trying to follow the 'Last Responsible Moment Principle' [1]. Implemention details: - Creation of PGBrowser.treeMenu - Initial version of the Tree Adaptor 'pgadmin/static/js/tree/tree.js' - TreeFake test double that can replace the Tree for testing purposes - Tests, As an interesting asside because Fake’s need to behave like the real object you will noticed that there are tests for this type of double and they the same as of the real object. [1] https://medium.com/@aidanjcasey/guiding-principles-for-an-evolutionary-software-architecture-b6dc2cb24680 Patched by: Victoria && Joao Reviewed by: Khushboo & Ashesh
This commit is contained in:
parent
a34b3f27d4
commit
bc4d16eb83
@ -1,4 +1,5 @@
|
||||
define('pgadmin.browser', [
|
||||
'sources/tree/tree',
|
||||
'sources/gettext', 'sources/url_for', 'require', 'jquery', 'underscore', 'underscore.string',
|
||||
'bootstrap', 'sources/pgadmin', 'pgadmin.alertifyjs', 'bundled_codemirror',
|
||||
'sources/check_node_visibility', 'sources/modify_animation', 'pgadmin.browser.utils', 'wcdocker',
|
||||
@ -10,6 +11,7 @@ define('pgadmin.browser', [
|
||||
'sources/codemirror/addon/fold/pgadmin-sqlfoldcode',
|
||||
'pgadmin.browser.keyboard',
|
||||
], function(
|
||||
tree,
|
||||
gettext, url_for, require, $, _, S, Bootstrap, pgAdmin, Alertify,
|
||||
codemirror, checkNodeVisibility, modifyAnimation
|
||||
) {
|
||||
@ -86,6 +88,7 @@ define('pgadmin.browser', [
|
||||
});
|
||||
|
||||
b.tree = $('#tree').aciTree('api');
|
||||
b.treeMenu.register($('#tree'));
|
||||
};
|
||||
|
||||
// Extend the browser class attributes
|
||||
@ -100,6 +103,7 @@ define('pgadmin.browser', [
|
||||
editor:null,
|
||||
// Left hand browser tree
|
||||
tree:null,
|
||||
treeMenu: new tree.Tree(),
|
||||
// list of script to be loaded, when a certain type of node is loaded
|
||||
// It will be used to register extensions, tools, child node scripts,
|
||||
// etc.
|
||||
|
212
web/pgadmin/static/js/tree/tree.js
Normal file
212
web/pgadmin/static/js/tree/tree.js
Normal file
@ -0,0 +1,212 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
export class TreeNode {
|
||||
constructor(id, data, domNode, parent) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.setParent(parent);
|
||||
this.children = [];
|
||||
this.domNode = domNode;
|
||||
}
|
||||
|
||||
hasParent() {
|
||||
return this.parentNode !== null && this.parentNode !== undefined;
|
||||
}
|
||||
|
||||
parent() {
|
||||
return this.parentNode;
|
||||
}
|
||||
|
||||
setParent(parent) {
|
||||
this.parentNode = parent;
|
||||
this.path = this.id;
|
||||
if (parent !== null && parent !== undefined && parent.path !== undefined) {
|
||||
this.path = parent.path + '.' + this.id;
|
||||
}
|
||||
}
|
||||
|
||||
getData() {
|
||||
if (this.data === undefined) {
|
||||
return undefined;
|
||||
} else if (this.data === null) {
|
||||
return null;
|
||||
}
|
||||
return Object.assign({}, this.data);
|
||||
}
|
||||
|
||||
getHtmlIdentifier() {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
reload(tree) {
|
||||
this.unload(tree);
|
||||
tree.aciTreeApi.setInode(this.domNode);
|
||||
tree.aciTreeApi.deselect(this.domNode);
|
||||
setTimeout(() => {
|
||||
tree.selectNode(this.domNode);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
unload(tree) {
|
||||
this.children = [];
|
||||
tree.aciTreeApi.unload(this.domNode);
|
||||
}
|
||||
|
||||
anyParent(condition) {
|
||||
let node = this;
|
||||
|
||||
while (node.hasParent()) {
|
||||
node = node.parent();
|
||||
if (condition(node)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a condition returns true if the current node
|
||||
* or any of the parent nodes condition result is true
|
||||
*/
|
||||
anyFamilyMember(condition) {
|
||||
if(condition(this)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.anyParent(condition);
|
||||
}
|
||||
}
|
||||
|
||||
export class Tree {
|
||||
constructor() {
|
||||
this.rootNode = new TreeNode(undefined, {});
|
||||
this.aciTreeApi = undefined;
|
||||
}
|
||||
|
||||
addNewNode(id, data, domNode, parentPath) {
|
||||
const parent = this.findNode(parentPath);
|
||||
return this.createOrUpdateNode(id, data, parent, domNode);
|
||||
}
|
||||
|
||||
findNode(path) {
|
||||
if (path === null || path === undefined || path.length === 0) {
|
||||
return this.rootNode;
|
||||
}
|
||||
return findInTree(this.rootNode, path.join('.'));
|
||||
}
|
||||
|
||||
findNodeByDomElement(domElement) {
|
||||
const path = this.translateTreeNodeIdFromACITree(domElement);
|
||||
if(!path || !path[0]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.findNode(path);
|
||||
}
|
||||
|
||||
selected() {
|
||||
return this.aciTreeApi.selected();
|
||||
}
|
||||
|
||||
selectNode(aciTreeIdentifier) {
|
||||
this.aciTreeApi.select(aciTreeIdentifier);
|
||||
}
|
||||
|
||||
createOrUpdateNode(id, data, parent, domNode) {
|
||||
let oldNodePath = [id];
|
||||
if(parent !== null && parent !== undefined) {
|
||||
oldNodePath = [parent.path, id];
|
||||
}
|
||||
const oldNode = this.findNode(oldNodePath);
|
||||
if (oldNode !== null) {
|
||||
oldNode.data = Object.assign({}, data);
|
||||
return oldNode;
|
||||
}
|
||||
|
||||
const node = new TreeNode(id, data, domNode, parent);
|
||||
if (parent === this.rootNode) {
|
||||
node.parentNode = null;
|
||||
}
|
||||
parent.children.push(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the JQuery object that contains the ACI Tree
|
||||
* this method is responsible for registering this tree class
|
||||
* to listen to all the events that happen in the ACI Tree.
|
||||
*
|
||||
* At this point in time the only event that we care about is
|
||||
* the addition of a new node.
|
||||
* The function will create a new tree node to store the information
|
||||
* that exist in the ACI for it.
|
||||
*/
|
||||
register($treeJQuery) {
|
||||
$treeJQuery.on('acitree', function (event, api, item, eventName) {
|
||||
if (api.isItem(item)) {
|
||||
if (eventName === 'added') {
|
||||
const id = api.getId(item);
|
||||
const data = api.itemData(item);
|
||||
const parentId = this.translateTreeNodeIdFromACITree(api.parent(item));
|
||||
this.addNewNode(id, data, item, parentId);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
this.aciTreeApi = $treeJQuery.aciTree('api');
|
||||
}
|
||||
|
||||
/**
|
||||
* As the name hints this functions works as a layer in between ACI and
|
||||
* the adaptor. Given a ACITree JQuery node find the location of it in the
|
||||
* Tree and then returns and array with the path to to the Tree Node in
|
||||
* question
|
||||
*
|
||||
* This is not optimized and will always go through the full tree
|
||||
*/
|
||||
translateTreeNodeIdFromACITree(aciTreeNode) {
|
||||
let currentTreeNode = aciTreeNode;
|
||||
let path = [];
|
||||
while (currentTreeNode !== null && currentTreeNode !== undefined && currentTreeNode.length > 0) {
|
||||
path.unshift(this.aciTreeApi.getId(currentTreeNode));
|
||||
if (this.aciTreeApi.hasParent(currentTreeNode)) {
|
||||
currentTreeNode = this.aciTreeApi.parent(currentTreeNode);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an initial node and a path, it will navigate through
|
||||
* the new tree to find the node that matches the path
|
||||
*/
|
||||
function findInTree(rootNode, path) {
|
||||
if (path === null) {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
return (function findInNode(currentNode) {
|
||||
for (let i = 0, length = currentNode.children.length; i < length; i++) {
|
||||
const calculatedNode = findInNode(currentNode.children[i]);
|
||||
if (calculatedNode !== null) {
|
||||
return calculatedNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNode.path === path) {
|
||||
return currentNode;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})(rootNode);
|
||||
}
|
69
web/regression/javascript/tree/tree_fake.js
Normal file
69
web/regression/javascript/tree/tree_fake.js
Normal file
@ -0,0 +1,69 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import {Tree} from '../../../pgadmin/static/js/tree/tree';
|
||||
|
||||
export class TreeFake extends Tree {
|
||||
constructor() {
|
||||
super();
|
||||
this.aciTreeToOurTreeTranslator = {};
|
||||
this.aciTreeApi = jasmine.createSpyObj(
|
||||
['ACITreeApi'], ['setInode', 'unload', 'deselect', 'select']);
|
||||
}
|
||||
|
||||
addNewNode(id, data, domNode, path) {
|
||||
this.aciTreeToOurTreeTranslator[id] = [id];
|
||||
if (path !== null && path !== undefined) {
|
||||
this.aciTreeToOurTreeTranslator[id] = path.concat(id);
|
||||
}
|
||||
return super.addNewNode(id, data, domNode, path);
|
||||
}
|
||||
|
||||
addChild(parent, child) {
|
||||
child.setParent(parent);
|
||||
this.aciTreeToOurTreeTranslator[child.id] = this.aciTreeToOurTreeTranslator[parent.id].concat(child.id);
|
||||
parent.children.push(child);
|
||||
}
|
||||
|
||||
hasParent(aciTreeNode) {
|
||||
return this.translateTreeNodeIdFromACITree(aciTreeNode).length > 1;
|
||||
}
|
||||
|
||||
parent(aciTreeNode) {
|
||||
if (this.hasParent(aciTreeNode)) {
|
||||
let path = this.translateTreeNodeIdFromACITree(aciTreeNode);
|
||||
return [{id: this.findNode(path).parent().id}];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
translateTreeNodeIdFromACITree(aciTreeNode) {
|
||||
if(aciTreeNode === undefined || aciTreeNode[0] === undefined) {
|
||||
return null;
|
||||
}
|
||||
return this.aciTreeToOurTreeTranslator[aciTreeNode[0].id];
|
||||
}
|
||||
|
||||
itemData(aciTreeNode) {
|
||||
let node = this.findNodeByDomElement(aciTreeNode);
|
||||
if (node === undefined || node === null) {
|
||||
return undefined;
|
||||
}
|
||||
return node.getData();
|
||||
}
|
||||
|
||||
selected() {
|
||||
return this.selectedNode;
|
||||
}
|
||||
|
||||
selectNode(selectedNode) {
|
||||
this.selectedNode = selectedNode;
|
||||
}
|
||||
}
|
420
web/regression/javascript/tree/tree_spec.js
Normal file
420
web/regression/javascript/tree/tree_spec.js
Normal file
@ -0,0 +1,420 @@
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
import {Tree, TreeNode} from '../../../pgadmin/static/js/tree/tree';
|
||||
import {TreeFake} from './tree_fake';
|
||||
|
||||
const context = describe;
|
||||
|
||||
const treeTests = (treeClass, setDefaultCallBack) => {
|
||||
let tree;
|
||||
beforeEach(() => {
|
||||
tree = new treeClass();
|
||||
});
|
||||
|
||||
describe('#addNewNode', () => {
|
||||
describe('when add a new root element', () => {
|
||||
context('using [] as the parent', () => {
|
||||
beforeEach(() => {
|
||||
tree.addNewNode('some new node', {data: 'interesting'}, undefined, []);
|
||||
});
|
||||
|
||||
it('can be retrieved', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.data).toEqual({data: 'interesting'});
|
||||
});
|
||||
|
||||
it('return false for #hasParent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.hasParent()).toBe(false);
|
||||
});
|
||||
|
||||
it('return null for #parent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.parent()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
context('using null as the parent', () => {
|
||||
beforeEach(() => {
|
||||
tree.addNewNode('some new node', {data: 'interesting'}, undefined, null);
|
||||
});
|
||||
|
||||
it('can be retrieved', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.data).toEqual({data: 'interesting'});
|
||||
});
|
||||
|
||||
it('return false for #hasParent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.hasParent()).toBe(false);
|
||||
});
|
||||
|
||||
it('return null for #parent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.parent()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
context('using undefined as the parent', () => {
|
||||
beforeEach(() => {
|
||||
tree.addNewNode('some new node', {data: 'interesting'});
|
||||
});
|
||||
|
||||
it('can be retrieved', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.data).toEqual({data: 'interesting'});
|
||||
});
|
||||
|
||||
it('return false for #hasParent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.hasParent()).toBe(false);
|
||||
});
|
||||
|
||||
it('return null for #parent()', () => {
|
||||
const node = tree.findNode(['some new node']);
|
||||
expect(node.parent()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when add a new element as a child', () => {
|
||||
let parentNode;
|
||||
beforeEach(() => {
|
||||
parentNode = tree.addNewNode('parent node', {data: 'parent data'}, undefined, []);
|
||||
tree.addNewNode('some new node', {data: 'interesting'}, undefined, ['parent' +
|
||||
' node']);
|
||||
});
|
||||
|
||||
it('can be retrieved', () => {
|
||||
const node = tree.findNode(['parent node', 'some new node']);
|
||||
expect(node.data).toEqual({data: 'interesting'});
|
||||
});
|
||||
|
||||
it('return true for #hasParent()', () => {
|
||||
const node = tree.findNode(['parent node', 'some new node']);
|
||||
expect(node.hasParent()).toBe(true);
|
||||
});
|
||||
|
||||
it('return "parent node" object for #parent()', () => {
|
||||
const node = tree.findNode(['parent node', 'some new node']);
|
||||
expect(node.parent()).toEqual(parentNode);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when add an element that already exists under a parent', () => {
|
||||
beforeEach(() => {
|
||||
tree.addNewNode('parent node', {data: 'parent data'}, undefined, []);
|
||||
tree.addNewNode('some new node', {data: 'interesting'}, undefined, ['parent' +
|
||||
' node']);
|
||||
});
|
||||
|
||||
it('does not add a new child', () => {
|
||||
tree.addNewNode('some new node', {data: 'interesting 1'}, undefined, ['parent' +
|
||||
' node']);
|
||||
const parentNode = tree.findNode(['parent node']);
|
||||
expect(parentNode.children.length).toBe(1);
|
||||
});
|
||||
|
||||
it('updates the existing node data', () => {
|
||||
tree.addNewNode('some new node', {data: 'interesting 1'}, undefined, ['parent' +
|
||||
' node']);
|
||||
const node = tree.findNode(['parent node', 'some new node']);
|
||||
expect(node.data).toEqual({data: 'interesting 1'});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#translateTreeNodeIdFromACITree', () => {
|
||||
let aciTreeApi;
|
||||
beforeEach(() => {
|
||||
aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
|
||||
'hasParent',
|
||||
'parent',
|
||||
'getId',
|
||||
]);
|
||||
|
||||
aciTreeApi.getId.and.callFake((node) => {
|
||||
return node[0].id;
|
||||
});
|
||||
tree.aciTreeApi = aciTreeApi;
|
||||
});
|
||||
|
||||
describe('When tree as a single level', () => {
|
||||
beforeEach(() => {
|
||||
aciTreeApi.hasParent.and.returnValue(false);
|
||||
});
|
||||
|
||||
it('returns an array with the ID of the first level', () => {
|
||||
let node = [{
|
||||
id: 'some id',
|
||||
}];
|
||||
tree.addNewNode('some id', {}, undefined, []);
|
||||
|
||||
expect(tree.translateTreeNodeIdFromACITree(node)).toEqual(['some id']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When tree as a 2 levels', () => {
|
||||
describe('When we try to retrieve the node in the second level', () => {
|
||||
it('returns an array with the ID of the first level and second level', () => {
|
||||
aciTreeApi.hasParent.and.returnValues(true, false);
|
||||
aciTreeApi.parent.and.returnValue([{
|
||||
id: 'parent id',
|
||||
}]);
|
||||
let node = [{
|
||||
id: 'some id',
|
||||
}];
|
||||
|
||||
tree.addNewNode('parent id', {}, undefined, []);
|
||||
tree.addNewNode('some id', {}, undefined, ['parent id']);
|
||||
|
||||
expect(tree.translateTreeNodeIdFromACITree(node))
|
||||
.toEqual(['parent id', 'some id']);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#selected', () => {
|
||||
context('a node in the tree is selected', () => {
|
||||
it('returns that node object', () => {
|
||||
let selectedNode = new TreeNode('bamm', {}, []);
|
||||
setDefaultCallBack(tree, selectedNode);
|
||||
expect(tree.selected()).toEqual(selectedNode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#findNodeByTreeElement', () => {
|
||||
context('retrieve data from node not found', () => {
|
||||
it('return undefined', () => {
|
||||
let aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
|
||||
'hasParent',
|
||||
'parent',
|
||||
'getId',
|
||||
]);
|
||||
|
||||
aciTreeApi.getId.and.callFake((node) => {
|
||||
return node[0].id;
|
||||
});
|
||||
tree.aciTreeApi = aciTreeApi;
|
||||
expect(tree.findNodeByDomElement(['<li>something</li>'])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe('tree tests', () => {
|
||||
describe('TreeNode', () => {
|
||||
describe('#hasParent', () => {
|
||||
context('parent is null', () => {
|
||||
it('returns false', () => {
|
||||
let treeNode = new TreeNode('123', {}, [], null);
|
||||
expect(treeNode.hasParent()).toBe(false);
|
||||
});
|
||||
});
|
||||
context('parent is undefined', () => {
|
||||
it('returns false', () => {
|
||||
let treeNode = new TreeNode('123', {}, [], undefined);
|
||||
expect(treeNode.hasParent()).toBe(false);
|
||||
});
|
||||
});
|
||||
context('parent exists', () => {
|
||||
it('returns true', () => {
|
||||
let parentNode = new TreeNode('456', {}, []);
|
||||
let treeNode = new TreeNode('123', {}, [], parentNode);
|
||||
expect(treeNode.hasParent()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reload', () => {
|
||||
let tree;
|
||||
let level2;
|
||||
beforeEach(() => {
|
||||
tree = new TreeFake();
|
||||
tree.addNewNode('level1', {data: 'interesting'}, [{id: 'level1'}], []);
|
||||
level2 = tree.addNewNode('level2', {data: 'data'}, [{id: 'level2'}], ['level1']);
|
||||
tree.addNewNode('level3', {data: 'more data'}, [{id: 'level3'}], ['level1', 'level2']);
|
||||
|
||||
tree.aciTreeApi = jasmine.createSpyObj(
|
||||
'ACITreeApi', ['setInode', 'unload', 'deselect', 'select']);
|
||||
});
|
||||
|
||||
it('reloads the node and its children', () => {
|
||||
level2.reload(tree);
|
||||
expect(tree.findNodeByDomElement([{id: 'level2'}])).toEqual(level2);
|
||||
});
|
||||
|
||||
it('does not reload the children of node', () => {
|
||||
level2.reload(tree);
|
||||
expect(tree.findNodeByDomElement([{id: 'level3'}])).toBeNull();
|
||||
});
|
||||
|
||||
it('select the node', (done) => {
|
||||
level2.reload(tree);
|
||||
setTimeout(() => {
|
||||
expect(tree.selected()).toEqual([{id: 'level2'}]);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
|
||||
describe('ACITree specific', () => {
|
||||
it('sets the current node as a Inode, changing the Icon back to +', () => {
|
||||
level2.reload(tree);
|
||||
expect(tree.aciTreeApi.setInode).toHaveBeenCalledWith([{id: 'level2'}]);
|
||||
});
|
||||
|
||||
it('deselect the node and selects it again to trigger ACI tree' +
|
||||
' events', (done) => {
|
||||
level2.reload(tree);
|
||||
setTimeout(() => {
|
||||
expect(tree.aciTreeApi.deselect).toHaveBeenCalledWith([{id: 'level2'}]);
|
||||
done();
|
||||
}, 20);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#unload', () => {
|
||||
let tree;
|
||||
let level2;
|
||||
beforeEach(() => {
|
||||
tree = new TreeFake();
|
||||
tree.addNewNode('level1', {data: 'interesting'}, ['<li>level1</li>'], []);
|
||||
level2 = tree.addNewNode('level2', {data: 'data'}, ['<li>level2</li>'], ['level1']);
|
||||
tree.addNewNode('level3', {data: 'more data'}, ['<li>level3</li>'], ['level1', 'level2']);
|
||||
tree.aciTreeApi = jasmine.createSpyObj('ACITreeApi', ['unload']);
|
||||
});
|
||||
|
||||
it('unloads the children of the current node', () => {
|
||||
level2.unload(tree);
|
||||
expect(tree.findNodeByDomElement([{id: 'level2'}])).toEqual(level2);
|
||||
expect(tree.findNodeByDomElement([{id: 'level3'}])).toBeNull();
|
||||
});
|
||||
|
||||
it('calls unload on the ACI Tree', () => {
|
||||
level2.unload(tree);
|
||||
expect(tree.aciTreeApi.unload).toHaveBeenCalledWith(['<li>level2</li>']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tree', () => {
|
||||
function realTreeSelectNode(tree, selectedNode) {
|
||||
let aciTreeApi = jasmine.createSpyObj('ACITreeApi', [
|
||||
'selected',
|
||||
]);
|
||||
tree.aciTreeApi = aciTreeApi;
|
||||
aciTreeApi.selected.and.returnValue(selectedNode);
|
||||
}
|
||||
|
||||
treeTests(Tree, realTreeSelectNode);
|
||||
});
|
||||
|
||||
describe('TreeFake', () => {
|
||||
function fakeTreeSelectNode(tree, selectedNode) {
|
||||
tree.selectNode(selectedNode);
|
||||
}
|
||||
|
||||
treeTests(TreeFake, fakeTreeSelectNode);
|
||||
|
||||
describe('#hasParent', () => {
|
||||
context('tree contains multiple levels', () => {
|
||||
let tree;
|
||||
beforeEach(() => {
|
||||
tree = new TreeFake();
|
||||
tree.addNewNode('level1', {data: 'interesting'}, undefined, []);
|
||||
tree.addNewNode('level2', {data: 'interesting'}, undefined, ['level1']);
|
||||
});
|
||||
|
||||
context('node is at the first level', () => {
|
||||
it('returns false', () => {
|
||||
expect(tree.hasParent([{id: 'level1'}])).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('node is at the second level', () => {
|
||||
it('returns true', () => {
|
||||
expect(tree.hasParent([{id: 'level2'}])).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#parent', () => {
|
||||
let tree;
|
||||
beforeEach(() => {
|
||||
tree = new TreeFake();
|
||||
tree.addNewNode('level1', {data: 'interesting'}, undefined, []);
|
||||
tree.addNewNode('level2', {data: 'interesting'}, undefined, ['level1']);
|
||||
});
|
||||
|
||||
context('node is the root', () => {
|
||||
it('returns null', () => {
|
||||
expect(tree.parent([{id: 'level1'}])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
context('node is not root', () => {
|
||||
it('returns root element', () => {
|
||||
expect(tree.parent([{id: 'level2'}])).toEqual([{id: 'level1'}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#itemData', () => {
|
||||
let tree;
|
||||
beforeEach(() => {
|
||||
tree = new TreeFake();
|
||||
tree.addNewNode('level1', {data: 'interesting'}, undefined, []);
|
||||
tree.addNewNode('level2', {data: 'expected data'}, undefined, ['level1']);
|
||||
});
|
||||
|
||||
context('retrieve data from the node', () => {
|
||||
it('return the node data', () => {
|
||||
expect(tree.itemData([{id: 'level2'}])).toEqual({
|
||||
data: 'expected' +
|
||||
' data',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('retrieve data from node not found', () => {
|
||||
it('return undefined', () => {
|
||||
expect(tree.itemData([{id: 'bamm'}])).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#addChild', () => {
|
||||
let root, child;
|
||||
beforeEach(() => {
|
||||
let tree = new TreeFake();
|
||||
root = tree.addNewNode('root', {}, [{id: 'root'}]);
|
||||
child = new TreeNode('node.1', {}, [{id: 'node.1'}]);
|
||||
tree.addChild(root, child);
|
||||
});
|
||||
|
||||
it('adds a new child to a node', () => {
|
||||
expect(root.children).toEqual([child]);
|
||||
});
|
||||
|
||||
it('changes the parent of the child node', () => {
|
||||
expect(root.children[0].parentNode).toEqual(root);
|
||||
expect(child.parentNode).toEqual(root);
|
||||
});
|
||||
|
||||
it('changes the path of the child', () => {
|
||||
expect(child.path).toEqual('root.node.1');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user