返回 oh-my-ppt
page-sidebar-page-switch.test.ts
根目录 / tests / unit / session / page-sidebar-page-switch.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 { TooltipProvider } from '../../../src/renderer/src/components/ui/Tooltip'
8 import { PageSidebar } from '../../../src/renderer/src/components/session-detail/sidebar/PageSidebar'
9 import { useEditHistoryStore } from '../../../src/renderer/src/store/editHistoryStore'
10 import { useEditSessionStore } from '../../../src/renderer/src/store/editSessionStore'
11 import { useSessionDetailUiStore } from '../../../src/renderer/src/store/sessionDetailStore'
12
13 vi.mock('../../../src/renderer/src/i18n', () => ({
14 useT: () => (key: string) => key
15 }))
16
17 vi.mock('../../../src/renderer/src/components/session-detail/hooks/usePreviewWindow', () => ({
18 usePreviewWindow: () => ({
19 activePreviewIds: new Set<string>(),
20 viewportRef: { current: null },
21 schedulePreviewWindowUpdate: vi.fn()
22 })
23 }))
24
25 vi.mock('../../../src/renderer/src/components/session-detail/sidebar/PageThumbnail', () => ({
26 PageThumbnail: ({
27 page,
28 onSelect
29 }: {
30 page: { id: string; title: string }
31 onSelect?: (pageId: string) => void
32 }) =>
33 React.createElement(
34 'button',
35 {
36 type: 'button',
37 'data-testid': `page-thumbnail-${page.id}`,
38 onClick: () => onSelect?.(page.id)
39 },
40 page.title
41 )
42 }))
43
44 const controllerMock = vi.hoisted(() => ({
45 pages: [
46 {
47 id: 'page-1',
48 pageId: 'page-1',
49 pageNumber: 1,
50 title: 'Page 1',
51 contentOutline: 'Outline 1',
52 html: '<html></html>',
53 sourceUrl: 'session://page-1.html'
54 },
55 {
56 id: 'page-2',
57 pageId: 'page-2',
58 pageNumber: 2,
59 title: 'Page 2',
60 contentOutline: 'Outline 2',
61 html: '<html></html>',
62 sourceUrl: 'session://page-2.html'
63 }
64 ]
65 }))
66
67 vi.mock(
68 '../../../src/renderer/src/components/session-detail/sidebar/usePageSidebarController',
69 () => ({
70 usePageSidebarController: () => ({
71 pages: controllerMock.pages,
72 disabled: false,
73 pageManagementDisabled: false,
74 isPageActionDisabled: () => false,
75 collapsed: false,
76 onToggleCollapsed: vi.fn()
77 })
78 })
79 )
80
81 async function renderSidebar(): Promise<{ container: HTMLDivElement; root: Root }> {
82 const container = document.createElement('div')
83 document.body.appendChild(container)
84 const root = createRoot(container)
85 await act(async () => {
86 root.render(
87 React.createElement(
88 TooltipProvider,
89 { delayDuration: 0 },
90 React.createElement(PageSidebar, { sessionId: 'session-1' })
91 )
92 )
93 })
94 return { container, root }
95 }
96
97 async function cleanupRoot(root: Root, container: HTMLDivElement): Promise<void> {
98 await act(async () => {
99 root.unmount()
100 })
101 container.remove()
102 }
103
104 describe('PageSidebar page switching', () => {
105 beforeEach(() => {
106 useEditHistoryStore.getState().clear()
107 useEditSessionStore.getState().reset()
108 useSessionDetailUiStore.getState().resetForSessionChange()
109 useSessionDetailUiStore.setState({
110 selectedPageId: 'page-1',
111 workspaceTab: 'edit',
112 interactionMode: 'edit'
113 })
114 })
115
116 afterEach(() => {
117 document.body.innerHTML = ''
118 })
119
120 it('returns to preview workspace when selecting another page', async () => {
121 const { container, root } = await renderSidebar()
122
123 try {
124 await act(async () => {
125 container.querySelector<HTMLButtonElement>('[data-testid="page-thumbnail-page-2"]')?.click()
126 })
127
128 expect(useSessionDetailUiStore.getState()).toMatchObject({
129 selectedPageId: 'page-2',
130 workspaceTab: 'preview'
131 })
132 } finally {
133 await cleanupRoot(root, container)
134 }
135 })
136
137 it('keeps the current workspace tab when starting outline editing for another page', async () => {
138 const { container, root } = await renderSidebar()
139
140 try {
141 await act(async () => {
142 Array.from(container.querySelectorAll('button'))
143 .find((button) => button.textContent === 'sessionDetail.outlineTab')
144 ?.click()
145 })
146 await act(async () => {
147 const editButtons = container.querySelectorAll<HTMLButtonElement>(
148 '[aria-label="pageManagement.editPageOutline"]'
149 )
150 editButtons[1]?.click()
151 })
152
153 expect(useSessionDetailUiStore.getState()).toMatchObject({
154 selectedPageId: 'page-2',
155 workspaceTab: 'edit'
156 })
157 } finally {
158 await cleanupRoot(root, container)
159 }
160 })
161
162 it('clears unsaved history before switching when the user chooses not to save', async () => {
163 const discardAll = vi.fn(() => useEditHistoryStore.getState().clearPage('page-1'))
164 const originalDiscardAll = useEditSessionStore.getState().discardAll
165 useEditSessionStore.setState({ discardAll })
166 const { container, root } = await renderSidebar()
167
168 try {
169 await act(async () => {
170 useEditHistoryStore.getState().upsertTextEdit({
171 pageId: 'page-1',
172 htmlPath: '/tmp/page-1.html',
173 selector: '[data-block-id="title"]',
174 patch: { text: 'Updated title', style: {} }
175 })
176 })
177 await act(async () => {
178 container.querySelector<HTMLButtonElement>('[data-testid="page-thumbnail-page-2"]')?.click()
179 })
180 await act(async () => {
181 Array.from(document.querySelectorAll<HTMLButtonElement>('button'))
182 .find((button) => button.textContent === 'common.dontSave')
183 ?.click()
184 })
185
186 expect(discardAll).toHaveBeenCalledOnce()
187 expect(useEditHistoryStore.getState().hasPendingEdits('page-1')).toBe(false)
188 expect(useSessionDetailUiStore.getState()).toMatchObject({
189 selectedPageId: 'page-2',
190 workspaceTab: 'preview'
191 })
192 } finally {
193 useEditSessionStore.setState({ discardAll: originalDiscardAll })
194 await cleanupRoot(root, container)
195 }
196 })
197 })
198
198 lines TYPESCRIPT