返回 oh-my-ppt
generation-status-panel-rendering.test.ts
根目录 / tests / unit / generation / generation-status-panel-rendering.test.ts
1 /**
2 * @vitest-environment happy-dom
3 */
4 import React, { act } from 'react'
5 import { afterEach, describe, expect, it, vi } from 'vitest'
6 import { createRoot } from 'react-dom/client'
7 import { GenerationStatusPanel } from '../../../src/renderer/src/components/session-generating/GenerationStatusPanel'
8
9 vi.mock('@renderer/i18n', () => ({ useT: () => (key: string) => key }))
10
11 const createModelAction = () => ({
12 modelConfigs: [],
13 selectedModelConfigId: '',
14 activatingModelConfigId: null,
15 hasMultipleModelConfigs: false,
16 currentModelConfig: null,
17 ensureModelActive: vi.fn(async () => null)
18 })
19
20 async function renderPanel({
21 isCancelling,
22 onCancel
23 }: {
24 isCancelling: boolean
25 onCancel: () => void
26 }): Promise<{ container: HTMLDivElement; unmount: () => Promise<void> }> {
27 const container = document.createElement('div')
28 document.body.appendChild(container)
29 const root = createRoot(container)
30
31 await act(async () => {
32 root.render(
33 React.createElement(GenerationStatusPanel, {
34 status: 'running',
35 progress: 24,
36 stages: ['preflight', 'planning', 'rendering', 'validation'],
37 stageLabels: {
38 preflight: 'Preflight',
39 planning: 'Planning',
40 rendering: 'Rendering',
41 validation: 'Validation'
42 },
43 currentStage: 'rendering',
44 completedPageCount: 1,
45 totalPages: 3,
46 error: null,
47 interruptedLabel: 'Interrupted',
48 enterEditorLabel: 'Enter editor',
49 continueRemainingLabel: 'Continue',
50 regenerateLabel: 'Regenerate',
51 cancelLabel: '取消生成',
52 isCancelling,
53 hasGeneratedPages: false,
54 canEnterEditor: false,
55 showEditorShortcut: false,
56 modelAction: createModelAction(),
57 onEnterEditor: vi.fn(),
58 onContinueRemaining: vi.fn(),
59 onRegenerate: vi.fn(),
60 onCancel
61 })
62 )
63 })
64
65 return {
66 container,
67 unmount: async () => {
68 await act(async () => root.unmount())
69 container.remove()
70 }
71 }
72 }
73
74 describe('GenerationStatusPanel', () => {
75 afterEach(() => {
76 vi.restoreAllMocks()
77 document.body.innerHTML = ''
78 })
79
80 it('disables the cancel button and shows a spinner while cancellation is pending', async () => {
81 const onCancel = vi.fn()
82 const { container, unmount } = await renderPanel({ isCancelling: true, onCancel })
83
84 try {
85 const cancelButton = Array.from(container.querySelectorAll('button')).find((button) =>
86 button.textContent?.includes('取消生成')
87 ) as HTMLButtonElement | undefined
88
89 expect(cancelButton).toBeTruthy()
90 expect(cancelButton?.disabled).toBe(true)
91 expect(cancelButton?.getAttribute('aria-busy')).toBe('true')
92 expect(cancelButton?.querySelector('svg')?.getAttribute('class')).toContain('animate-spin')
93
94 cancelButton?.click()
95
96 expect(onCancel).not.toHaveBeenCalled()
97 } finally {
98 await unmount()
99 }
100 })
101
102 it('keeps the cancel button clickable before cancellation starts', async () => {
103 const onCancel = vi.fn()
104 const { container, unmount } = await renderPanel({ isCancelling: false, onCancel })
105
106 try {
107 const cancelButton = Array.from(container.querySelectorAll('button')).find((button) =>
108 button.textContent?.includes('取消生成')
109 ) as HTMLButtonElement | undefined
110
111 expect(cancelButton).toBeTruthy()
112 expect(cancelButton?.disabled).toBe(false)
113 expect(cancelButton?.hasAttribute('aria-busy')).toBe(false)
114
115 cancelButton?.click()
116
117 expect(onCancel).toHaveBeenCalledTimes(1)
118 } finally {
119 await unmount()
120 }
121 })
122 })
123
123 lines TYPESCRIPT