返回 oh-my-ppt
session-create-layout.test.ts
根目录 / tests / unit / session-create / session-create-layout.test.ts
1 /**
2 * @vitest-environment happy-dom
3 */
4 import React, { act } from 'react'
5 import { readFileSync } from 'fs'
6 import { afterEach, describe, expect, it, vi } from 'vitest'
7 import { createRoot } from 'react-dom/client'
8 import { MemoryRouter } from 'react-router-dom'
9 import { SessionCreatePage } from '../../../src/renderer/src/pages/session-create'
10 ;(
11 globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
12 ).IS_REACT_ACT_ENVIRONMENT = true
13
14 const state = vi.hoisted(() => ({
15 listStyles: vi.fn(async () => ({
16 items: [
17 {
18 id: 'style-1',
19 label: 'Style One',
20 description: '',
21 createdAt: 1,
22 updatedAt: 1
23 }
24 ]
25 })),
26 listFonts: vi.fn(async () => ({ googleFonts: [], userFonts: [] })),
27 translate: vi.fn((key: string) => key),
28 success: vi.fn(),
29 error: vi.fn(),
30 warning: vi.fn()
31 }))
32
33 vi.mock('../../../src/renderer/src/store', () => ({
34 useSessionStore: () => ({
35 createSession: vi.fn(),
36 loading: false
37 }),
38 useSettingsStore: () => ({
39 settings: {
40 storagePath: '/tmp'
41 }
42 }),
43 useToastStore: () => ({
44 success: state.success,
45 error: state.error,
46 warning: state.warning
47 })
48 }))
49
50 vi.mock('@renderer/lib/ipc', () => ({
51 ipc: {
52 listStyles: state.listStyles,
53 listFonts: state.listFonts
54 }
55 }))
56
57 vi.mock('@renderer/i18n', () => ({
58 useT: () => state.translate
59 }))
60
61 vi.mock('../../../src/renderer/src/hooks/useModelAction', () => ({
62 useModelAction: () => ({
63 modelConfigs: [{ id: 'model-1', apiKey: 'key', model: 'model' }],
64 selectedModelConfigId: 'model-1',
65 ensureModelActive: vi.fn(async (id: string) => id)
66 })
67 }))
68
69 vi.mock('../../../src/renderer/src/components/style/StyleSelect', () => ({
70 StyleSelect: ({
71 className,
72 dropdownAlign,
73 dropdownClassName
74 }: {
75 className?: string
76 dropdownAlign?: string
77 dropdownClassName?: string
78 }) =>
79 React.createElement(
80 'button',
81 {
82 type: 'button',
83 className,
84 'data-dropdown-align': dropdownAlign,
85 'data-dropdown-class': dropdownClassName
86 },
87 'style-select'
88 )
89 }))
90
91 vi.mock('../../../src/renderer/src/components/model/ModelActionButton', () => ({
92 ModelSplitButton: ({ ariaLabel, className }: { ariaLabel: string; className?: string }) =>
93 React.createElement('button', { type: 'button', className, 'aria-label': ariaLabel }, ariaLabel)
94 }))
95
96 vi.mock(
97 '../../../src/renderer/src/components/session-create/SessionCreateSuggestionDialog',
98 () => ({
99 buildSuggestionDraft: vi.fn(),
100 formatSourceOutlineBriefText: vi.fn(),
101 SessionCreateSuggestionDialog: () => null
102 })
103 )
104
105 describe('SessionCreatePage layout', () => {
106 afterEach(() => {
107 vi.clearAllMocks()
108 document.body.innerHTML = ''
109 })
110
111 it('groups content and generation settings into one responsive two-column workspace', async () => {
112 const container = document.createElement('div')
113 document.body.appendChild(container)
114 const root = createRoot(container)
115
116 await act(async () => {
117 root.render(React.createElement(MemoryRouter, null, React.createElement(SessionCreatePage)))
118 await Promise.resolve()
119 await Promise.resolve()
120 })
121
122 const main = container.querySelector('[data-session-create-main]')
123 const settings = container.querySelector('[data-session-create-settings]')
124 const page = container.querySelector('.session-create-page')
125
126 expect(page).toBeTruthy()
127 expect(main).toBeTruthy()
128 expect(settings).toBeTruthy()
129 expect(main?.parentElement).toBe(settings?.parentElement)
130 expect(main?.parentElement?.className).toContain('lg:grid-cols-')
131 expect(main?.className).toContain('bg-transparent')
132 expect(settings?.className).toContain('bg-transparent')
133 expect(main?.closest('[data-session-create-workspace]')?.className).toContain(
134 'border-[#ded8cb]'
135 )
136
137 expect(main?.querySelector('input[placeholder="home.topicPlaceholder"]')).toBeTruthy()
138 expect(main?.querySelector('textarea[placeholder="home.briefPlaceholder"]')).toBeTruthy()
139 const createButton = main?.querySelector('button[aria-label="home.createAndStart"]')
140 expect(createButton).toBeTruthy()
141 expect(createButton?.className).toContain('w-full')
142
143 const referenceActions = main?.querySelector('[data-session-create-reference-actions]')
144 expect(referenceActions).toBeTruthy()
145 expect(referenceActions?.className).toContain('justify-end')
146 expect(referenceActions?.previousElementSibling?.querySelector('textarea')).toBeTruthy()
147 expect(settings?.querySelector('[data-session-create-reference-actions]')).toBeNull()
148
149 const styleSelect = settings?.querySelector('[data-dropdown-align="end"]')
150 expect(styleSelect).toBeTruthy()
151 expect(styleSelect?.className).toContain('h-8')
152 expect(styleSelect?.getAttribute('data-dropdown-class')).toContain('700px')
153 expect(settings?.querySelector('input[inputmode="numeric"]')).toBeTruthy()
154
155 const animationButtons = settings?.querySelectorAll('button[aria-pressed]')
156 expect(animationButtons).toHaveLength(20)
157 expect(animationButtons?.[0]?.parentElement?.className).toContain('grid-cols-2')
158
159 await act(async () => root.unmount())
160 })
161
162 it('caps the attached file name at 150px with ellipsis', () => {
163 const source = readFileSync('src/renderer/src/pages/session-create.tsx', 'utf8')
164
165 expect(source).toContain('w-[150px] min-w-0 max-w-[150px] truncate text-left hover:underline')
166 })
167 })
168
168 lines TYPESCRIPT