feat(web): unify onboarding dialogs

This commit is contained in:
ZiyaZhang
2026-08-13 01:19:33 -07:00
parent fb0f0ba799
commit 9ee5aef98c
54 changed files with 1388 additions and 342 deletions

View File

@@ -8,6 +8,7 @@ import { TestRemote, usePinnedBrowserLanguages } from '@deepseek-ai/dsh-client-t
import { apply, inject, refreshIfLoaded } from '@deepseek-ai/dsh-client-ui-settings-models/client'
import { ModelsSection } from '../src/client/ModelsSection.tsx'
import { DeepSeekOnboardingDialog } from '../src/client/DeepSeekOnboardingDialog.tsx'
import { WelcomeNotice } from '../src/client/WelcomeNotice.tsx'
// The service reads its initial locale from the browser; these specs assert
// the shipped Chinese copy, so they state the browser they assume.
@@ -23,7 +24,7 @@ async function bench() {
new TestRemote(ctx)
// The apply path only captures the wire face; no call leaves this fake
// until a section actually loads.
ctx.provide('connection', { api: {} } as never)
ctx.provide('connection', { api: {}, isLoopback: true } as never)
return { ctx, slots: ctx.get('slots') as SlotRegistry, locale }
}
@@ -60,9 +61,20 @@ describe('ui-settings-models apply', () => {
expect(typeof injected.controller.load).toBe('function')
expect(typeof injected.useSnapshot).toBe('function')
expect(injected.api).toBeDefined()
const onboarding = before.slots.entries('settings.onboarding')[0]!
expect(onboarding.component).toBe(DeepSeekOnboardingDialog)
expect(onboarding.options).toMatchObject({ id: 'deepseek-official', order: 0 })
const onboarding = before.slots.entries('settings.onboarding')
expect(onboarding).toHaveLength(2)
expect(onboarding.find(entry => entry.options.id === 'welcome-notice')).toMatchObject({
component: WelcomeNotice,
options: { id: 'welcome-notice', order: -100 },
})
const deepSeek = onboarding.find(entry => entry.options.id === 'deepseek-official')!
expect(deepSeek.component).toBe(DeepSeekOnboardingDialog)
expect(deepSeek.options).toMatchObject({ id: 'deepseek-official', order: 0 })
const deepSeekInjected = (
deepSeek.inject as unknown as () => import('../src/client/DeepSeekOnboardingDialog.tsx').DeepSeekOnboardingInjected
)()
expect(deepSeekInjected.hooks.models).toBe(injected.controller.store)
expect(deepSeekInjected.api).toBeDefined()
const after = await bench()
await after.ctx.plugin({ inject: [...inject], apply }).await()
@@ -71,7 +83,7 @@ describe('ui-settings-models apply', () => {
declare(after.slots)
await Promise.resolve()
expect(after.slots.entries('settings.section')[0]!.component).toBe(ModelsSection)
expect(after.slots.entries('settings.onboarding')[0]!.component).toBe(DeepSeekOnboardingDialog)
expect(after.slots.entries('settings.onboarding')).toHaveLength(2)
// The self-inflicted ledger notifications hit the duplicate guard.
expect(after.slots.entries('settings.section')).toHaveLength(1)
})
@@ -110,7 +122,7 @@ describe('ui-settings-models apply', () => {
declare(b.slots)
await Promise.resolve()
expect(b.slots.entries('settings.section')[0]!.component).toBe(ModelsSection)
expect(b.slots.entries('settings.onboarding')[0]!.component).toBe(DeepSeekOnboardingDialog)
expect(b.slots.entries('settings.onboarding')).toHaveLength(2)
// The locale path also recovers through the same ledger re-check.
b.locale.setLocale('en')
expect(resolveSlotLabel(b.slots.entries('settings.section')[0]!.options.label)).toBe('Models')
@@ -164,8 +176,10 @@ describe('pushed invalidations', () => {
const b = await bench()
declare(b.slots)
await b.ctx.plugin({ inject: [...inject], apply }).await()
const entry = b.slots.entries('settings.onboarding')
.find(candidate => candidate.options.id === 'deepseek-official')!
const injected = (
b.slots.entries('settings.onboarding')[0]!.inject as unknown as
entry.inject as unknown as
() => import('../src/client/DeepSeekOnboardingDialog.tsx').DeepSeekOnboardingInjected
)()
injected.controller.store.update((state) => { state.status = 'ready' })
@@ -173,4 +187,25 @@ describe('pushed invalidations', () => {
b.ctx.remote.$dispatch('credentials/updated', ['DEEPSEEK_API_KEY'])
expect(load).toHaveBeenCalledTimes(1)
})
it('routes only the onboarding namespace invalidation into welcome state', async () => {
const b = await bench()
declare(b.slots)
await b.ctx.plugin({ inject: [...inject], apply }).await()
const entry = b.slots.entries('settings.onboarding')
.find(candidate => candidate.options.id === 'welcome-notice')!
const injected = (
entry.inject as unknown as
() => import('../src/client/WelcomeNotice.tsx').WelcomeNoticeInjected
)()
injected.hooks.welcome.update((state) => { state.status = 'ready' })
const load = vi.spyOn(injected.controller, 'load').mockResolvedValue()
b.ctx.remote.$dispatch('settings/document-updated', ['llm-deepseek', 1])
expect(load).not.toHaveBeenCalled()
b.ctx.remote.$dispatch('settings/document-updated', ['ui-onboarding', 2])
expect(load).toHaveBeenCalledOnce()
b.ctx.emit('connection/reset')
expect(load).toHaveBeenCalledTimes(2)
})
})