Skip to content

Commit 55fbe5a

Browse files
committed
fix(hub-ui): prevent nested docks in popup panels
1 parent 415a307 commit 55fbe5a

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import type { DocksContext } from '@devframes/hub/client'
2+
import { CLIENT_CONTEXT_KEY } from '@devframes/hub/client'
3+
import { afterEach, expect, it, vi } from 'vitest'
4+
import { isInsideHub } from '../embedded/is-inside-hub'
5+
import { closeDockPopup, requestDockPopupOpen, useIsDockPopupOpen } from './popup'
6+
7+
vi.mock('./branding', () => ({
8+
applyDocumentHead: vi.fn(),
9+
applyPrimaryColor: vi.fn(),
10+
useBranding: () => ({ value: {} }),
11+
}))
12+
vi.mock('./color-mode', async () => {
13+
const { ref } = await import('vue')
14+
return {
15+
isDark: ref(false),
16+
}
17+
})
18+
19+
const { onMount } = vi.hoisted(() => ({
20+
onMount: vi.fn(),
21+
}))
22+
vi.mock('../components/DockStandalone', () => ({
23+
DockStandalone: class {
24+
constructor() { onMount() }
25+
remove() {}
26+
},
27+
}))
28+
29+
afterEach(() => {
30+
closeDockPopup()
31+
vi.unstubAllGlobals()
32+
vi.clearAllMocks()
33+
})
34+
35+
it('identifies the popup as a Hub before mounting its iframe views', async () => {
36+
const element = () => ({
37+
appendChild: vi.fn(),
38+
style: {
39+
setProperty: vi.fn(),
40+
},
41+
})
42+
const popup = {
43+
document: {
44+
createElement: element,
45+
documentElement: element(),
46+
head: element(),
47+
body: {
48+
...element(),
49+
textContent: '',
50+
},
51+
},
52+
addEventListener: vi.fn(),
53+
removeEventListener: vi.fn(),
54+
close: vi.fn(),
55+
}
56+
// The fixture supplies only the context fields used by popup initialization.
57+
// eslint-disable-next-line slop/no-chained-type-assertions
58+
const context = {
59+
panel: {
60+
store: { width: 80, height: 80 },
61+
},
62+
rpc: { connectionMeta: {} },
63+
docks: {
64+
switchEntry: vi.fn(),
65+
},
66+
} as unknown as DocksContext
67+
vi.stubGlobal('window', {
68+
innerWidth: 1000,
69+
innerHeight: 800,
70+
documentPictureInPicture: {
71+
requestWindow: async () => popup,
72+
},
73+
})
74+
let insideHubAtMount: boolean | undefined
75+
onMount.mockImplementation(() => {
76+
insideHubAtMount = isInsideHub({ parent: popup })
77+
})
78+
79+
requestDockPopupOpen(context)
80+
await vi.waitFor(() => expect(useIsDockPopupOpen().value).toBe(true))
81+
expect(insideHubAtMount).toBe(true)
82+
expect(Reflect.get(popup, CLIENT_CONTEXT_KEY)).toBe(context)
83+
})

‎packages/hub-ui/src/client/state/popup.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DocksContext } from '@devframes/hub/client'
2+
import { CLIENT_CONTEXT_KEY } from '@devframes/hub/client'
23
import { createEventEmitter } from 'devframe/utils/events'
34
import { shallowRef, watch } from 'vue'
45
import { applyDocumentHead, applyPrimaryColor, useBranding } from './branding'
@@ -125,6 +126,9 @@ async function mountStandaloneApp(context: DocksContext, popup: Window) {
125126
;(popup as Window & { [DEVFRAME_CONNECTION_META_KEY]?: unknown })[DEVFRAME_CONNECTION_META_KEY]
126127
= (globalThis as Record<string, unknown>)[DEVFRAME_CONNECTION_META_KEY] ?? context.rpc.connectionMeta
127128

129+
// Let iframe bootstraps recognize the Hub before its views mount.
130+
;(popup as Window & { [CLIENT_CONTEXT_KEY]?: DocksContext })[CLIENT_CONTEXT_KEY] = context
131+
128132
const appRoot = popup.document.createElement('div')
129133
appRoot.id = 'devframes-popup-root'
130134
popup.document.body.appendChild(appRoot)

0 commit comments

Comments
 (0)