Skip to content

Commit 7257947

Browse files
committed
fix(hub-ui): select a visible dock on standalone startup
1 parent 5f6d5be commit 7257947

3 files changed

Lines changed: 107 additions & 4 deletions

File tree

‎packages/hub-ui/src/client/components/dock/DockStandalone.vue‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ const isRpcTrusted = useIsRpcTrusted(context, (isTrusted) => {
2929
}
3030
})
3131
32+
/** Select from the visible dock rail through normal activation, including client scripts and group routing. */
3233
watch(
33-
() => context.docks.entries,
34-
() => {
35-
context.docks.selectedId ||= context.docks.entries[0]?.id ?? null
34+
[() => context.docks.groupedEntries, isRpcTrusted],
35+
([groups, trusted]) => {
36+
if (!trusted || context.docks.selectedId)
37+
return
38+
const entry = groups.flatMap(([, entries]) => entries)
39+
.find(entry => entry.type !== 'action' && entry.type !== '~builtin')
40+
if (entry)
41+
void context.docks.switchEntry(entry.id)
3642
},
3743
{ immediate: true },
3844
)

‎packages/hub-ui/src/client/state/context.test.ts‎

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { DEVFRAME_EVENTS } from 'devframe/constants'
66
import { createEventEmitter } from 'devframe/utils/events'
77
import { createSharedState } from 'devframe/utils/shared-state'
88
import { describe, expect, it, vi } from 'vitest'
9-
import { nextTick, ref } from 'vue'
9+
import { createRenderer, nextTick, ref, ssrContextKey } from 'vue'
10+
import DockStandalone from '../components/dock/DockStandalone.vue'
1011
import { createDocksContext } from './context'
1112
import { executeSetupScript } from './setup-script'
1213

@@ -430,3 +431,88 @@ describe('dock group command activation', () => {
430431
])
431432
})
432433
})
434+
435+
describe('standalone initial dock', () => {
436+
/** Mount the real component setup without rendering its unrelated dock panels or creating browser iframes. */
437+
const renderer = createRenderer<object, object>({
438+
createElement: () => ({}),
439+
createText: () => ({}),
440+
createComment: () => ({}),
441+
insert: vi.fn(),
442+
remove: vi.fn(),
443+
setText: vi.fn(),
444+
setElementText: vi.fn(),
445+
parentNode: () => null,
446+
nextSibling: () => null,
447+
patchProp: vi.fn(),
448+
})
449+
450+
async function fixture(entries: DevframeDockEntry[]) {
451+
const { rpc, sharedStates, trust } = createStubRpc()
452+
const context = await createDocksContext('standalone', rpc)
453+
sharedStates.get(HUB_EVENTS.sharedState.docks)!.push(entries)
454+
const switchEntry = vi.spyOn(context.docks, 'switchEntry')
455+
const component = renderer.createApp({ ...DockStandalone, render: () => null }, { context })
456+
component.provide(ssrContextKey, {})
457+
component.mount({})
458+
return {
459+
context,
460+
stop: () => component.unmount(),
461+
switchEntry,
462+
async connect() {
463+
trust()
464+
await flushRestore()
465+
},
466+
async publish(next: DevframeDockEntry[]) {
467+
sharedStates.get(HUB_EVENTS.sharedState.docks)!.push(next)
468+
await flushRestore()
469+
},
470+
}
471+
}
472+
473+
it('skips a hidden wrapper and activates the first visible dock through its client script', async () => {
474+
expect.assertions(4)
475+
const { context, connect, stop, switchEntry } = await fixture([
476+
{ id: 'wrapper', title: 'Wrapper', icon: 'ph:box', type: 'iframe', url: '/devframe/', when: 'false' },
477+
gitEntry,
478+
])
479+
expect(context.docks.selectedId).toBeNull()
480+
await connect()
481+
expect(context.docks.selectedId).toBe('git')
482+
expect(switchEntry).toHaveBeenCalledWith('git')
483+
expect(executeSetupScript).toHaveBeenCalledWith(gitEntry, expect.anything())
484+
stop()
485+
})
486+
487+
it('waits for an eligible entry without running actions or selecting a built-in fallback', async () => {
488+
expect.assertions(2)
489+
const { context, connect, publish, stop } = await fixture([
490+
{ id: 'command', title: 'Command', icon: 'ph:play', type: 'action', action: { importFrom: '/command.js' } },
491+
])
492+
await connect()
493+
expect(context.docks.selectedId).toBeNull()
494+
await publish([gitEntry])
495+
expect(context.docks.selectedId).toBe('git')
496+
stop()
497+
})
498+
499+
it('keeps an existing selection when the catalog changes', async () => {
500+
expect.assertions(1)
501+
const { context, connect, publish, stop } = await fixture([gitEntry])
502+
await connect()
503+
await publish([{ ...gitEntry, id: 'new-first' }, gitEntry])
504+
expect(context.docks.selectedId).toBe('git')
505+
stop()
506+
})
507+
508+
it('uses normal group routing to open a visible member', async () => {
509+
expect.assertions(1)
510+
const { context, connect, stop } = await fixture([
511+
{ id: 'tools', title: 'Tools', icon: 'ph:folder', type: 'group' },
512+
{ ...gitEntry, groupId: 'tools' },
513+
])
514+
await connect()
515+
expect(context.docks.selectedId).toBe('git')
516+
stop()
517+
})
518+
})

‎packages/hub-ui/vitest.config.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import vue from '@vitejs/plugin-vue'
2+
import { defineConfig } from 'vitest/config'
3+
import { alias } from '../../alias'
4+
5+
export default defineConfig({
6+
plugins: [vue()],
7+
resolve: { alias },
8+
test: {
9+
name: '@devframes/hub-ui',
10+
},
11+
})

0 commit comments

Comments
 (0)