tests: add tests

This commit is contained in:
mathuo 2023-02-13 16:44:21 +07:00
parent bf350e404b
commit 321f78ec0e
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
5 changed files with 546 additions and 24 deletions

View File

@ -1,4 +1,12 @@
import { Droptarget, Position } from '../../dnd/droptarget';
import {
calculateQuadrantAsPercentage,
calculateQuadrantAsPixels,
directionToPosition,
Droptarget,
DropTargetDirections,
Position,
Quadrant,
} from '../../dnd/droptarget';
import { fireEvent } from '@testing-library/dom';
function createOffsetDragOverEvent(params: {
@ -27,6 +35,17 @@ describe('droptarget', () => {
jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200);
});
test('directionToPosition', () => {
expect(directionToPosition('above')).toBe(Position.Top);
expect(directionToPosition('below')).toBe(Position.Bottom);
expect(directionToPosition('left')).toBe(Position.Left);
expect(directionToPosition('right')).toBe(Position.Right);
expect(directionToPosition('within')).toBe(Position.Center);
expect(() => directionToPosition('bad_input' as any)).toThrow(
"invalid direction 'bad_input'"
);
});
test('non-directional', () => {
let position: Position | undefined = undefined;
@ -197,4 +216,116 @@ describe('droptarget', () => {
viewQuery = element.querySelectorAll('.drop-target');
expect(viewQuery.length).toBe(0);
});
describe('calculateQuadrantAsPercentage', () => {
test('variety of cases', () => {
const inputs: Array<{
directions: DropTargetDirections[];
x: number;
y: number;
result: Quadrant | null | undefined;
}> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{
directions: ['left', 'right'],
x: 81,
y: 50,
result: 'right',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 19,
result: 'top',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 81,
result: 'bottom',
},
{
directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50,
y: 50,
result: null,
},
{
directions: ['left', 'right', 'top', 'bottom'],
x: 50,
y: 50,
result: undefined,
},
];
for (const input of inputs) {
expect(
calculateQuadrantAsPercentage(
new Set(input.directions),
input.x,
input.y,
100,
100,
20
)
).toBe(input.result);
}
});
});
describe('calculateQuadrantAsPixels', () => {
test('variety of cases', () => {
const inputs: Array<{
directions: DropTargetDirections[];
x: number;
y: number;
result: Quadrant | null | undefined;
}> = [
{ directions: ['left', 'right'], x: 19, y: 50, result: 'left' },
{
directions: ['left', 'right'],
x: 81,
y: 50,
result: 'right',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 19,
result: 'top',
},
{
directions: ['top', 'bottom'],
x: 50,
y: 81,
result: 'bottom',
},
{
directions: ['left', 'right', 'top', 'bottom', 'center'],
x: 50,
y: 50,
result: null,
},
{
directions: ['left', 'right', 'top', 'bottom'],
x: 50,
y: 50,
result: undefined,
},
];
for (const input of inputs) {
expect(
calculateQuadrantAsPixels(
new Set(input.directions),
input.x,
input.y,
100,
100,
20
)
).toBe(input.result);
}
});
});
});

View File

@ -32,7 +32,7 @@ class PanelContentPartTest implements IContentRenderer {
isDisposed: boolean = false;
constructor(public readonly id: string, component: string) {
constructor(public readonly id: string, public readonly component: string) {
this.element.classList.add(`testpanel-${id}`);
}
@ -53,7 +53,7 @@ class PanelContentPartTest implements IContentRenderer {
}
toJSON(): object {
return { id: this.id };
return { id: this.component };
}
focus(): void {
@ -2017,4 +2017,379 @@ describe('dockviewComponent', () => {
// load a layout with a default tab identifier when react default is present
// load a layout with invialid panel identifier
test('orthogonal realigment #1', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel2',
component: 'default',
position: {
direction: 'left',
},
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel2'],
id: '1',
activeView: 'panel2',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 1000,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
},
options: {},
});
});
test('orthogonal realigment #2', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel2'],
id: 'group-2',
activeView: 'panel2',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel3',
component: 'default',
position: {
direction: 'left',
},
});
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel3'],
id: '1',
activeView: 'panel3',
},
size: 500,
},
{
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
{
type: 'leaf',
data: {
views: ['panel2'],
id: 'group-2',
activeView: 'panel2',
},
size: 500,
},
],
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.HORIZONTAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
panel3: {
id: 'panel3',
view: { content: { id: 'default' } },
title: 'panel3',
},
},
options: {},
});
});
test('orthogonal realigment #3', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent(container, {
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.deserializer = new ReactPanelDeserialzier(dockview);
expect(dockview.orientation).toBe(Orientation.HORIZONTAL);
dockview.fromJSON({
activeGroup: 'group-1',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 500,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
dockview.addPanel({
id: 'panel2',
component: 'default',
position: {
direction: 'above',
},
});
dockview.addPanel({
id: 'panel3',
component: 'default',
position: {
direction: 'below',
},
});
expect(dockview.orientation).toBe(Orientation.VERTICAL);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: '2',
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel2'],
id: '1',
activeView: 'panel2',
},
size: 333,
},
{
type: 'leaf',
data: {
views: ['panel1'],
id: 'group-1',
activeView: 'panel1',
},
size: 333,
},
{
type: 'leaf',
data: {
views: ['panel3'],
id: '2',
activeView: 'panel3',
},
size: 334,
},
],
size: 1000,
},
height: 1000,
width: 1000,
orientation: Orientation.VERTICAL,
},
panels: {
panel1: {
id: 'panel1',
view: { content: { id: 'default' } },
title: 'panel1',
},
panel2: {
id: 'panel2',
view: { content: { id: 'default' } },
title: 'panel2',
},
panel3: {
id: 'panel3',
view: { content: { id: 'default' } },
title: 'panel3',
},
},
options: {},
});
});
});

