Skip to content

Commit 5fe974f

Browse files
committed
wip: properties panel tests - useFaketimers and implement mock useTracker()
1 parent d3e0ca7 commit 5fe974f

File tree

2 files changed

+96
-79
lines changed

2 files changed

+96
-79
lines changed

packages/webui/jest.config.cjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
module.exports = {
2-
setupFilesAfterEnv: ['./src/__mocks__/_setupMocks.ts', '<rootDir>/src/client/__tests__/jest-setup.cjs'],
2+
setupFilesAfterEnv: [
3+
'./src/__mocks__/_setupMocks.ts',
4+
'<rootDir>/src/client/__tests__/jest-setup.cjs',
5+
'@testing-library/jest-dom',
6+
],
37
globals: {},
48
moduleFileExtensions: ['js', 'ts', 'tsx'],
59
moduleNameMapper: {

packages/webui/src/client/ui/UserEditOperations/__tests__/PropertiesPanel.test.tsx

Lines changed: 91 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
jest.mock(
2+
'../../../../__mocks__/tracker',
3+
() => ({
4+
setup: () => ({
5+
Tracker: {
6+
autorun: jest.fn((fn) => {
7+
fn()
8+
return {
9+
stop: jest.fn(),
10+
}
11+
}),
12+
nonreactive: jest.fn((fn) => fn()),
13+
active: false,
14+
currentComputation: null,
15+
Dependency: jest.fn().mockImplementation(() => ({
16+
depend: jest.fn(),
17+
changed: jest.fn(),
18+
hasDependents: jest.fn(),
19+
})),
20+
},
21+
}),
22+
}),
23+
{
24+
virtual: true,
25+
}
26+
)
27+
128
// Mock the ReactiveDataHelper:
229
jest.mock('../../../lib/reactiveData/ReactiveDataHelper', () => {
330
class MockReactiveDataHelper {
@@ -70,7 +97,7 @@ jest.mock('react-i18next', () => ({
7097

7198
import React from 'react'
7299
// eslint-disable-next-line node/no-unpublished-import
73-
import { renderHook, act, render, screen, RenderResult } from '@testing-library/react'
100+
import { renderHook, act, render, screen, waitFor } from '@testing-library/react'
74101
// eslint-disable-next-line node/no-unpublished-import
75102
import '@testing-library/jest-dom'
76103
import { MeteorCall } from '../../../lib/meteorApi'
@@ -122,6 +149,11 @@ describe('PropertiesPanel', () => {
122149
mockSegmentsCollection.remove({})
123150
mockPartsCollection.remove({})
124151
jest.clearAllMocks()
152+
jest.useFakeTimers()
153+
})
154+
155+
afterEach(() => {
156+
jest.useRealTimers()
125157
})
126158

127159
const createMockSegment = (id: string): DBSegment => ({
@@ -133,12 +165,14 @@ describe('PropertiesPanel', () => {
133165
userEditOperations: [
134166
{
135167
id: 'operation1',
136-
label: { key: 'TEST_LABEL' },
168+
label: { key: 'TEST_LABEL', namespaces: ['blueprint_main-showstyle'] },
137169
type: UserEditingType.ACTION,
138170
buttonType: UserEditingButtonType.SWITCH,
139171
isActive: false,
172+
svgIcon: '<svg></svg>',
140173
},
141174
],
175+
isHidden: false,
142176
})
143177

144178
const createMockPart = (id: string, segmentId: string): DBPart => ({
@@ -152,7 +186,7 @@ describe('PropertiesPanel', () => {
152186
userEditOperations: [
153187
{
154188
id: 'operation2',
155-
label: { key: 'TEST_PART_LABEL' },
189+
label: { key: 'TEST_PART_LABEL', namespaces: ['blueprint_main-showstyle'] },
156190
type: UserEditingType.ACTION,
157191
buttonType: UserEditingButtonType.BUTTON,
158192
isActive: true,
@@ -168,44 +202,39 @@ describe('PropertiesPanel', () => {
168202

169203
test('renders segment properties when segment is selected', async () => {
170204
const mockSegment = createMockSegment('segment1')
171-
mockSegmentsCollection.insert(mockSegment)
172205

173-
// Create a custom wrapper that includes both providers
174-
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
175-
<SelectedElementProvider>{children}</SelectedElementProvider>
176-
)
206+
mockSegmentsCollection.insert(mockSegment)
177207

178-
// Render both the hook and component in the same provider tree
179-
const { result } = renderHook(() => useSelection(), { wrapper: TestWrapper })
180-
let rendered: RenderResult
208+
expect(mockSegmentsCollection.findOne({ _id: mockSegment._id })).toBeTruthy()
181209

182-
await act(async () => {
183-
rendered = render(<PropertiesPanel />, { wrapper: TestWrapper })
184-
})
210+
const { result } = renderHook(() => useSelection(), { wrapper })
185211

186-
// Update selection
212+
// Update selection and wait for component to update
187213
await act(async () => {
188214
result.current.clearAndSetSelection({
189215
type: 'segment',
190216
elementId: mockSegment._id,
191217
})
192218
})
193-
//@ts-expect-error error because avoiding an undefined type
194-
if (!rendered) throw new Error('Component not rendered')
195219

196-
// Force a rerender
220+
// Open component after segment is selected (as used in rundownview)
221+
const { container } = render(<PropertiesPanel />, { wrapper })
222+
197223
await act(async () => {
198-
rendered.rerender(<PropertiesPanel />)
224+
jest.advanceTimersByTime(100)
199225
})
200226

201-
// Wait for the header element to appear
202-
await screen.findByText('SEGMENT : Segment segment1')
203-
204-
const header = rendered.container.querySelector('.propertiespanel-pop-up__header')
205-
const switchButton = rendered.container.querySelector('.propertiespanel-pop-up__switchbutton')
227+
console.log('result', result.current.listSelectedElements())
228+
// Use findByTestId instead of querySelector
229+
await waitFor(
230+
() => {
231+
expect(screen.getByText(`SEGMENT : ${mockSegment.name}`)).toBeInTheDocument()
232+
},
233+
{ timeout: 1000 }
234+
)
206235

207-
expect(header).toHaveTextContent('SEGMENT : Segment segment1')
208-
expect(switchButton).toBeTruthy()
236+
const button = container.querySelector('.propertiespanel-pop-up__button')
237+
expect(button).toBeInTheDocument()
209238
})
210239

211240
test('renders part properties when part is selected', async () => {
@@ -215,75 +244,59 @@ describe('PropertiesPanel', () => {
215244
mockSegmentsCollection.insert(mockSegment)
216245
mockPartsCollection.insert(mockPart)
217246

218-
// Create a custom wrapper that includes both providers
219-
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (
220-
<SelectedElementProvider>{children}</SelectedElementProvider>
221-
)
222-
223-
// Render both the hook and component in the same provider tree
224-
const { result } = renderHook(() => useSelection(), { wrapper: TestWrapper })
225-
let rendered: RenderResult
226-
227-
await act(async () => {
228-
rendered = render(<PropertiesPanel />, { wrapper: TestWrapper })
229-
})
247+
const { result } = renderHook(() => useSelection(), { wrapper })
230248

231-
// Update selection
232249
await act(async () => {
233250
result.current.clearAndSetSelection({
234251
type: 'part',
235252
elementId: mockPart._id,
236253
})
237254
})
255+
// Open component after part is selected (as used in rundownview)
256+
const { container } = render(<PropertiesPanel />, { wrapper })
238257

239-
//@ts-expect-error error because avoiding an undefined type
240-
if (!rendered) throw new Error('Component not rendered')
241-
242-
// Force a rerender
243-
await act(async () => {
244-
rendered.rerender(<PropertiesPanel />)
245-
})
246-
247-
// Wait for the header element to appear
248-
await screen.findByText('PART : Part part1')
249-
250-
const header = rendered.container.querySelector('.propertiespanel-pop-up__header')
251-
const button = rendered.container.querySelector('.propertiespanel-pop-up__button')
258+
await waitFor(
259+
() => {
260+
expect(screen.getByText(`PART : ${mockPart.title}`)).toBeInTheDocument()
261+
},
262+
{ timeout: 1000 }
263+
)
252264

253-
expect(header).toHaveTextContent('PART : Part part1')
254-
expect(button).toBeTruthy()
265+
const button = container.querySelector('.propertiespanel-pop-up__button')
266+
expect(button).toBeInTheDocument()
255267
})
256268

257269
test('handles user edit operations for segments', async () => {
258270
const mockSegment = createMockSegment('segment1')
259271
mockSegmentsCollection.insert(mockSegment)
260272

261-
// First render the selection hook
262273
const { result } = renderHook(() => useSelection(), { wrapper })
263-
264-
// Then render the properties panel
265274
const { container } = render(<PropertiesPanel />, { wrapper })
266275

267-
// Update selection using the hook result
268-
act(() => {
276+
await act(async () => {
269277
result.current.clearAndSetSelection({
270278
type: 'segment',
271279
elementId: mockSegment._id,
272280
})
273281
})
274282

275-
const switchButton = container.querySelector('.propertiespanel-pop-up__switchbutton')
283+
// Wait for the switch button to be available
284+
const switchButton = await waitFor(() => container.querySelector('.propertiespanel-pop-up__switchbutton'))
276285
expect(switchButton).toBeTruthy()
277286

278287
// Toggle the switch
279-
await userEvent.click(switchButton!)
288+
await act(async () => {
289+
await userEvent.click(switchButton!)
290+
})
280291

281292
// Check if commit button is enabled
282293
const commitButton = screen.getByText('COMMIT CHANGES')
283294
expect(commitButton).toBeEnabled()
284295

285296
// Commit changes
286-
await userEvent.click(commitButton)
297+
await act(async () => {
298+
await userEvent.click(commitButton)
299+
})
287300

288301
expect(MeteorCall.userAction.executeUserChangeOperation).toHaveBeenCalledWith(
289302
expect.anything(),
@@ -305,27 +318,29 @@ describe('PropertiesPanel', () => {
305318
const mockSegment = createMockSegment('segment1')
306319
mockSegmentsCollection.insert(mockSegment)
307320

308-
// First render the selection hook
309321
const { result } = renderHook(() => useSelection(), { wrapper })
310-
311-
// Then render the properties panel
312322
const { container } = render(<PropertiesPanel />, { wrapper })
313323

314-
// Update selection using the hook result
315-
act(() => {
324+
await act(async () => {
316325
result.current.clearAndSetSelection({
317326
type: 'segment',
318327
elementId: mockSegment._id,
319328
})
320329
})
321330

331+
// Wait for the switch button to be available
332+
const switchButton = await waitFor(() => container.querySelector('.propertiespanel-pop-up__switchbutton'))
333+
322334
// Make a change
323-
const switchButton = container.querySelector('.propertiespanel-pop-up__switchbutton')
324-
await userEvent.click(switchButton!)
335+
await act(async () => {
336+
await userEvent.click(switchButton!)
337+
})
325338

326339
// Click revert button
327340
const revertButton = screen.getByText('REVERT CHANGES')
328-
await userEvent.click(revertButton)
341+
await act(async () => {
342+
await userEvent.click(revertButton)
343+
})
329344

330345
expect(MeteorCall.userAction.executeUserChangeOperation).toHaveBeenCalledWith(
331346
expect.anything(),
@@ -340,30 +355,28 @@ describe('PropertiesPanel', () => {
340355
id: 'REVERT_SEGMENT',
341356
}
342357
)
343-
})
358+
}, 10000) // Increase timeout for this test
344359

345360
test('closes panel when close button is clicked', async () => {
346361
const mockSegment = createMockSegment('segment1')
347362
mockSegmentsCollection.insert(mockSegment)
348363

349-
// First render the selection hook
350364
const { result } = renderHook(() => useSelection(), { wrapper })
351-
352-
// Then render the properties panel
353365
const { container } = render(<PropertiesPanel />, { wrapper })
354366

355-
// Update selection using the hook result
356-
act(() => {
367+
await act(async () => {
357368
result.current.clearAndSetSelection({
358369
type: 'segment',
359370
elementId: mockSegment._id,
360371
})
361372
})
362373

363-
const closeButton = container.querySelector('.propertiespanel-pop-up_close')
374+
const closeButton = await waitFor(() => container.querySelector('.propertiespanel-pop-up_close'))
364375
expect(closeButton).toBeTruthy()
365376

366-
await userEvent.click(closeButton!)
377+
await act(async () => {
378+
await userEvent.click(closeButton!)
379+
})
367380

368381
expect(container.querySelector('.propertiespanel-pop-up__contents')).toBeFalsy()
369382
})

0 commit comments

Comments
 (0)