diff --git a/packages/dockview-core/src/__tests__/__mocks__/mockWindow.ts b/packages/dockview-core/src/__tests__/__mocks__/mockWindow.ts new file mode 100644 index 000000000..cc5f5ed0d --- /dev/null +++ b/packages/dockview-core/src/__tests__/__mocks__/mockWindow.ts @@ -0,0 +1,35 @@ +import { fromPartial } from "@total-typescript/shoehorn"; + +export function setupMockWindow() { + const listeners: Record void)[]> = {}; + + let width = 1000; + let height = 2000; + + return fromPartial({ + addEventListener: (type: string, listener: () => void) => { + if (!listeners[type]) { + listeners[type] = []; + } + listeners[type].push(listener); + if (type === 'load') { + listener(); + } + }, + dispatchEvent: (event: Event) => { + const items = listeners[event.type]; + if (!items) { + return; + } + items.forEach((item) => item()); + }, + document: document, + close: jest.fn(), + get innerWidth() { + return width++; + }, + get innerHeight() { + return height++; + }, + }); +} diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index f6511e5d6..d544001fa 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -19,6 +19,8 @@ import { import { fromPartial } from '@total-typescript/shoehorn'; import { DockviewApi } from '../../api/component.api'; import { DockviewDndOverlayEvent } from '../../dockview/options'; +import { SizeEvent } from '../../api/gridviewPanelApi'; +import { setupMockWindow } from '../__mocks__/mockWindow'; class PanelContentPartTest implements IContentRenderer { element: HTMLElement = document.createElement('div'); @@ -115,7 +117,7 @@ describe('dockviewComponent', () => { }, }); - window.open = jest.fn(); // not implemented by jest + window.open = jest.fn(); }); test('update className', () => { @@ -4819,6 +4821,8 @@ describe('dockviewComponent', () => { test('add a popout group', async () => { const container = document.createElement('div'); + window.open = () => setupMockWindow(); + const dockview = new DockviewComponent(container, { createComponent(options) { switch (options.name) { @@ -4850,12 +4854,36 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(1); expect(dockview.panels.length).toBe(2); - await dockview.addPopoutGroup(panel2.group); + const events: SizeEvent[] = []; + + panel2.api.onDidDimensionsChange((event) => { + events.push(event); + }); + + const originalGroup = panel2.group; + + expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy(); + + expect(events).toEqual([{ height: 2000, width: 1000 }]); + + expect(originalGroup.api.location.type).toBe('grid'); + expect(originalGroup.api.isVisible).toBeFalsy(); expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('popout'); expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); + + if (panel2.api.location.type !== 'popout') { + fail('unexpected'); + } + const alternativeWindow = panel2.api.location.getWindow(); + alternativeWindow.dispatchEvent(new Event('resize')); + + expect(events).toEqual([ + { height: 2000, width: 1000 }, + { height: 2001, width: 1001 }, + ]); }); test('remove all panels from popout group', async () => { @@ -4894,7 +4922,7 @@ describe('dockviewComponent', () => { position: { referencePanel: panel2 }, }); - await dockview.addPopoutGroup(panel2.group); + expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy(); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('popout'); @@ -4936,7 +4964,7 @@ describe('dockviewComponent', () => { component: 'default', }); - await dockview.addPopoutGroup(panel1); + expect(await dockview.addPopoutGroup(panel1)).toBeTruthy(); expect(dockview.panels.length).toBe(1); expect(dockview.groups.length).toBe(2); @@ -4991,7 +5019,7 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - await dockview.addPopoutGroup(panel2.group); + expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy(); expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('popout'); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 6978df401..fe7d5c2d3 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -913,7 +913,7 @@ export class DockviewApi implements CommonApi { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): Promise { + ): Promise { return this.component.addPopoutGroup(item, options); } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index af3f887d1..ba027fa55 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -218,7 +218,7 @@ export interface IDockviewComponent extends IBaseGrid { onDidOpen?: (event: { id: string; window: Window }) => void; onWillClose?: (event: { id: string; window: Window }) => void; } - ): Promise; + ): Promise; } export class DockviewComponent @@ -548,7 +548,7 @@ export class DockviewComponent onWillClose?: (event: { id: string; window: Window }) => void; overridePopoutGroup?: DockviewGroupPanel; } - ): Promise { + ): Promise { if ( itemToPopout instanceof DockviewPanel && itemToPopout.group.size === 1 @@ -579,10 +579,6 @@ export class DockviewComponent const groupId = options?.overridePopoutGroup?.id ?? this.getNextGroupId(); - if (itemToPopout.api.location.type === 'grid') { - itemToPopout.api.setVisible(false); - } - const _window = new PopoutWindow( `${this.id}-${groupId}`, // unique id theme ?? '', @@ -608,12 +604,12 @@ export class DockviewComponent .open() .then((popoutContainer) => { if (_window.isDisposed) { - return; + return false; } if (popoutContainer === null) { popoutWindowDisposable.dispose(); - return; + return false; } const gready = document.createElement('div'); @@ -635,6 +631,10 @@ export class DockviewComponent options?.overridePopoutGroup ?? this.createGroup({ id: groupId }); group.model.renderContainer = overlayRenderContainer; + group.layout( + _window.window!.innerWidth, + _window.window!.innerHeight + ); if (!options?.overridePopoutGroup) { this._onDidAddGroup.fire(group); @@ -676,6 +676,10 @@ export class DockviewComponent getWindow: () => _window.window!, }; + if (itemToPopout.api.location.type === 'grid') { + itemToPopout.api.setVisible(false); + } + this.doSetGroupAndPanelActive(group); popoutWindowDisposable.addDisposables( @@ -715,7 +719,10 @@ export class DockviewComponent _window.window!, 'resize', () => { - group.layout(window.innerWidth, window.innerHeight); + group.layout( + _window.window!.innerWidth, + _window.window!.innerHeight + ); } ), overlayRenderContainer, @@ -752,9 +759,12 @@ export class DockviewComponent this._popoutGroups.push(value); this.updateWatermark(); + + return true; }) .catch((err) => { console.error('dockview: failed to create popout window', err); + return false; }); } diff --git a/packages/dockview-core/src/popoutWindow.ts b/packages/dockview-core/src/popoutWindow.ts index 14564cdef..9e4926f09 100644 --- a/packages/dockview-core/src/popoutWindow.ts +++ b/packages/dockview-core/src/popoutWindow.ts @@ -124,7 +124,7 @@ export class PopoutWindow extends CompositeDisposable { window: externalWindow, }); - return new Promise((resolve) => { + return new Promise((resolve, reject) => { externalWindow.addEventListener('unload', (e) => { // if page fails to load before unloading // this.close(); @@ -135,29 +135,34 @@ export class PopoutWindow extends CompositeDisposable { * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event */ - const externalDocument = externalWindow.document; - externalDocument.title = document.title; + try { + const externalDocument = externalWindow.document; + externalDocument.title = document.title; - externalDocument.body.appendChild(container); + externalDocument.body.appendChild(container); - addStyles(externalDocument, window.document.styleSheets); + addStyles(externalDocument, window.document.styleSheets); - /** - * beforeunload must be registered after load for reasons I could not determine - * otherwise the beforeunload event will not fire when the window is closed - */ - addDisposableWindowListener( - externalWindow, - 'beforeunload', - () => { - /** - * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event - */ - this.close(); - } - ); + /** + * beforeunload must be registered after load for reasons I could not determine + * otherwise the beforeunload event will not fire when the window is closed + */ + addDisposableWindowListener( + externalWindow, + 'beforeunload', + () => { + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event + */ + this.close(); + } + ); - resolve(container); + resolve(container); + } catch (err) { + // only except this is the DOM isn't setup. e.g. in a in correctly configured test + reject(err); + } }); }); }