feat: add Husky hooks and fix provider tutorial UI/logic
- Add Husky pre-commit and pre-push hooks for linting, type checking, and build validation - Fix visual hierarchy bug in the provider onboarding tutorial - Remove feedback modal - Move MinIO bucket creation logic to before app startup - Wire MiniMax audio through voice generation pipeline - Fix scene insertion issues - Fix portal tutorial modal and harden panel variant task flow
This commit is contained in:
169
tests/unit/api-config/provider-card-tutorial-modal.test.ts
Normal file
169
tests/unit/api-config/provider-card-tutorial-modal.test.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
ProviderCardShell,
|
||||
{
|
||||
provider: {
|
||||
id: 'ark',
|
||||
name: '阿里云百炼',
|
||||
hasApiKey: true,
|
||||
},
|
||||
onDeleteProvider: () => undefined,
|
||||
t,
|
||||
state,
|
||||
},
|
||||
createElement('div', null, 'provider-body'),
|
||||
),
|
||||
)
|
||||
|
||||
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"')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user