Merge pull request #275 from mathuo/274-gridview-tests

test: gridview tests
This commit is contained in:
mathuo 2023-06-12 21:28:15 +01:00 committed by GitHub
commit fbe6c65186
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 674 additions and 66 deletions

View File

@ -100,67 +100,67 @@ describe('dockviewComponent', () => {
}); });
}); });
// test('event leakage', () => { test('event leakage', () => {
// Emitter.setLeakageMonitorEnabled(true); Emitter.setLeakageMonitorEnabled(true);
// dockview = new DockviewComponent({ dockview = new DockviewComponent({
// parentElement: container, parentElement: container,
// components: { components: {
// default: PanelContentPartTest, default: PanelContentPartTest,
// }, },
// }); });
// dockview.layout(500, 1000); dockview.layout(500, 1000);
// dockview.addPanel({ dockview.addPanel({
// id: 'panel1', id: 'panel1',
// component: 'default', component: 'default',
// }); });
// const panel2 = dockview.addPanel({ const panel2 = dockview.addPanel({
// id: 'panel2', id: 'panel2',
// component: 'default', component: 'default',
// }); });
// dockview.removePanel(panel2); dockview.removePanel(panel2);
// const panel3 = dockview.addPanel({ const panel3 = dockview.addPanel({
// id: 'panel3', id: 'panel3',
// component: 'default', component: 'default',
// position: { position: {
// direction: 'right', direction: 'right',
// referencePanel: 'panel1', referencePanel: 'panel1',
// }, },
// }); });
// const panel4 = dockview.addPanel({ const panel4 = dockview.addPanel({
// id: 'panel4', id: 'panel4',
// component: 'default', component: 'default',
// position: { position: {
// direction: 'above', direction: 'above',
// }, },
// }); });
// dockview.moveGroupOrPanel( dockview.moveGroupOrPanel(
// panel4.group, panel4.group,
// panel3.group.id, panel3.group.id,
// panel3.id, panel3.id,
// 'center' 'center'
// ); );
// dockview.dispose(); dockview.dispose();
// if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
// for (const entry of Array.from( for (const entry of Array.from(
// Emitter.MEMORY_LEAK_WATCHER.events Emitter.MEMORY_LEAK_WATCHER.events
// )) { )) {
// console.log('disposal', entry[1]); console.log('disposal', entry[1]);
// } }
// throw new Error('not all listeners disposed'); throw new Error('not all listeners disposed');
// } }
// Emitter.setLeakageMonitorEnabled(false); Emitter.setLeakageMonitorEnabled(false);
// }); });
test('duplicate panel', () => { test('duplicate panel', () => {
dockview.layout(500, 1000); dockview.layout(500, 1000);
@ -2449,7 +2449,7 @@ describe('dockviewComponent', () => {
}); });
}); });
test('check', () => { test('check dockview component is rendering to the DOM as expected', () => {
const container = document.createElement('div'); const container = document.createElement('div');
const dockview = new DockviewComponent({ const dockview = new DockviewComponent({
@ -2470,16 +2470,22 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
const panel2 = dockview.addPanel({ const panel2 = dockview.addPanel({
id: 'panel2', id: 'panel2',
component: 'default', component: 'default',
}); });
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
const panel3 = dockview.addPanel({ const panel3 = dockview.addPanel({
id: 'panel3', id: 'panel3',
component: 'default', component: 'default',
}); });
expect(dockview.element.querySelectorAll('.view').length).toBe(1);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel(
panel3.group, panel3.group,
panel3.group.id, panel3.group.id,
@ -2488,6 +2494,7 @@ describe('dockviewComponent', () => {
); );
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
expect(dockview.element.querySelectorAll('.view').length).toBe(2);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel(
panel3.group, panel3.group,
@ -2497,6 +2504,7 @@ describe('dockviewComponent', () => {
); );
expect(dockview.groups.length).toBe(3); expect(dockview.groups.length).toBe(3);
expect(dockview.element.querySelectorAll('.view').length).toBe(4);
dockview.moveGroupOrPanel( dockview.moveGroupOrPanel(
panel2.group, panel2.group,
@ -2507,7 +2515,6 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(2); expect(dockview.groups.length).toBe(2);
const viewQuery = container.querySelectorAll('.split-view-container'); expect(dockview.element.querySelectorAll('.view').length).toBe(2);
expect(viewQuery).toBeTruthy();
}); });
}); });

View File

@ -18,6 +18,10 @@ class MockGridview implements IGridView {
>().event; >().event;
element: HTMLElement = document.createElement('div'); element: HTMLElement = document.createElement('div');
constructor() {
this.element.className = 'mock-grid-view';
}
layout(width: number, height: number): void { layout(width: number, height: number): void {
// //
} }
@ -116,4 +120,574 @@ describe('gridview', () => {
checkOrientationFlipsAtEachLevel((gridview as any).root as BranchNode); checkOrientationFlipsAtEachLevel((gridview as any).root as BranchNode);
}); });
test('removeView: remove leaf from branch where branch becomes leaf and parent is root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(3);
gridview.removeView([1, 0], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(2);
});
test('removeView: remove leaf from branch where branch remains branch and parent is root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 1]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: {},
size: 333,
type: 'leaf',
},
{
data: {},
size: 333,
type: 'leaf',
},
{
data: {},
size: 334,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(4);
gridview.removeView([1, 0], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(3);
});
test('removeView: remove leaf where parent is root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(3);
gridview.removeView([0], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'VERTICAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(2);
});
test('removeView: remove leaf from branch where branch becomes leaf and parent is not root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: [
{
data: {},
size: 250,
type: 'leaf',
},
{
data: {},
size: 250,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(4);
gridview.removeView([1, 0, 0], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(3);
});
test('removeView: remove leaf from branch where branch remains branch and parent is not root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: [
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 168,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(5);
gridview.removeView([1, 0, 1], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: [
{
data: {},
size: 250,
type: 'leaf',
},
{
data: {},
size: 250,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(4);
});
test('removeView: remove leaf where parent is root', () => {
const gridview = new Gridview(
false,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
gridview.addView(new MockGridview(), Sizing.Distribute, [0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 0]);
gridview.addView(new MockGridview(), Sizing.Distribute, [1, 0, 1]);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: [
{
data: [
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 168,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
{
data: {},
size: 500,
type: 'leaf',
},
],
size: 500,
type: 'branch',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(5);
gridview.removeView([1, 1], Sizing.Distribute);
expect(gridview.serialize()).toEqual({
height: 1000,
orientation: 'HORIZONTAL',
root: {
data: [
{
data: {},
size: 500,
type: 'leaf',
},
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 166,
type: 'leaf',
},
{
data: {},
size: 168,
type: 'leaf',
},
],
size: 1000,
type: 'branch',
},
width: 1000,
});
expect(
gridview.element.querySelectorAll('.mock-grid-view').length
).toBe(4);
});
}); });

View File

@ -678,60 +678,82 @@ export class Gridview implements IDisposable {
throw new Error('Invalid location'); throw new Error('Invalid location');
} }
const node = parent.children[index]; const nodeToRemove = parent.children[index];
if (!(node instanceof LeafNode)) { if (!(nodeToRemove instanceof LeafNode)) {
throw new Error('Invalid location'); throw new Error('Invalid location');
} }
parent.removeChild(index, sizing); parent.removeChild(index, sizing);
nodeToRemove.dispose();
if (parent.children.length === 0) { if (parent.children.length !== 1) {
return node.view; return nodeToRemove.view;
} }
if (parent.children.length > 1) { // if the parent has only one child and we know the parent is a BranchNode we can make the tree
return node.view; // more efficiently spaced by replacing the parent BranchNode with the child.
} // if that child is a LeafNode then we simply replace the BranchNode with the child otherwise if the child
// is a BranchNode too we should spread it's children into the grandparent.
// refer to the remaining child as the sibling
const sibling = parent.children[0]; const sibling = parent.children[0];
if (pathToParent.length === 0) { if (pathToParent.length === 0) {
// parent is root // if the parent is root
if (sibling instanceof LeafNode) { if (sibling instanceof LeafNode) {
return node.view; // if the sibling is a leaf node no action is required
return nodeToRemove.view;
} }
// we must promote sibling to be the new root // otherwise the sibling is a branch node. since the parent is the root and the root has only one child
// which is a branch node we can just set this branch node to be the new root node
// for good housekeeping we'll removing the sibling from it's existing tree
parent.removeChild(0, sizing); parent.removeChild(0, sizing);
// and set that sibling node to be root
this.root = sibling; this.root = sibling;
return node.view;
return nodeToRemove.view;
} }
// otherwise the parent is apart of a large sub-tree
const [grandParent, ..._] = [...pathToParent].reverse(); const [grandParent, ..._] = [...pathToParent].reverse();
const [parentIndex, ...__] = [...rest].reverse(); const [parentIndex, ...__] = [...rest].reverse();
const isSiblingVisible = parent.isChildVisible(0); const isSiblingVisible = parent.isChildVisible(0);
// either way we need to remove the sibling from it's existing tree
parent.removeChild(0, sizing); parent.removeChild(0, sizing);
// note the sizes of all of the grandparents children
const sizes = grandParent.children.map((_size, i) => const sizes = grandParent.children.map((_size, i) =>
grandParent.getChildSize(i) grandParent.getChildSize(i)
); );
grandParent.removeChild(parentIndex, sizing);
// remove the parent from the grandparent since we are moving the sibling to take the parents place
// this parent is no longer used and can be disposed of
grandParent.removeChild(parentIndex, sizing).dispose();
if (sibling instanceof BranchNode) { if (sibling instanceof BranchNode) {
// replace the parent with the siblings children
sizes.splice( sizes.splice(
parentIndex, parentIndex,
1, 1,
...sibling.children.map((c) => c.size) ...sibling.children.map((c) => c.size)
); );
// and add those siblings to the grandparent
for (let i = 0; i < sibling.children.length; i++) { for (let i = 0; i < sibling.children.length; i++) {
const child = sibling.children[i]; const child = sibling.children[i];
grandParent.addChild(child, child.size, parentIndex + i); grandParent.addChild(child, child.size, parentIndex + i);
} }
} else { } else {
// otherwise create a new leaf node and add that to the grandparent
const newSibling = new LeafNode( const newSibling = new LeafNode(
sibling.view, sibling.view,
orthogonal(sibling.orientation), orthogonal(sibling.orientation),
@ -740,14 +762,19 @@ export class Gridview implements IDisposable {
const siblingSizing = isSiblingVisible const siblingSizing = isSiblingVisible
? sibling.orthogonalSize ? sibling.orthogonalSize
: Sizing.Invisible(sibling.orthogonalSize); : Sizing.Invisible(sibling.orthogonalSize);
grandParent.addChild(newSibling, siblingSizing, parentIndex); grandParent.addChild(newSibling, siblingSizing, parentIndex);
} }
// the containing node of the sibling is no longer required and can be disposed of
sibling.dispose();
// resize everything
for (let i = 0; i < sizes.length; i++) { for (let i = 0; i < sizes.length; i++) {
grandParent.resizeChild(i, sizes[i]); grandParent.resizeChild(i, sizes[i]);
} }
return node.view; return nodeToRemove.view;
} }
public layout(width: number, height: number): void { public layout(width: number, height: number): void {