Files
deepseek-harness/packages/client/ui-models/tests/onboarding-dialog.spec.tsx
NI0317 fb80732e7d Merge updated DeepSeek onboarding base
# Conflicts:
#	.agents/notes/implemented/feature/2026-07-30-deepseek-onboarding-credential-setup.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-30-deepseek-onboarding-credential-setup.md
#	.agents/notes/implemented/feature/2026-07-30-deepseek-onboarding-credential-setup.zh.md
#	packages/client/ui-models/README.i18n.yaml
#	packages/client/ui-models/README.md
#	packages/client/ui-models/README.zh.md
#	packages/client/ui-models/src/client/DeepSeekOnboardingDialog.module.css
#	packages/client/ui-models/src/client/DeepSeekOnboardingDialog.tsx
#	packages/client/ui-models/tests/onboarding-dialog.spec.tsx
2026-07-31 10:29:20 +08:00

181 lines
6.8 KiB
TypeScript

// @vitest-environment jsdom
/** First-run DeepSeek prompt behavior over the shared Models join. */
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { RpcResponse } from '@deepseek-ai/dsh-client-connection/client'
import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
import { DeepSeekOnboardingDialog } from '../src/client/DeepSeekOnboardingDialog.tsx'
import type { DeepSeekOnboardingDialogProps } from '../src/client/DeepSeekOnboardingDialog.tsx'
import { ModelsSettingsStore } from '../src/client/store.ts'
import { en } from '../src/client/locales.ts'
afterEach(cleanup)
let nextRpc = 0
function ok<T>(value: T): RpcResponse<T> {
return { rpcId: `onboarding-${nextRpc++}` as never, result: { ok: true, value } }
}
function fail<T>(message: string): RpcResponse<T> {
return {
rpcId: `onboarding-${nextRpc++}` as never,
result: { ok: false, error: { code: 'internal', message, details: {} } },
}
}
function harness(options: {
provider?: boolean
providerSettingsNs?: string
providerActive?: boolean
settingsNamespace?: boolean
apiKeyEnv?: string | null
literal?: boolean
configured?: () => boolean
credential?: { source?: string; writable: boolean }
describeFailure?: string
settingsWritable?: boolean
providersReject?: boolean
} = {}) {
let fileConfigured = false
const configured = options.configured ?? (() => fileConfigured)
const face = {
llm: {
providers: () => {
if (options.providersReject === true) return Promise.reject(new Error('provider transport unavailable'))
return Promise.resolve(ok({
providers: options.provider === false
? []
: [{
provider: 'deepseek-official',
displayName: 'DeepSeek',
settingsNs: options.providerSettingsNs ?? 'llm-deepseek',
settingsPath: [],
active: options.providerActive ?? true,
}],
}))
},
},
settings: {
describe: () => Promise.resolve(ok({
writable: options.settingsWritable ?? true,
namespaces: options.settingsNamespace === false
? []
: [{
ns: 'llm-deepseek',
schema: {},
value: options.apiKeyEnv === null
? {}
: { apiKeyEnv: options.apiKeyEnv ?? 'DEEPSEEK_API_KEY' },
applies: 'live' as const,
secrets: [{ path: ['apiKey'], set: options.literal === true }],
revision: 0,
}],
})),
},
credentials: {
describe: () => options.describeFailure === undefined
? Promise.resolve(ok({
credentials: {
DEEPSEEK_API_KEY: {
configured: configured(),
...configured() && options.credential?.source !== undefined
? { source: options.credential.source }
: {},
writable: options.credential?.writable ?? true,
},
},
}))
: Promise.resolve(fail(options.describeFailure)),
},
}
const controller = new ModelsSettingsStore(face as never)
const openSection = vi.fn()
const complete = vi.fn()
const unusedHook = (() => { throw new Error('unused standard hook') }) as never
const props: DeepSeekOnboardingDialogProps = {
stepId: 'deepseek-official',
complete,
openSection,
useSessions: unusedHook,
useWorkspaces: unusedHook,
controller,
useSnapshot: bindSnapshotSelector(controller.store),
t: key => en[key],
}
return { controller, complete, openSection, props, configure: () => { fileConfigured = true } }
}
describe('DeepSeekOnboardingDialog', () => {
it('loads on first entry and presents one accessible route to Models', async () => {
const h = harness()
render(<DeepSeekOnboardingDialog {...h.props} />)
expect(await screen.findByRole('region', { name: en.onboardingTitle })).toBeTruthy()
expect(screen.getByText(en.onboardingDescription)).toBeTruthy()
const action = screen.getByRole('button', { name: en.onboardingGoToSettings })
expect(action).toBeTruthy()
expect(document.activeElement).toBe(screen.getByRole('heading', { name: en.onboardingTitle }))
expect(screen.queryByRole('textbox')).toBeNull()
})
it('opens the Models section and dismisses the prompt', async () => {
const h = harness()
render(<DeepSeekOnboardingDialog {...h.props} />)
await screen.findByRole('region')
fireEvent.click(screen.getByRole('button', { name: en.onboardingGoToSettings }))
expect(h.complete).toHaveBeenCalledOnce()
expect(h.openSection).toHaveBeenCalledWith('models')
})
it('allows configure-later dismissal without opening settings', async () => {
const h = harness()
render(<DeepSeekOnboardingDialog {...h.props} />)
await screen.findByRole('region')
fireEvent.click(screen.getByRole('button', { name: en.onboardingLater }))
expect(h.complete).toHaveBeenCalledOnce()
expect(h.openSection).not.toHaveBeenCalled()
})
it('does not block the product when DeepSeek setup is unavailable', async () => {
for (const h of [
harness({ describeFailure: 'credentials service is absent' }),
harness({ credential: { writable: false } }),
harness({ settingsWritable: false }),
harness({ providersReject: true }),
harness({ providerActive: false }),
harness({ settingsNamespace: false }),
harness({ apiKeyEnv: null }),
]) {
const view = render(<DeepSeekOnboardingDialog {...h.props} />)
await act(async () => { await h.controller.load() })
expect(screen.queryByRole('region')).toBeNull()
await waitFor(() => { expect(h.complete).toHaveBeenCalledOnce() })
expect(h.openSection).not.toHaveBeenCalled()
view.unmount()
}
})
it('skips an absent adapter and already-configured literal or environment credentials', async () => {
for (const h of [
harness({ provider: false }),
harness({ providerSettingsNs: '' }),
harness({ literal: true, describeFailure: 'credential seam absent' }),
harness({ configured: () => true, credential: { source: 'env', writable: false } }),
]) {
const view = render(<DeepSeekOnboardingDialog {...h.props} />)
await act(async () => { await h.controller.load() })
expect(screen.queryByRole('region')).toBeNull()
await waitFor(() => { expect(h.complete).toHaveBeenCalledOnce() })
view.unmount()
}
})
it('closes when an external credential invalidation refreshes the shared join', async () => {
const h = harness()
render(<DeepSeekOnboardingDialog {...h.props} />)
await screen.findByRole('region')
h.configure()
await act(async () => { await h.controller.load() })
await waitFor(() => { expect(screen.queryByRole('region')).toBeNull() })
expect(h.complete).toHaveBeenCalledOnce()
})
})