View File

@ -5,6 +5,10 @@ import { DragAndDropObserver } from './dnd';
import { clamp } from '../math';
import { Direction } from '../gridview/baseComponentGridview';
function numberOrFallback(maybeNumber: any, fallback: number): number {
return typeof maybeNumber === 'number' ? maybeNumber : fallback;
}
export enum Position {
Top = 'Top',
Left = 'Left',
@ -26,15 +30,15 @@ export function directionToPosition(direction: Direction): Position {
case 'within':
return Position.Center;
default:
throw new Error(`invalid direction ${direction}`);
throw new Error(`invalid direction '${direction}'`);
}
}
export type Quadrant = 'top' | 'bottom' | 'left' | 'right';
export interface DroptargetEvent {
position: Position;
nativeEvent: DragEvent;
readonly position: Position;
readonly nativeEvent: DragEvent;
}
export type DropTargetDirections =
@ -62,7 +66,7 @@ export class Droptarget extends CompositeDisposable {
private readonly _onDrop = new Emitter<DroptargetEvent>();
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
get state() {
get state(): Position | undefined {
return this._state;
}
@ -172,7 +176,7 @@ export class Droptarget extends CompositeDisposable {
);
}
public dispose() {
public dispose(): void {
this.removeDropTarget();
}
@ -180,7 +184,7 @@ export class Droptarget extends CompositeDisposable {
quadrant: Quadrant | null,
width: number,
height: number
) {
): void {
if (!this.overlay) {
return;
}
@ -242,7 +246,7 @@ export class Droptarget extends CompositeDisposable {
toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom);
}
private setState(quadrant: Quadrant | null) {
private setState(quadrant: Quadrant | null): void {
switch (quadrant) {
case 'top':
this._state = Position.Top;
@ -273,13 +277,13 @@ export class Droptarget extends CompositeDisposable {
this.options.overlayModel?.activationSize === undefined ||
this.options.overlayModel?.activationSize?.type === 'percentage';
const value =
typeof this.options.overlayModel?.activationSize?.value === 'number'
? this.options.overlayModel?.activationSize?.value
: 20;
const value = numberOrFallback(
this.options?.overlayModel?.activationSize?.value,
20
);
if (isPercentage) {
return calculateQuadrant_Percentage(
return calculateQuadrantAsPercentage(
overlayType,
x,
y,
@ -289,7 +293,7 @@ export class Droptarget extends CompositeDisposable {
);
}
return calculateQuadrant_Pixels(
return calculateQuadrantAsPixels(
overlayType,
x,
y,
@ -310,7 +314,7 @@ export class Droptarget extends CompositeDisposable {
}
}
function calculateQuadrant_Percentage(
export function calculateQuadrantAsPercentage(
overlayType: Set<DropTargetDirections>,
x: number,
y: number,
@ -341,7 +345,7 @@ function calculateQuadrant_Percentage(
return null;
}
function calculateQuadrant_Pixels(
export function calculateQuadrantAsPixels(
overlayType: Set<DropTargetDirections>,
x: number,
y: number,
@ -358,7 +362,7 @@ function calculateQuadrant_Pixels(
if (overlayType.has('top') && y < threshold) {
return 'top';
}
if (overlayType.has('right') && y > height - threshold) {
if (overlayType.has('bottom') && y > height - threshold) {
return 'bottom';
}

View File

@ -47,8 +47,6 @@ import { GroupPanel, IGroupviewPanel } from '../groupview/groupviewPanel';
import { DefaultGroupPanelView } from './defaultGroupPanelView';
import { getPanelData } from '../dnd/dataTransfer';
const nextGroupId = sequentialNumberGenerator();
export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void;
remove: () => void;
@ -89,6 +87,7 @@ export interface IDockviewComponent extends IBaseGrid<GroupPanel> {
readonly totalPanels: number;
readonly panels: IDockviewPanel[];
readonly onDidDrop: Event<DockviewDropEvent>;
readonly orientation: Orientation;
tabHeight: number | undefined;
deserializer: IPanelDeserializer | undefined;
updateOptions(options: DockviewComponentUpdateOptions): void;
@ -127,6 +126,7 @@ export class DockviewComponent
extends BaseGrid<GroupPanel>
implements IDockviewComponent
{
private readonly nextGroupId = sequentialNumberGenerator();
private _deserializer: IPanelDeserializer | undefined;
private _api: DockviewApi;
private _options: Exclude<DockviewComponentOptions, 'orientation'>;
@ -150,6 +150,10 @@ export class DockviewComponent
readonly onDidActivePanelChange: Event<IDockviewPanel | undefined> =
this._onDidActivePanelChange.event;
get orientation(): Orientation {
return this.gridview.orientation;
}
get totalPanels(): number {
return this.panels.length;
}
@ -841,9 +845,9 @@ export class DockviewComponent
}
if (!id) {
id = nextGroupId.next();
id = this.nextGroupId.next();
while (this._groups.has(id)) {
id = nextGroupId.next();
id = this.nextGroupId.next();
}
}

View File

@ -434,7 +434,15 @@ export class Gridview implements IDisposable {
this.root.size
);
this._root.addChild(oldRoot, Sizing.Distribute, 0);
if (oldRoot.children.length === 1) {
// can remove one level of redundant branching if there is only a single child
const childReference = oldRoot.children[0];
oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
oldRoot.dispose();
this._root.addChild(childReference, Sizing.Distribute, 0);
} else {
this._root.addChild(oldRoot, Sizing.Distribute, 0);
}
this.element.appendChild(this._root.element);