Skip to content

Commit 4ee8873

Browse files
author
Greg Trihus
committed
TT-7017 Add ability to display flat projects
- Introduced comprehensive Cypress testing instructions for component testing in the APM Vite application, detailing setup patterns, mocking strategies, and debugging tips. - Added a guide for writing Jest tests for React hooks, addressing common challenges and providing patterns for effective testing. - Updated PassageCard and related components to utilize new testing patterns and improved mock data structures. - Created new PassageGraphic and PassageRef components to enhance rendering logic and improve testability.
1 parent 96c148b commit 4ee8873

15 files changed

+2819
-211
lines changed

.github/instructions/cypress-testing.instructions.md

Lines changed: 706 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
---
2+
applyTo: 'src/renderer/src/**/*.test.tsx'
3+
---
4+
5+
# Hook Testing Guide
6+
7+
## Scope
8+
9+
These instructions cover writing Jest tests for React hooks in the renderer application under `src/renderer/src`. This guide addresses the specific challenges and patterns encountered in this project's testing environment.
10+
11+
## Key Testing Challenges in This Project
12+
13+
### 1. Complex Dependency Chains
14+
15+
- Many hooks import contexts that have deep dependency trees leading to problematic modules like `react-localization`
16+
- Direct context provider usage in tests can cause "Cannot use import statement outside a module" errors
17+
- Solution: Mock dependencies comprehensively rather than trying to provide real context
18+
19+
### 2. Context Dependencies
20+
21+
- Hooks often depend on contexts like `PlanContext`, `GlobalContext`, etc.
22+
- These contexts import Redux stores, localization, and other complex dependencies
23+
- Solution: Mock `useContext` directly instead of trying to provide context values
24+
25+
## Testing Pattern for Hooks
26+
27+
### Basic Test Structure
28+
29+
```typescript
30+
import { renderHook } from '@testing-library/react';
31+
import React from 'react';
32+
33+
// Mock dependencies BEFORE importing the hook
34+
jest.mock('react', () => ({
35+
...jest.requireActual('react'),
36+
useContext: jest.fn(),
37+
useMemo: jest.fn(),
38+
// Add other React hooks as needed
39+
}));
40+
41+
jest.mock('../../context/useGlobal', () => ({
42+
useGlobal: jest.fn(),
43+
}));
44+
45+
jest.mock('../../crud', () => ({
46+
// Mock all crud functions the hook uses
47+
findRecord: jest.fn(),
48+
sectionDescription: jest.fn(),
49+
}));
50+
51+
// Import the hook AFTER mocking
52+
import { useYourHook } from './useYourHook';
53+
54+
// Get references to mocked functions
55+
const { useContext, useMemo } = React;
56+
const { useGlobal } = require('../../context/useGlobal');
57+
const { findRecord } = require('../../crud');
58+
```
59+
60+
### Mock Setup Pattern
61+
62+
```typescript
63+
describe('useYourHook', () => {
64+
const mockContextValue = {
65+
// Mock context state structure
66+
state: {
67+
sectionArr: [[1, 'Section One']],
68+
},
69+
};
70+
71+
beforeEach(() => {
72+
jest.clearAllMocks();
73+
74+
// Setup default mocks
75+
(useGlobal as jest.Mock).mockReturnValue([mockMemory, jest.fn()]);
76+
(useContext as jest.Mock).mockReturnValue(mockContextValue);
77+
(findRecord as jest.Mock).mockReturnValue(undefined);
78+
79+
// Mock useMemo to return the factory result directly
80+
(useMemo as jest.Mock).mockImplementation((factory) => factory());
81+
});
82+
});
83+
```
84+
85+
## Comprehensive Mocking Strategy
86+
87+
### Required Mocks
88+
89+
1. **React hooks**: `useContext`, `useMemo`, `useCallback`, etc.
90+
2. **Custom contexts**: `useGlobal`, context imports
91+
3. **CRUD functions**: Any imported utility functions
92+
4. **Model imports**: Usually work fine, but mock if causing issues
93+
94+
### Mock Context Providers
95+
96+
Instead of creating context providers, mock `useContext` directly:
97+
98+
```typescript
99+
// DON'T do this (causes dependency issues):
100+
const Wrapper = ({ children }) => (
101+
<PlanContext.Provider value={mockValue}>
102+
{children}
103+
</PlanContext.Provider>
104+
);
105+
106+
// DO this instead:
107+
(useContext as jest.Mock).mockReturnValue(mockValue);
108+
```
109+
110+
## Common Test Cases for Hooks
111+
112+
### 1. Basic Functionality
113+
114+
```typescript
115+
it('should return expected value/function', () => {
116+
const { result } = renderHook(() => useYourHook());
117+
expect(typeof result.current).toBe('function');
118+
});
119+
```
120+
121+
### 2. Dependency Calls
122+
123+
```typescript
124+
it('should call dependencies with correct parameters', () => {
125+
const { result } = renderHook(() => useYourHook());
126+
const hookFunction = result.current;
127+
128+
hookFunction(mockInput);
129+
130+
expect(mockedDependency).toHaveBeenCalledWith(expectedParams);
131+
});
132+
```
133+
134+
### 3. Edge Cases
135+
136+
```typescript
137+
it('should handle undefined/null inputs gracefully', () => {
138+
(mockedDependency as jest.Mock).mockReturnValue(undefined);
139+
140+
const { result } = renderHook(() => useYourHook());
141+
const hookFunction = result.current;
142+
143+
expect(() => hookFunction(null)).not.toThrow();
144+
});
145+
```
146+
147+
### 4. Context Changes (useMemo behavior)
148+
149+
```typescript
150+
it('should update when dependencies change', () => {
151+
const initialValue = { data: 'initial' };
152+
const updatedValue = { data: 'updated' };
153+
154+
(useContext as jest.Mock).mockReturnValue(initialValue);
155+
156+
const { result, rerender } = renderHook(() => useYourHook());
157+
158+
// Update context
159+
(useContext as jest.Mock).mockReturnValue(updatedValue);
160+
rerender();
161+
162+
// Test that hook uses updated value
163+
expect(result.current).toBeDefined();
164+
});
165+
```
166+
167+
## Model Type Utilities
168+
169+
### Creating Mock Objects
170+
171+
Create helper functions for complex model objects:
172+
173+
```typescript
174+
const createMockRow = (overrides: Partial<ISheet> = {}): ISheet => ({
175+
level: SheetLevel.Section,
176+
kind: IwsKind.Section,
177+
sectionSeq: 1,
178+
passageSeq: 0,
179+
passageType: PassageTypeEnum.PASSAGE,
180+
deleted: false,
181+
filtered: false,
182+
published: [],
183+
...overrides,
184+
});
185+
```
186+
187+
## Import Pattern Gotchas
188+
189+
### 1. Import Order Matters
190+
191+
- Always mock dependencies before importing the hook
192+
- Use `require()` for getting mock references after mocking
193+
194+
### 2. Enum Usage
195+
196+
- Import enums from their correct locations
197+
- Common enums: `PassageTypeEnum`, `SheetLevel`, `IwsKind`
198+
- `PublishDestinationEnum` comes from `../../crud`, not model
199+
200+
### 3. Interface Requirements
201+
202+
- `ISheet` requires `deleted`, `filtered`, and `published` properties
203+
- Use mock helper functions to ensure all required properties are present
204+
205+
## Testing Environment
206+
207+
### Jest Configuration
208+
209+
- Tests run in `jsdom` environment
210+
- TypeScript configuration uses `tsconfig.jest.json`
211+
- Setup file: `jest.setup.ts` provides global test utilities
212+
213+
### Running Tests
214+
215+
```bash
216+
cd src/renderer
217+
npm test -- yourHookName.test.tsx
218+
```
219+
220+
## Common Errors and Solutions
221+
222+
### "Cannot use import statement outside a module"
223+
224+
- **Cause**: Complex dependency chain importing problematic modules
225+
- **Solution**: Mock all dependencies comprehensively, avoid real context providers
226+
227+
### "Property does not exist on type"
228+
229+
- **Cause**: Missing required properties on mock objects
230+
- **Solution**: Use helper functions that include all required properties
231+
232+
### "Cannot access before initialization"
233+
234+
- **Cause**: React reference in mock setup before import
235+
- **Solution**: Use `require()` syntax for mock references
236+
237+
### Mock function not working
238+
239+
- **Cause**: Mock setup after import, or incorrect mock pattern
240+
- **Solution**: Ensure mocks are set up before imports, use proper jest.Mock casting
241+
242+
## Best Practices
243+
244+
1. **Comprehensive Mocking**: Mock all external dependencies, don't try to use real ones
245+
2. **Helper Functions**: Create mock object builders for complex types
246+
3. **Clear Test Names**: Describe exactly what scenario is being tested
247+
4. **Edge Case Coverage**: Test null/undefined inputs and error conditions
248+
5. **Isolation**: Each test should be independent with proper cleanup
249+
6. **Documentation**: Add comments explaining complex mock setups
250+
251+
## Example Template
252+
253+
```typescript
254+
/**
255+
* Test suite for useExampleHook
256+
*
257+
* Brief description of what the hook does
258+
*/
259+
260+
import { renderHook } from '@testing-library/react';
261+
import React from 'react';
262+
263+
// Mock all dependencies first
264+
jest.mock('react', () => ({
265+
...jest.requireActual('react'),
266+
useContext: jest.fn(),
267+
useMemo: jest.fn(),
268+
}));
269+
270+
jest.mock('../../context/useGlobal');
271+
jest.mock('../../crud', () => ({
272+
someFunction: jest.fn(),
273+
}));
274+
275+
// Import hook after mocking
276+
import { useExampleHook } from './useExampleHook';
277+
import { ISheet } from '../../model';
278+
279+
// Get mock references
280+
const { useContext, useMemo } = React;
281+
const { useGlobal } = require('../../context/useGlobal');
282+
const { someFunction } = require('../../crud');
283+
284+
describe('useExampleHook', () => {
285+
const mockData = {
286+
/* mock setup */
287+
};
288+
289+
const createMockInput = (overrides = {}): InputType => ({
290+
// default properties
291+
...overrides,
292+
});
293+
294+
beforeEach(() => {
295+
jest.clearAllMocks();
296+
297+
// Setup default mocks
298+
(useGlobal as jest.Mock).mockReturnValue([mockMemory, jest.fn()]);
299+
(useContext as jest.Mock).mockReturnValue(mockData);
300+
(useMemo as jest.Mock).mockImplementation((factory) => factory());
301+
});
302+
303+
it('should handle basic functionality', () => {
304+
// Test implementation
305+
});
306+
307+
it('should handle edge cases', () => {
308+
// Test edge cases
309+
});
310+
});
311+
```
312+
313+
This pattern ensures reliable, isolated testing of hooks in this project's complex environment.

0 commit comments

Comments
 (0)