bug: delay popup opens when deserializing

This commit is contained in:
mathuo 2025-07-31 20:52:02 +01:00
parent 3f74e0037b
commit 0a19313cc7
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
2 changed files with 44 additions and 12 deletions

View File

@ -5810,9 +5810,9 @@ describe('dockviewComponent', () => {
dockview.fromJSON(state);
/**
* exhaust task queue since popout group completion is async but not awaited in `fromJSON(...)`
* Wait for delayed popout group creation to complete
*/
await new Promise((resolve) => setTimeout(resolve, 0));
await dockview.popoutRestorationPromise;
expect(dockview.panels.length).toBe(4);
@ -6113,6 +6113,7 @@ describe('dockviewComponent', () => {
});
test('persistance with custom url', async () => {
jest.useFakeTimers();
const container = document.createElement('div');
window.open = () => setupMockWindow();
@ -6196,7 +6197,12 @@ describe('dockviewComponent', () => {
expect(dockview.groups.length).toBe(0);
dockview.fromJSON(state);
await new Promise((resolve) => setTimeout(resolve, 0)); // popout views are completed as a promise so must complete microtask-queue
// Advance timers to trigger delayed popout creation (0ms, 100ms delays)
jest.advanceTimersByTime(200);
// Wait for the popout restoration to complete
await dockview.popoutRestorationPromise;
expect(dockview.toJSON().popoutGroups).toEqual([
{
@ -6230,6 +6236,8 @@ describe('dockviewComponent', () => {
url: '/custom.html',
},
]);
jest.useRealTimers();
});
describe('when browsers block popups', () => {

View File

@ -351,6 +351,7 @@ export class DockviewComponent
disposable: { dispose: () => DockviewGroupPanel | undefined };
}[] = [];
private readonly _rootDropTarget: Droptarget;
private _popoutRestorationPromise: Promise<void> = Promise.resolve();
private readonly _onDidRemoveGroup = new Emitter<DockviewGroupPanel>();
readonly onDidRemoveGroup: Event<DockviewGroupPanel> =
@ -407,6 +408,14 @@ export class DockviewComponent
return this._floatingGroups;
}
/**
* Promise that resolves when all popout groups from the last fromJSON call are restored.
* Useful for tests that need to wait for delayed popout creation.
*/
get popoutRestorationPromise(): Promise<void> {
return this._popoutRestorationPromise;
}
constructor(container: HTMLElement, options: DockviewComponentOptions) {
super(container, {
proportionalLayout: true,
@ -1522,21 +1531,36 @@ export class DockviewComponent
const serializedPopoutGroups = data.popoutGroups ?? [];
for (const serializedPopoutGroup of serializedPopoutGroups) {
// Create a promise that resolves when all popout groups are created
const popoutPromises: Promise<void>[] = [];
// Queue popup group creation with delays to avoid browser blocking
serializedPopoutGroups.forEach((serializedPopoutGroup, index) => {
const { data, position, gridReferenceGroup, url } =
serializedPopoutGroup;
const group = createGroupFromSerializedState(data);
this.addPopoutGroup(group, {
position: position ?? undefined,
overridePopoutGroup: gridReferenceGroup ? group : undefined,
referenceGroup: gridReferenceGroup
? this.getPanel(gridReferenceGroup)
: undefined,
popoutUrl: url,
// Add a small delay for each popup after the first to avoid browser popup blocking
const popoutPromise = new Promise<void>((resolve) => {
setTimeout(() => {
this.addPopoutGroup(group, {
position: position ?? undefined,
overridePopoutGroup: gridReferenceGroup ? group : undefined,
referenceGroup: gridReferenceGroup
? this.getPanel(gridReferenceGroup)
: undefined,
popoutUrl: url,
});
resolve();
}, index * 100); // 100ms delay between each popup
});
}
popoutPromises.push(popoutPromise);
});
// Store the promise for tests to wait on
this._popoutRestorationPromise = Promise.all(popoutPromises).then(() => void 0);
for (const floatingGroup of this._floatingGroups) {
floatingGroup.overlay.setBounds();