@memberjunction/react-test-harness 2.71.0 → 2.73.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 +8 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/component-linter.d.ts +26 -0
- package/dist/lib/component-linter.d.ts.map +1 -0
- package/dist/lib/component-linter.js +429 -0
- package/dist/lib/component-linter.js.map +1 -0
- package/dist/lib/component-runner.d.ts +64 -0
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +283 -40
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/test-harness.d.ts +6 -11
- package/dist/lib/test-harness.d.ts.map +1 -1
- package/dist/lib/test-harness.js +64 -22
- package/dist/lib/test-harness.js.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -680,6 +680,14 @@ const result = await harness.evaluateInPage(() => {
|
|
|
680
680
|
const screenshot = await harness.screenshot('./output.png');
|
|
681
681
|
```
|
|
682
682
|
|
|
683
|
+
## Limitations
|
|
684
|
+
|
|
685
|
+
Due to the architecture of the test harness (Node.js controlling a browser via Playwright), there are some important limitations to be aware of. See [docs/limitations.md](./docs/limitations.md) for details on:
|
|
686
|
+
|
|
687
|
+
- Serialization requirements between Node.js and browser
|
|
688
|
+
- BaseEntity method access limitations
|
|
689
|
+
- Differences between test and production environments
|
|
690
|
+
|
|
683
691
|
## Best Practices
|
|
684
692
|
|
|
685
693
|
1. **Always close the harness** after tests to free resources:
|
package/dist/index.d.ts
CHANGED
|
@@ -2,4 +2,5 @@ export { ReactTestHarness, TestHarnessOptions } from './lib/test-harness';
|
|
|
2
2
|
export { BrowserManager, BrowserContextOptions } from './lib/browser-context';
|
|
3
3
|
export { ComponentRunner, ComponentExecutionOptions, ComponentExecutionResult, ComponentSpec } from './lib/component-runner';
|
|
4
4
|
export { AssertionHelpers } from './lib/assertion-helpers';
|
|
5
|
+
export { ComponentLinter, LintResult, Violation, FixSuggestion, ComponentType } from './lib/component-linter';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC7H,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AssertionHelpers = exports.ComponentRunner = exports.BrowserManager = exports.ReactTestHarness = void 0;
|
|
3
|
+
exports.ComponentLinter = exports.AssertionHelpers = exports.ComponentRunner = exports.BrowserManager = exports.ReactTestHarness = void 0;
|
|
4
4
|
var test_harness_1 = require("./lib/test-harness");
|
|
5
5
|
Object.defineProperty(exports, "ReactTestHarness", { enumerable: true, get: function () { return test_harness_1.ReactTestHarness; } });
|
|
6
6
|
var browser_context_1 = require("./lib/browser-context");
|
|
@@ -9,4 +9,6 @@ var component_runner_1 = require("./lib/component-runner");
|
|
|
9
9
|
Object.defineProperty(exports, "ComponentRunner", { enumerable: true, get: function () { return component_runner_1.ComponentRunner; } });
|
|
10
10
|
var assertion_helpers_1 = require("./lib/assertion-helpers");
|
|
11
11
|
Object.defineProperty(exports, "AssertionHelpers", { enumerable: true, get: function () { return assertion_helpers_1.AssertionHelpers; } });
|
|
12
|
+
var component_linter_1 = require("./lib/component-linter");
|
|
13
|
+
Object.defineProperty(exports, "ComponentLinter", { enumerable: true, get: function () { return component_linter_1.ComponentLinter; } });
|
|
12
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mDAA0E;AAAjE,gHAAA,gBAAgB,OAAA;AACzB,yDAA8E;AAArE,iHAAA,cAAc,OAAA;AACvB,2DAA6H;AAApH,mHAAA,eAAe,OAAA;AACxB,6DAA2D;AAAlD,qHAAA,gBAAgB,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mDAA0E;AAAjE,gHAAA,gBAAgB,OAAA;AACzB,yDAA8E;AAArE,iHAAA,cAAc,OAAA;AACvB,2DAA6H;AAApH,mHAAA,eAAe,OAAA;AACxB,6DAA2D;AAAlD,qHAAA,gBAAgB,OAAA;AACzB,2DAA8G;AAArG,mHAAA,eAAe,OAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface LintResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
violations: Violation[];
|
|
4
|
+
suggestions: FixSuggestion[];
|
|
5
|
+
}
|
|
6
|
+
export interface Violation {
|
|
7
|
+
rule: string;
|
|
8
|
+
severity: 'error' | 'warning';
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
message: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface FixSuggestion {
|
|
15
|
+
violation: string;
|
|
16
|
+
suggestion: string;
|
|
17
|
+
example?: string;
|
|
18
|
+
}
|
|
19
|
+
export type ComponentType = 'root' | 'child';
|
|
20
|
+
export declare class ComponentLinter {
|
|
21
|
+
private static childComponentRules;
|
|
22
|
+
private static rootComponentRules;
|
|
23
|
+
static lintComponent(code: string, componentType: ComponentType, componentName: string): Promise<LintResult>;
|
|
24
|
+
private static generateFixSuggestions;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=component-linter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-linter.d.ts","sourceRoot":"","sources":["../../src/lib/component-linter.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,WAAW,EAAE,aAAa,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC;AAO7C,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAyLhC;IAEF,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAgK/B;WAEkB,aAAa,CAC/B,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,UAAU,CAAC;IA4CtB,OAAO,CAAC,MAAM,CAAC,sBAAsB;CAuEtC"}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.ComponentLinter = void 0;
|
|
30
|
+
const parser = __importStar(require("@babel/parser"));
|
|
31
|
+
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
32
|
+
const t = __importStar(require("@babel/types"));
|
|
33
|
+
class ComponentLinter {
|
|
34
|
+
static async lintComponent(code, componentType, componentName) {
|
|
35
|
+
try {
|
|
36
|
+
const ast = parser.parse(code, {
|
|
37
|
+
sourceType: 'module',
|
|
38
|
+
plugins: ['jsx', 'typescript'],
|
|
39
|
+
errorRecovery: true
|
|
40
|
+
});
|
|
41
|
+
const rules = componentType === 'root'
|
|
42
|
+
? this.rootComponentRules
|
|
43
|
+
: this.childComponentRules;
|
|
44
|
+
const violations = [];
|
|
45
|
+
// Run each rule
|
|
46
|
+
for (const rule of rules) {
|
|
47
|
+
const ruleViolations = rule.test(ast, componentName);
|
|
48
|
+
violations.push(...ruleViolations);
|
|
49
|
+
}
|
|
50
|
+
// Generate fix suggestions
|
|
51
|
+
const suggestions = this.generateFixSuggestions(violations, componentType);
|
|
52
|
+
return {
|
|
53
|
+
success: violations.filter(v => v.severity === 'error').length === 0,
|
|
54
|
+
violations,
|
|
55
|
+
suggestions
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// If parsing fails, return a parse error
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
violations: [{
|
|
63
|
+
rule: 'parse-error',
|
|
64
|
+
severity: 'error',
|
|
65
|
+
line: 0,
|
|
66
|
+
column: 0,
|
|
67
|
+
message: `Failed to parse component: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
68
|
+
}],
|
|
69
|
+
suggestions: []
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
static generateFixSuggestions(violations, componentType) {
|
|
74
|
+
const suggestions = [];
|
|
75
|
+
for (const violation of violations) {
|
|
76
|
+
switch (violation.rule) {
|
|
77
|
+
case 'no-use-state':
|
|
78
|
+
suggestions.push({
|
|
79
|
+
violation: violation.rule,
|
|
80
|
+
suggestion: 'Remove useState and accept the value as a prop from the parent component',
|
|
81
|
+
example: 'Replace: const [value, setValue] = useState(initialValue);\nWith: Accept "value" and "onValueChange" as props'
|
|
82
|
+
});
|
|
83
|
+
break;
|
|
84
|
+
case 'no-data-fetching':
|
|
85
|
+
suggestions.push({
|
|
86
|
+
violation: violation.rule,
|
|
87
|
+
suggestion: 'Move data fetching to the root component and pass data as props',
|
|
88
|
+
example: 'Remove utilities.rv.RunView() calls and accept the data as props instead'
|
|
89
|
+
});
|
|
90
|
+
break;
|
|
91
|
+
case 'no-async-effects':
|
|
92
|
+
suggestions.push({
|
|
93
|
+
violation: violation.rule,
|
|
94
|
+
suggestion: 'Remove async operations from useEffect. Let the root component handle data loading',
|
|
95
|
+
example: 'Remove the useEffect with async operations and accept loading/error states as props'
|
|
96
|
+
});
|
|
97
|
+
break;
|
|
98
|
+
case 'must-use-update-state':
|
|
99
|
+
suggestions.push({
|
|
100
|
+
violation: violation.rule,
|
|
101
|
+
suggestion: 'Add an updateState function that syncs with callbacks',
|
|
102
|
+
example: `const updateState = (updates) => {
|
|
103
|
+
const newState = { ...state, ...updates };
|
|
104
|
+
setState(newState);
|
|
105
|
+
if (callbacks?.UpdateUserState) {
|
|
106
|
+
callbacks.UpdateUserState(newState);
|
|
107
|
+
}
|
|
108
|
+
};`
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
case 'sync-user-state':
|
|
112
|
+
suggestions.push({
|
|
113
|
+
violation: violation.rule,
|
|
114
|
+
suggestion: 'Call callbacks?.UpdateUserState in your updateState function',
|
|
115
|
+
example: 'Add: if (callbacks?.UpdateUserState) callbacks.UpdateUserState(newState);'
|
|
116
|
+
});
|
|
117
|
+
break;
|
|
118
|
+
case 'spread-user-state':
|
|
119
|
+
suggestions.push({
|
|
120
|
+
violation: violation.rule,
|
|
121
|
+
suggestion: 'Include ...userState in your initial state',
|
|
122
|
+
example: 'const [state, setState] = useState({ /* your fields */, ...userState });'
|
|
123
|
+
});
|
|
124
|
+
break;
|
|
125
|
+
case 'no-child-implementation':
|
|
126
|
+
suggestions.push({
|
|
127
|
+
violation: violation.rule,
|
|
128
|
+
suggestion: 'Remove child component implementations. Only the root component function should be in this file',
|
|
129
|
+
example: 'Move child component functions to separate generation requests'
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return suggestions;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
exports.ComponentLinter = ComponentLinter;
|
|
138
|
+
ComponentLinter.childComponentRules = [
|
|
139
|
+
// State Management Rules
|
|
140
|
+
{
|
|
141
|
+
name: 'no-use-state',
|
|
142
|
+
test: (ast, componentName) => {
|
|
143
|
+
const violations = [];
|
|
144
|
+
(0, traverse_1.default)(ast, {
|
|
145
|
+
CallExpression(path) {
|
|
146
|
+
const callee = path.node.callee;
|
|
147
|
+
// Check for React.useState or just useState
|
|
148
|
+
if ((t.isIdentifier(callee) && callee.name === 'useState') ||
|
|
149
|
+
(t.isMemberExpression(callee) &&
|
|
150
|
+
t.isIdentifier(callee.object) && callee.object.name === 'React' &&
|
|
151
|
+
t.isIdentifier(callee.property) && callee.property.name === 'useState')) {
|
|
152
|
+
violations.push({
|
|
153
|
+
rule: 'no-use-state',
|
|
154
|
+
severity: 'error',
|
|
155
|
+
line: path.node.loc?.start.line || 0,
|
|
156
|
+
column: path.node.loc?.start.column || 0,
|
|
157
|
+
message: `Child component "${componentName}" uses useState at line ${path.node.loc?.start.line}. Child components must be purely controlled - receive state via props instead.`,
|
|
158
|
+
code: path.toString()
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return violations;
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'no-use-reducer',
|
|
168
|
+
test: (ast, componentName) => {
|
|
169
|
+
const violations = [];
|
|
170
|
+
(0, traverse_1.default)(ast, {
|
|
171
|
+
CallExpression(path) {
|
|
172
|
+
const callee = path.node.callee;
|
|
173
|
+
if ((t.isIdentifier(callee) && callee.name === 'useReducer') ||
|
|
174
|
+
(t.isMemberExpression(callee) &&
|
|
175
|
+
t.isIdentifier(callee.object) && callee.object.name === 'React' &&
|
|
176
|
+
t.isIdentifier(callee.property) && callee.property.name === 'useReducer')) {
|
|
177
|
+
violations.push({
|
|
178
|
+
rule: 'no-use-reducer',
|
|
179
|
+
severity: 'error',
|
|
180
|
+
line: path.node.loc?.start.line || 0,
|
|
181
|
+
column: path.node.loc?.start.column || 0,
|
|
182
|
+
message: `Child component "${componentName}" uses useReducer at line ${path.node.loc?.start.line}. Child components must be purely controlled.`,
|
|
183
|
+
code: path.toString()
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return violations;
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'no-data-fetching',
|
|
193
|
+
test: (ast, componentName) => {
|
|
194
|
+
const violations = [];
|
|
195
|
+
(0, traverse_1.default)(ast, {
|
|
196
|
+
MemberExpression(path) {
|
|
197
|
+
const object = path.node.object;
|
|
198
|
+
const property = path.node.property;
|
|
199
|
+
// Check for utilities.rv.RunView or utilities.rv.RunQuery
|
|
200
|
+
if (t.isMemberExpression(object) &&
|
|
201
|
+
t.isIdentifier(object.object) && object.object.name === 'utilities' &&
|
|
202
|
+
t.isIdentifier(object.property) && object.property.name === 'rv' &&
|
|
203
|
+
t.isIdentifier(property) &&
|
|
204
|
+
(property.name === 'RunView' || property.name === 'RunQuery' || property.name === 'RunViews')) {
|
|
205
|
+
violations.push({
|
|
206
|
+
rule: 'no-data-fetching',
|
|
207
|
+
severity: 'error',
|
|
208
|
+
line: path.node.loc?.start.line || 0,
|
|
209
|
+
column: path.node.loc?.start.column || 0,
|
|
210
|
+
message: `Child component "${componentName}" fetches data at line ${path.node.loc?.start.line}. Only root components should load data.`,
|
|
211
|
+
code: path.toString()
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Check for utilities.md operations
|
|
215
|
+
if (t.isMemberExpression(object) &&
|
|
216
|
+
t.isIdentifier(object.object) && object.object.name === 'utilities' &&
|
|
217
|
+
t.isIdentifier(object.property) && object.property.name === 'md') {
|
|
218
|
+
violations.push({
|
|
219
|
+
rule: 'no-data-fetching',
|
|
220
|
+
severity: 'error',
|
|
221
|
+
line: path.node.loc?.start.line || 0,
|
|
222
|
+
column: path.node.loc?.start.column || 0,
|
|
223
|
+
message: `Child component "${componentName}" accesses entity operations at line ${path.node.loc?.start.line}. Only root components should manage entities.`,
|
|
224
|
+
code: path.toString()
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
return violations;
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'no-async-effects',
|
|
234
|
+
test: (ast, componentName) => {
|
|
235
|
+
const violations = [];
|
|
236
|
+
(0, traverse_1.default)(ast, {
|
|
237
|
+
CallExpression(path) {
|
|
238
|
+
const callee = path.node.callee;
|
|
239
|
+
// Check for useEffect
|
|
240
|
+
if ((t.isIdentifier(callee) && callee.name === 'useEffect') ||
|
|
241
|
+
(t.isMemberExpression(callee) &&
|
|
242
|
+
t.isIdentifier(callee.object) && callee.object.name === 'React' &&
|
|
243
|
+
t.isIdentifier(callee.property) && callee.property.name === 'useEffect')) {
|
|
244
|
+
// Check if the effect function contains async operations
|
|
245
|
+
const effectFn = path.node.arguments[0];
|
|
246
|
+
if (t.isArrowFunctionExpression(effectFn) || t.isFunctionExpression(effectFn)) {
|
|
247
|
+
let hasAsync = false;
|
|
248
|
+
// Check if the effect function itself is async
|
|
249
|
+
if (effectFn.async) {
|
|
250
|
+
hasAsync = true;
|
|
251
|
+
}
|
|
252
|
+
// Traverse the effect function body to look for async patterns
|
|
253
|
+
(0, traverse_1.default)(effectFn, {
|
|
254
|
+
CallExpression(innerPath) {
|
|
255
|
+
const innerCallee = innerPath.node.callee;
|
|
256
|
+
// Check for async patterns
|
|
257
|
+
if ((t.isIdentifier(innerCallee) && innerCallee.name === 'fetch') ||
|
|
258
|
+
(t.isMemberExpression(innerCallee) &&
|
|
259
|
+
t.isIdentifier(innerCallee.property) &&
|
|
260
|
+
(innerCallee.property.name === 'then' || innerCallee.property.name === 'catch'))) {
|
|
261
|
+
hasAsync = true;
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
AwaitExpression() {
|
|
265
|
+
hasAsync = true;
|
|
266
|
+
},
|
|
267
|
+
FunctionDeclaration(innerPath) {
|
|
268
|
+
if (innerPath.node.async)
|
|
269
|
+
hasAsync = true;
|
|
270
|
+
},
|
|
271
|
+
ArrowFunctionExpression(innerPath) {
|
|
272
|
+
if (innerPath.node.async)
|
|
273
|
+
hasAsync = true;
|
|
274
|
+
}
|
|
275
|
+
}, path.scope, path.state, path.parentPath);
|
|
276
|
+
if (hasAsync) {
|
|
277
|
+
violations.push({
|
|
278
|
+
rule: 'no-async-effects',
|
|
279
|
+
severity: 'error',
|
|
280
|
+
line: path.node.loc?.start.line || 0,
|
|
281
|
+
column: path.node.loc?.start.column || 0,
|
|
282
|
+
message: `Child component "${componentName}" has async operations in useEffect at line ${path.node.loc?.start.line}. Data should be loaded by the root component and passed as props.`,
|
|
283
|
+
code: path.toString().substring(0, 100) + '...'
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
return violations;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
];
|
|
294
|
+
ComponentLinter.rootComponentRules = [
|
|
295
|
+
{
|
|
296
|
+
name: 'must-use-update-state',
|
|
297
|
+
test: (ast, componentName) => {
|
|
298
|
+
const violations = [];
|
|
299
|
+
let hasUpdateState = false;
|
|
300
|
+
(0, traverse_1.default)(ast, {
|
|
301
|
+
VariableDeclarator(path) {
|
|
302
|
+
if (t.isIdentifier(path.node.id) && path.node.id.name === 'updateState') {
|
|
303
|
+
hasUpdateState = true;
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
FunctionDeclaration(path) {
|
|
307
|
+
if (path.node.id && path.node.id.name === 'updateState') {
|
|
308
|
+
hasUpdateState = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
if (!hasUpdateState) {
|
|
313
|
+
violations.push({
|
|
314
|
+
rule: 'must-use-update-state',
|
|
315
|
+
severity: 'error',
|
|
316
|
+
line: 1,
|
|
317
|
+
column: 0,
|
|
318
|
+
message: `Root component "${componentName}" must have an updateState function that syncs with callbacks.UpdateUserState.`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
return violations;
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'sync-user-state',
|
|
326
|
+
test: (ast, componentName) => {
|
|
327
|
+
const violations = [];
|
|
328
|
+
let hasUserStateSync = false;
|
|
329
|
+
(0, traverse_1.default)(ast, {
|
|
330
|
+
CallExpression(path) {
|
|
331
|
+
const callee = path.node.callee;
|
|
332
|
+
// Look for callbacks?.UpdateUserState or callbacks.UpdateUserState
|
|
333
|
+
if (t.isMemberExpression(callee)) {
|
|
334
|
+
// Check for callbacks.UpdateUserState
|
|
335
|
+
if (t.isIdentifier(callee.object) && callee.object.name === 'callbacks' &&
|
|
336
|
+
t.isIdentifier(callee.property) && callee.property.name === 'UpdateUserState') {
|
|
337
|
+
hasUserStateSync = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Check for callbacks?.UpdateUserState (optional chaining)
|
|
341
|
+
else if (t.isOptionalMemberExpression(callee)) {
|
|
342
|
+
if (t.isIdentifier(callee.object) && callee.object.name === 'callbacks' &&
|
|
343
|
+
t.isIdentifier(callee.property) && callee.property.name === 'UpdateUserState') {
|
|
344
|
+
hasUserStateSync = true;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
if (!hasUserStateSync) {
|
|
350
|
+
violations.push({
|
|
351
|
+
rule: 'sync-user-state',
|
|
352
|
+
severity: 'error',
|
|
353
|
+
line: 1,
|
|
354
|
+
column: 0,
|
|
355
|
+
message: `Root component "${componentName}" must call callbacks?.UpdateUserState to sync state changes.`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
return violations;
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: 'spread-user-state',
|
|
363
|
+
test: (ast, componentName) => {
|
|
364
|
+
const violations = [];
|
|
365
|
+
let hasUserStateSpread = false;
|
|
366
|
+
(0, traverse_1.default)(ast, {
|
|
367
|
+
CallExpression(path) {
|
|
368
|
+
const callee = path.node.callee;
|
|
369
|
+
// Check for useState call
|
|
370
|
+
if ((t.isIdentifier(callee) && callee.name === 'useState') ||
|
|
371
|
+
(t.isMemberExpression(callee) &&
|
|
372
|
+
t.isIdentifier(callee.object) && callee.object.name === 'React' &&
|
|
373
|
+
t.isIdentifier(callee.property) && callee.property.name === 'useState')) {
|
|
374
|
+
// Check if initial state includes ...userState
|
|
375
|
+
const initialState = path.node.arguments[0];
|
|
376
|
+
if (t.isObjectExpression(initialState)) {
|
|
377
|
+
for (const prop of initialState.properties) {
|
|
378
|
+
if (t.isSpreadElement(prop) && t.isIdentifier(prop.argument) && prop.argument.name === 'userState') {
|
|
379
|
+
hasUserStateSpread = true;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
if (!hasUserStateSpread) {
|
|
388
|
+
violations.push({
|
|
389
|
+
rule: 'spread-user-state',
|
|
390
|
+
severity: 'error',
|
|
391
|
+
line: 1,
|
|
392
|
+
column: 0,
|
|
393
|
+
message: `Root component "${componentName}" must spread ...userState in initial state to preserve user preferences.`,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
return violations;
|
|
397
|
+
}
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
name: 'no-child-implementation',
|
|
401
|
+
test: (ast, componentName) => {
|
|
402
|
+
const violations = [];
|
|
403
|
+
const rootFunctionName = componentName;
|
|
404
|
+
const declaredFunctions = [];
|
|
405
|
+
// First pass: collect all function declarations
|
|
406
|
+
(0, traverse_1.default)(ast, {
|
|
407
|
+
FunctionDeclaration(path) {
|
|
408
|
+
if (path.node.id) {
|
|
409
|
+
declaredFunctions.push(path.node.id.name);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
// If there are multiple function declarations and they look like components
|
|
414
|
+
// (start with capital letter), it's likely implementing children
|
|
415
|
+
const componentFunctions = declaredFunctions.filter(name => name !== rootFunctionName && /^[A-Z]/.test(name));
|
|
416
|
+
if (componentFunctions.length > 0) {
|
|
417
|
+
violations.push({
|
|
418
|
+
rule: 'no-child-implementation',
|
|
419
|
+
severity: 'error',
|
|
420
|
+
line: 1,
|
|
421
|
+
column: 0,
|
|
422
|
+
message: `Root component file contains child component implementations: ${componentFunctions.join(', ')}. Root should only reference child components, not implement them.`,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return violations;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
];
|
|
429
|
+
//# sourceMappingURL=component-linter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-linter.js","sourceRoot":"","sources":["../../src/lib/component-linter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { BrowserManager } from './browser-context';
|
|
3
|
+
import { ComponentType, FixSuggestion } from './component-linter';
|
|
3
4
|
export interface ComponentExecutionOptions {
|
|
4
5
|
componentSpec: ComponentSpec;
|
|
5
6
|
props?: Record<string, any>;
|
|
@@ -18,20 +19,83 @@ export interface ComponentExecutionResult {
|
|
|
18
19
|
success: boolean;
|
|
19
20
|
html: string;
|
|
20
21
|
errors: string[];
|
|
22
|
+
warnings: string[];
|
|
23
|
+
criticalWarnings: string[];
|
|
21
24
|
console: {
|
|
22
25
|
type: string;
|
|
23
26
|
text: string;
|
|
24
27
|
}[];
|
|
25
28
|
screenshot?: Buffer;
|
|
26
29
|
executionTime: number;
|
|
30
|
+
renderCount?: number;
|
|
31
|
+
lintViolations?: string[];
|
|
32
|
+
fixSuggestions?: FixSuggestion[];
|
|
27
33
|
}
|
|
28
34
|
export declare class ComponentRunner {
|
|
29
35
|
private browserManager;
|
|
30
36
|
private compiler;
|
|
31
37
|
private registry;
|
|
32
38
|
private runtimeContext;
|
|
39
|
+
private static readonly CRITICAL_WARNING_PATTERNS;
|
|
40
|
+
private static readonly MAX_RENDER_COUNT;
|
|
33
41
|
constructor(browserManager: BrowserManager);
|
|
42
|
+
/**
|
|
43
|
+
* Lint component code before execution
|
|
44
|
+
*/
|
|
45
|
+
lintComponent(componentCode: string, componentName: string, componentType: ComponentType): Promise<{
|
|
46
|
+
violations: string[];
|
|
47
|
+
suggestions: FixSuggestion[];
|
|
48
|
+
hasErrors: boolean;
|
|
49
|
+
}>;
|
|
34
50
|
executeComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
|
|
35
51
|
private createHTMLTemplate;
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a console message is a warning
|
|
54
|
+
*/
|
|
55
|
+
private isWarning;
|
|
56
|
+
/**
|
|
57
|
+
* Checks if a warning is critical and should fail the test
|
|
58
|
+
*/
|
|
59
|
+
private isCriticalWarning;
|
|
60
|
+
/**
|
|
61
|
+
* Sets up console logging with warning detection
|
|
62
|
+
*/
|
|
63
|
+
private setupConsoleLogging;
|
|
64
|
+
/**
|
|
65
|
+
* Sets up error handling for the page
|
|
66
|
+
*/
|
|
67
|
+
private setupErrorHandling;
|
|
68
|
+
/**
|
|
69
|
+
* Injects render tracking code into the page
|
|
70
|
+
*/
|
|
71
|
+
private injectRenderTracking;
|
|
72
|
+
/**
|
|
73
|
+
* Waits for component to render and checks for timeouts
|
|
74
|
+
*/
|
|
75
|
+
private waitForRender;
|
|
76
|
+
/**
|
|
77
|
+
* Gets the render count from the page
|
|
78
|
+
*/
|
|
79
|
+
private getRenderCount;
|
|
80
|
+
/**
|
|
81
|
+
* Determines if the component execution was successful
|
|
82
|
+
*/
|
|
83
|
+
private determineSuccess;
|
|
84
|
+
/**
|
|
85
|
+
* Expose MemberJunction utilities to the browser context
|
|
86
|
+
*/
|
|
87
|
+
private exposeMJUtilities;
|
|
88
|
+
/**
|
|
89
|
+
* Analyze component errors to identify failed components
|
|
90
|
+
* @param errors Array of error messages
|
|
91
|
+
* @returns Array of component names that failed
|
|
92
|
+
*/
|
|
93
|
+
static analyzeComponentErrors(errors: string[]): string[];
|
|
94
|
+
/**
|
|
95
|
+
* Get detailed error analysis
|
|
96
|
+
* @param errors Array of error messages
|
|
97
|
+
* @returns Detailed failure information
|
|
98
|
+
*/
|
|
99
|
+
static getDetailedErrorAnalysis(errors: string[]): import("@memberjunction/react-runtime").FailedComponentInfo[];
|
|
36
100
|
}
|
|
37
101
|
//# sourceMappingURL=component-runner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAanD,OAAO,EAAmB,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnF,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;CAChE;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,aAAa,EAAE,CAAC;IAClC,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,aAAa,EAAE,CAAC;CAClC;AAED,qBAAa,eAAe;IAoBd,OAAO,CAAC,cAAc;IAnBlC,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,QAAQ,CAAoB;IACpC,OAAO,CAAC,cAAc,CAAiB;IAGvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAS/C;IAGF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;gBAE5B,cAAc,EAAE,cAAc;IAQlD;;OAEG;IACG,aAAa,CACjB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,MAAM,EACrB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,EAAE,aAAa,EAAE,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAiBhF,gBAAgB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,wBAAwB,CAAC;IAwE7F,OAAO,CAAC,kBAAkB;IA8a1B;;OAEG;IACH,OAAO,CAAC,SAAS;IAIjB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAM1B;;OAEG;YACW,oBAAoB;IAMlC;;OAEG;YACW,aAAa;IA0B3B;;OAEG;YACW,cAAc;IAI5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAoBxB;;OAEG;YACW,iBAAiB;IAgE/B;;;;OAIG;IACH,MAAM,CAAC,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE;IAIzD;;;;OAIG;IACH,MAAM,CAAC,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE;CAGjD"}
|