183 lines
5.6 KiB
TypeScript
183 lines
5.6 KiB
TypeScript
import * as React from 'react'
|
|
import { createElement } from 'react'
|
|
import { renderToStaticMarkup } from 'react-dom/server'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import type { UseProviderCardStateResult } from '@/app/[locale]/profile/components/api-config/provider-card/hooks/useProviderCardState'
|
|
import { ProviderCardShell } from '@/app/[locale]/profile/components/api-config/provider-card/ProviderCardShell'
|
|
import type { ProviderTutorial } from '@/app/[locale]/profile/components/api-config/types'
|
|
|
|
const portalMocks = vi.hoisted(() => {
|
|
return {
|
|
currentPortalTarget: null as unknown,
|
|
createPortalMock: vi.fn((node: React.ReactNode, target: unknown) => {
|
|
const targetLabel = target === portalMocks.currentPortalTarget ? 'body' : 'unknown'
|
|
return createElement('div', { 'data-portal-target': targetLabel }, node)
|
|
}),
|
|
}
|
|
})
|
|
|
|
vi.mock('react-dom', async () => {
|
|
const actual = await vi.importActual<typeof import('react-dom')>('react-dom')
|
|
return {
|
|
...actual,
|
|
createPortal: portalMocks.createPortalMock,
|
|
}
|
|
})
|
|
|
|
function createState(tutorial: ProviderTutorial): UseProviderCardStateResult {
|
|
return {
|
|
providerKey: 'ark',
|
|
isPresetProvider: true,
|
|
showBaseUrlEdit: false,
|
|
tutorial,
|
|
groupedModels: {},
|
|
hasModels: false,
|
|
isEditing: false,
|
|
isEditingUrl: false,
|
|
showKey: false,
|
|
tempKey: '',
|
|
tempUrl: '',
|
|
showTutorial: true,
|
|
showAddForm: null,
|
|
newModel: {
|
|
name: '',
|
|
modelId: '',
|
|
enableCustomPricing: false,
|
|
priceInput: '',
|
|
priceOutput: '',
|
|
basePrice: '',
|
|
optionPricesJson: '',
|
|
},
|
|
batchMode: false,
|
|
editingModelId: null,
|
|
editModel: {
|
|
name: '',
|
|
modelId: '',
|
|
enableCustomPricing: false,
|
|
priceInput: '',
|
|
priceOutput: '',
|
|
basePrice: '',
|
|
optionPricesJson: '',
|
|
},
|
|
maskedKey: '',
|
|
isPresetModel: () => false,
|
|
isDefaultModel: () => false,
|
|
setShowKey: () => undefined,
|
|
setShowTutorial: () => undefined,
|
|
setShowAddForm: () => undefined,
|
|
setBatchMode: () => undefined,
|
|
setNewModel: () => undefined,
|
|
setEditModel: () => undefined,
|
|
setTempKey: () => undefined,
|
|
setTempUrl: () => undefined,
|
|
startEditKey: () => undefined,
|
|
startEditUrl: () => undefined,
|
|
handleSaveKey: () => Promise.resolve(),
|
|
handleCancelEdit: () => undefined,
|
|
handleSaveUrl: () => undefined,
|
|
handleCancelUrlEdit: () => undefined,
|
|
handleEditModel: () => undefined,
|
|
handleCancelEditModel: () => undefined,
|
|
handleSaveModel: () => Promise.resolve(),
|
|
handleAddModel: () => Promise.resolve(),
|
|
handleCancelAdd: () => undefined,
|
|
needsCustomPricing: false,
|
|
keyTestStatus: 'idle',
|
|
keyTestSteps: [],
|
|
handleForceSaveKey: () => undefined,
|
|
handleTestOnly: () => undefined,
|
|
handleDismissTest: () => undefined,
|
|
isModelSavePending: false,
|
|
assistantEnabled: false,
|
|
isAssistantOpen: false,
|
|
assistantSavedEvent: null,
|
|
assistantChat: {
|
|
messages: [],
|
|
input: '',
|
|
status: 'ready',
|
|
pending: false,
|
|
error: undefined,
|
|
setInput: () => undefined,
|
|
send: async () => undefined,
|
|
clear: () => undefined,
|
|
},
|
|
openAssistant: () => undefined,
|
|
closeAssistant: () => undefined,
|
|
handleAssistantSend: () => Promise.resolve(),
|
|
}
|
|
}
|
|
|
|
function ProviderCardShellWithBody(
|
|
props: Omit<React.ComponentProps<typeof ProviderCardShell>, 'children'>,
|
|
): React.ReactElement {
|
|
const ProviderCardShellComponent =
|
|
ProviderCardShell as unknown as React.ComponentType<
|
|
React.PropsWithChildren<Omit<React.ComponentProps<typeof ProviderCardShell>, 'children'>>
|
|
>
|
|
return createElement(
|
|
ProviderCardShellComponent,
|
|
props,
|
|
createElement('div', null, 'provider-body'),
|
|
)
|
|
}
|
|
|
|
describe('ProviderCardShell tutorial modal', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
portalMocks.currentPortalTarget = null
|
|
Reflect.deleteProperty(globalThis, 'React')
|
|
Reflect.deleteProperty(globalThis, 'document')
|
|
})
|
|
|
|
it('mounts the tutorial modal through a portal to document.body', () => {
|
|
const fakeDocument = {
|
|
body: { nodeName: 'BODY' },
|
|
}
|
|
Reflect.set(globalThis, 'React', React)
|
|
portalMocks.currentPortalTarget = fakeDocument.body
|
|
Reflect.set(globalThis, 'document', fakeDocument)
|
|
|
|
const tutorial: ProviderTutorial = {
|
|
providerId: 'ark',
|
|
steps: [
|
|
{
|
|
text: 'ark_step1',
|
|
url: 'https://example.com/ark-key',
|
|
},
|
|
],
|
|
}
|
|
const state = createState(tutorial)
|
|
const t = (key: string): string => {
|
|
if (key === 'tutorial.button') return '开通教程'
|
|
if (key === 'tutorial.title') return '开通教程'
|
|
if (key === 'tutorial.subtitle') return '按照以下步骤完成配置'
|
|
if (key === 'tutorial.steps.ark_step1') return '进入控制台创建 API Key'
|
|
if (key === 'tutorial.openLink') return '点击打开'
|
|
if (key === 'tutorial.close') return '关闭'
|
|
return key
|
|
}
|
|
|
|
const html = renderToStaticMarkup(
|
|
createElement(
|
|
ProviderCardShellWithBody,
|
|
{
|
|
provider: {
|
|
id: 'ark',
|
|
name: '阿里云百炼',
|
|
hasApiKey: true,
|
|
},
|
|
onDeleteProvider: () => undefined,
|
|
t,
|
|
state,
|
|
},
|
|
),
|
|
)
|
|
|
|
expect(portalMocks.createPortalMock).toHaveBeenCalledTimes(1)
|
|
expect(portalMocks.createPortalMock.mock.calls[0]?.[1]).toBe(fakeDocument.body)
|
|
expect(html).toContain('data-portal-target="body"')
|
|
expect(html).toContain('进入控制台创建 API Key')
|
|
expect(html).toContain('href="https://example.com/ark-key"')
|
|
})
|
|
})
|