@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
|
@@ -8,17 +8,13 @@
|
|
|
8
8
|
import type { HTMLAttributes } from 'react'
|
|
9
9
|
import { TRACE_ATTR } from '../../../component-renderer'
|
|
10
10
|
import type { PropSpyMeta, TrackingStores } from '../types'
|
|
11
|
-
import { type
|
|
11
|
+
import { type PropSpyRegistrar, generateMockProps, resetMockCounter } from '../utils/mock-generator'
|
|
12
12
|
import type { CreateElementEvent, ReactExtractor, RenderContext } from './core/types'
|
|
13
13
|
|
|
14
14
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
15
|
// Types
|
|
16
16
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
const SPY_REGEX = /__spy_\d+__/g
|
|
19
|
-
|
|
20
|
-
export type GetSpyMetadataFn = (id: string) => PropSpyMeta | null
|
|
21
|
-
|
|
22
18
|
export interface PropTrackerData {
|
|
23
19
|
tag: string
|
|
24
20
|
role?: string
|
|
@@ -28,7 +24,6 @@ export interface PropTrackerData {
|
|
|
28
24
|
|
|
29
25
|
export interface PropTrackerExtractorState {
|
|
30
26
|
stores: TrackingStores
|
|
31
|
-
spyContext: PropSpyContext
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -37,28 +32,54 @@ export interface PropTrackerExtractorState {
|
|
|
37
32
|
|
|
38
33
|
/**
|
|
39
34
|
* Creates a prop tracker extractor that:
|
|
40
|
-
* 1.
|
|
35
|
+
* 1. Generates spy-instrumented mock props during beforeRender
|
|
41
36
|
* 2. Detects spy markers in element props during onCreateElement
|
|
42
|
-
* 3.
|
|
37
|
+
* 3. propUsages tracking data to the store namespaced by 'prop-tracker'
|
|
43
38
|
*/
|
|
44
39
|
export function createPropTrackerExtractor(): {
|
|
45
40
|
extractor: ReactExtractor
|
|
46
41
|
state: PropTrackerExtractorState
|
|
47
42
|
} {
|
|
48
|
-
const
|
|
43
|
+
const stringMeta = new Map<string, PropSpyMeta>()
|
|
44
|
+
const numberMeta = new Map<number, PropSpyMeta>()
|
|
45
|
+
const fnMeta = new WeakMap<(...args: unknown[]) => unknown, PropSpyMeta>()
|
|
49
46
|
const stores: TrackingStores = {
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
propUsages: new Map(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const registrar: PropSpyRegistrar = {
|
|
51
|
+
registerString(path, propName, value) {
|
|
52
|
+
stringMeta.set(value, { path, propName, originalValue: value })
|
|
53
|
+
},
|
|
54
|
+
registerNumber(path, propName, value) {
|
|
55
|
+
numberMeta.set(value, { path, propName, originalValue: value })
|
|
56
|
+
},
|
|
57
|
+
registerFunction(path, propName, value) {
|
|
58
|
+
fnMeta.set(value, { path, propName, originalValue: value })
|
|
59
|
+
},
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
const extractSpies = (
|
|
62
|
+
const extractSpies = (
|
|
63
|
+
value: unknown,
|
|
64
|
+
seen = new WeakSet<object>(),
|
|
65
|
+
): { propName: string; path: string; embedded: boolean }[] => {
|
|
55
66
|
if (typeof value === 'string') {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
67
|
+
const results: { propName: string; path: string; embedded: boolean }[] = []
|
|
68
|
+
for (const [key, meta] of stringMeta) {
|
|
69
|
+
if (value.includes(key)) {
|
|
70
|
+
// embedded = spy marker was part of a longer string (e.g. `mailto:${email}`)
|
|
71
|
+
results.push({ propName: meta.propName, path: meta.path, embedded: value !== key })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return results
|
|
75
|
+
}
|
|
76
|
+
if (typeof value === 'number') {
|
|
77
|
+
const meta = numberMeta.get(value)
|
|
78
|
+
return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === 'function') {
|
|
81
|
+
const meta = fnMeta.get(value as (...args: unknown[]) => unknown)
|
|
82
|
+
return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
|
|
62
83
|
}
|
|
63
84
|
if (value && typeof value === 'object') {
|
|
64
85
|
// Prevent infinite recursion from circular references
|
|
@@ -79,8 +100,8 @@ export function createPropTrackerExtractor(): {
|
|
|
79
100
|
name: 'prop-tracker',
|
|
80
101
|
|
|
81
102
|
onBeforeRender(context: RenderContext): void {
|
|
82
|
-
|
|
83
|
-
context.props =
|
|
103
|
+
resetMockCounter()
|
|
104
|
+
context.props = generateMockProps(context.componentInfo, registrar)
|
|
84
105
|
},
|
|
85
106
|
|
|
86
107
|
onCreateElement(event: CreateElementEvent): void {
|
|
@@ -97,18 +118,18 @@ export function createPropTrackerExtractor(): {
|
|
|
97
118
|
|
|
98
119
|
spies.forEach((spy) => {
|
|
99
120
|
boundProps.add(spy.propName)
|
|
100
|
-
const path = spy.path
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!stores.writes.has(path)) {
|
|
104
|
-
stores.writes.set(path, entry)
|
|
121
|
+
const path = spy.path.replace(/\[\d+\]/g, '')
|
|
122
|
+
if (!stores.propUsages.has(path)) {
|
|
123
|
+
stores.propUsages.set(path, { elements: new Map(), attributes: new Map() })
|
|
105
124
|
}
|
|
125
|
+
const entry = stores.propUsages.get(path)!
|
|
106
126
|
|
|
107
127
|
entry.elements.set(traceId, { tag, elementId: traceId })
|
|
108
|
-
|
|
128
|
+
const isConcat = spies.length > 1 || spy.embedded
|
|
129
|
+
entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated: isConcat })
|
|
109
130
|
|
|
110
131
|
// Track concatenated attributes
|
|
111
|
-
if (
|
|
132
|
+
if (isConcat) {
|
|
112
133
|
concatenatedAttrs.set(key, spy.propName)
|
|
113
134
|
}
|
|
114
135
|
})
|
|
@@ -127,6 +148,6 @@ export function createPropTrackerExtractor(): {
|
|
|
127
148
|
|
|
128
149
|
return {
|
|
129
150
|
extractor,
|
|
130
|
-
state: { stores
|
|
151
|
+
state: { stores },
|
|
131
152
|
}
|
|
132
153
|
}
|
|
@@ -42,13 +42,6 @@ export type {
|
|
|
42
42
|
CoupledProp,
|
|
43
43
|
TrackingStores,
|
|
44
44
|
DOMBinding,
|
|
45
|
-
PropReadInfo,
|
|
46
45
|
PropWriteInfo,
|
|
47
46
|
PropSpyMeta,
|
|
48
47
|
} from './types'
|
|
49
|
-
|
|
50
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
-
// Utility Exports
|
|
52
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
export type { PropSpyContext } from './utils/prop-spy'
|
|
@@ -9,41 +9,23 @@ import type { ExtractedElement } from './extractors/core/tree-builder'
|
|
|
9
9
|
// Prop Spy (for tracking prop access)
|
|
10
10
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
export const PROP_SPY_SYMBOL = Symbol.for('__prop_spy__')
|
|
13
|
-
|
|
14
12
|
export interface PropSpyMeta {
|
|
15
13
|
path: string
|
|
16
14
|
propName: string
|
|
17
|
-
uniqueId: string
|
|
18
15
|
originalValue: unknown
|
|
19
16
|
}
|
|
20
17
|
|
|
21
|
-
export interface PropSpy<T = unknown> {
|
|
22
|
-
[PROP_SPY_SYMBOL]: true
|
|
23
|
-
__meta: PropSpyMeta
|
|
24
|
-
valueOf: () => T
|
|
25
|
-
toString: () => string
|
|
26
|
-
toJSON: () => T
|
|
27
|
-
[Symbol.toPrimitive]?: (hint: string) => T | string | number
|
|
28
|
-
}
|
|
29
|
-
|
|
30
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
19
|
// Tracking Stores
|
|
32
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
21
|
|
|
34
|
-
export interface PropReadInfo {
|
|
35
|
-
components: Set<string>
|
|
36
|
-
value: unknown
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
export interface PropWriteInfo {
|
|
40
23
|
elements: Map<string, { tag: string; elementId: string }>
|
|
41
24
|
attributes: Map<string, { attr: string; concatenated: boolean }>
|
|
42
25
|
}
|
|
43
26
|
|
|
44
27
|
export interface TrackingStores {
|
|
45
|
-
|
|
46
|
-
writes: Map<string, PropWriteInfo>
|
|
28
|
+
propUsages: Map<string, PropWriteInfo>
|
|
47
29
|
}
|
|
48
30
|
|
|
49
31
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -58,7 +40,8 @@ export interface DOMBinding {
|
|
|
58
40
|
}
|
|
59
41
|
|
|
60
42
|
export interface CoupledProp extends PropInfo {
|
|
61
|
-
|
|
43
|
+
/** Full stores.propUsages key for this prop, e.g. "props.linkUrl". */
|
|
44
|
+
propPath: string
|
|
62
45
|
logicOnly: boolean
|
|
63
46
|
}
|
|
64
47
|
|
|
@@ -67,4 +50,5 @@ export interface CoupledComponentInfo {
|
|
|
67
50
|
props: Record<string, CoupledProp>
|
|
68
51
|
elements: ExtractedElement[]
|
|
69
52
|
innerElementProps?: Map<string, Record<string, CoupledProp>>
|
|
53
|
+
propUsages: TrackingStores['propUsages']
|
|
70
54
|
}
|
|
@@ -6,21 +6,57 @@
|
|
|
6
6
|
import { faker } from '@faker-js/faker'
|
|
7
7
|
import type { ComponentInfo, DefaultValue, PropInfo, ResolvedType } from '../../ts/types'
|
|
8
8
|
|
|
9
|
+
export const PRESETS_WRAPPER_CLASS_NAME = 'mock-presets-wrapper-probe'
|
|
10
|
+
|
|
11
|
+
// Unique primes used as traceable number values. Each rendered number prop gets
|
|
12
|
+
// one; the value is distinct enough to be identified in DOM attributes later.
|
|
13
|
+
const TRACEABLE_PRIMES = [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
|
|
14
|
+
const FALLBACK_NUMBER_BASE = 101
|
|
15
|
+
let primeIndex = 0
|
|
16
|
+
|
|
17
|
+
function nextTraceableNumber(): number {
|
|
18
|
+
const withinPrimeList = primeIndex < TRACEABLE_PRIMES.length
|
|
19
|
+
if (withinPrimeList) {
|
|
20
|
+
return TRACEABLE_PRIMES[primeIndex++]
|
|
21
|
+
}
|
|
22
|
+
const overflowOffset = primeIndex++ - TRACEABLE_PRIMES.length
|
|
23
|
+
return FALLBACK_NUMBER_BASE + overflowOffset
|
|
24
|
+
}
|
|
25
|
+
|
|
9
26
|
/**
|
|
10
27
|
* Reset faker's seed and internal state for reproducible results
|
|
11
28
|
*/
|
|
12
29
|
export function resetMockCounter(): void {
|
|
13
30
|
faker.seed(42)
|
|
31
|
+
primeIndex = 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Narrow interface for registering spy markers during mock generation.
|
|
36
|
+
* Satisfied structurally by PropSpyContext.
|
|
37
|
+
*/
|
|
38
|
+
export interface PropSpyRegistrar {
|
|
39
|
+
registerString(path: string, propName: string, value: string): void
|
|
40
|
+
registerNumber(path: string, propName: string, value: number): void
|
|
41
|
+
registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void
|
|
14
42
|
}
|
|
15
43
|
|
|
16
44
|
/**
|
|
17
|
-
* Generate mock props object from ComponentInfo
|
|
45
|
+
* Generate mock props object from ComponentInfo.
|
|
46
|
+
* When a registrar is provided, string and number values are spy-instrumented
|
|
47
|
+
* for DOM binding detection.
|
|
18
48
|
*/
|
|
19
|
-
export function generateMockProps(componentInfo: ComponentInfo): Record<string, unknown> {
|
|
49
|
+
export function generateMockProps(componentInfo: ComponentInfo, registrar?: PropSpyRegistrar): Record<string, unknown> {
|
|
20
50
|
const mockProps: Record<string, unknown> = {}
|
|
21
51
|
|
|
22
52
|
for (const [propName, propInfo] of Object.entries(componentInfo.props)) {
|
|
23
|
-
mockProps[propName] = generateMockValue(propInfo, propName)
|
|
53
|
+
mockProps[propName] = generateMockValue(propInfo, propName, `props.${propName}`, registrar)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Always inject wix renderer object; components that don't use it will ignore it
|
|
57
|
+
mockProps.wix = {
|
|
58
|
+
elementsRemovalState: {},
|
|
59
|
+
presetsWrapperProps: { className: PRESETS_WRAPPER_CLASS_NAME },
|
|
24
60
|
}
|
|
25
61
|
|
|
26
62
|
return mockProps
|
|
@@ -29,13 +65,13 @@ export function generateMockProps(componentInfo: ComponentInfo): Record<string,
|
|
|
29
65
|
/**
|
|
30
66
|
* Generate a mock value based on PropInfo
|
|
31
67
|
*/
|
|
32
|
-
function generateMockValue(propInfo: PropInfo, propName: string): unknown {
|
|
33
|
-
//
|
|
34
|
-
if (propInfo.defaultValue !== undefined) {
|
|
68
|
+
function generateMockValue(propInfo: PropInfo, propName: string, path: string, registrar?: PropSpyRegistrar): unknown {
|
|
69
|
+
// In plain mode (no registrar), honour default values for realistic rendering
|
|
70
|
+
if (!registrar && propInfo.defaultValue !== undefined) {
|
|
35
71
|
return extractDefaultValueValue(propInfo.defaultValue)
|
|
36
72
|
}
|
|
37
73
|
|
|
38
|
-
return generateValueFromResolvedType(propInfo.resolvedType, propName)
|
|
74
|
+
return generateValueFromResolvedType(propInfo.resolvedType, propName, path, registrar)
|
|
39
75
|
}
|
|
40
76
|
|
|
41
77
|
/**
|
|
@@ -57,10 +93,15 @@ function extractDefaultValueValue(defaultValue: DefaultValue): unknown {
|
|
|
57
93
|
/**
|
|
58
94
|
* Generate a mock value from a ResolvedType
|
|
59
95
|
*/
|
|
60
|
-
function generateValueFromResolvedType(
|
|
96
|
+
function generateValueFromResolvedType(
|
|
97
|
+
resolvedType: ResolvedType,
|
|
98
|
+
propName: string,
|
|
99
|
+
path: string,
|
|
100
|
+
registrar?: PropSpyRegistrar,
|
|
101
|
+
): unknown {
|
|
61
102
|
const kind = resolvedType.kind
|
|
62
103
|
|
|
63
|
-
// Handle semantic types (from React or Wix packages)
|
|
104
|
+
// Handle semantic types (from React or Wix packages) — returned as plain objects
|
|
64
105
|
if (kind === 'semantic') {
|
|
65
106
|
return generateSemanticValue(resolvedType.value as string, propName)
|
|
66
107
|
}
|
|
@@ -68,28 +109,28 @@ function generateValueFromResolvedType(resolvedType: ResolvedType, propName: str
|
|
|
68
109
|
// Handle structural types
|
|
69
110
|
switch (kind) {
|
|
70
111
|
case 'primitive':
|
|
71
|
-
return generatePrimitiveValue(resolvedType.value as string, propName)
|
|
112
|
+
return generatePrimitiveValue(resolvedType.value as string, propName, path, registrar)
|
|
72
113
|
|
|
73
114
|
case 'literal':
|
|
74
115
|
return resolvedType.value
|
|
75
116
|
|
|
76
117
|
case 'union':
|
|
77
|
-
return generateUnionValue(resolvedType, propName)
|
|
118
|
+
return generateUnionValue(resolvedType, propName, path, registrar)
|
|
78
119
|
|
|
79
120
|
case 'intersection':
|
|
80
|
-
return generateIntersectionValue(resolvedType, propName)
|
|
121
|
+
return generateIntersectionValue(resolvedType, propName, path, registrar)
|
|
81
122
|
|
|
82
123
|
case 'array':
|
|
83
|
-
return generateArrayValue(resolvedType, propName)
|
|
124
|
+
return generateArrayValue(resolvedType, propName, path, registrar)
|
|
84
125
|
|
|
85
126
|
case 'object':
|
|
86
|
-
return generateObjectValue(resolvedType, propName)
|
|
127
|
+
return generateObjectValue(resolvedType, propName, path, registrar)
|
|
87
128
|
|
|
88
129
|
case 'enum':
|
|
89
130
|
return generateEnumValue(resolvedType)
|
|
90
131
|
|
|
91
132
|
case 'function':
|
|
92
|
-
return generateMockFunction(propName)
|
|
133
|
+
return generateMockFunction(propName, path, registrar)
|
|
93
134
|
|
|
94
135
|
default:
|
|
95
136
|
// Default to a string for unknown types
|
|
@@ -155,12 +196,23 @@ function generateSemanticValue(semanticType: string, propName: string): unknown
|
|
|
155
196
|
/**
|
|
156
197
|
* Generate a primitive value
|
|
157
198
|
*/
|
|
158
|
-
function generatePrimitiveValue(
|
|
199
|
+
function generatePrimitiveValue(
|
|
200
|
+
primitiveType: string,
|
|
201
|
+
propName: string,
|
|
202
|
+
path: string,
|
|
203
|
+
registrar?: PropSpyRegistrar,
|
|
204
|
+
): unknown {
|
|
159
205
|
switch (primitiveType) {
|
|
160
|
-
case 'string':
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return
|
|
206
|
+
case 'string': {
|
|
207
|
+
const raw = `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
208
|
+
if (registrar) registrar.registerString(path, propName, raw)
|
|
209
|
+
return raw
|
|
210
|
+
}
|
|
211
|
+
case 'number': {
|
|
212
|
+
const val = nextTraceableNumber()
|
|
213
|
+
if (registrar) registrar.registerNumber(path, propName, val)
|
|
214
|
+
return val
|
|
215
|
+
}
|
|
164
216
|
case 'boolean':
|
|
165
217
|
return faker.datatype.boolean()
|
|
166
218
|
case 'null':
|
|
@@ -175,7 +227,12 @@ function generatePrimitiveValue(primitiveType: string, propName: string): unknow
|
|
|
175
227
|
/**
|
|
176
228
|
* Generate a value from a union type (pick the first non-null option)
|
|
177
229
|
*/
|
|
178
|
-
function generateUnionValue(
|
|
230
|
+
function generateUnionValue(
|
|
231
|
+
resolvedType: ResolvedType,
|
|
232
|
+
propName: string,
|
|
233
|
+
path: string,
|
|
234
|
+
registrar?: PropSpyRegistrar,
|
|
235
|
+
): unknown {
|
|
179
236
|
const types = resolvedType.types ?? []
|
|
180
237
|
|
|
181
238
|
// Prefer string literals for textEnum-like unions
|
|
@@ -186,7 +243,7 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
|
|
|
186
243
|
|
|
187
244
|
// Otherwise, use the first type
|
|
188
245
|
if (types.length > 0) {
|
|
189
|
-
return generateValueFromResolvedType(types[0], propName)
|
|
246
|
+
return generateValueFromResolvedType(types[0], propName, path, registrar)
|
|
190
247
|
}
|
|
191
248
|
|
|
192
249
|
return `mock_${propName}_${faker.string.alphanumeric(6)}`
|
|
@@ -195,14 +252,19 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
|
|
|
195
252
|
/**
|
|
196
253
|
* Generate a value from an intersection type (merge object properties)
|
|
197
254
|
*/
|
|
198
|
-
function generateIntersectionValue(
|
|
255
|
+
function generateIntersectionValue(
|
|
256
|
+
resolvedType: ResolvedType,
|
|
257
|
+
propName: string,
|
|
258
|
+
path: string,
|
|
259
|
+
registrar?: PropSpyRegistrar,
|
|
260
|
+
): unknown {
|
|
199
261
|
const types = resolvedType.types ?? []
|
|
200
262
|
const merged: Record<string, unknown> = {}
|
|
201
263
|
|
|
202
264
|
for (const type of types) {
|
|
203
265
|
if (type.kind === 'object' && type.properties) {
|
|
204
266
|
for (const [key, propInfo] of Object.entries(type.properties)) {
|
|
205
|
-
merged[key] = generateMockValue(propInfo, `${propName}.${key}
|
|
267
|
+
merged[key] = generateMockValue(propInfo, `${propName}.${key}`, `${path}.${key}`, registrar)
|
|
206
268
|
}
|
|
207
269
|
}
|
|
208
270
|
}
|
|
@@ -213,29 +275,38 @@ function generateIntersectionValue(resolvedType: ResolvedType, propName: string)
|
|
|
213
275
|
/**
|
|
214
276
|
* Generate an array value
|
|
215
277
|
*/
|
|
216
|
-
function generateArrayValue(
|
|
278
|
+
function generateArrayValue(
|
|
279
|
+
resolvedType: ResolvedType,
|
|
280
|
+
propName: string,
|
|
281
|
+
path: string,
|
|
282
|
+
registrar?: PropSpyRegistrar,
|
|
283
|
+
): unknown[] {
|
|
217
284
|
const elementType = resolvedType.elementType
|
|
218
285
|
|
|
219
286
|
if (!elementType) {
|
|
220
287
|
return [`mock_${propName}[0]_${faker.string.alphanumeric(6)}`]
|
|
221
288
|
}
|
|
222
289
|
|
|
223
|
-
// Generate 2-3 items for the array
|
|
224
290
|
return [
|
|
225
|
-
generateValueFromResolvedType(elementType, `${propName}[0]
|
|
226
|
-
generateValueFromResolvedType(elementType, `${propName}[1]
|
|
291
|
+
generateValueFromResolvedType(elementType, `${propName}[0]`, `${path}[0]`, registrar),
|
|
292
|
+
generateValueFromResolvedType(elementType, `${propName}[1]`, `${path}[1]`, registrar),
|
|
227
293
|
]
|
|
228
294
|
}
|
|
229
295
|
|
|
230
296
|
/**
|
|
231
297
|
* Generate an object value from properties
|
|
232
298
|
*/
|
|
233
|
-
function generateObjectValue(
|
|
299
|
+
function generateObjectValue(
|
|
300
|
+
resolvedType: ResolvedType,
|
|
301
|
+
propName: string,
|
|
302
|
+
path: string,
|
|
303
|
+
registrar?: PropSpyRegistrar,
|
|
304
|
+
): Record<string, unknown> {
|
|
234
305
|
const properties = resolvedType.properties ?? {}
|
|
235
306
|
const obj: Record<string, unknown> = {}
|
|
236
307
|
|
|
237
308
|
for (const [key, propInfo] of Object.entries(properties)) {
|
|
238
|
-
obj[key] = generateMockValue(propInfo, `${
|
|
309
|
+
obj[key] = generateMockValue(propInfo, key, `${path}.${key}`, registrar)
|
|
239
310
|
}
|
|
240
311
|
|
|
241
312
|
return obj
|
|
@@ -258,11 +329,16 @@ function generateEnumValue(resolvedType: ResolvedType): unknown {
|
|
|
258
329
|
/**
|
|
259
330
|
* Generate a mock function
|
|
260
331
|
*/
|
|
261
|
-
function generateMockFunction(
|
|
332
|
+
function generateMockFunction(
|
|
333
|
+
propName: string,
|
|
334
|
+
path: string,
|
|
335
|
+
registrar?: PropSpyRegistrar,
|
|
336
|
+
): (...args: unknown[]) => void {
|
|
262
337
|
const fn = function mockFn(): void {
|
|
263
338
|
// No-op mock function
|
|
264
339
|
}
|
|
265
340
|
Object.defineProperty(fn, 'name', { value: `mock_${propName}` })
|
|
341
|
+
if (registrar) registrar.registerFunction(path, propName, fn)
|
|
266
342
|
return fn
|
|
267
343
|
}
|
|
268
344
|
|
package/src/manifest-pipeline.ts
CHANGED
|
@@ -109,6 +109,7 @@ export function processComponent(
|
|
|
109
109
|
props: coupledProps,
|
|
110
110
|
elements: convertElements(extractedElements),
|
|
111
111
|
innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
|
|
112
|
+
propUsages: state.stores.propUsages,
|
|
112
113
|
}
|
|
113
114
|
} catch (error) {
|
|
114
115
|
warnings.push({
|
|
@@ -128,9 +129,13 @@ export function processComponent(
|
|
|
128
129
|
enhancedInfo = {
|
|
129
130
|
componentName: componentInfo.componentName,
|
|
130
131
|
props: Object.fromEntries(
|
|
131
|
-
Object.entries(componentInfo.props).map(([name, info]) => [
|
|
132
|
+
Object.entries(componentInfo.props).map(([name, info]) => [
|
|
133
|
+
name,
|
|
134
|
+
{ ...info, logicOnly: false, propPath: `props.${name}` },
|
|
135
|
+
]),
|
|
132
136
|
),
|
|
133
137
|
elements: [],
|
|
138
|
+
propUsages: new Map(),
|
|
134
139
|
}
|
|
135
140
|
}
|
|
136
141
|
|
|
@@ -175,14 +180,12 @@ function buildCoupledProps(
|
|
|
175
180
|
const result: Record<string, CoupledProp> = {}
|
|
176
181
|
|
|
177
182
|
for (const [name, info] of Object.entries(componentInfo.props)) {
|
|
178
|
-
const
|
|
179
|
-
const wasRead = stores.reads.has(path)
|
|
180
|
-
const writeInfo = stores.writes.get(path)
|
|
183
|
+
const topLevelWriteInfo = stores.propUsages.get(`props.${name}`)
|
|
181
184
|
|
|
182
185
|
result[name] = {
|
|
183
186
|
...info,
|
|
184
|
-
logicOnly:
|
|
185
|
-
|
|
187
|
+
logicOnly: !topLevelWriteInfo,
|
|
188
|
+
propPath: `props.${name}`,
|
|
186
189
|
}
|
|
187
190
|
}
|
|
188
191
|
|
|
@@ -194,7 +197,7 @@ function buildCoupledProps(
|
|
|
194
197
|
const ELEMENT_PROPS_PREFIX = 'props.elementProps.'
|
|
195
198
|
|
|
196
199
|
/**
|
|
197
|
-
* Processes stores.
|
|
200
|
+
* Processes stores.propUsages entries with paths starting with "props.elementProps."
|
|
198
201
|
* to extract inner element prop bindings grouped by elementId (traceId).
|
|
199
202
|
*/
|
|
200
203
|
function processElementPropsWrites(
|
|
@@ -206,7 +209,7 @@ function processElementPropsWrites(
|
|
|
206
209
|
const elementPropsInfo = componentInfo.props.elementProps
|
|
207
210
|
if (!elementPropsInfo) return result
|
|
208
211
|
|
|
209
|
-
for (const [path, writeInfo] of stores.
|
|
212
|
+
for (const [path, writeInfo] of stores.propUsages) {
|
|
210
213
|
if (!path.startsWith(ELEMENT_PROPS_PREFIX)) continue
|
|
211
214
|
|
|
212
215
|
// Resolve the PropInfo for this leaf prop by walking the type tree
|
|
@@ -230,10 +233,9 @@ function processElementPropsWrites(
|
|
|
230
233
|
propsForElement[leafName] = {
|
|
231
234
|
...propInfo,
|
|
232
235
|
logicOnly: false,
|
|
233
|
-
|
|
236
|
+
propPath: path,
|
|
234
237
|
}
|
|
235
238
|
}
|
|
236
|
-
propsForElement[leafName].bindings.push(binding)
|
|
237
239
|
}
|
|
238
240
|
}
|
|
239
241
|
|
|
@@ -268,7 +270,9 @@ function resolveInnerPropInfo(elementPropsPropInfo: PropInfo, relativePath: stri
|
|
|
268
270
|
return null
|
|
269
271
|
}
|
|
270
272
|
|
|
271
|
-
function extractBindings(
|
|
273
|
+
function extractBindings(
|
|
274
|
+
writeInfo: TrackingStores['propUsages'] extends Map<string, infer V> ? V : never,
|
|
275
|
+
): DOMBinding[] {
|
|
272
276
|
const bindings: DOMBinding[] = []
|
|
273
277
|
|
|
274
278
|
for (const [key, attrInfo] of writeInfo.attributes) {
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { PropSpyMeta, TrackingStores } from '../types';
|
|
2
|
-
export interface PropSpyContext {
|
|
3
|
-
createAuditedProps: <T extends object | null>(target: T, stores: TrackingStores, getComponent: () => string, basePath?: string) => T;
|
|
4
|
-
getSpyMetadataByUniqueId: (id: string) => PropSpyMeta | null;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Creates an encapsulated prop spy context.
|
|
8
|
-
* Each context has its own ID counter and metadata map, avoiding global state.
|
|
9
|
-
*/
|
|
10
|
-
export declare function createPropSpyContext(): PropSpyContext;
|