@wix/zero-config-implementation 1.8.0 → 1.10.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/dist/converters/data-item-builder.d.ts +4 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +12822 -12787
- package/dist/information-extractors/react/extractors/prop-tracker.d.ts +3 -6
- package/dist/information-extractors/react/index.d.ts +1 -2
- package/dist/information-extractors/react/types.d.ts +4 -17
- package/dist/information-extractors/react/utils/mock-generator.d.ts +14 -2
- package/package.json +2 -2
- package/src/converters/data-item-builder.ts +120 -35
- package/src/converters/to-editor-component.ts +11 -6
- package/src/index.ts +0 -2
- package/src/information-extractors/react/extractors/core/runner.ts +2 -6
- package/src/information-extractors/react/extractors/core/tree-builder.ts +23 -2
- package/src/information-extractors/react/extractors/prop-tracker.ts +49 -28
- package/src/information-extractors/react/index.ts +0 -7
- package/src/information-extractors/react/types.ts +4 -20
- package/src/information-extractors/react/utils/mock-generator.ts +107 -31
- package/src/manifest-pipeline.ts +15 -11
- package/dist/information-extractors/react/utils/prop-spy.d.ts +0 -10
- package/src/information-extractors/react/utils/prop-spy.ts +0 -168
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { PropSpyContext } from '../utils/prop-spy';
|
|
1
|
+
import { TrackingStores } from '../types';
|
|
3
2
|
import { ReactExtractor } from './core/types';
|
|
4
|
-
export type GetSpyMetadataFn = (id: string) => PropSpyMeta | null;
|
|
5
3
|
export interface PropTrackerData {
|
|
6
4
|
tag: string;
|
|
7
5
|
role?: string;
|
|
@@ -10,13 +8,12 @@ export interface PropTrackerData {
|
|
|
10
8
|
}
|
|
11
9
|
export interface PropTrackerExtractorState {
|
|
12
10
|
stores: TrackingStores;
|
|
13
|
-
spyContext: PropSpyContext;
|
|
14
11
|
}
|
|
15
12
|
/**
|
|
16
13
|
* Creates a prop tracker extractor that:
|
|
17
|
-
* 1.
|
|
14
|
+
* 1. Generates spy-instrumented mock props during beforeRender
|
|
18
15
|
* 2. Detects spy markers in element props during onCreateElement
|
|
19
|
-
* 3.
|
|
16
|
+
* 3. propUsages tracking data to the store namespaced by 'prop-tracker'
|
|
20
17
|
*/
|
|
21
18
|
export declare function createPropTrackerExtractor(): {
|
|
22
19
|
extractor: ReactExtractor;
|
|
@@ -5,5 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { ExtractorStore, runExtractors, buildElementTree, createPropTrackerExtractor, createCssPropertiesExtractor, } from './extractors';
|
|
7
7
|
export type { ExtractionResult, RunExtractorsOptions, ExtractedElement, ReactExtractor, RenderContext, CreateElementEvent, RenderCompleteEvent, PropTrackerData, PropTrackerExtractorState, CssPropertiesData, } from './extractors';
|
|
8
|
-
export type { CoupledComponentInfo, CoupledProp, TrackingStores, DOMBinding,
|
|
9
|
-
export type { PropSpyContext } from './utils/prop-spy';
|
|
8
|
+
export type { CoupledComponentInfo, CoupledProp, TrackingStores, DOMBinding, PropWriteInfo, PropSpyMeta, } from './types';
|
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { PropInfo } from '../ts/types';
|
|
2
2
|
import { ExtractedElement } from './extractors/core/tree-builder';
|
|
3
|
-
export declare const PROP_SPY_SYMBOL: unique symbol;
|
|
4
3
|
export interface PropSpyMeta {
|
|
5
4
|
path: string;
|
|
6
5
|
propName: string;
|
|
7
|
-
uniqueId: string;
|
|
8
6
|
originalValue: unknown;
|
|
9
7
|
}
|
|
10
|
-
export interface PropSpy<T = unknown> {
|
|
11
|
-
[PROP_SPY_SYMBOL]: true;
|
|
12
|
-
__meta: PropSpyMeta;
|
|
13
|
-
valueOf: () => T;
|
|
14
|
-
toString: () => string;
|
|
15
|
-
toJSON: () => T;
|
|
16
|
-
[Symbol.toPrimitive]?: (hint: string) => T | string | number;
|
|
17
|
-
}
|
|
18
|
-
export interface PropReadInfo {
|
|
19
|
-
components: Set<string>;
|
|
20
|
-
value: unknown;
|
|
21
|
-
}
|
|
22
8
|
export interface PropWriteInfo {
|
|
23
9
|
elements: Map<string, {
|
|
24
10
|
tag: string;
|
|
@@ -30,8 +16,7 @@ export interface PropWriteInfo {
|
|
|
30
16
|
}>;
|
|
31
17
|
}
|
|
32
18
|
export interface TrackingStores {
|
|
33
|
-
|
|
34
|
-
writes: Map<string, PropWriteInfo>;
|
|
19
|
+
propUsages: Map<string, PropWriteInfo>;
|
|
35
20
|
}
|
|
36
21
|
export interface DOMBinding {
|
|
37
22
|
element: string;
|
|
@@ -40,7 +25,8 @@ export interface DOMBinding {
|
|
|
40
25
|
elementId: string;
|
|
41
26
|
}
|
|
42
27
|
export interface CoupledProp extends PropInfo {
|
|
43
|
-
|
|
28
|
+
/** Full stores.propUsages key for this prop, e.g. "props.linkUrl". */
|
|
29
|
+
propPath: string;
|
|
44
30
|
logicOnly: boolean;
|
|
45
31
|
}
|
|
46
32
|
export interface CoupledComponentInfo {
|
|
@@ -48,4 +34,5 @@ export interface CoupledComponentInfo {
|
|
|
48
34
|
props: Record<string, CoupledProp>;
|
|
49
35
|
elements: ExtractedElement[];
|
|
50
36
|
innerElementProps?: Map<string, Record<string, CoupledProp>>;
|
|
37
|
+
propUsages: TrackingStores['propUsages'];
|
|
51
38
|
}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { ComponentInfo } from '../../ts/types';
|
|
2
|
+
export declare const PRESETS_WRAPPER_CLASS_NAME = "mock-presets-wrapper-probe";
|
|
2
3
|
/**
|
|
3
4
|
* Reset faker's seed and internal state for reproducible results
|
|
4
5
|
*/
|
|
5
6
|
export declare function resetMockCounter(): void;
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* Narrow interface for registering spy markers during mock generation.
|
|
9
|
+
* Satisfied structurally by PropSpyContext.
|
|
8
10
|
*/
|
|
9
|
-
export
|
|
11
|
+
export interface PropSpyRegistrar {
|
|
12
|
+
registerString(path: string, propName: string, value: string): void;
|
|
13
|
+
registerNumber(path: string, propName: string, value: number): void;
|
|
14
|
+
registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generate mock props object from ComponentInfo.
|
|
18
|
+
* When a registrar is provided, string and number values are spy-instrumented
|
|
19
|
+
* for DOM binding detection.
|
|
20
|
+
*/
|
|
21
|
+
export declare function generateMockProps(componentInfo: ComponentInfo, registrar?: PropSpyRegistrar): Record<string, unknown>;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.10.0",
|
|
8
8
|
"description": "Core library for extracting component manifests from JS and CSS files",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/index.js",
|
|
@@ -74,5 +74,5 @@
|
|
|
74
74
|
]
|
|
75
75
|
}
|
|
76
76
|
},
|
|
77
|
-
"falconPackageHash": "
|
|
77
|
+
"falconPackageHash": "21d3bc497b17268af6551d502a02de709d17f09eaa98040d2017ae4b"
|
|
78
78
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { Result } from 'neverthrow'
|
|
16
16
|
import { err, ok } from 'neverthrow'
|
|
17
17
|
import { ParseError } from '../errors'
|
|
18
|
+
import type { DOMBinding, TrackingStores } from '../information-extractors/react'
|
|
18
19
|
import type { PropInfo, ResolvedType } from '../information-extractors/ts/types'
|
|
19
20
|
import type { DataItem } from '../schema'
|
|
20
21
|
import { DATA, MEDIA } from '../schema'
|
|
@@ -24,15 +25,42 @@ const { DATA_TYPE, WIX_TYPE_TO_DATA_TYPE } = DATA
|
|
|
24
25
|
|
|
25
26
|
type ParseErrorInstance = InstanceType<typeof ParseError>
|
|
26
27
|
|
|
28
|
+
function getBindingsForPath(propUsages: TrackingStores['propUsages'], path: string): DOMBinding[] {
|
|
29
|
+
const writeInfo = propUsages.get(path)
|
|
30
|
+
if (!writeInfo) return []
|
|
31
|
+
|
|
32
|
+
const result: DOMBinding[] = []
|
|
33
|
+
for (const [key, attrInfo] of writeInfo.attributes) {
|
|
34
|
+
const elementId = key.split(':')[0]
|
|
35
|
+
const elementInfo = writeInfo.elements.get(elementId)
|
|
36
|
+
if (elementInfo) {
|
|
37
|
+
result.push({
|
|
38
|
+
element: elementInfo.tag,
|
|
39
|
+
attribute: attrInfo.attr,
|
|
40
|
+
concatenated: attrInfo.concatenated,
|
|
41
|
+
elementId: elementInfo.elementId,
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result
|
|
46
|
+
}
|
|
47
|
+
|
|
27
48
|
/**
|
|
28
49
|
* Converts a single PropInfo to a DataItem for the Wix Editor component schema.
|
|
29
50
|
*
|
|
30
51
|
* @param propInfo - The resolved TypeScript prop information to convert.
|
|
31
52
|
* @param defaultValue - Optional default value for the data item.
|
|
53
|
+
* @param propUsages - The full stores.propUsages map for on-demand binding lookups.
|
|
54
|
+
* @param propPath - The full stores.propUsages key for this prop, e.g. "props.linkUrl".
|
|
32
55
|
* @returns `Ok<DataItem>` on success, `Err<ParseError>` if an invariant is violated
|
|
33
56
|
* (e.g. an array type missing its element type).
|
|
34
57
|
*/
|
|
35
|
-
export function buildDataItem(
|
|
58
|
+
export function buildDataItem(
|
|
59
|
+
propInfo: PropInfo,
|
|
60
|
+
defaultValue?: unknown,
|
|
61
|
+
propUsages?: TrackingStores['propUsages'],
|
|
62
|
+
propPath?: string,
|
|
63
|
+
): Result<DataItem, ParseErrorInstance> {
|
|
36
64
|
const dataItem: DataItem = {
|
|
37
65
|
displayName: formatDisplayName(propInfo.name),
|
|
38
66
|
}
|
|
@@ -41,7 +69,7 @@ export function buildDataItem(propInfo: PropInfo, defaultValue?: unknown): Resul
|
|
|
41
69
|
dataItem.defaultValue = defaultValue
|
|
42
70
|
}
|
|
43
71
|
|
|
44
|
-
const result = applyResolvedTypeToDataItem(dataItem, propInfo.resolvedType, propInfo)
|
|
72
|
+
const result = applyResolvedTypeToDataItem(dataItem, propInfo.resolvedType, propInfo, propUsages, propPath)
|
|
45
73
|
if (result.isErr()) {
|
|
46
74
|
return err(result.error)
|
|
47
75
|
}
|
|
@@ -59,10 +87,14 @@ function applyResolvedTypeToDataItem(
|
|
|
59
87
|
dataItem: DataItem,
|
|
60
88
|
resolvedType: ResolvedType,
|
|
61
89
|
propInfo: PropInfo,
|
|
90
|
+
propUsages?: TrackingStores['propUsages'],
|
|
91
|
+
propPath?: string,
|
|
62
92
|
): Result<void, ParseErrorInstance> {
|
|
93
|
+
const bindings = propUsages && propPath ? getBindingsForPath(propUsages, propPath) : undefined
|
|
94
|
+
|
|
63
95
|
switch (resolvedType.kind) {
|
|
64
96
|
case 'primitive':
|
|
65
|
-
handlePrimitiveType(dataItem, resolvedType, propInfo)
|
|
97
|
+
handlePrimitiveType(dataItem, resolvedType, propInfo, bindings)
|
|
66
98
|
return ok(undefined)
|
|
67
99
|
|
|
68
100
|
case 'literal':
|
|
@@ -74,16 +106,16 @@ function applyResolvedTypeToDataItem(
|
|
|
74
106
|
return ok(undefined)
|
|
75
107
|
|
|
76
108
|
case 'array':
|
|
77
|
-
return handleArrayType(dataItem, resolvedType)
|
|
109
|
+
return handleArrayType(dataItem, resolvedType, propUsages, propPath)
|
|
78
110
|
|
|
79
111
|
case 'object':
|
|
80
|
-
return handleObjectType(dataItem, resolvedType)
|
|
112
|
+
return handleObjectType(dataItem, resolvedType, propUsages, propPath)
|
|
81
113
|
|
|
82
114
|
case 'union':
|
|
83
115
|
return handleUnionType(dataItem, resolvedType, propInfo)
|
|
84
116
|
|
|
85
117
|
case 'function':
|
|
86
|
-
handleFunctionType(dataItem, propInfo)
|
|
118
|
+
handleFunctionType(dataItem, propInfo, bindings)
|
|
87
119
|
return ok(undefined)
|
|
88
120
|
|
|
89
121
|
case 'semantic':
|
|
@@ -100,14 +132,34 @@ function applyResolvedTypeToDataItem(
|
|
|
100
132
|
|
|
101
133
|
/**
|
|
102
134
|
* Handles primitive types (string, number, boolean).
|
|
135
|
+
* When bindings are provided, uses bound DOM attributes to infer more specific types:
|
|
136
|
+
* - string bound to `dir` → direction
|
|
137
|
+
* - string bound to `href` on an anchor element (non-concatenated) → webUrl
|
|
138
|
+
* - string bound to `id` → guid
|
|
139
|
+
* - string bound to `pattern` → regex
|
|
103
140
|
* Unrecognized primitives silently fall back to text.
|
|
104
141
|
*/
|
|
105
|
-
function handlePrimitiveType(
|
|
142
|
+
function handlePrimitiveType(
|
|
143
|
+
dataItem: DataItem,
|
|
144
|
+
resolvedType: ResolvedType,
|
|
145
|
+
propInfo: PropInfo,
|
|
146
|
+
bindings?: DOMBinding[],
|
|
147
|
+
): void {
|
|
106
148
|
const typeValue = (resolvedType.value as string | undefined)?.toLowerCase() || propInfo.type.toLowerCase()
|
|
107
149
|
|
|
108
150
|
if (typeValue.includes('string')) {
|
|
109
|
-
|
|
110
|
-
|
|
151
|
+
if (bindings?.some((b) => b.attribute === 'dir')) {
|
|
152
|
+
dataItem.dataType = DATA_TYPE.direction
|
|
153
|
+
} else if (bindings?.some((b) => b.attribute === 'href' && b.element === 'a' && !b.concatenated)) {
|
|
154
|
+
dataItem.dataType = DATA_TYPE.webUrl
|
|
155
|
+
} else if (bindings?.some((b) => b.attribute === 'id')) {
|
|
156
|
+
dataItem.dataType = DATA_TYPE.guid
|
|
157
|
+
} else if (bindings?.some((b) => b.attribute === 'pattern')) {
|
|
158
|
+
dataItem.dataType = DATA_TYPE.regex
|
|
159
|
+
} else {
|
|
160
|
+
dataItem.dataType = DATA_TYPE.text
|
|
161
|
+
dataItem.text = {}
|
|
162
|
+
}
|
|
111
163
|
} else if (typeValue.includes('number')) {
|
|
112
164
|
dataItem.dataType = DATA_TYPE.number
|
|
113
165
|
dataItem.number = {}
|
|
@@ -141,8 +193,15 @@ function handleEnumType(dataItem: DataItem): void {
|
|
|
141
193
|
/**
|
|
142
194
|
* Handles array types. Fails with a `ParseError` if the resolved type
|
|
143
195
|
* has kind 'array' but is missing its `elementType` (invariant violation).
|
|
196
|
+
* propPath is forwarded unchanged — getBindingsForPath strips numeric indices
|
|
197
|
+
* so `props.items.url` matches propUsages like `props.items[0].url`.
|
|
144
198
|
*/
|
|
145
|
-
function handleArrayType(
|
|
199
|
+
function handleArrayType(
|
|
200
|
+
dataItem: DataItem,
|
|
201
|
+
resolvedType: ResolvedType,
|
|
202
|
+
propUsages?: TrackingStores['propUsages'],
|
|
203
|
+
propPath?: string,
|
|
204
|
+
): Result<void, ParseErrorInstance> {
|
|
146
205
|
if (resolvedType.kind !== 'array' || resolvedType.elementType === undefined) {
|
|
147
206
|
return err(
|
|
148
207
|
new ParseError('Invalid array type: resolved type has kind "array" but is missing elementType', {
|
|
@@ -153,12 +212,18 @@ function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result
|
|
|
153
212
|
|
|
154
213
|
dataItem.dataType = DATA_TYPE.arrayItems
|
|
155
214
|
const elementDataItem: DataItem = {}
|
|
156
|
-
const result = applyResolvedTypeToDataItem(
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
215
|
+
const result = applyResolvedTypeToDataItem(
|
|
216
|
+
elementDataItem,
|
|
217
|
+
resolvedType.elementType,
|
|
218
|
+
{
|
|
219
|
+
name: 'element',
|
|
220
|
+
required: false,
|
|
221
|
+
type: resolvedType.elementType.kind,
|
|
222
|
+
resolvedType: resolvedType.elementType,
|
|
223
|
+
} satisfies PropInfo,
|
|
224
|
+
propUsages,
|
|
225
|
+
propPath,
|
|
226
|
+
)
|
|
162
227
|
|
|
163
228
|
if (result.isErr()) {
|
|
164
229
|
return result
|
|
@@ -173,16 +238,23 @@ function handleArrayType(dataItem: DataItem, resolvedType: ResolvedType): Result
|
|
|
173
238
|
|
|
174
239
|
/**
|
|
175
240
|
* Handles object types with nested properties.
|
|
176
|
-
* Recursively calls `buildDataItem` for each property
|
|
241
|
+
* Recursively calls `buildDataItem` for each property, appending the property name
|
|
242
|
+
* to propPath so nested bindings can be looked up on-demand.
|
|
177
243
|
*/
|
|
178
|
-
function handleObjectType(
|
|
244
|
+
function handleObjectType(
|
|
245
|
+
dataItem: DataItem,
|
|
246
|
+
resolvedType: ResolvedType,
|
|
247
|
+
propUsages?: TrackingStores['propUsages'],
|
|
248
|
+
propPath?: string,
|
|
249
|
+
): Result<void, ParseErrorInstance> {
|
|
179
250
|
dataItem.dataType = DATA_TYPE.data
|
|
180
251
|
|
|
181
252
|
if (resolvedType.properties) {
|
|
182
253
|
const nestedItems: Record<string, DataItem> = {}
|
|
183
254
|
|
|
184
255
|
for (const [propName, propInfo] of Object.entries(resolvedType.properties)) {
|
|
185
|
-
const
|
|
256
|
+
const childPath = propPath ? `${propPath}.${propName}` : propName
|
|
257
|
+
const result = buildDataItem(propInfo, undefined, propUsages, childPath)
|
|
186
258
|
if (result.isErr()) {
|
|
187
259
|
return err(result.error)
|
|
188
260
|
}
|
|
@@ -297,26 +369,39 @@ function handleSemanticType(dataItem: DataItem, resolvedType: ResolvedType): voi
|
|
|
297
369
|
dataItem.text = {}
|
|
298
370
|
}
|
|
299
371
|
|
|
372
|
+
const EVENT_HANDLER_ATTR_TO_DATA_TYPE: Record<string, string> = {
|
|
373
|
+
onclick: DATA_TYPE.onClick,
|
|
374
|
+
onchange: DATA_TYPE.onChange,
|
|
375
|
+
onkeypress: DATA_TYPE.onKeyPress,
|
|
376
|
+
onkeyup: DATA_TYPE.onKeyUp,
|
|
377
|
+
onsubmit: DATA_TYPE.onSubmit,
|
|
378
|
+
}
|
|
379
|
+
|
|
300
380
|
/**
|
|
301
381
|
* Handles function types — maps to event handlers.
|
|
382
|
+
* Checks bound DOM attributes first (e.g. `onChange` → onChange), then falls back to prop name.
|
|
302
383
|
*/
|
|
303
|
-
function handleFunctionType(dataItem: DataItem, propInfo: PropInfo): void {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
dataItem.dataType = DATA_TYPE.onKeyUp
|
|
314
|
-
} else if (propName === 'onsubmit') {
|
|
315
|
-
dataItem.dataType = DATA_TYPE.onSubmit
|
|
316
|
-
} else {
|
|
317
|
-
dataItem.dataType = DATA_TYPE.function
|
|
318
|
-
dataItem.function = {}
|
|
384
|
+
function handleFunctionType(dataItem: DataItem, propInfo: PropInfo, bindings?: DOMBinding[]): void {
|
|
385
|
+
// Check bound attributes first — they reflect actual DOM wiring regardless of prop name
|
|
386
|
+
if (bindings) {
|
|
387
|
+
for (const binding of bindings) {
|
|
388
|
+
const dataType = EVENT_HANDLER_ATTR_TO_DATA_TYPE[binding.attribute.toLowerCase()]
|
|
389
|
+
if (dataType) {
|
|
390
|
+
dataItem.dataType = dataType
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
}
|
|
319
394
|
}
|
|
395
|
+
|
|
396
|
+
// Fall back to prop name matching
|
|
397
|
+
const dataType = EVENT_HANDLER_ATTR_TO_DATA_TYPE[propInfo.name.toLowerCase()]
|
|
398
|
+
if (dataType) {
|
|
399
|
+
dataItem.dataType = dataType
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
dataItem.dataType = DATA_TYPE.function
|
|
404
|
+
dataItem.function = {}
|
|
320
405
|
}
|
|
321
406
|
|
|
322
407
|
/**
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
CoupledProp,
|
|
6
6
|
CssPropertiesData,
|
|
7
7
|
ExtractedElement,
|
|
8
|
+
TrackingStores,
|
|
8
9
|
} from '../information-extractors/react'
|
|
9
10
|
import type {
|
|
10
11
|
CssCustomPropertyItem,
|
|
@@ -34,8 +35,8 @@ function buildEditorElement(component: ComponentInfoWithCss): EditorElement {
|
|
|
34
35
|
return {
|
|
35
36
|
selector: buildSelector(rootElement),
|
|
36
37
|
displayName: formatDisplayName(component.componentName),
|
|
37
|
-
data: buildData(component.props),
|
|
38
|
-
elements: buildElements(childElements, component.innerElementProps),
|
|
38
|
+
data: buildData(component.props, component.propUsages),
|
|
39
|
+
elements: buildElements(childElements, component.innerElementProps, component.propUsages),
|
|
39
40
|
cssProperties: buildCssProperties(rootElement),
|
|
40
41
|
cssCustomProperties: buildCssCustomPropertiesForElement(rootElement),
|
|
41
42
|
}
|
|
@@ -48,7 +49,10 @@ function buildSelector(rootElement?: ExtractedElement): string {
|
|
|
48
49
|
return rootElement?.tag ?? ''
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function buildData(
|
|
52
|
+
function buildData(
|
|
53
|
+
props: Record<string, CoupledProp>,
|
|
54
|
+
propUsages: TrackingStores['propUsages'],
|
|
55
|
+
): Record<string, DataItem> {
|
|
52
56
|
const data: Record<string, DataItem> = {}
|
|
53
57
|
|
|
54
58
|
for (const [name, prop] of Object.entries(props)) {
|
|
@@ -58,7 +62,7 @@ function buildData(props: Record<string, CoupledProp>): Record<string, DataItem>
|
|
|
58
62
|
// Extract default value for buildDataItem
|
|
59
63
|
const defaultValue = prop.defaultValue?.kind !== 'unresolved' ? prop.defaultValue?.value : undefined
|
|
60
64
|
|
|
61
|
-
const result = buildDataItem(prop, defaultValue)
|
|
65
|
+
const result = buildDataItem(prop, defaultValue, propUsages, prop.propPath)
|
|
62
66
|
if (result.isOk()) {
|
|
63
67
|
data[name] = result.value
|
|
64
68
|
}
|
|
@@ -70,12 +74,13 @@ function buildData(props: Record<string, CoupledProp>): Record<string, DataItem>
|
|
|
70
74
|
function buildElements(
|
|
71
75
|
elements: ExtractedElement[],
|
|
72
76
|
innerElementProps?: CoupledComponentInfo['innerElementProps'],
|
|
77
|
+
propUsages?: TrackingStores['propUsages'],
|
|
73
78
|
): Record<string, ElementItem> {
|
|
74
79
|
const result: Record<string, ElementItem> = {}
|
|
75
80
|
|
|
76
81
|
for (const el of elements) {
|
|
77
82
|
const elementData = innerElementProps?.get(el.traceId)
|
|
78
|
-
const data = elementData ? buildData(elementData) : undefined
|
|
83
|
+
const data = elementData && propUsages ? buildData(elementData, propUsages) : undefined
|
|
79
84
|
const cssProps = buildCssProperties(el)
|
|
80
85
|
const cssCustomProps = buildCssCustomPropertiesForElement(el)
|
|
81
86
|
|
|
@@ -91,7 +96,7 @@ function buildElements(
|
|
|
91
96
|
// CSS custom properties from matched rules for this element
|
|
92
97
|
...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
|
|
93
98
|
// Recursively build nested elements
|
|
94
|
-
elements: el.children.length > 0 ? buildElements(el.children, innerElementProps) : undefined,
|
|
99
|
+
elements: el.children.length > 0 ? buildElements(el.children, innerElementProps, propUsages) : undefined,
|
|
95
100
|
},
|
|
96
101
|
}
|
|
97
102
|
}
|
package/src/index.ts
CHANGED
|
@@ -193,14 +193,12 @@ export type {
|
|
|
193
193
|
PropTrackerData,
|
|
194
194
|
PropTrackerExtractorState,
|
|
195
195
|
CssPropertiesData,
|
|
196
|
-
PropSpyContext,
|
|
197
196
|
} from './information-extractors/react'
|
|
198
197
|
export type {
|
|
199
198
|
CoupledComponentInfo,
|
|
200
199
|
CoupledProp,
|
|
201
200
|
DOMBinding,
|
|
202
201
|
TrackingStores,
|
|
203
|
-
PropReadInfo,
|
|
204
202
|
PropWriteInfo,
|
|
205
203
|
PropSpyMeta,
|
|
206
204
|
} from './information-extractors/react'
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import type { ComponentType } from 'react'
|
|
13
13
|
import { type CreateElementListener, renderWithExtractors } from '../../../../component-renderer'
|
|
14
14
|
import type { ComponentInfo } from '../../../ts/types'
|
|
15
|
-
import { generateMockProps, resetMockCounter } from '../../utils/mock-generator'
|
|
16
15
|
import { ExtractorStore } from './store'
|
|
17
16
|
import { type ExtractedElement, buildElementTree } from './tree-builder'
|
|
18
17
|
import type { CreateElementEvent, ReactExtractor, RenderContext } from './types'
|
|
@@ -50,17 +49,14 @@ export function runExtractors(
|
|
|
50
49
|
extractors: ReactExtractor[],
|
|
51
50
|
options?: RunExtractorsOptions,
|
|
52
51
|
): ExtractionResult {
|
|
53
|
-
// Reset mock counter for reproducible results
|
|
54
|
-
resetMockCounter()
|
|
55
|
-
|
|
56
52
|
// Create shared store
|
|
57
53
|
const store = new ExtractorStore()
|
|
58
54
|
|
|
59
|
-
// Create render context
|
|
55
|
+
// Create render context; extractors populate props in onBeforeRender
|
|
60
56
|
const context: RenderContext = {
|
|
61
57
|
componentInfo,
|
|
62
58
|
component,
|
|
63
|
-
props:
|
|
59
|
+
props: {},
|
|
64
60
|
store,
|
|
65
61
|
}
|
|
66
62
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { pascalCase } from 'case-anything'
|
|
9
9
|
import { type DefaultTreeAdapterMap, parseFragment } from 'parse5'
|
|
10
10
|
import { TRACE_ATTR } from '../../../../component-renderer'
|
|
11
|
+
import { PRESETS_WRAPPER_CLASS_NAME } from '../../utils/mock-generator'
|
|
11
12
|
import type { CssPropertiesData } from '../css-properties'
|
|
12
13
|
import { addTextProperties } from '../css-properties'
|
|
13
14
|
import type { ExtractorStore } from './store'
|
|
@@ -146,7 +147,9 @@ function getTextContent(element: Element): string {
|
|
|
146
147
|
*/
|
|
147
148
|
function getElementNamePart(element: Element, getElementById: (id: string) => Element | undefined): string {
|
|
148
149
|
const id = getAttribute(element, 'id')
|
|
149
|
-
|
|
150
|
+
// Skip spy-instrumented ids (mock_propName_XXXXXX) — they are runtime mock values,
|
|
151
|
+
// not semantic identifiers, and would produce garbage element names.
|
|
152
|
+
if (id && !id.startsWith('mock_')) {
|
|
150
153
|
return pascalCase(id)
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -173,6 +176,22 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
|
|
|
173
176
|
// Tree Building
|
|
174
177
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
175
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Replaces any presetsWrapper element (identified by PRESETS_WRAPPER_CLASS_NAME on its class
|
|
181
|
+
* attribute) with its children, so the real root element is promoted to the top level.
|
|
182
|
+
* The presetsWrapper is injected by the Wix renderer and is not a real component element.
|
|
183
|
+
*/
|
|
184
|
+
function unwrapPresetsWrappers(nodes: Node[]): Node[] {
|
|
185
|
+
return nodes.flatMap((node) => {
|
|
186
|
+
if (!isElement(node)) return [node]
|
|
187
|
+
const classAttr = getAttribute(node, 'class')
|
|
188
|
+
if (getAttribute(node, TRACE_ATTR) && classAttr?.includes(PRESETS_WRAPPER_CLASS_NAME)) {
|
|
189
|
+
return [...node.childNodes] as Array<Node>
|
|
190
|
+
}
|
|
191
|
+
return [node]
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
|
|
176
195
|
/**
|
|
177
196
|
* Builds an element tree from HTML, merging with store data.
|
|
178
197
|
* Each element gets a semantic name from concatenated ancestor names.
|
|
@@ -258,10 +277,12 @@ export function buildElementTree(html: string, store: ExtractorStore): Extracted
|
|
|
258
277
|
}
|
|
259
278
|
|
|
260
279
|
// Start with empty ancestor path, first traced element is root
|
|
280
|
+
const topLevelNodes = unwrapPresetsWrappers([...fragment.childNodes])
|
|
281
|
+
|
|
261
282
|
let isFirst = true
|
|
262
283
|
const result: ExtractedElement[] = []
|
|
263
284
|
|
|
264
|
-
for (const node of
|
|
285
|
+
for (const node of topLevelNodes) {
|
|
265
286
|
const elements = walkTree(node, '', isFirst)
|
|
266
287
|
if (elements.length > 0) {
|
|
267
288
|
result.push(...elements)
|