@memberjunction/react-test-harness 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -914
- package/dist/cli/index.js +0 -0
- package/package.json +9 -9
- package/dist/lib/test-broken-7.d.ts +0 -2
- package/dist/lib/test-broken-7.d.ts.map +0 -1
- package/dist/lib/test-broken-7.js +0 -73
- package/dist/lib/test-broken-7.js.map +0 -1
- package/dist/lib/test-broken-9.d.ts +0 -3
- package/dist/lib/test-broken-9.d.ts.map +0 -1
- package/dist/lib/test-broken-9.js +0 -122
- package/dist/lib/test-broken-9.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,979 +1,195 @@
|
|
|
1
1
|
# @memberjunction/react-test-harness
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Automated test harness for MemberJunction React components using Playwright. Provides static analysis (linting), constraint validation, browser-based rendering tests, and a CLI for running test suites against dynamically compiled components.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
This package provides a comprehensive testing solution for React components, allowing you to:
|
|
8
|
-
- Load and execute React components in a real browser environment
|
|
9
|
-
- Dynamically configure external libraries for testing different scenarios
|
|
10
|
-
- Run assertions on rendered output
|
|
11
|
-
- Execute tests via CLI or programmatically
|
|
12
|
-
- Capture screenshots and console output
|
|
13
|
-
- Run in headless or headed mode for debugging
|
|
14
|
-
|
|
15
|
-
## Installation
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npm install @memberjunction/react-test-harness
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## CLI Usage
|
|
5
|
+
## Architecture
|
|
22
6
|
|
|
23
|
-
|
|
7
|
+
```mermaid
|
|
8
|
+
graph TD
|
|
9
|
+
subgraph "@memberjunction/react-test-harness"
|
|
10
|
+
A[TestHarness] --> B[ComponentLinter]
|
|
11
|
+
A --> C[ComponentRunner]
|
|
12
|
+
A --> D[BrowserContext]
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# With props
|
|
30
|
-
mj-react-test run MyComponent.jsx --props '{"title":"Hello","count":42}'
|
|
31
|
-
|
|
32
|
-
# With screenshot
|
|
33
|
-
mj-react-test run MyComponent.jsx --screenshot ./output.png
|
|
14
|
+
B --> E[Type Inference Engine]
|
|
15
|
+
B --> F[Control Flow Analyzer]
|
|
16
|
+
B --> G[Prop Value Extractor]
|
|
17
|
+
B --> H[Styles Type Analyzer]
|
|
34
18
|
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
subgraph "Constraint Validators"
|
|
20
|
+
I[BaseConstraintValidator]
|
|
21
|
+
I --> J[RequiredWhenValidator]
|
|
22
|
+
I --> K[SQLWhereClauseValidator]
|
|
23
|
+
I --> L[SubsetOfEntityFieldsValidator]
|
|
24
|
+
end
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
# Wait for specific selector
|
|
42
|
-
mj-react-test run MyComponent.jsx --selector "#loaded-content" --timeout 5000
|
|
43
|
-
```
|
|
26
|
+
C --> D
|
|
27
|
+
D --> M["Playwright Browser"]
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# Run a test file with multiple test cases
|
|
49
|
-
mj-react-test test my-tests.js
|
|
50
|
-
|
|
51
|
-
# With options
|
|
52
|
-
mj-react-test test my-tests.js --headed --debug
|
|
53
|
-
```
|
|
29
|
+
N[CLI] --> A
|
|
30
|
+
end
|
|
54
31
|
|
|
55
|
-
|
|
32
|
+
subgraph "Dependencies"
|
|
33
|
+
O["@memberjunction/react-runtime<br/>(Component Compilation)"]
|
|
34
|
+
P["Babel Parser<br/>(AST Analysis)"]
|
|
35
|
+
end
|
|
56
36
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
mj-react-test create-example
|
|
37
|
+
B --> P
|
|
38
|
+
C --> O
|
|
60
39
|
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
style A fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
41
|
+
style B fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
42
|
+
style C fill:#2d8659,stroke:#1a5c3a,color:#fff
|
|
43
|
+
style D fill:#7c5295,stroke:#563a6b,color:#fff
|
|
44
|
+
style E fill:#b8762f,stroke:#8a5722,color:#fff
|
|
45
|
+
style I fill:#7c5295,stroke:#563a6b,color:#fff
|
|
46
|
+
style N fill:#2d6a9f,stroke:#1a4971,color:#fff
|
|
63
47
|
```
|
|
64
48
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
The test harness now supports dynamic library configuration, allowing you to test components with different sets of external libraries.
|
|
49
|
+
## Overview
|
|
68
50
|
|
|
69
|
-
|
|
51
|
+
This package provides comprehensive testing for MemberJunction's dynamically compiled React components. It combines static analysis (without executing) and runtime testing (via Playwright) to validate components before deployment.
|
|
70
52
|
|
|
71
|
-
|
|
72
|
-
import { ReactTestHarness } from '@memberjunction/react-test-harness';
|
|
73
|
-
import type { LibraryConfiguration } from '@memberjunction/react-runtime';
|
|
74
|
-
|
|
75
|
-
const customLibraryConfig: LibraryConfiguration = {
|
|
76
|
-
libraries: [
|
|
77
|
-
// Runtime libraries (always needed)
|
|
78
|
-
{
|
|
79
|
-
id: 'react',
|
|
80
|
-
name: 'React',
|
|
81
|
-
category: 'runtime',
|
|
82
|
-
globalVariable: 'React',
|
|
83
|
-
version: '18',
|
|
84
|
-
cdnUrl: 'https://unpkg.com/react@18/umd/react.development.js',
|
|
85
|
-
isEnabled: true,
|
|
86
|
-
isCore: true,
|
|
87
|
-
isRuntimeOnly: true
|
|
88
|
-
},
|
|
89
|
-
// Component libraries (available to components)
|
|
90
|
-
{
|
|
91
|
-
id: 'lodash',
|
|
92
|
-
name: 'lodash',
|
|
93
|
-
displayName: 'Lodash',
|
|
94
|
-
category: 'utility',
|
|
95
|
-
globalVariable: '_',
|
|
96
|
-
version: '4.17.21',
|
|
97
|
-
cdnUrl: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js',
|
|
98
|
-
isEnabled: true,
|
|
99
|
-
isCore: false
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: 'chart-js',
|
|
103
|
-
name: 'Chart',
|
|
104
|
-
displayName: 'Chart.js',
|
|
105
|
-
category: 'charting',
|
|
106
|
-
globalVariable: 'Chart',
|
|
107
|
-
version: '4.4.0',
|
|
108
|
-
cdnUrl: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.js',
|
|
109
|
-
isEnabled: true,
|
|
110
|
-
isCore: false
|
|
111
|
-
}
|
|
112
|
-
],
|
|
113
|
-
metadata: {
|
|
114
|
-
version: '1.0.0',
|
|
115
|
-
lastUpdated: '2024-01-01'
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Test with custom libraries
|
|
120
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
121
|
-
await harness.initialize();
|
|
122
|
-
|
|
123
|
-
const result = await harness.testComponent(
|
|
124
|
-
`const Component = () => {
|
|
125
|
-
if (!_) return <div>Lodash not available</div>;
|
|
126
|
-
const sorted = _.sortBy([3, 1, 2]);
|
|
127
|
-
return <div>{sorted.join(', ')}</div>;
|
|
128
|
-
}`,
|
|
129
|
-
{},
|
|
130
|
-
{ libraryConfiguration: customLibraryConfig }
|
|
131
|
-
);
|
|
132
|
-
```
|
|
53
|
+
**Key capabilities:**
|
|
133
54
|
|
|
134
|
-
|
|
55
|
+
- **Component Linting**: Static analysis of component source using Babel AST parsing
|
|
56
|
+
- **Type Inference**: Infers component prop types from source code and usage patterns
|
|
57
|
+
- **Constraint Validation**: Validates data constraints like SQL WHERE clauses, required-when conditions, and entity field subsets
|
|
58
|
+
- **Control Flow Analysis**: Detects unreachable code, missing returns, and complex control flow patterns
|
|
59
|
+
- **Browser Rendering**: Launches components in a real browser via Playwright for visual/functional testing
|
|
60
|
+
- **Prop Value Extraction**: Extracts and validates prop values from JSX source
|
|
61
|
+
- **Library Lint Caching**: Caches lint results for external libraries to improve performance
|
|
62
|
+
- **CLI Tool**: `mj-react-test` command for running test suites
|
|
135
63
|
|
|
136
|
-
|
|
137
|
-
// Test with minimal libraries
|
|
138
|
-
const minimalConfig: LibraryConfiguration = {
|
|
139
|
-
libraries: [
|
|
140
|
-
// Only runtime essentials
|
|
141
|
-
{ id: 'react', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
142
|
-
{ id: 'react-dom', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
143
|
-
{ id: 'babel', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
144
|
-
// Just one utility library
|
|
145
|
-
{ id: 'lodash', category: 'utility', isEnabled: true, ... }
|
|
146
|
-
],
|
|
147
|
-
metadata: { version: '1.0.0', lastUpdated: '2024-01-01' }
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// Test with full library suite
|
|
151
|
-
const fullConfig: LibraryConfiguration = {
|
|
152
|
-
libraries: [
|
|
153
|
-
// All runtime libraries
|
|
154
|
-
// All UI libraries (antd, react-bootstrap)
|
|
155
|
-
// All charting libraries (chart.js, d3)
|
|
156
|
-
// All utilities (lodash, dayjs)
|
|
157
|
-
],
|
|
158
|
-
metadata: { version: '1.0.0', lastUpdated: '2024-01-01' }
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Test component behavior with different configurations
|
|
162
|
-
const componentCode = `
|
|
163
|
-
const Component = () => {
|
|
164
|
-
const hasChart = typeof Chart !== 'undefined';
|
|
165
|
-
const hasAntd = typeof antd !== 'undefined';
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div>
|
|
169
|
-
<p>Chart.js: {hasChart ? 'Available' : 'Not Available'}</p>
|
|
170
|
-
<p>Ant Design: {hasAntd ? 'Available' : 'Not Available'}</p>
|
|
171
|
-
</div>
|
|
172
|
-
);
|
|
173
|
-
};
|
|
174
|
-
`;
|
|
175
|
-
|
|
176
|
-
const minimalResult = await harness.testComponent(componentCode, {}, {
|
|
177
|
-
libraryConfiguration: minimalConfig
|
|
178
|
-
});
|
|
64
|
+
## Installation
|
|
179
65
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
});
|
|
66
|
+
```bash
|
|
67
|
+
npm install @memberjunction/react-test-harness
|
|
183
68
|
```
|
|
184
69
|
|
|
185
|
-
##
|
|
186
|
-
|
|
187
|
-
The test harness is designed to be used as a library in your TypeScript/JavaScript code, not just via CLI. All classes and types are fully exported for programmatic use.
|
|
70
|
+
## Usage
|
|
188
71
|
|
|
189
|
-
###
|
|
72
|
+
### CLI
|
|
190
73
|
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
ReactTestHarness,
|
|
195
|
-
BrowserManager,
|
|
196
|
-
ComponentRunner,
|
|
197
|
-
AssertionHelpers,
|
|
198
|
-
FluentMatcher
|
|
199
|
-
} from '@memberjunction/react-test-harness';
|
|
74
|
+
```bash
|
|
75
|
+
# Run the test harness
|
|
76
|
+
npx mj-react-test
|
|
200
77
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
TestHarnessOptions,
|
|
204
|
-
ComponentExecutionResult,
|
|
205
|
-
ComponentExecutionOptions,
|
|
206
|
-
BrowserContextOptions,
|
|
207
|
-
TestCase,
|
|
208
|
-
TestSummary
|
|
209
|
-
} from '@memberjunction/react-test-harness';
|
|
78
|
+
# Or if installed globally
|
|
79
|
+
mj-react-test
|
|
210
80
|
```
|
|
211
81
|
|
|
212
|
-
###
|
|
82
|
+
### Programmatic API
|
|
213
83
|
|
|
214
84
|
```typescript
|
|
215
|
-
import {
|
|
216
|
-
|
|
217
|
-
async function testMyComponent() {
|
|
218
|
-
const harness = new ReactTestHarness({
|
|
219
|
-
headless: true,
|
|
220
|
-
debug: false
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
await harness.initialize();
|
|
225
|
-
|
|
226
|
-
// Test component code directly
|
|
227
|
-
const result = await harness.testComponent(`
|
|
228
|
-
const Component = ({ message }) => {
|
|
229
|
-
return <div className="greeting">{message}</div>;
|
|
230
|
-
};
|
|
231
|
-
`, { message: 'Hello World' });
|
|
232
|
-
|
|
233
|
-
console.log('Success:', result.success);
|
|
234
|
-
console.log('HTML:', result.html);
|
|
235
|
-
console.log('Console logs:', result.console);
|
|
236
|
-
|
|
237
|
-
return result;
|
|
238
|
-
} finally {
|
|
239
|
-
await harness.close();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
85
|
+
import { TestHarness } from '@memberjunction/react-test-harness';
|
|
243
86
|
|
|
244
|
-
|
|
87
|
+
const harness = new TestHarness();
|
|
245
88
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
describe('My React Components', () => {
|
|
251
|
-
let harness: ReactTestHarness;
|
|
252
|
-
|
|
253
|
-
beforeAll(async () => {
|
|
254
|
-
harness = new ReactTestHarness({ headless: true });
|
|
255
|
-
await harness.initialize();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
afterAll(async () => {
|
|
259
|
-
await harness.close();
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
it('should render greeting component', async () => {
|
|
263
|
-
const result = await harness.testComponent(`
|
|
264
|
-
const Component = ({ name }) => <h1>Hello, {name}!</h1>;
|
|
265
|
-
`, { name: 'World' });
|
|
266
|
-
|
|
267
|
-
AssertionHelpers.assertSuccess(result);
|
|
268
|
-
AssertionHelpers.assertContainsText(result.html, 'Hello, World!');
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('should handle click events', async () => {
|
|
272
|
-
const result = await harness.testComponent(`
|
|
273
|
-
const Component = () => {
|
|
274
|
-
const [count, setCount] = React.useState(0);
|
|
275
|
-
return (
|
|
276
|
-
<button onClick={() => setCount(count + 1)}>
|
|
277
|
-
Count: {count}
|
|
278
|
-
</button>
|
|
279
|
-
);
|
|
280
|
-
};
|
|
281
|
-
`);
|
|
282
|
-
|
|
283
|
-
AssertionHelpers.assertContainsText(result.html, 'Count: 0');
|
|
284
|
-
});
|
|
89
|
+
// Run all tests for a component
|
|
90
|
+
const results = await harness.RunTests(componentSource, {
|
|
91
|
+
libraries: libraryConfigs,
|
|
92
|
+
entityMetadata: entityInfo
|
|
285
93
|
});
|
|
286
94
|
```
|
|
287
95
|
|
|
288
|
-
###
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
import {
|
|
292
|
-
ReactTestHarness,
|
|
293
|
-
BrowserManager,
|
|
294
|
-
ComponentRunner,
|
|
295
|
-
AssertionHelpers
|
|
296
|
-
} from '@memberjunction/react-test-harness';
|
|
297
|
-
|
|
298
|
-
class ComponentTestSuite {
|
|
299
|
-
private harness: ReactTestHarness;
|
|
300
|
-
private browserManager: BrowserManager;
|
|
301
|
-
private componentRunner: ComponentRunner;
|
|
302
|
-
|
|
303
|
-
constructor() {
|
|
304
|
-
// You can also use the underlying classes directly
|
|
305
|
-
this.browserManager = new BrowserManager({
|
|
306
|
-
viewport: { width: 1920, height: 1080 },
|
|
307
|
-
headless: true
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
this.componentRunner = new ComponentRunner(this.browserManager);
|
|
311
|
-
|
|
312
|
-
// Or use the high-level harness
|
|
313
|
-
this.harness = new ReactTestHarness({
|
|
314
|
-
headless: true,
|
|
315
|
-
debug: true
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async initialize() {
|
|
320
|
-
await this.harness.initialize();
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async testComponent(code: string, props?: any) {
|
|
324
|
-
const result = await this.harness.testComponent(code, props);
|
|
325
|
-
|
|
326
|
-
// Use static assertion methods
|
|
327
|
-
AssertionHelpers.assertSuccess(result);
|
|
328
|
-
|
|
329
|
-
// Or create a fluent matcher
|
|
330
|
-
const matcher = AssertionHelpers.createMatcher(result.html);
|
|
331
|
-
matcher.toContainText('Expected text');
|
|
332
|
-
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async cleanup() {
|
|
337
|
-
await this.harness.close();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Usage
|
|
342
|
-
const suite = new ComponentTestSuite();
|
|
343
|
-
await suite.initialize();
|
|
344
|
-
await suite.testComponent(`const Component = () => <div>Test</div>;`);
|
|
345
|
-
await suite.cleanup();
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
### Test Component Files
|
|
96
|
+
### Component Linting
|
|
349
97
|
|
|
350
98
|
```typescript
|
|
351
|
-
|
|
352
|
-
'./MyComponent.jsx',
|
|
353
|
-
{ title: 'Test', value: 123 },
|
|
354
|
-
{
|
|
355
|
-
waitForSelector: '.loaded',
|
|
356
|
-
timeout: 10000
|
|
357
|
-
}
|
|
358
|
-
);
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
### Running Multiple Tests
|
|
99
|
+
import { ComponentLinter } from '@memberjunction/react-test-harness';
|
|
362
100
|
|
|
363
|
-
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const Component = () => <h1>Test</h1>;
|
|
369
|
-
`);
|
|
370
|
-
|
|
371
|
-
const { AssertionHelpers } = harness;
|
|
372
|
-
AssertionHelpers.assertSuccess(result);
|
|
373
|
-
AssertionHelpers.assertContainsText(result.html, 'Test');
|
|
101
|
+
const linter = new ComponentLinter();
|
|
102
|
+
const lintResults = linter.Lint(componentSource, {
|
|
103
|
+
checkPropTypes: true,
|
|
104
|
+
checkControlFlow: true,
|
|
105
|
+
validateConstraints: true
|
|
374
106
|
});
|
|
375
107
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
{
|
|
379
|
-
name: 'Has correct elements',
|
|
380
|
-
fn: async () => {
|
|
381
|
-
const result = await harness.testComponent(`
|
|
382
|
-
const Component = () => (
|
|
383
|
-
<div>
|
|
384
|
-
<h1 id="title">Title</h1>
|
|
385
|
-
<button className="action">Click</button>
|
|
386
|
-
</div>
|
|
387
|
-
);
|
|
388
|
-
`);
|
|
389
|
-
|
|
390
|
-
const matcher = harness.createMatcher(result.html);
|
|
391
|
-
matcher.toHaveElement('#title');
|
|
392
|
-
matcher.toHaveElement('.action');
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
name: 'Handles props correctly',
|
|
397
|
-
fn: async () => {
|
|
398
|
-
const result = await harness.testComponent(`
|
|
399
|
-
const Component = ({ items }) => (
|
|
400
|
-
<ul>
|
|
401
|
-
{items.map((item, i) => <li key={i}>{item}</li>)}
|
|
402
|
-
</ul>
|
|
403
|
-
);
|
|
404
|
-
`, { items: ['A', 'B', 'C'] });
|
|
405
|
-
|
|
406
|
-
const { AssertionHelpers } = harness;
|
|
407
|
-
AssertionHelpers.assertElementCount(result.html, 'li', 3);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
]);
|
|
411
|
-
|
|
412
|
-
console.log(`Tests passed: ${summary.passed}/${summary.total}`);
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
## Complete API Reference
|
|
416
|
-
|
|
417
|
-
### ReactTestHarness Class
|
|
418
|
-
|
|
419
|
-
The main class for testing React components.
|
|
420
|
-
|
|
421
|
-
```typescript
|
|
422
|
-
class ReactTestHarness {
|
|
423
|
-
constructor(options?: TestHarnessOptions);
|
|
424
|
-
|
|
425
|
-
// Lifecycle methods
|
|
426
|
-
async initialize(): Promise<void>;
|
|
427
|
-
async close(): Promise<void>;
|
|
428
|
-
|
|
429
|
-
// Component testing methods
|
|
430
|
-
async testComponent(
|
|
431
|
-
componentCode: string,
|
|
432
|
-
props?: Record<string, any>,
|
|
433
|
-
options?: Partial<ComponentExecutionOptions>
|
|
434
|
-
): Promise<ComponentExecutionResult>;
|
|
435
|
-
|
|
436
|
-
async testComponentFromFile(
|
|
437
|
-
filePath: string,
|
|
438
|
-
props?: Record<string, any>,
|
|
439
|
-
options?: Partial<ComponentExecutionOptions>
|
|
440
|
-
): Promise<ComponentExecutionResult>;
|
|
441
|
-
|
|
442
|
-
// Test running methods
|
|
443
|
-
async runTest(name: string, fn: () => Promise<void>): Promise<void>;
|
|
444
|
-
async runTests(tests: TestCase[]): Promise<TestSummary>;
|
|
445
|
-
|
|
446
|
-
// Utility methods
|
|
447
|
-
getAssertionHelpers(): typeof AssertionHelpers;
|
|
448
|
-
createMatcher(html: string): FluentMatcher;
|
|
449
|
-
async screenshot(path?: string): Promise<Buffer>;
|
|
450
|
-
async evaluateInPage<T>(fn: () => T): Promise<T>;
|
|
451
|
-
}
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
### BrowserManager Class
|
|
455
|
-
|
|
456
|
-
Manages the Playwright browser instance.
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
class BrowserManager {
|
|
460
|
-
constructor(options?: BrowserContextOptions);
|
|
461
|
-
|
|
462
|
-
async initialize(): Promise<void>;
|
|
463
|
-
async close(): Promise<void>;
|
|
464
|
-
async getPage(): Promise<Page>;
|
|
465
|
-
async navigate(url: string): Promise<void>;
|
|
466
|
-
async evaluateInPage<T>(fn: () => T): Promise<T>;
|
|
467
|
-
async screenshot(path?: string): Promise<Buffer>;
|
|
468
|
-
}
|
|
469
|
-
```
|
|
470
|
-
|
|
471
|
-
### ComponentRunner Class
|
|
472
|
-
|
|
473
|
-
Executes React components in the browser.
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
476
|
-
class ComponentRunner {
|
|
477
|
-
constructor(browserManager: BrowserManager);
|
|
478
|
-
|
|
479
|
-
async executeComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
|
|
480
|
-
async executeComponentFromFile(
|
|
481
|
-
filePath: string,
|
|
482
|
-
props?: Record<string, any>,
|
|
483
|
-
options?: Partial<ComponentExecutionOptions>
|
|
484
|
-
): Promise<ComponentExecutionResult>;
|
|
108
|
+
for (const issue of lintResults.issues) {
|
|
109
|
+
console.log(`${issue.severity}: ${issue.message} (line ${issue.line})`);
|
|
485
110
|
}
|
|
486
111
|
```
|
|
487
112
|
|
|
488
|
-
###
|
|
489
|
-
|
|
490
|
-
Provides assertion methods for testing.
|
|
113
|
+
### Browser-Based Testing
|
|
491
114
|
|
|
492
115
|
```typescript
|
|
493
|
-
|
|
494
|
-
// Result assertions
|
|
495
|
-
static assertSuccess(result: ComponentExecutionResult): void;
|
|
496
|
-
static assertNoErrors(result: ComponentExecutionResult): void;
|
|
497
|
-
static assertNoConsoleErrors(console: Array<{ type: string; text: string }>): void;
|
|
498
|
-
|
|
499
|
-
// Content assertions
|
|
500
|
-
static assertContainsText(html: string, text: string): void;
|
|
501
|
-
static assertNotContainsText(html: string, text: string): void;
|
|
502
|
-
static assertHasElement(html: string, selector: string): void;
|
|
503
|
-
static assertElementCount(html: string, tagName: string, expectedCount: number): void;
|
|
504
|
-
|
|
505
|
-
// Utility methods
|
|
506
|
-
static containsText(html: string, text: string): boolean;
|
|
507
|
-
static hasElement(html: string, selector: string): boolean;
|
|
508
|
-
static countElements(html: string, tagName: string): number;
|
|
509
|
-
static hasAttribute(html: string, selector: string, attribute: string, value?: string): boolean;
|
|
510
|
-
|
|
511
|
-
// Fluent matcher creation
|
|
512
|
-
static createMatcher(html: string): FluentMatcher;
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### FluentMatcher Interface
|
|
116
|
+
import { ComponentRunner, BrowserContext } from '@memberjunction/react-test-harness';
|
|
517
117
|
|
|
518
|
-
|
|
118
|
+
const browser = await BrowserContext.Create();
|
|
119
|
+
const runner = new ComponentRunner(browser);
|
|
519
120
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
toHaveElementCount(tagName: string, count: number): void;
|
|
525
|
-
toHaveAttribute(selector: string, attribute: string, value?: string): void;
|
|
526
|
-
}
|
|
527
|
-
```
|
|
528
|
-
|
|
529
|
-
## Parallel Testing
|
|
530
|
-
|
|
531
|
-
### Important: Test Harness Instance Limitations
|
|
532
|
-
|
|
533
|
-
The ReactTestHarness uses a single browser page instance and is **NOT safe for parallel test execution** on the same instance. This is due to Playwright's internal limitations with `exposeFunction` and potential race conditions when multiple tests try to modify the same page context simultaneously.
|
|
534
|
-
|
|
535
|
-
### Sequential Testing (Single Instance)
|
|
536
|
-
|
|
537
|
-
For sequential test execution, you can safely reuse a single harness instance:
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
541
|
-
await harness.initialize();
|
|
542
|
-
|
|
543
|
-
// ✅ CORRECT - Sequential testing on same instance
|
|
544
|
-
for (const test of tests) {
|
|
545
|
-
const result = await harness.testComponent(test.code, test.props);
|
|
546
|
-
// Each test runs one after another, no conflicts
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
await harness.close();
|
|
550
|
-
```
|
|
551
|
-
|
|
552
|
-
### Parallel Testing (Multiple Instances)
|
|
553
|
-
|
|
554
|
-
For parallel test execution, you **MUST** create separate ReactTestHarness instances:
|
|
555
|
-
|
|
556
|
-
```typescript
|
|
557
|
-
// ✅ CORRECT - Parallel testing with separate instances
|
|
558
|
-
const results = await Promise.all(tests.map(async (test) => {
|
|
559
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
560
|
-
await harness.initialize();
|
|
561
|
-
|
|
562
|
-
try {
|
|
563
|
-
return await harness.testComponent(test.code, test.props);
|
|
564
|
-
} finally {
|
|
565
|
-
await harness.close(); // Clean up each instance
|
|
566
|
-
}
|
|
567
|
-
}));
|
|
568
|
-
```
|
|
569
|
-
|
|
570
|
-
### Common Mistake to Avoid
|
|
571
|
-
|
|
572
|
-
```typescript
|
|
573
|
-
// ❌ WRONG - DO NOT DO THIS
|
|
574
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
575
|
-
await harness.initialize();
|
|
576
|
-
|
|
577
|
-
// This will cause conflicts and errors!
|
|
578
|
-
const results = await Promise.all(
|
|
579
|
-
tests.map(test => harness.testComponent(test.code, test.props))
|
|
580
|
-
);
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
This approach will fail with errors like:
|
|
584
|
-
- "Function '__mjGetEntityObject' has been already registered"
|
|
585
|
-
- "Cannot read properties of undefined (reading 'addBinding')"
|
|
586
|
-
|
|
587
|
-
### Performance Considerations
|
|
588
|
-
|
|
589
|
-
While creating multiple harness instances has some overhead (each launches its own browser context), the benefits of parallel execution typically outweigh this cost:
|
|
590
|
-
|
|
591
|
-
- **Sequential (1 instance)**: Lower memory usage, but tests run one by one
|
|
592
|
-
- **Parallel (N instances)**: Higher memory usage, but tests complete much faster
|
|
593
|
-
|
|
594
|
-
### Example: Test Runner with Configurable Parallelism
|
|
595
|
-
|
|
596
|
-
```typescript
|
|
597
|
-
class TestRunner {
|
|
598
|
-
async runTests(tests: TestCase[], parallel = false) {
|
|
599
|
-
if (parallel) {
|
|
600
|
-
// Create new instance for each test
|
|
601
|
-
return Promise.all(tests.map(async (test) => {
|
|
602
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
603
|
-
await harness.initialize();
|
|
604
|
-
try {
|
|
605
|
-
return await harness.testComponent(test.code, test.props);
|
|
606
|
-
} finally {
|
|
607
|
-
await harness.close();
|
|
608
|
-
}
|
|
609
|
-
}));
|
|
610
|
-
} else {
|
|
611
|
-
// Reuse single instance for all tests
|
|
612
|
-
const harness = new ReactTestHarness({ headless: true });
|
|
613
|
-
await harness.initialize();
|
|
614
|
-
|
|
615
|
-
const results = [];
|
|
616
|
-
for (const test of tests) {
|
|
617
|
-
results.push(await harness.testComponent(test.code, test.props));
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
await harness.close();
|
|
621
|
-
return results;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
## Usage Examples for TypeScript Projects
|
|
628
|
-
|
|
629
|
-
### Creating a Reusable Test Utility
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
// test-utils.ts
|
|
633
|
-
import { ReactTestHarness, AssertionHelpers } from '@memberjunction/react-test-harness';
|
|
634
|
-
import type { ComponentExecutionResult } from '@memberjunction/react-test-harness';
|
|
635
|
-
|
|
636
|
-
export class ReactComponentTester {
|
|
637
|
-
private harness: ReactTestHarness;
|
|
638
|
-
|
|
639
|
-
constructor() {
|
|
640
|
-
this.harness = new ReactTestHarness({
|
|
641
|
-
headless: process.env.HEADED !== 'true',
|
|
642
|
-
debug: process.env.DEBUG === 'true'
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async setup() {
|
|
647
|
-
await this.harness.initialize();
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async teardown() {
|
|
651
|
-
await this.harness.close();
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
async testMJComponent(
|
|
655
|
-
componentCode: string,
|
|
656
|
-
data: any,
|
|
657
|
-
userState?: any,
|
|
658
|
-
callbacks?: any,
|
|
659
|
-
utilities?: any,
|
|
660
|
-
styles?: any
|
|
661
|
-
): Promise<ComponentExecutionResult> {
|
|
662
|
-
// Test with MJ-style props structure
|
|
663
|
-
const props = { data, userState, callbacks, utilities, styles };
|
|
664
|
-
return this.harness.testComponent(componentCode, props);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
expectSuccess(result: ComponentExecutionResult) {
|
|
668
|
-
AssertionHelpers.assertSuccess(result);
|
|
669
|
-
return this;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
expectText(result: ComponentExecutionResult, text: string) {
|
|
673
|
-
AssertionHelpers.assertContainsText(result.html, text);
|
|
674
|
-
return this;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
expectNoText(result: ComponentExecutionResult, text: string) {
|
|
678
|
-
AssertionHelpers.assertNotContainsText(result.html, text);
|
|
679
|
-
return this;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Usage in tests
|
|
684
|
-
const tester = new ReactComponentTester();
|
|
685
|
-
await tester.setup();
|
|
686
|
-
|
|
687
|
-
const result = await tester.testMJComponent(
|
|
688
|
-
componentCode,
|
|
689
|
-
{ title: 'Test', items: [] },
|
|
690
|
-
{ viewMode: 'grid' }
|
|
691
|
-
);
|
|
692
|
-
|
|
693
|
-
tester
|
|
694
|
-
.expectSuccess(result)
|
|
695
|
-
.expectText(result, 'Test')
|
|
696
|
-
.expectNoText(result, 'Error');
|
|
697
|
-
|
|
698
|
-
await tester.teardown();
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### Testing with Different Library Configurations
|
|
702
|
-
|
|
703
|
-
```typescript
|
|
704
|
-
import { ReactTestHarness } from '@memberjunction/react-test-harness';
|
|
705
|
-
import type { LibraryConfiguration } from '@memberjunction/react-runtime';
|
|
706
|
-
|
|
707
|
-
class LibraryCompatibilityTester {
|
|
708
|
-
private harness: ReactTestHarness;
|
|
709
|
-
|
|
710
|
-
constructor() {
|
|
711
|
-
this.harness = new ReactTestHarness({ headless: true });
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
async testWithLibraries(
|
|
715
|
-
componentCode: string,
|
|
716
|
-
enabledLibraries: string[]
|
|
717
|
-
) {
|
|
718
|
-
const config: LibraryConfiguration = {
|
|
719
|
-
libraries: [
|
|
720
|
-
// Always include runtime
|
|
721
|
-
{ id: 'react', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
722
|
-
{ id: 'react-dom', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
723
|
-
{ id: 'babel', category: 'runtime', isEnabled: true, isRuntimeOnly: true, ... },
|
|
724
|
-
// Conditionally enable other libraries
|
|
725
|
-
{ id: 'lodash', category: 'utility', isEnabled: enabledLibraries.includes('lodash'), ... },
|
|
726
|
-
{ id: 'chart-js', category: 'charting', isEnabled: enabledLibraries.includes('chart-js'), ... },
|
|
727
|
-
{ id: 'antd', category: 'ui', isEnabled: enabledLibraries.includes('antd'), ... },
|
|
728
|
-
],
|
|
729
|
-
metadata: { version: '1.0.0', lastUpdated: '2024-01-01' }
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
return this.harness.testComponent(componentCode, {}, {
|
|
733
|
-
libraryConfiguration: config
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
### CI/CD Integration
|
|
740
|
-
|
|
741
|
-
```typescript
|
|
742
|
-
// ci-test-runner.ts
|
|
743
|
-
import { ReactTestHarness } from '@memberjunction/react-test-harness';
|
|
744
|
-
import * as fs from 'fs';
|
|
745
|
-
import * as path from 'path';
|
|
746
|
-
|
|
747
|
-
export async function runComponentTests(testDir: string) {
|
|
748
|
-
const harness = new ReactTestHarness({
|
|
749
|
-
headless: true,
|
|
750
|
-
screenshotOnError: true,
|
|
751
|
-
screenshotPath: './test-failures/'
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
const results = {
|
|
755
|
-
total: 0,
|
|
756
|
-
passed: 0,
|
|
757
|
-
failed: 0,
|
|
758
|
-
failures: [] as Array<{ component: string; error: string }>
|
|
759
|
-
};
|
|
760
|
-
|
|
761
|
-
await harness.initialize();
|
|
762
|
-
|
|
763
|
-
try {
|
|
764
|
-
const files = fs.readdirSync(testDir)
|
|
765
|
-
.filter(f => f.endsWith('.jsx') || f.endsWith('.tsx'));
|
|
766
|
-
|
|
767
|
-
for (const file of files) {
|
|
768
|
-
results.total++;
|
|
769
|
-
|
|
770
|
-
try {
|
|
771
|
-
const result = await harness.testComponentFromFile(
|
|
772
|
-
path.join(testDir, file)
|
|
773
|
-
);
|
|
774
|
-
|
|
775
|
-
if (result.success) {
|
|
776
|
-
results.passed++;
|
|
777
|
-
} else {
|
|
778
|
-
results.failed++;
|
|
779
|
-
results.failures.push({
|
|
780
|
-
component: file,
|
|
781
|
-
error: result.error || 'Unknown error'
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
} catch (error) {
|
|
785
|
-
results.failed++;
|
|
786
|
-
results.failures.push({
|
|
787
|
-
component: file,
|
|
788
|
-
error: String(error)
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
} finally {
|
|
793
|
-
await harness.close();
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
return results;
|
|
797
|
-
}
|
|
121
|
+
const result = await runner.Render(compiledComponent, {
|
|
122
|
+
props: { data: testData },
|
|
123
|
+
timeout: 5000
|
|
124
|
+
});
|
|
798
125
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
console.log(`Tests: ${results.passed}/${results.total} passed`);
|
|
126
|
+
console.log('Rendered successfully:', result.success);
|
|
127
|
+
console.log('Console errors:', result.consoleErrors);
|
|
802
128
|
|
|
803
|
-
|
|
804
|
-
console.error('Failures:', results.failures);
|
|
805
|
-
process.exit(1);
|
|
806
|
-
}
|
|
129
|
+
await browser.Close();
|
|
807
130
|
```
|
|
808
131
|
|
|
809
|
-
|
|
132
|
+
### Constraint Validation
|
|
810
133
|
|
|
811
134
|
```typescript
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
waitForSelector?: string; // Wait for element before capture
|
|
818
|
-
waitForLoadState?: 'load' | 'domcontentloaded' | 'networkidle';
|
|
819
|
-
contextUser: UserInfo;
|
|
820
|
-
libraryConfiguration?: LibraryConfiguration; // New: Custom library configuration
|
|
821
|
-
}
|
|
822
|
-
```
|
|
823
|
-
|
|
824
|
-
## Test Harness Options
|
|
825
|
-
|
|
826
|
-
```typescript
|
|
827
|
-
interface TestHarnessOptions {
|
|
828
|
-
headless?: boolean; // Default: true
|
|
829
|
-
viewport?: { // Default: 1280x720
|
|
830
|
-
width: number;
|
|
831
|
-
height: number;
|
|
832
|
-
};
|
|
833
|
-
debug?: boolean; // Default: false
|
|
834
|
-
screenshotOnError?: boolean; // Default: true
|
|
835
|
-
screenshotPath?: string; // Default: './error-screenshot.png'
|
|
836
|
-
userAgent?: string;
|
|
837
|
-
deviceScaleFactor?: number;
|
|
838
|
-
locale?: string;
|
|
839
|
-
timezoneId?: string;
|
|
840
|
-
}
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
## Writing Test Files
|
|
844
|
-
|
|
845
|
-
Test files should export a default async function:
|
|
135
|
+
import {
|
|
136
|
+
SQLWhereClauseValidator,
|
|
137
|
+
RequiredWhenValidator,
|
|
138
|
+
SubsetOfEntityFieldsValidator
|
|
139
|
+
} from '@memberjunction/react-test-harness';
|
|
846
140
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const { AssertionHelpers } = harness;
|
|
141
|
+
// Validate a SQL WHERE clause
|
|
142
|
+
const sqlValidator = new SQLWhereClauseValidator();
|
|
143
|
+
const sqlResult = sqlValidator.Validate("Status = 'Active' AND Age > 18", context);
|
|
851
144
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
});
|
|
145
|
+
// Validate required-when conditions
|
|
146
|
+
const reqValidator = new RequiredWhenValidator();
|
|
147
|
+
const reqResult = reqValidator.Validate("IsAdmin = true", context);
|
|
856
148
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
{ value: 100 }
|
|
861
|
-
);
|
|
862
|
-
AssertionHelpers.assertContainsText(result.html, '100');
|
|
863
|
-
});
|
|
864
|
-
}
|
|
149
|
+
// Validate field subset
|
|
150
|
+
const subsetValidator = new SubsetOfEntityFieldsValidator();
|
|
151
|
+
const subsetResult = subsetValidator.Validate(["Name", "Email", "Status"], context);
|
|
865
152
|
```
|
|
866
153
|
|
|
867
|
-
##
|
|
154
|
+
## Components
|
|
868
155
|
|
|
869
|
-
|
|
156
|
+
| Component | Description |
|
|
157
|
+
|-----------|-------------|
|
|
158
|
+
| `TestHarness` | Main orchestrator for running all test types |
|
|
159
|
+
| `ComponentLinter` | Static analysis of component source code |
|
|
160
|
+
| `ComponentRunner` | Browser-based component rendering tests |
|
|
161
|
+
| `BrowserContext` | Manages Playwright browser lifecycle |
|
|
162
|
+
| `TypeInferenceEngine` | Infers prop types from source code |
|
|
163
|
+
| `ControlFlowAnalyzer` | Detects control flow issues |
|
|
164
|
+
| `PropValueExtractor` | Extracts and validates prop values |
|
|
165
|
+
| `StylesTypeAnalyzer` | Analyzes component style patterns |
|
|
166
|
+
| `LibraryLintCache` | Caches lint results for external libraries |
|
|
167
|
+
| `LinterTestTool` | Testing utilities for linter development |
|
|
870
168
|
|
|
871
|
-
|
|
872
|
-
import { BrowserManager } from '@memberjunction/react-test-harness';
|
|
169
|
+
## Testing
|
|
873
170
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
timezoneId: 'America/New_York'
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
await browser.initialize();
|
|
881
|
-
const page = await browser.getPage();
|
|
171
|
+
```bash
|
|
172
|
+
npm test
|
|
173
|
+
npm run test:watch
|
|
882
174
|
```
|
|
883
175
|
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
```typescript
|
|
887
|
-
const harness = new ReactTestHarness();
|
|
888
|
-
await harness.initialize();
|
|
889
|
-
|
|
890
|
-
// Evaluate JavaScript in the page context
|
|
891
|
-
const result = await harness.evaluateInPage(() => {
|
|
892
|
-
return document.querySelector('h1')?.textContent;
|
|
893
|
-
});
|
|
176
|
+
Uses Vitest for unit testing.
|
|
894
177
|
|
|
895
|
-
|
|
896
|
-
const screenshot = await harness.screenshot('./output.png');
|
|
897
|
-
```
|
|
178
|
+
## Dependencies
|
|
898
179
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
try {
|
|
912
|
-
// Run tests
|
|
913
|
-
} finally {
|
|
914
|
-
await harness.close();
|
|
915
|
-
}
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
2. **Use waitForSelector** for dynamic content:
|
|
919
|
-
```typescript
|
|
920
|
-
const result = await harness.testComponent(componentCode, props, {
|
|
921
|
-
waitForSelector: '.async-content',
|
|
922
|
-
timeout: 5000
|
|
923
|
-
});
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
3. **Enable debug mode** during development:
|
|
927
|
-
```typescript
|
|
928
|
-
const harness = new ReactTestHarness({ debug: true });
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
4. **Group related tests** for better organization:
|
|
932
|
-
```typescript
|
|
933
|
-
await harness.runTests([
|
|
934
|
-
{ name: 'Feature A - Test 1', fn: async () => { /* ... */ } },
|
|
935
|
-
{ name: 'Feature A - Test 2', fn: async () => { /* ... */ } },
|
|
936
|
-
{ name: 'Feature B - Test 1', fn: async () => { /* ... */ } },
|
|
937
|
-
]);
|
|
938
|
-
```
|
|
939
|
-
|
|
940
|
-
5. **Test with different library configurations** to ensure compatibility:
|
|
941
|
-
```typescript
|
|
942
|
-
// Test with minimal libraries
|
|
943
|
-
const minimalResult = await harness.testComponent(code, props, {
|
|
944
|
-
libraryConfiguration: minimalLibraryConfig
|
|
945
|
-
});
|
|
946
|
-
|
|
947
|
-
// Test with full libraries
|
|
948
|
-
const fullResult = await harness.testComponent(code, props, {
|
|
949
|
-
libraryConfiguration: fullLibraryConfig
|
|
950
|
-
});
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
## Troubleshooting
|
|
954
|
-
|
|
955
|
-
### Component Not Rendering
|
|
956
|
-
- Ensure your component is named `Component` or modify the execution template
|
|
957
|
-
- Check for syntax errors in your component code
|
|
958
|
-
- Enable debug mode to see console output
|
|
959
|
-
- Verify required libraries are included in libraryConfiguration
|
|
960
|
-
|
|
961
|
-
### Timeout Errors
|
|
962
|
-
- Increase timeout value: `--timeout 60000`
|
|
963
|
-
- Use `waitForLoadState: 'networkidle'` for components that load external resources
|
|
964
|
-
- Check if the selector in `waitForSelector` actually exists
|
|
965
|
-
|
|
966
|
-
### Screenshot Issues
|
|
967
|
-
- Ensure the screenshot path directory exists
|
|
968
|
-
- Use absolute paths for consistent results
|
|
969
|
-
- Check file permissions
|
|
970
|
-
|
|
971
|
-
### Library Loading Issues
|
|
972
|
-
- Verify CDN URLs are accessible
|
|
973
|
-
- Check that globalVariable names match what components expect
|
|
974
|
-
- Ensure runtime libraries (React, ReactDOM, Babel) are always included
|
|
975
|
-
- Use isRuntimeOnly flag for libraries not exposed to components
|
|
180
|
+
| Package | Purpose |
|
|
181
|
+
|---------|---------|
|
|
182
|
+
| `@memberjunction/react-runtime` | Component compilation and registry |
|
|
183
|
+
| `@memberjunction/interactive-component-types` | Component type definitions |
|
|
184
|
+
| `@memberjunction/core` | Core MJ functionality |
|
|
185
|
+
| `@memberjunction/core-entities` | Entity types |
|
|
186
|
+
| `@babel/parser` | AST parsing for static analysis |
|
|
187
|
+
| `@babel/traverse` | AST traversal |
|
|
188
|
+
| `@playwright/test` | Browser automation |
|
|
189
|
+
| `commander` | CLI framework |
|
|
190
|
+
| `chalk` | Terminal styling |
|
|
191
|
+
| `node-sql-parser` | SQL WHERE clause validation |
|
|
976
192
|
|
|
977
193
|
## License
|
|
978
194
|
|
|
979
|
-
ISC
|
|
195
|
+
ISC
|