@memberjunction/react-test-harness 6.2.0-edge.0 → 6.2.0-edge.1
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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/assertion-helpers.d.ts +49 -0
- package/dist/lib/assertion-helpers.d.ts.map +1 -1
- package/dist/lib/assertion-helpers.js +85 -25
- package/dist/lib/assertion-helpers.js.map +1 -1
- package/dist/lib/browser-context.d.ts +22 -0
- package/dist/lib/browser-context.d.ts.map +1 -1
- package/dist/lib/browser-context.js +58 -18
- package/dist/lib/browser-context.js.map +1 -1
- package/dist/lib/component-runner.d.ts +192 -0
- package/dist/lib/component-runner.d.ts.map +1 -1
- package/dist/lib/component-runner.js +238 -32
- package/dist/lib/component-runner.js.map +1 -1
- package/dist/lib/linter-test-tool.d.ts +12 -2
- package/dist/lib/linter-test-tool.d.ts.map +1 -1
- package/dist/lib/linter-test-tool.js +39 -19
- package/dist/lib/linter-test-tool.js.map +1 -1
- package/dist/lib/test-harness.d.ts +50 -0
- package/dist/lib/test-harness.d.ts.map +1 -1
- package/dist/lib/test-harness.js +68 -16
- package/dist/lib/test-harness.js.map +1 -1
- package/package.json +15 -15
|
@@ -2,6 +2,175 @@ import { BrowserManager } from './browser-context.js';
|
|
|
2
2
|
import type { UserInfo } from '@memberjunction/core';
|
|
3
3
|
import { Violation, type LinterOptions } from '@memberjunction/react-linter';
|
|
4
4
|
import { ComponentSpec, SimpleAITools } from '@memberjunction/interactive-component-types';
|
|
5
|
+
/**
|
|
6
|
+
* Row ceilings applied to the data bridges the component under test calls through
|
|
7
|
+
* (`__mjRunView`, `__mjRunViews`, `__mjRunQuery`).
|
|
8
|
+
*
|
|
9
|
+
* A test run only needs enough rows to render and be looked at, so the harness does not
|
|
10
|
+
* honour an unbounded request. `MaxRows` is applied at the SQL level by the provider, so
|
|
11
|
+
* the surplus is never produced rather than fetched and discarded — which matters because
|
|
12
|
+
* a bridge result is serialized in Node, shipped over CDP and rehydrated in Chromium,
|
|
13
|
+
* leaving several resident copies of the same data.
|
|
14
|
+
*/
|
|
15
|
+
export interface DataCapOptions {
|
|
16
|
+
/** Ceiling for `RunView` / `RunViews`. @default 1000 */
|
|
17
|
+
runViewMaxRows?: number;
|
|
18
|
+
/** Ceiling for `RunQuery`. @default 1000 */
|
|
19
|
+
runQueryMaxRows?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One data call made by the component under test, recorded by the harness bridges.
|
|
23
|
+
*
|
|
24
|
+
* Lets a caller tell apart failure modes that a screenshot alone cannot: a component
|
|
25
|
+
* showing no data because its query returned zero rows is an upstream data or parameter
|
|
26
|
+
* problem, while one showing no data after rows *were* returned is a render or binding
|
|
27
|
+
* bug.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Machine-readable identity of what a data call resolved to.
|
|
31
|
+
*
|
|
32
|
+
* Field names mirror `ComponentSpec.dataRequirements` (`ComponentQueryDataRequirement.name`
|
|
33
|
+
* / `.categoryPath`, `ComponentEntityDataRequirement.name`) so a caller can correlate what
|
|
34
|
+
* a component declared against what it actually called, without parsing a display string.
|
|
35
|
+
*
|
|
36
|
+
* It mirrors that vocabulary rather than referencing the spec, because a record must be
|
|
37
|
+
* able to describe a call that appears nowhere in `dataRequirements`.
|
|
38
|
+
*/
|
|
39
|
+
export interface DataAccessIdentity {
|
|
40
|
+
/** Entity name — corresponds to `ComponentEntityDataRequirement.name`. */
|
|
41
|
+
entityName?: string;
|
|
42
|
+
/** Query name — corresponds to `ComponentQueryDataRequirement.name`. Not unique alone. */
|
|
43
|
+
queryName?: string;
|
|
44
|
+
/** Category path that disambiguates `queryName` — `ComponentQueryDataRequirement.categoryPath`. */
|
|
45
|
+
categoryPath?: string;
|
|
46
|
+
/** Present when the call named a query by ID, which takes precedence over name. */
|
|
47
|
+
queryId?: string;
|
|
48
|
+
/** Present when the call named a saved view by ID. */
|
|
49
|
+
viewId?: string;
|
|
50
|
+
/** Present when the call named a saved view by name. */
|
|
51
|
+
viewName?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface DataAccessRecord {
|
|
54
|
+
kind: 'RunView' | 'RunViews' | 'RunQuery';
|
|
55
|
+
/**
|
|
56
|
+
* Human/LLM-readable rendering of {@link identity} — a category-qualified query path,
|
|
57
|
+
* or the view/entity a `RunView` resolved to. For display and prompts; use `identity`
|
|
58
|
+
* for matching.
|
|
59
|
+
*/
|
|
60
|
+
target: string;
|
|
61
|
+
/** Structured identity, for correlating against declared data requirements. */
|
|
62
|
+
identity: DataAccessIdentity;
|
|
63
|
+
/** Rows actually handed to the component. */
|
|
64
|
+
rowsReturned: number;
|
|
65
|
+
/**
|
|
66
|
+
* Rows the query would have produced without the harness ceiling, when the provider
|
|
67
|
+
* reports it (`TotalRowCount`). Equal to `rowsReturned` when nothing was capped.
|
|
68
|
+
*/
|
|
69
|
+
totalRowCount?: number;
|
|
70
|
+
/** True when the harness ceiling reduced the result below what the source held. */
|
|
71
|
+
capped: boolean;
|
|
72
|
+
/** Ceiling actually applied to this call. */
|
|
73
|
+
appliedMaxRows?: number;
|
|
74
|
+
/** `MaxRows` the component asked for, when it asked for one. */
|
|
75
|
+
requestedMaxRows?: number;
|
|
76
|
+
/** Wall-clock duration of the call, in milliseconds. */
|
|
77
|
+
durationMs: number;
|
|
78
|
+
/** Present when the call failed; the bridge returns an empty result set in that case. */
|
|
79
|
+
error?: string;
|
|
80
|
+
}
|
|
81
|
+
/** Default row ceiling for `RunView` / `RunViews`. @see DataCapOptions */
|
|
82
|
+
export declare const DEFAULT_RUN_VIEW_MAX_ROWS = 1000;
|
|
83
|
+
/** Default row ceiling for `RunQuery`. @see DataCapOptions */
|
|
84
|
+
export declare const DEFAULT_RUN_QUERY_MAX_ROWS = 1000;
|
|
85
|
+
/**
|
|
86
|
+
* Resolves the `MaxRows` to send for one data call.
|
|
87
|
+
*
|
|
88
|
+
* Clamps downward only: a component asking for 50 rows still gets 50, while one asking for
|
|
89
|
+
* nothing or for more than the ceiling gets the ceiling. A nonsensical request (zero,
|
|
90
|
+
* negative, non-numeric) is treated as absent.
|
|
91
|
+
*
|
|
92
|
+
* Returns the requested value alongside the applied one, so a capped call can report what
|
|
93
|
+
* the component wanted.
|
|
94
|
+
*
|
|
95
|
+
* Pure (no I/O) so it can be unit-tested without launching a browser.
|
|
96
|
+
*/
|
|
97
|
+
export declare function ResolveDataCapMaxRows(requested: number | undefined, ceiling: number): {
|
|
98
|
+
applied: number;
|
|
99
|
+
requested?: number;
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Builds a {@link DataAccessRecord} from a completed provider call.
|
|
103
|
+
*
|
|
104
|
+
* `TotalRowCount` is the provider's count of rows the query would have produced without
|
|
105
|
+
* paging, making it the denominator for "capped at N of M". Providers that omit it, or
|
|
106
|
+
* leave it equal to `RowCount`, yield `capped: false` rather than a guess.
|
|
107
|
+
*
|
|
108
|
+
* Pure (no I/O) so it can be unit-tested without launching a browser.
|
|
109
|
+
*/
|
|
110
|
+
export declare function BuildDataAccessRecord(kind: DataAccessRecord['kind'], resolved: {
|
|
111
|
+
target: string;
|
|
112
|
+
identity: DataAccessIdentity;
|
|
113
|
+
}, result: {
|
|
114
|
+
Results?: any[];
|
|
115
|
+
TotalRowCount?: number;
|
|
116
|
+
} | undefined, maxRows: {
|
|
117
|
+
applied: number;
|
|
118
|
+
requested?: number;
|
|
119
|
+
}, durationMs: number): DataAccessRecord;
|
|
120
|
+
/** Debug-log suffix naming the cap, e.g. ` (capped at 1000 of 205802)`. Empty when uncapped. */
|
|
121
|
+
export declare function DescribeDataCap(record: DataAccessRecord): string;
|
|
122
|
+
/**
|
|
123
|
+
* Names the query a `RunQuery` call resolved to, as both a display string and a structured
|
|
124
|
+
* {@link DataAccessIdentity}.
|
|
125
|
+
*
|
|
126
|
+
* A query name is not unique on its own — name and category path identify a query together
|
|
127
|
+
* — so the display form is category-qualified.
|
|
128
|
+
*
|
|
129
|
+
* Precedence mirrors how `RunQuery` itself resolves: `QueryID` wins and `QueryName` is
|
|
130
|
+
* ignored when both are supplied, so the ID is reported as the identity and the supplied
|
|
131
|
+
* name is kept only as a label. `identity` retains every part the caller passed, so a
|
|
132
|
+
* correlation by name and path still works when an ID drove the resolution.
|
|
133
|
+
*/
|
|
134
|
+
export declare function ResolveQueryTarget(params: {
|
|
135
|
+
QueryID?: string;
|
|
136
|
+
QueryName?: string;
|
|
137
|
+
CategoryPath?: string;
|
|
138
|
+
CategoryID?: string;
|
|
139
|
+
}): {
|
|
140
|
+
target: string;
|
|
141
|
+
identity: DataAccessIdentity;
|
|
142
|
+
};
|
|
143
|
+
/** Display-only form of {@link ResolveQueryTarget}. */
|
|
144
|
+
export declare function DescribeQueryTarget(params: {
|
|
145
|
+
QueryID?: string;
|
|
146
|
+
QueryName?: string;
|
|
147
|
+
CategoryPath?: string;
|
|
148
|
+
CategoryID?: string;
|
|
149
|
+
}): string;
|
|
150
|
+
/**
|
|
151
|
+
* Names the view a `RunView` call resolved to, as both a display string and a structured
|
|
152
|
+
* {@link DataAccessIdentity}.
|
|
153
|
+
*
|
|
154
|
+
* Follows `RunViewParams`' own precedence — `ViewEntity` → `ViewID` → `ViewName` →
|
|
155
|
+
* `EntityName` — where each earlier field causes the later ones to be ignored. Reporting
|
|
156
|
+
* `EntityName` regardless would name something the call did not resolve by.
|
|
157
|
+
*/
|
|
158
|
+
export declare function ResolveViewTarget(params: {
|
|
159
|
+
ViewEntity?: unknown;
|
|
160
|
+
ViewID?: string;
|
|
161
|
+
ViewName?: string;
|
|
162
|
+
EntityName?: string;
|
|
163
|
+
}): {
|
|
164
|
+
target: string;
|
|
165
|
+
identity: DataAccessIdentity;
|
|
166
|
+
};
|
|
167
|
+
/** Display-only form of {@link ResolveViewTarget}. */
|
|
168
|
+
export declare function DescribeViewTarget(params: {
|
|
169
|
+
ViewEntity?: unknown;
|
|
170
|
+
ViewID?: string;
|
|
171
|
+
ViewName?: string;
|
|
172
|
+
EntityName?: string;
|
|
173
|
+
}): string;
|
|
5
174
|
/**
|
|
6
175
|
* Browser-execution options for {@link ComponentRunner}. Extends {@link LinterOptions}
|
|
7
176
|
* (which carries `componentSpec`, `contextUser`, `entityMetadata`, `utilities` —
|
|
@@ -94,6 +263,15 @@ export interface ComponentExecutionOptions extends LinterOptions {
|
|
|
94
263
|
* @default 15000
|
|
95
264
|
*/
|
|
96
265
|
dataIdleTimeoutMs?: number;
|
|
266
|
+
/**
|
|
267
|
+
* Row ceilings for the data bridges. Omitted fields fall back to
|
|
268
|
+
* {@link DEFAULT_RUN_VIEW_MAX_ROWS} /
|
|
269
|
+
* {@link DEFAULT_RUN_QUERY_MAX_ROWS} (1000).
|
|
270
|
+
*
|
|
271
|
+
* A component asking for fewer rows than the ceiling keeps its own smaller number;
|
|
272
|
+
* the ceiling only ever clamps downward.
|
|
273
|
+
*/
|
|
274
|
+
dataCaps?: DataCapOptions;
|
|
97
275
|
}
|
|
98
276
|
export interface ComponentExecutionResult {
|
|
99
277
|
success: boolean;
|
|
@@ -134,6 +312,13 @@ export interface ComponentExecutionResult {
|
|
|
134
312
|
* This field gives you the "did the code work?" answer directly.
|
|
135
313
|
*/
|
|
136
314
|
codeExecutionSuccess?: boolean;
|
|
315
|
+
/**
|
|
316
|
+
* Every data call the component made, in the order the bridges served them.
|
|
317
|
+
*
|
|
318
|
+
* Empty when the component fetched nothing — itself a useful signal, and distinct from
|
|
319
|
+
* a component whose calls all returned zero rows.
|
|
320
|
+
*/
|
|
321
|
+
dataAccess?: DataAccessRecord[];
|
|
137
322
|
}
|
|
138
323
|
/**
|
|
139
324
|
* ComponentRunner that uses the actual React runtime via Playwright UMD bundle
|
|
@@ -157,10 +342,17 @@ export declare class ComponentRunner {
|
|
|
157
342
|
/**
|
|
158
343
|
* Lint component code before execution
|
|
159
344
|
*/
|
|
345
|
+
LintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
|
|
346
|
+
violations: Violation[];
|
|
347
|
+
hasErrors: boolean;
|
|
348
|
+
}>;
|
|
349
|
+
/** @deprecated Use {@link LintComponent}. */
|
|
160
350
|
lintComponent(componentCode: string, componentName: string, componentSpec?: any, isRootComponent?: boolean, contextUser?: UserInfo, options?: any): Promise<{
|
|
161
351
|
violations: Violation[];
|
|
162
352
|
hasErrors: boolean;
|
|
163
353
|
}>;
|
|
354
|
+
ExecuteComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
|
|
355
|
+
/** @deprecated Use {@link ExecuteComponent}. */
|
|
164
356
|
executeComponent(options: ComponentExecutionOptions): Promise<ComponentExecutionResult>;
|
|
165
357
|
/**
|
|
166
358
|
* Load core runtime libraries into the page (React, ReactDOM, Babel, MJRuntime)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMnD,OAAO,KAAK,EAAiC,QAAQ,EAAyD,MAAM,sBAAsB,CAAC;AAC3I,OAAO,EAAmB,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC9F,OAAO,EACL,aAAa,EAEb,aAAa,EAOd,MAAM,6CAA6C,CAAC;AAmCrD;;;;;;;GAOG;AACH,MAAM,WAAW,yBAA0B,SAAQ,aAAa;IAC9D,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,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,aAAa,CAAC;IAC/D,WAAW,EAAE,QAAQ,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE5C;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,wBAAwB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7E;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"component-runner.d.ts","sourceRoot":"","sources":["../../src/lib/component-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,141 @@ async function preResolveComponentSpec(spec, contextUser) {
|
|
|
28
28
|
// For other types, return as-is
|
|
29
29
|
return spec;
|
|
30
30
|
}
|
|
31
|
+
/** Default row ceiling for `RunView` / `RunViews`. @see DataCapOptions */
|
|
32
|
+
export const DEFAULT_RUN_VIEW_MAX_ROWS = 1000;
|
|
33
|
+
/** Default row ceiling for `RunQuery`. @see DataCapOptions */
|
|
34
|
+
export const DEFAULT_RUN_QUERY_MAX_ROWS = 1000;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the `MaxRows` to send for one data call.
|
|
37
|
+
*
|
|
38
|
+
* Clamps downward only: a component asking for 50 rows still gets 50, while one asking for
|
|
39
|
+
* nothing or for more than the ceiling gets the ceiling. A nonsensical request (zero,
|
|
40
|
+
* negative, non-numeric) is treated as absent.
|
|
41
|
+
*
|
|
42
|
+
* Returns the requested value alongside the applied one, so a capped call can report what
|
|
43
|
+
* the component wanted.
|
|
44
|
+
*
|
|
45
|
+
* Pure (no I/O) so it can be unit-tested without launching a browser.
|
|
46
|
+
*/
|
|
47
|
+
export function ResolveDataCapMaxRows(requested, ceiling) {
|
|
48
|
+
const validRequest = typeof requested === 'number' && Number.isFinite(requested) && requested > 0 ? requested : undefined;
|
|
49
|
+
return {
|
|
50
|
+
applied: validRequest === undefined ? ceiling : Math.min(validRequest, ceiling),
|
|
51
|
+
requested: validRequest,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds a {@link DataAccessRecord} from a completed provider call.
|
|
56
|
+
*
|
|
57
|
+
* `TotalRowCount` is the provider's count of rows the query would have produced without
|
|
58
|
+
* paging, making it the denominator for "capped at N of M". Providers that omit it, or
|
|
59
|
+
* leave it equal to `RowCount`, yield `capped: false` rather than a guess.
|
|
60
|
+
*
|
|
61
|
+
* Pure (no I/O) so it can be unit-tested without launching a browser.
|
|
62
|
+
*/
|
|
63
|
+
export function BuildDataAccessRecord(kind, resolved, result, maxRows, durationMs) {
|
|
64
|
+
const rowsReturned = result?.Results?.length ?? 0;
|
|
65
|
+
const total = typeof result?.TotalRowCount === 'number' ? result.TotalRowCount : undefined;
|
|
66
|
+
return {
|
|
67
|
+
kind,
|
|
68
|
+
target: resolved.target,
|
|
69
|
+
identity: resolved.identity,
|
|
70
|
+
rowsReturned,
|
|
71
|
+
totalRowCount: total,
|
|
72
|
+
capped: total !== undefined && total > rowsReturned,
|
|
73
|
+
appliedMaxRows: maxRows.applied,
|
|
74
|
+
requestedMaxRows: maxRows.requested,
|
|
75
|
+
durationMs,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Debug-log suffix naming the cap, e.g. ` (capped at 1000 of 205802)`. Empty when uncapped. */
|
|
79
|
+
export function DescribeDataCap(record) {
|
|
80
|
+
return record.capped ? ` (capped at ${record.rowsReturned} of ${record.totalRowCount})` : '';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Normalizes a category path and appends a leaf name, e.g. (`/MJ/AI/Agents/`, `Foo`) →
|
|
84
|
+
* `/MJ/AI/Agents/Foo`. Tolerates missing or doubled slashes on either side.
|
|
85
|
+
*/
|
|
86
|
+
function joinCategoryPath(categoryPath, name) {
|
|
87
|
+
const trimmed = categoryPath.replace(/^\/+|\/+$/g, '');
|
|
88
|
+
return trimmed.length === 0 ? `/${name}` : `/${trimmed}/${name}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Names the query a `RunQuery` call resolved to, as both a display string and a structured
|
|
92
|
+
* {@link DataAccessIdentity}.
|
|
93
|
+
*
|
|
94
|
+
* A query name is not unique on its own — name and category path identify a query together
|
|
95
|
+
* — so the display form is category-qualified.
|
|
96
|
+
*
|
|
97
|
+
* Precedence mirrors how `RunQuery` itself resolves: `QueryID` wins and `QueryName` is
|
|
98
|
+
* ignored when both are supplied, so the ID is reported as the identity and the supplied
|
|
99
|
+
* name is kept only as a label. `identity` retains every part the caller passed, so a
|
|
100
|
+
* correlation by name and path still works when an ID drove the resolution.
|
|
101
|
+
*/
|
|
102
|
+
export function ResolveQueryTarget(params) {
|
|
103
|
+
// `identity` records what the caller supplied, so a correlation against declared
|
|
104
|
+
// requirements can match on name+categoryPath even when an ID drove the resolution.
|
|
105
|
+
const identity = {};
|
|
106
|
+
if (params.QueryID)
|
|
107
|
+
identity.queryId = params.QueryID;
|
|
108
|
+
if (params.QueryName)
|
|
109
|
+
identity.queryName = params.QueryName;
|
|
110
|
+
if (params.CategoryPath)
|
|
111
|
+
identity.categoryPath = params.CategoryPath;
|
|
112
|
+
if (params.QueryID) {
|
|
113
|
+
return {
|
|
114
|
+
target: params.QueryName
|
|
115
|
+
? `${params.QueryName} (resolved by QueryID ${params.QueryID})`
|
|
116
|
+
: `QueryID ${params.QueryID}`,
|
|
117
|
+
identity,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (params.QueryName) {
|
|
121
|
+
if (params.CategoryPath) {
|
|
122
|
+
return { target: joinCategoryPath(params.CategoryPath, params.QueryName), identity };
|
|
123
|
+
}
|
|
124
|
+
if (params.CategoryID) {
|
|
125
|
+
return { target: `${params.QueryName} (CategoryID ${params.CategoryID})`, identity };
|
|
126
|
+
}
|
|
127
|
+
// Neither qualifier given: the name is all the caller supplied, so it is all we can
|
|
128
|
+
// report — flagged, because this is the ambiguous case rather than a resolved identity.
|
|
129
|
+
return { target: `${params.QueryName} (uncategorized)`, identity };
|
|
130
|
+
}
|
|
131
|
+
return { target: 'unknown', identity };
|
|
132
|
+
}
|
|
133
|
+
/** Display-only form of {@link ResolveQueryTarget}. */
|
|
134
|
+
export function DescribeQueryTarget(params) {
|
|
135
|
+
return ResolveQueryTarget(params).target;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Names the view a `RunView` call resolved to, as both a display string and a structured
|
|
139
|
+
* {@link DataAccessIdentity}.
|
|
140
|
+
*
|
|
141
|
+
* Follows `RunViewParams`' own precedence — `ViewEntity` → `ViewID` → `ViewName` →
|
|
142
|
+
* `EntityName` — where each earlier field causes the later ones to be ignored. Reporting
|
|
143
|
+
* `EntityName` regardless would name something the call did not resolve by.
|
|
144
|
+
*/
|
|
145
|
+
export function ResolveViewTarget(params) {
|
|
146
|
+
const identity = {};
|
|
147
|
+
if (params.EntityName)
|
|
148
|
+
identity.entityName = params.EntityName;
|
|
149
|
+
if (params.ViewID)
|
|
150
|
+
identity.viewId = params.ViewID;
|
|
151
|
+
if (params.ViewName)
|
|
152
|
+
identity.viewName = params.ViewName;
|
|
153
|
+
const entitySuffix = params.EntityName ? ` on ${params.EntityName}` : '';
|
|
154
|
+
if (params.ViewEntity)
|
|
155
|
+
return { target: `saved view via ViewEntity${entitySuffix}`, identity };
|
|
156
|
+
if (params.ViewID)
|
|
157
|
+
return { target: `ViewID ${params.ViewID}${entitySuffix}`, identity };
|
|
158
|
+
if (params.ViewName)
|
|
159
|
+
return { target: `view "${params.ViewName}"${entitySuffix}`, identity };
|
|
160
|
+
return { target: params.EntityName || 'unknown', identity };
|
|
161
|
+
}
|
|
162
|
+
/** Display-only form of {@link ResolveViewTarget}. */
|
|
163
|
+
export function DescribeViewTarget(params) {
|
|
164
|
+
return ResolveViewTarget(params).target;
|
|
165
|
+
}
|
|
31
166
|
/**
|
|
32
167
|
* ComponentRunner that uses the actual React runtime via Playwright UMD bundle
|
|
33
168
|
*/
|
|
@@ -94,7 +229,7 @@ export class ComponentRunner {
|
|
|
94
229
|
/**
|
|
95
230
|
* Lint component code before execution
|
|
96
231
|
*/
|
|
97
|
-
async
|
|
232
|
+
async LintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
|
|
98
233
|
const lintResult = await ComponentLinter.lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, false, // debugMode
|
|
99
234
|
options);
|
|
100
235
|
const hasErrors = lintResult.violations.some(v => v.severity === 'critical' || v.severity === 'high');
|
|
@@ -103,12 +238,17 @@ export class ComponentRunner {
|
|
|
103
238
|
hasErrors
|
|
104
239
|
};
|
|
105
240
|
}
|
|
106
|
-
|
|
241
|
+
/** @deprecated Use {@link LintComponent}. */
|
|
242
|
+
async lintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options) {
|
|
243
|
+
return this.LintComponent(componentCode, componentName, componentSpec, isRootComponent, contextUser, options);
|
|
244
|
+
}
|
|
245
|
+
async ExecuteComponent(options) {
|
|
107
246
|
const startTime = Date.now();
|
|
108
247
|
const errors = [];
|
|
109
248
|
const warnings = [];
|
|
110
249
|
const consoleLogs = [];
|
|
111
250
|
const dataErrors = []; // Track data access errors from RunView/RunQuery
|
|
251
|
+
const dataAccess = []; // Track every data call: target, rows, cap state
|
|
112
252
|
let renderCount = 0;
|
|
113
253
|
const debug = options.debug !== false; // Default to true for debugging
|
|
114
254
|
const globalTimeout = options.timeout || 30000; // Default 30 seconds total timeout
|
|
@@ -178,7 +318,7 @@ export class ComponentRunner {
|
|
|
178
318
|
// Set up console logging
|
|
179
319
|
this.setupConsoleLogging(page, consoleLogs, warnings);
|
|
180
320
|
// Expose MJ utilities to the page
|
|
181
|
-
await this.exposeMJUtilities(page, options, dataErrors, debug);
|
|
321
|
+
await this.exposeMJUtilities(page, options, dataErrors, dataAccess, debug);
|
|
182
322
|
if (debug) {
|
|
183
323
|
console.log('📤 NODE: About to call page.evaluate with:');
|
|
184
324
|
console.log(' - spec.name:', options.componentSpec.name);
|
|
@@ -682,6 +822,10 @@ export class ComponentRunner {
|
|
|
682
822
|
super(props);
|
|
683
823
|
this.state = { hasError: false, error: null };
|
|
684
824
|
}
|
|
825
|
+
// React reads this static into a local and calls it UNBOUND
|
|
826
|
+
// (`var f = fiber.type.getDerivedStateFromError; f(error)`), so a stub
|
|
827
|
+
// that forwards through `this` throws before the capture below can run —
|
|
828
|
+
// the harness would then miss the user component's error entirely.
|
|
685
829
|
static getDerivedStateFromError(error) {
|
|
686
830
|
// Capture the actual error message IMMEDIATELY
|
|
687
831
|
window.__testHarnessRuntimeErrors = window.__testHarnessRuntimeErrors || [];
|
|
@@ -907,9 +1051,13 @@ export class ComponentRunner {
|
|
|
907
1051
|
try {
|
|
908
1052
|
// Try to get HTML content with a reasonable size limit
|
|
909
1053
|
html = await page.content();
|
|
910
|
-
//
|
|
1054
|
+
// Backstop only — the row ceilings on the data bridges (see DataCapOptions) stop a
|
|
1055
|
+
// runaway result set before it reaches the DOM. Deliberately far below Node's ~536MB
|
|
1056
|
+
// string limit: the harness shares a heap with the host process, so by the time a
|
|
1057
|
+
// page serializes to tens of megabytes the run is pathological whether or not the
|
|
1058
|
+
// string itself fits.
|
|
911
1059
|
const htmlSizeEstimate = Buffer.byteLength(html, 'utf8');
|
|
912
|
-
const maxSafeSize =
|
|
1060
|
+
const maxSafeSize = 25 * 1024 * 1024; // 25MB
|
|
913
1061
|
if (htmlSizeEstimate > maxSafeSize) {
|
|
914
1062
|
console.warn(`⚠️ HTML content is very large (${Math.round(htmlSizeEstimate / 1024 / 1024)}MB). Truncating to prevent crashes.`);
|
|
915
1063
|
// Truncate to a safe size
|
|
@@ -1047,23 +1195,40 @@ export class ComponentRunner {
|
|
|
1047
1195
|
// Medium, low, and warning severity don't affect this metric
|
|
1048
1196
|
const nonCrashCriticalHighErrors = allErrorViolations.filter(e => (e.severity === 'critical' || e.severity === 'high') && e.rule !== 'browser-crash');
|
|
1049
1197
|
const codeExecutionSuccess = nonCrashCriticalHighErrors.length === 0;
|
|
1198
|
+
// Surface capped data calls as their own warnings, so a downstream evaluator does not
|
|
1199
|
+
// read a deliberately truncated render as a defect.
|
|
1200
|
+
const capWarnings = dataAccess
|
|
1201
|
+
.filter(d => d.capped)
|
|
1202
|
+
.map(d => ({
|
|
1203
|
+
message: `Data capped: ${d.kind} "${d.target}" returned ${d.rowsReturned} of ${d.totalRowCount} rows ` +
|
|
1204
|
+
`(harness ceiling ${d.appliedMaxRows}). Values aggregated in the component from this ` +
|
|
1205
|
+
`result are computed over partial data.`,
|
|
1206
|
+
severity: 'medium',
|
|
1207
|
+
rule: 'data-row-cap',
|
|
1208
|
+
line: 0,
|
|
1209
|
+
column: 0
|
|
1210
|
+
}));
|
|
1050
1211
|
const result = {
|
|
1051
1212
|
success: success && dataErrors.length === 0 && criticalWarningViolations.length === 0, // Fail on critical warnings too
|
|
1052
1213
|
html,
|
|
1053
1214
|
errors: allErrorViolations,
|
|
1054
1215
|
browserCrash: hasBrowserCrash,
|
|
1055
|
-
warnings:
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1216
|
+
warnings: [
|
|
1217
|
+
...regularWarnings.map(w => ({
|
|
1218
|
+
message: w,
|
|
1219
|
+
severity: 'low',
|
|
1220
|
+
rule: 'warning',
|
|
1221
|
+
line: 0,
|
|
1222
|
+
column: 0
|
|
1223
|
+
})),
|
|
1224
|
+
...capWarnings,
|
|
1225
|
+
],
|
|
1062
1226
|
console: consoleLogs,
|
|
1063
1227
|
screenshot,
|
|
1064
1228
|
executionTime: Date.now() - startTime,
|
|
1065
1229
|
renderCount,
|
|
1066
1230
|
codeExecutionSuccess,
|
|
1231
|
+
dataAccess,
|
|
1067
1232
|
sourceMaps: this._lastSourceMaps,
|
|
1068
1233
|
};
|
|
1069
1234
|
if (debug) {
|
|
@@ -1134,7 +1299,9 @@ export class ComponentRunner {
|
|
|
1134
1299
|
executionTime: Date.now() - startTime,
|
|
1135
1300
|
renderCount,
|
|
1136
1301
|
browserCrash: hasBrowserCrash,
|
|
1137
|
-
codeExecutionSuccess
|
|
1302
|
+
codeExecutionSuccess,
|
|
1303
|
+
// Data calls made before the failure still describe what the component reached for.
|
|
1304
|
+
dataAccess,
|
|
1138
1305
|
};
|
|
1139
1306
|
if (debug) {
|
|
1140
1307
|
if (hasBrowserCrash && codeExecutionSuccess) {
|
|
@@ -1163,6 +1330,10 @@ export class ComponentRunner {
|
|
|
1163
1330
|
}
|
|
1164
1331
|
}
|
|
1165
1332
|
}
|
|
1333
|
+
/** @deprecated Use {@link ExecuteComponent}. */
|
|
1334
|
+
async executeComponent(options) {
|
|
1335
|
+
return this.ExecuteComponent(options);
|
|
1336
|
+
}
|
|
1166
1337
|
/**
|
|
1167
1338
|
* Load core runtime libraries into the page (React, ReactDOM, Babel, MJRuntime)
|
|
1168
1339
|
* Component-specific libraries are loaded by the runtime's ComponentCompiler
|
|
@@ -1880,7 +2051,10 @@ export class ComponentRunner {
|
|
|
1880
2051
|
/**
|
|
1881
2052
|
* Expose MJ utilities to the browser context
|
|
1882
2053
|
*/
|
|
1883
|
-
async exposeMJUtilities(page, options, dataErrors, debug = false) {
|
|
2054
|
+
async exposeMJUtilities(page, options, dataErrors, dataAccess, debug = false) {
|
|
2055
|
+
// Row ceilings for this run, resolved once rather than per call.
|
|
2056
|
+
const viewCeiling = options.dataCaps?.runViewMaxRows ?? DEFAULT_RUN_VIEW_MAX_ROWS;
|
|
2057
|
+
const queryCeiling = options.dataCaps?.runQueryMaxRows ?? DEFAULT_RUN_QUERY_MAX_ROWS;
|
|
1884
2058
|
// Don't check if already exposed - we always start fresh after goto('about:blank')
|
|
1885
2059
|
// The page.exposeFunction calls need to be made for each new page instance
|
|
1886
2060
|
// Serialize contextUser to pass to the browser context
|
|
@@ -1966,12 +2140,16 @@ export class ComponentRunner {
|
|
|
1966
2140
|
}
|
|
1967
2141
|
});
|
|
1968
2142
|
await page.exposeFunction('__mjRunView', async (params) => {
|
|
2143
|
+
const viewTarget = ResolveViewTarget(params);
|
|
2144
|
+
const maxRows = ResolveDataCapMaxRows(params.MaxRows, viewCeiling);
|
|
2145
|
+
const startedAt = Date.now();
|
|
1969
2146
|
try {
|
|
1970
|
-
const result = await util.rv.RunView(params, options.contextUser);
|
|
2147
|
+
const result = await util.rv.RunView({ ...params, MaxRows: maxRows.applied }, options.contextUser);
|
|
2148
|
+
const record = BuildDataAccessRecord('RunView', viewTarget, result, maxRows, Date.now() - startedAt);
|
|
2149
|
+
dataAccess.push(record);
|
|
1971
2150
|
// Debug logging for successful calls
|
|
1972
2151
|
if (debug) {
|
|
1973
|
-
|
|
1974
|
-
console.log(`💾 RunView SUCCESS: Entity="${params.EntityName}" Rows=${rowCount}`);
|
|
2152
|
+
console.log(`💾 RunView SUCCESS: View="${viewTarget.target}" Rows=${record.rowsReturned}${DescribeDataCap(record)}`);
|
|
1975
2153
|
if (params.ExtraFilter) {
|
|
1976
2154
|
console.log(` Filter: ${params.ExtraFilter}`);
|
|
1977
2155
|
}
|
|
@@ -1980,36 +2158,53 @@ export class ComponentRunner {
|
|
|
1980
2158
|
}
|
|
1981
2159
|
catch (error) {
|
|
1982
2160
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2161
|
+
dataAccess.push({
|
|
2162
|
+
kind: 'RunView', target: viewTarget.target, identity: viewTarget.identity, rowsReturned: 0, capped: false,
|
|
2163
|
+
appliedMaxRows: maxRows.applied, requestedMaxRows: maxRows.requested,
|
|
2164
|
+
durationMs: Date.now() - startedAt, error: errorMessage,
|
|
2165
|
+
});
|
|
1983
2166
|
// Debug logging for errors
|
|
1984
2167
|
if (debug) {
|
|
1985
|
-
console.log(`❌ RunView FAILED:
|
|
2168
|
+
console.log(`❌ RunView FAILED: View="${viewTarget.target}"`);
|
|
1986
2169
|
console.log(` Error: ${errorMessage}`);
|
|
1987
2170
|
}
|
|
1988
2171
|
else {
|
|
1989
2172
|
console.error('Error in __mjRunView:', errorMessage);
|
|
1990
2173
|
}
|
|
1991
2174
|
// Collect this error for the test report
|
|
1992
|
-
dataErrors.push(`RunView error: ${errorMessage} (
|
|
2175
|
+
dataErrors.push(`RunView error: ${errorMessage} (View: ${viewTarget.target})`);
|
|
1993
2176
|
// Return error result that won't crash the component
|
|
1994
2177
|
return { Success: false, ErrorMessage: errorMessage, Results: [] };
|
|
1995
2178
|
}
|
|
1996
2179
|
});
|
|
1997
2180
|
await page.exposeFunction('__mjRunViews', async (params) => {
|
|
2181
|
+
const perCallMaxRows = params.map(p => ResolveDataCapMaxRows(p.MaxRows, viewCeiling));
|
|
2182
|
+
const startedAt = Date.now();
|
|
1998
2183
|
try {
|
|
1999
|
-
const results = await util.rv.RunViews(params, options.contextUser);
|
|
2184
|
+
const results = await util.rv.RunViews(params.map((p, i) => ({ ...p, MaxRows: perCallMaxRows[i].applied })), options.contextUser);
|
|
2185
|
+
// One record per view in the batch — a batch that partially returns data is a
|
|
2186
|
+
// meaningfully different signal from one that returns none.
|
|
2187
|
+
const elapsed = Date.now() - startedAt;
|
|
2188
|
+
const records = params.map((p, i) => BuildDataAccessRecord('RunViews', ResolveViewTarget(p), results[i], perCallMaxRows[i], elapsed));
|
|
2189
|
+
records.forEach(r => dataAccess.push(r));
|
|
2000
2190
|
// Debug logging for successful calls
|
|
2001
2191
|
if (debug) {
|
|
2002
2192
|
console.log(`💾 RunViews SUCCESS: ${params.length} queries executed`);
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
console.log(` [${i + 1}] Entity="${p.EntityName}" Rows=${rowCount}`);
|
|
2193
|
+
records.forEach((r, i) => {
|
|
2194
|
+
console.log(` [${i + 1}] Entity="${r.target}" Rows=${r.rowsReturned}${DescribeDataCap(r)}`);
|
|
2006
2195
|
});
|
|
2007
2196
|
}
|
|
2008
2197
|
return results;
|
|
2009
2198
|
}
|
|
2010
2199
|
catch (error) {
|
|
2011
2200
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2012
|
-
const entities = params.map(p => p
|
|
2201
|
+
const entities = params.map(p => DescribeViewTarget(p)).join(', ');
|
|
2202
|
+
// RunViews fails as a batch, so every view in it failed.
|
|
2203
|
+
params.forEach((p, i) => dataAccess.push({
|
|
2204
|
+
kind: 'RunViews', ...ResolveViewTarget(p), rowsReturned: 0, capped: false,
|
|
2205
|
+
appliedMaxRows: perCallMaxRows[i].applied, requestedMaxRows: perCallMaxRows[i].requested,
|
|
2206
|
+
durationMs: Date.now() - startedAt, error: errorMessage,
|
|
2207
|
+
}));
|
|
2013
2208
|
// Debug logging for errors
|
|
2014
2209
|
if (debug) {
|
|
2015
2210
|
console.log(`❌ RunViews FAILED: Entities=[${entities}]`);
|
|
@@ -2025,13 +2220,16 @@ export class ComponentRunner {
|
|
|
2025
2220
|
}
|
|
2026
2221
|
});
|
|
2027
2222
|
await page.exposeFunction('__mjRunQuery', async (params) => {
|
|
2223
|
+
const queryIdentifier = ResolveQueryTarget(params);
|
|
2224
|
+
const maxRows = ResolveDataCapMaxRows(params.MaxRows, queryCeiling);
|
|
2225
|
+
const startedAt = Date.now();
|
|
2028
2226
|
try {
|
|
2029
|
-
const result = await util.rq.RunQuery(params, options.contextUser);
|
|
2227
|
+
const result = await util.rq.RunQuery({ ...params, MaxRows: maxRows.applied }, options.contextUser);
|
|
2228
|
+
const record = BuildDataAccessRecord('RunQuery', queryIdentifier, result, maxRows, Date.now() - startedAt);
|
|
2229
|
+
dataAccess.push(record);
|
|
2030
2230
|
// Debug logging for successful calls
|
|
2031
2231
|
if (debug) {
|
|
2032
|
-
|
|
2033
|
-
const rowCount = result.Results?.length || 0;
|
|
2034
|
-
console.log(`💾 RunQuery SUCCESS: Query="${queryIdentifier}" Rows=${rowCount}`);
|
|
2232
|
+
console.log(`💾 RunQuery SUCCESS: Query="${queryIdentifier.target}" Rows=${record.rowsReturned}${DescribeDataCap(record)}`);
|
|
2035
2233
|
if (params.Parameters && Object.keys(params.Parameters).length > 0) {
|
|
2036
2234
|
console.log(` Parameters:`, params.Parameters);
|
|
2037
2235
|
}
|
|
@@ -2040,17 +2238,21 @@ export class ComponentRunner {
|
|
|
2040
2238
|
}
|
|
2041
2239
|
catch (error) {
|
|
2042
2240
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2043
|
-
|
|
2241
|
+
dataAccess.push({
|
|
2242
|
+
kind: 'RunQuery', target: queryIdentifier.target, identity: queryIdentifier.identity, rowsReturned: 0, capped: false,
|
|
2243
|
+
appliedMaxRows: maxRows.applied, requestedMaxRows: maxRows.requested,
|
|
2244
|
+
durationMs: Date.now() - startedAt, error: errorMessage,
|
|
2245
|
+
});
|
|
2044
2246
|
// Debug logging for errors
|
|
2045
2247
|
if (debug) {
|
|
2046
|
-
console.log(`❌ RunQuery FAILED: Query="${queryIdentifier}"`);
|
|
2248
|
+
console.log(`❌ RunQuery FAILED: Query="${queryIdentifier.target}"`);
|
|
2047
2249
|
console.log(` Error: ${errorMessage}`);
|
|
2048
2250
|
}
|
|
2049
2251
|
else {
|
|
2050
2252
|
console.error('Error in __mjRunQuery:', errorMessage);
|
|
2051
2253
|
}
|
|
2052
2254
|
// Collect this error for the test report
|
|
2053
|
-
dataErrors.push(`RunQuery error: ${errorMessage} (Query: ${queryIdentifier})`);
|
|
2255
|
+
dataErrors.push(`RunQuery error: ${errorMessage} (Query: ${queryIdentifier.target})`);
|
|
2054
2256
|
// Return error result that won't crash the component
|
|
2055
2257
|
return { Success: false, ErrorMessage: errorMessage, Results: [] };
|
|
2056
2258
|
}
|
|
@@ -2116,7 +2318,11 @@ export class ComponentRunner {
|
|
|
2116
2318
|
const VectorService = window.MJReactRuntime?.SimpleVectorService ||
|
|
2117
2319
|
class {
|
|
2118
2320
|
// Stub implementation if not available
|
|
2119
|
-
|
|
2321
|
+
CosineSimilarity(_a, _b) { return 0; }
|
|
2322
|
+
/** @deprecated Use {@link CosineSimilarity}. */
|
|
2323
|
+
cosineSimilarity(_a, _b) {
|
|
2324
|
+
return this.CosineSimilarity(_a, _b);
|
|
2325
|
+
}
|
|
2120
2326
|
};
|
|
2121
2327
|
window.__mjUtilities = {
|
|
2122
2328
|
md: {
|