From 10256672b4243dc210520c3972e37570c82f0705 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 3 Jan 2024 19:38:18 +0000 Subject: [PATCH] test: add tests --- package.json | 2 +- .../src/__tests__/__test_utils__/utils.ts | 19 +++ .../__tests__/overlayRenderContainer.spec.ts | 145 +++++++++++++++++- .../src/overlayRenderContainer.ts | 16 +- 4 files changed, 175 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1370fa761..ddcaa6a9e 100644 --- a/package.json +++ b/package.json @@ -71,4 +71,4 @@ "engines": { "node": ">=18.0" } -} \ No newline at end of file +} diff --git a/packages/dockview-core/src/__tests__/__test_utils__/utils.ts b/packages/dockview-core/src/__tests__/__test_utils__/utils.ts index 8204d797e..ac4daa88d 100644 --- a/packages/dockview-core/src/__tests__/__test_utils__/utils.ts +++ b/packages/dockview-core/src/__tests__/__test_utils__/utils.ts @@ -1,5 +1,14 @@ import * as React from 'react'; +/** + * useful utility type to erase readonly signatures for testing purposes + * + * @see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#readonly-mapped-type-modifiers-and-readonly-arrays + */ +export type Writable = T extends object + ? { -readonly [K in keyof T]: Writable } + : T; + export function setMockRefElement(node: Partial): void { const mockRef = { get current() { @@ -25,3 +34,13 @@ export function createOffsetDragOverEvent(params: { Object.defineProperty(event, 'clientY', { get: () => params.clientY }); return event; } + +/** + * `jest.runAllTicks` doesn't seem to exhaust all events in the micro-task queue so + * as a **hacky** alternative we'll wait for an empty Promise to complete which runs + * on the micro-task queue so will force a run-to-completion emptying the queue + * of any pending micro-task + */ +export function exhaustMicrotaskQueue(): Promise { + return new Promise((resolve) => resolve()); +} diff --git a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts index d366f83c8..9df8e1680 100644 --- a/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/overlayRenderContainer.spec.ts @@ -1,8 +1,145 @@ -import { OverlayRenderContainer } from '../overlayRenderContainer'; +import { Droptarget } from '../dnd/droptarget'; +import { IDockviewPanel } from '../dockview/dockviewPanel'; +import { Emitter } from '../events'; +import { IRenderable, OverlayRenderContainer } from '../overlayRenderContainer'; +import { fromPartial } from '@total-typescript/shoehorn'; +import { Writable, exhaustMicrotaskQueue } from './__test_utils__/utils'; describe('overlayRenderContainer', () => { - test('abc', () => { - const el = document.createElement('div'); - const cut = new OverlayRenderContainer(el); + test('add a view that is not currently in the DOM', async () => { + const parentContainer = document.createElement('div'); + + const cut = new OverlayRenderContainer(parentContainer); + + const panelContentEl = document.createElement('div'); + + const onDidVisibilityChange = new Emitter(); + const onDidDimensionsChange = new Emitter(); + + const panel = fromPartial({ + api: { + id: 'test_panel_id', + onDidVisibilityChange: onDidVisibilityChange.event, + onDidDimensionsChange: onDidDimensionsChange.event, + isVisible: true, + }, + view: { + content: { + element: panelContentEl, + }, + }, + group: { + api: { + location: 'grid', + }, + }, + }); + + const dropTarget = fromPartial({}); + + const refContainerEl = document.createElement('div'); + + const referenceContainer: IRenderable = { + element: refContainerEl, + dropTarget, + }; + + (parentContainer as jest.Mocked).getBoundingClientRect = + jest + .fn() + .mockReturnValueOnce( + fromPartial({ + left: 100, + top: 200, + width: 1000, + height: 500, + }) + ) + .mockReturnValueOnce( + fromPartial({ + left: 101, + top: 201, + width: 1000, + height: 500, + }) + ) + .mockReturnValueOnce( + fromPartial({ + left: 100, + top: 200, + width: 1000, + height: 500, + }) + ); + + (refContainerEl as jest.Mocked).getBoundingClientRect = + jest + .fn() + .mockReturnValueOnce( + fromPartial({ + left: 150, + top: 300, + width: 100, + height: 200, + }) + ) + .mockReturnValueOnce( + fromPartial({ + left: 150, + top: 300, + width: 101, + height: 201, + }) + ) + .mockReturnValueOnce( + fromPartial({ + left: 150, + top: 300, + width: 100, + height: 200, + }) + ); + + const container = cut.setReferenceContentContainer( + panel, + referenceContainer + ); + + await exhaustMicrotaskQueue(); + + expect(panelContentEl.parentElement).toBe(container); + expect(container.parentElement).toBe(parentContainer); + + expect(container.style.display).toBe(''); + + expect(container.style.left).toBe('50px'); + expect(container.style.top).toBe('100px'); + expect(container.style.width).toBe('100px'); + expect(container.style.height).toBe('200px'); + expect(refContainerEl.getBoundingClientRect).toHaveBeenCalledTimes(1); + + onDidDimensionsChange.fire({}); + expect(container.style.display).toBe(''); + + expect(container.style.left).toBe('49px'); + expect(container.style.top).toBe('99px'); + expect(container.style.width).toBe('101px'); + expect(container.style.height).toBe('201px'); + expect(refContainerEl.getBoundingClientRect).toHaveBeenCalledTimes(2); + + (panel as Writable).api.isVisible = false; + onDidVisibilityChange.fire({}); + expect(container.style.display).toBe('none'); + expect(refContainerEl.getBoundingClientRect).toHaveBeenCalledTimes(2); + + (panel as Writable).api.isVisible = true; + onDidVisibilityChange.fire({}); + expect(container.style.display).toBe(''); + + expect(container.style.left).toBe('50px'); + expect(container.style.top).toBe('100px'); + expect(container.style.width).toBe('100px'); + expect(container.style.height).toBe('200px'); + expect(refContainerEl.getBoundingClientRect).toHaveBeenCalledTimes(3); }); }); diff --git a/packages/dockview-core/src/overlayRenderContainer.ts b/packages/dockview-core/src/overlayRenderContainer.ts index 2eb0a650a..cee392cb2 100644 --- a/packages/dockview-core/src/overlayRenderContainer.ts +++ b/packages/dockview-core/src/overlayRenderContainer.ts @@ -89,6 +89,14 @@ export class OverlayRenderContainer extends CompositeDisposable { ); }; + const visibilityChanged = () => { + if (panel.api.isVisible) { + resize(); + } + + focusContainer.style.display = panel.api.isVisible ? '' : 'none'; + }; + const disposable = new CompositeDisposable( /** * since container is positioned absoutely we must explicitly forward @@ -117,9 +125,13 @@ export class OverlayRenderContainer extends CompositeDisposable { * the content is still maintained within the DOM hence DOM specific attributes * such as scroll position are maintained when next made visible. */ - focusContainer.style.display = event.isVisible ? '' : 'none'; + visibilityChanged(); }), panel.api.onDidDimensionsChange(() => { + if (!panel.api.isVisible) { + return; + } + resize(); }), { @@ -140,7 +152,7 @@ export class OverlayRenderContainer extends CompositeDisposable { * calling the first resize as other size-altering events may still occur before * the end of the stack-frame. */ - resize(); + visibilityChanged(); }); this.map[panel.api.id].disposable = disposable;