返回 oh-my-ppt
history-dialog.test.ts
根目录 / tests / unit / session / history-dialog.test.ts
1 /**
2 * @vitest-environment happy-dom
3 */
4 import React, { act } from 'react'
5 import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
6 import { createRoot, type Root } from 'react-dom/client'
7 import { HistoryDialog } from '../../../src/renderer/src/components/session-detail/modal/HistoryDialog'
8 import { useSessionDetailUiStore } from '../../../src/renderer/src/store/sessionDetailStore'
9 import { useGenerateStore } from '../../../src/renderer/src/store/generateStore'
10 import { useSessionStore } from '../../../src/renderer/src/store/sessionStore'
11
12 vi.mock('sonner', () => {
13 const fn = (() => '') as unknown as {
14 success: () => string
15 error: () => string
16 info: () => string
17 warning: () => string
18 loading: () => string
19 promise: () => void
20 dismiss: () => void
21 }
22 fn.success = fn
23 fn.error = fn
24 fn.info = fn
25 fn.warning = fn
26 fn.loading = fn
27 fn.promise = () => {}
28 fn.dismiss = () => {}
29 return { toast: fn }
30 })
31
32 const ipcMocks = vi.hoisted(() => ({
33 listHistoryVersions: vi.fn(),
34 rollbackToHistoryVersion: vi.fn()
35 }))
36
37 vi.mock('@renderer/lib/ipc', () => ({
38 ipc: {
39 listHistoryVersions: ipcMocks.listHistoryVersions,
40 rollbackToHistoryVersion: ipcMocks.rollbackToHistoryVersion
41 }
42 }))
43
44 vi.mock('@renderer/i18n', () => ({
45 useT: () => (key: string) => key
46 }))
47
48 async function renderDialog(): Promise<{ root: Root; container: HTMLDivElement }> {
49 const container = document.createElement('div')
50 document.body.appendChild(container)
51 const root = createRoot(container)
52 await act(async () => {
53 root.render(React.createElement(HistoryDialog, { sessionId: 'session-1' }))
54 await Promise.resolve()
55 })
56 return { root, container }
57 }
58
59 async function cleanup(root: Root, container: HTMLDivElement): Promise<void> {
60 await act(async () => {
61 root.unmount()
62 })
63 container.remove()
64 }
65
66 describe('HistoryDialog', () => {
67 beforeEach(() => {
68 ipcMocks.listHistoryVersions.mockReset()
69 ipcMocks.rollbackToHistoryVersion.mockReset()
70 useGenerateStore.getState().reset()
71 useSessionStore.getState().resetRuntimeState()
72 useSessionDetailUiStore.getState().resetForSessionChange()
73 useSessionDetailUiStore.getState().setHistoryDialogOpen(true)
74 })
75
76 afterEach(() => {
77 document.body.innerHTML = ''
78 })
79
80 it('uses a max-height scrolling panel and shows at most 20 history versions', async () => {
81 ipcMocks.listHistoryVersions.mockResolvedValue(
82 Array.from({ length: 25 }, (_, index) => ({
83 id: `history-${index + 1}`,
84 title: `Version ${index + 1}`,
85 description: `Change ${index + 1}`,
86 createdAt: 1_700_000_000 + index,
87 operation: 'edit',
88 changedPages: [],
89 isCurrent: index === 0,
90 isRestorable: index !== 0
91 }))
92 )
93
94 const { root, container } = await renderDialog()
95
96 try {
97 expect(ipcMocks.listHistoryVersions).toHaveBeenCalledWith({
98 sessionId: 'session-1',
99 limit: 20
100 })
101
102 const panel = container.querySelector('.fixed')?.firstElementChild as HTMLElement | null
103 expect(panel?.className).toContain('max-h-[min(640px,78vh)]')
104 expect(panel?.className).not.toContain(' h-[min(640px,78vh)]')
105 expect(panel?.querySelector('.overflow-y-auto')).toBeTruthy()
106
107 const renderedTitles = Array.from(container.querySelectorAll('p'))
108 .map((node) => node.textContent || '')
109 .filter((text) => /^Version \d+$/.test(text))
110 expect(renderedTitles).toHaveLength(20)
111 expect(renderedTitles).toContain('Version 20')
112 expect(renderedTitles).not.toContain('Version 21')
113 } finally {
114 await cleanup(root, container)
115 }
116 })
117 })
118
118 lines TYPESCRIPT