@wix/zero-config-implementation 1.71.0 → 1.72.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/{index-DT1jTZEG.js → index-CSi4SZpQ.js} +1 -1
- package/dist/{index-C4cP1dwy.js → index-CdHu4-o-.js} +20459 -18722
- package/dist/index.d.ts +15 -8
- package/dist/index.js +1 -1
- package/package.json +4 -3
- package/src/__fixtures__/cjs-ref-element-entry.cjs +1 -0
- package/src/__fixtures__/cjs-ref-element-lazy-entry.cjs +3 -0
- package/src/__fixtures__/cjs-ref-element-order-entry.cjs +8 -0
- package/src/__fixtures__/cjs-ref-element-order-target.cjs +7 -0
- package/src/__fixtures__/cjs-ref-element-target.cjs +5 -0
- package/src/__fixtures__/esm-ref-element-entry.mjs +3 -0
- package/src/component-renderer.ts +12 -9
- package/src/converters/to-editor-component.ts +24 -6
- package/src/index.ts +121 -54
- package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -3
- package/src/information-extractors/react/extractors/core/types.ts +4 -5
- package/src/information-extractors/react/extractors/css-properties.ts +2 -0
- package/src/information-extractors/react/extractors/prop-tracker.test.ts +251 -0
- package/src/information-extractors/react/extractors/prop-tracker.ts +78 -14
- package/src/information-extractors/react/types.ts +1 -1
- package/src/information-extractors/react/utils/mock-generator.test.ts +131 -0
- package/src/information-extractors/react/utils/mock-generator.ts +38 -13
- package/src/manifest-pipeline.ts +19 -14
- package/src/module-loader.test.ts +150 -0
- package/src/module-loader.ts +48 -8
- package/src/react-runtime-interceptor.ts +64 -0
- package/src/react-runtime-loader.ts +656 -0
- package/src/ref-elements/component-tag.ts +105 -0
- package/src/ref-elements/context.test.ts +179 -0
- package/src/ref-elements/context.ts +280 -0
- package/src/ref-elements/eligible-paths.ts +45 -0
- package/src/ref-elements/module-resolution.test.ts +50 -0
- package/src/ref-elements/module-resolution.ts +21 -0
- package/src/ref-elements/module-specifier.ts +17 -0
- package/src/ref-elements/path-utils.test.ts +14 -0
- package/src/ref-elements/path-utils.ts +55 -0
- package/src/ref-elements/types.ts +17 -0
- package/src/utils/css-class.ts +15 -0
- package/src/jsx-runtime-interceptor.ts +0 -245
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createRefElementMarker } from '../../../ref-elements/path-utils'
|
|
3
|
+
import type { RefElementMatch } from '../../../ref-elements/types'
|
|
4
|
+
import { ExtractorStore } from './core/store'
|
|
5
|
+
import { createPropTrackerExtractor } from './prop-tracker'
|
|
6
|
+
|
|
7
|
+
function markRefElementComponent(runtimeComponent: (...args: unknown[]) => unknown, refComponentType: string): void {
|
|
8
|
+
Object.defineProperty(
|
|
9
|
+
runtimeComponent as unknown as Record<PropertyKey, unknown>,
|
|
10
|
+
Symbol.for('zero-config:ref-element-component-type'),
|
|
11
|
+
{
|
|
12
|
+
value: refComponentType,
|
|
13
|
+
},
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('createPropTrackerExtractor', () => {
|
|
18
|
+
it('associates a ref-capable composite component with the rendered DOM trace id', () => {
|
|
19
|
+
const store = new ExtractorStore()
|
|
20
|
+
const runtimeRefComponent = function LocalButton(): null {
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
markRefElementComponent(runtimeRefComponent, 'myPkg.LocalButton')
|
|
24
|
+
const { extractor } = createPropTrackerExtractor({
|
|
25
|
+
eligiblePaths: ['actionButton'],
|
|
26
|
+
})
|
|
27
|
+
const taggedProps = { id: createRefElementMarker('actionButton') }
|
|
28
|
+
|
|
29
|
+
extractor.onCreateElement?.({
|
|
30
|
+
type: runtimeRefComponent,
|
|
31
|
+
isDomElement: false,
|
|
32
|
+
props: taggedProps,
|
|
33
|
+
children: [],
|
|
34
|
+
store,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
extractor.onCreateElement?.({
|
|
38
|
+
type: 'button',
|
|
39
|
+
isDomElement: true,
|
|
40
|
+
tag: 'button',
|
|
41
|
+
traceId: 'trace-1',
|
|
42
|
+
props: {
|
|
43
|
+
...taggedProps,
|
|
44
|
+
className: 'action-button action-button--accent',
|
|
45
|
+
},
|
|
46
|
+
children: [],
|
|
47
|
+
store,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
expect(store.get<RefElementMatch>('trace-1', 'ref-element')).toEqual({
|
|
51
|
+
elementPropsPath: 'actionButton',
|
|
52
|
+
refComponentType: 'myPkg.LocalButton',
|
|
53
|
+
selector: '.action-button',
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('uses the marker from the final id value that reaches the rendered element', () => {
|
|
58
|
+
const store = new ExtractorStore()
|
|
59
|
+
const runtimeRefComponent = function LocalButton(): null {
|
|
60
|
+
return null
|
|
61
|
+
}
|
|
62
|
+
markRefElementComponent(runtimeRefComponent, 'myPkg.LocalButton')
|
|
63
|
+
const { extractor } = createPropTrackerExtractor({
|
|
64
|
+
eligiblePaths: ['primaryAction', 'secondaryAction'],
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
extractor.onCreateElement?.({
|
|
68
|
+
type: runtimeRefComponent,
|
|
69
|
+
isDomElement: false,
|
|
70
|
+
props: { id: createRefElementMarker('secondaryAction') },
|
|
71
|
+
children: [],
|
|
72
|
+
store,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
extractor.onCreateElement?.({
|
|
76
|
+
type: 'button',
|
|
77
|
+
isDomElement: true,
|
|
78
|
+
tag: 'button',
|
|
79
|
+
traceId: 'trace-2',
|
|
80
|
+
props: {
|
|
81
|
+
id: createRefElementMarker('secondaryAction'),
|
|
82
|
+
className: 'action-button',
|
|
83
|
+
},
|
|
84
|
+
children: [],
|
|
85
|
+
store,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(store.get<RefElementMatch>('trace-2', 'ref-element')).toEqual({
|
|
89
|
+
elementPropsPath: 'secondaryAction',
|
|
90
|
+
refComponentType: 'myPkg.LocalButton',
|
|
91
|
+
selector: '.action-button',
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('waits for the first semantic DOM target before consuming a pending ref element', () => {
|
|
96
|
+
const store = new ExtractorStore()
|
|
97
|
+
const runtimeRefComponent = function LocalButton(): null {
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
markRefElementComponent(runtimeRefComponent, 'myPkg.LocalButton')
|
|
101
|
+
const { extractor } = createPropTrackerExtractor({
|
|
102
|
+
eligiblePaths: ['actionButton'],
|
|
103
|
+
})
|
|
104
|
+
const taggedProps = { id: createRefElementMarker('actionButton') }
|
|
105
|
+
|
|
106
|
+
extractor.onCreateElement?.({
|
|
107
|
+
type: runtimeRefComponent,
|
|
108
|
+
isDomElement: false,
|
|
109
|
+
props: taggedProps,
|
|
110
|
+
children: [],
|
|
111
|
+
store,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
extractor.onCreateElement?.({
|
|
115
|
+
type: 'div',
|
|
116
|
+
isDomElement: true,
|
|
117
|
+
tag: 'div',
|
|
118
|
+
traceId: 'trace-wrapper',
|
|
119
|
+
props: {
|
|
120
|
+
...taggedProps,
|
|
121
|
+
className: '_hashWrapper_123abc',
|
|
122
|
+
},
|
|
123
|
+
children: [],
|
|
124
|
+
store,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
extractor.onCreateElement?.({
|
|
128
|
+
type: 'button',
|
|
129
|
+
isDomElement: true,
|
|
130
|
+
tag: 'button',
|
|
131
|
+
traceId: 'trace-button',
|
|
132
|
+
props: {
|
|
133
|
+
...taggedProps,
|
|
134
|
+
className: 'action-button',
|
|
135
|
+
},
|
|
136
|
+
children: [],
|
|
137
|
+
store,
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
expect(store.get<RefElementMatch>('trace-wrapper', 'ref-element')).toBeUndefined()
|
|
141
|
+
expect(store.get<RefElementMatch>('trace-button', 'ref-element')).toEqual({
|
|
142
|
+
elementPropsPath: 'actionButton',
|
|
143
|
+
refComponentType: 'myPkg.LocalButton',
|
|
144
|
+
selector: '.action-button',
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('tracks repeated instances of the same ref-element marker in render order', () => {
|
|
149
|
+
const store = new ExtractorStore()
|
|
150
|
+
const runtimeRefComponent = function LocalButton(): null {
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
markRefElementComponent(runtimeRefComponent, 'myPkg.LocalButton')
|
|
154
|
+
const { extractor } = createPropTrackerExtractor({
|
|
155
|
+
eligiblePaths: ['actionButton'],
|
|
156
|
+
})
|
|
157
|
+
const taggedProps = { id: createRefElementMarker('actionButton') }
|
|
158
|
+
|
|
159
|
+
extractor.onCreateElement?.({
|
|
160
|
+
type: runtimeRefComponent,
|
|
161
|
+
isDomElement: false,
|
|
162
|
+
props: taggedProps,
|
|
163
|
+
children: [],
|
|
164
|
+
store,
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
extractor.onCreateElement?.({
|
|
168
|
+
type: runtimeRefComponent,
|
|
169
|
+
isDomElement: false,
|
|
170
|
+
props: taggedProps,
|
|
171
|
+
children: [],
|
|
172
|
+
store,
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
extractor.onCreateElement?.({
|
|
176
|
+
type: 'button',
|
|
177
|
+
isDomElement: true,
|
|
178
|
+
tag: 'button',
|
|
179
|
+
traceId: 'trace-first',
|
|
180
|
+
props: {
|
|
181
|
+
...taggedProps,
|
|
182
|
+
className: 'action-button',
|
|
183
|
+
},
|
|
184
|
+
children: [],
|
|
185
|
+
store,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
extractor.onCreateElement?.({
|
|
189
|
+
type: 'button',
|
|
190
|
+
isDomElement: true,
|
|
191
|
+
tag: 'button',
|
|
192
|
+
traceId: 'trace-second',
|
|
193
|
+
props: {
|
|
194
|
+
...taggedProps,
|
|
195
|
+
className: 'action-button action-button--secondary',
|
|
196
|
+
},
|
|
197
|
+
children: [],
|
|
198
|
+
store,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
expect(store.get<RefElementMatch>('trace-first', 'ref-element')).toEqual({
|
|
202
|
+
elementPropsPath: 'actionButton',
|
|
203
|
+
refComponentType: 'myPkg.LocalButton',
|
|
204
|
+
selector: '.action-button',
|
|
205
|
+
})
|
|
206
|
+
expect(store.get<RefElementMatch>('trace-second', 'ref-element')).toEqual({
|
|
207
|
+
elementPropsPath: 'actionButton',
|
|
208
|
+
refComponentType: 'myPkg.LocalButton',
|
|
209
|
+
selector: '.action-button',
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('preserves nested elementProps paths in ref-element runtime metadata', () => {
|
|
214
|
+
const store = new ExtractorStore()
|
|
215
|
+
const runtimeRefComponent = function LocalButton(): null {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
markRefElementComponent(runtimeRefComponent, 'myPkg.LocalButton')
|
|
219
|
+
const { extractor } = createPropTrackerExtractor({
|
|
220
|
+
eligiblePaths: ['card.elementProps.cta'],
|
|
221
|
+
})
|
|
222
|
+
const taggedProps = { id: createRefElementMarker('card.elementProps.cta') }
|
|
223
|
+
|
|
224
|
+
extractor.onCreateElement?.({
|
|
225
|
+
type: runtimeRefComponent,
|
|
226
|
+
isDomElement: false,
|
|
227
|
+
props: taggedProps,
|
|
228
|
+
children: [],
|
|
229
|
+
store,
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
extractor.onCreateElement?.({
|
|
233
|
+
type: 'button',
|
|
234
|
+
isDomElement: true,
|
|
235
|
+
tag: 'button',
|
|
236
|
+
traceId: 'trace-nested',
|
|
237
|
+
props: {
|
|
238
|
+
...taggedProps,
|
|
239
|
+
className: 'card-cta',
|
|
240
|
+
},
|
|
241
|
+
children: [],
|
|
242
|
+
store,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
expect(store.get<RefElementMatch>('trace-nested', 'ref-element')).toEqual({
|
|
246
|
+
elementPropsPath: 'card.elementProps.cta',
|
|
247
|
+
refComponentType: 'myPkg.LocalButton',
|
|
248
|
+
selector: '.card-cta',
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import type { HTMLAttributes } from 'react'
|
|
9
9
|
import { TRACE_ATTR } from '../../../component-renderer'
|
|
10
|
+
import { readRefElementComponentType } from '../../../ref-elements/component-tag'
|
|
11
|
+
import { parseRefElementMarker } from '../../../ref-elements/path-utils'
|
|
12
|
+
import type { RefElementMatch } from '../../../ref-elements/types'
|
|
13
|
+
import { findPreferredSemanticClass, normalizeClassNames } from '../../../utils/css-class'
|
|
10
14
|
import type { PropSpyMeta, TrackingStores } from '../types'
|
|
11
15
|
import { type PropSpyRegistrar, generateMockProps, resetMockCounter } from '../utils/mock-generator'
|
|
12
16
|
import type { CreateElementEvent, ReactExtractor, RenderContext } from './core/types'
|
|
@@ -28,6 +32,15 @@ export interface PropTrackerExtractorState {
|
|
|
28
32
|
stores: TrackingStores
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
interface CreatePropTrackerExtractorOptions {
|
|
36
|
+
eligiblePaths?: string[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PendingRefElement {
|
|
40
|
+
elementPropsPath: string
|
|
41
|
+
refComponentType: string
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
45
|
// Factory
|
|
33
46
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -36,9 +49,10 @@ export interface PropTrackerExtractorState {
|
|
|
36
49
|
* Creates a prop tracker extractor that:
|
|
37
50
|
* 1. Generates spy-instrumented mock props during beforeRender
|
|
38
51
|
* 2. Detects spy markers in element props during onCreateElement
|
|
39
|
-
* 3.
|
|
52
|
+
* 3. Tracks runtime refElement markers from composite components to rendered DOM
|
|
53
|
+
* 4. Writes prop usage data to the store namespaced by 'prop-tracker'
|
|
40
54
|
*/
|
|
41
|
-
export function createPropTrackerExtractor(): {
|
|
55
|
+
export function createPropTrackerExtractor(options?: CreatePropTrackerExtractorOptions): {
|
|
42
56
|
extractor: ReactExtractor
|
|
43
57
|
state: PropTrackerExtractorState
|
|
44
58
|
} {
|
|
@@ -48,6 +62,8 @@ export function createPropTrackerExtractor(): {
|
|
|
48
62
|
const stores: TrackingStores = {
|
|
49
63
|
propUsages: new Map(),
|
|
50
64
|
}
|
|
65
|
+
const eligiblePaths = new Set(options?.eligiblePaths ?? [])
|
|
66
|
+
const pendingRefElementsByPath = new Map<string, PendingRefElement[]>()
|
|
51
67
|
|
|
52
68
|
const registrar: PropSpyRegistrar = {
|
|
53
69
|
registerString(path, propName, value) {
|
|
@@ -84,16 +100,14 @@ export function createPropTrackerExtractor(): {
|
|
|
84
100
|
return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
|
|
85
101
|
}
|
|
86
102
|
if (value && typeof value === 'object') {
|
|
87
|
-
// Prevent infinite recursion from circular references
|
|
88
103
|
if (seen.has(value as object)) {
|
|
89
104
|
return []
|
|
90
105
|
}
|
|
91
106
|
seen.add(value as object)
|
|
92
|
-
// Skip React elements (have $$typeof symbol) - they have circular refs
|
|
93
107
|
if ('$$typeof' in value) {
|
|
94
108
|
return []
|
|
95
109
|
}
|
|
96
|
-
return Object.values(value).flatMap((
|
|
110
|
+
return Object.values(value).flatMap((nestedValue) => extractSpies(nestedValue, seen))
|
|
97
111
|
}
|
|
98
112
|
return []
|
|
99
113
|
}
|
|
@@ -103,10 +117,19 @@ export function createPropTrackerExtractor(): {
|
|
|
103
117
|
|
|
104
118
|
onBeforeRender(context: RenderContext): void {
|
|
105
119
|
resetMockCounter()
|
|
106
|
-
context.props = generateMockProps(context.componentInfo, registrar
|
|
120
|
+
context.props = generateMockProps(context.componentInfo, registrar, {
|
|
121
|
+
eligibleRefElementPaths: eligiblePaths,
|
|
122
|
+
})
|
|
107
123
|
},
|
|
108
124
|
|
|
109
125
|
onCreateElement(event: CreateElementEvent): void {
|
|
126
|
+
if (!event.isDomElement) {
|
|
127
|
+
queuePendingRefElement(event.props.id, readRefElementComponentType(event.type))
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!event.tag || !event.traceId) return
|
|
132
|
+
|
|
110
133
|
const { tag, props, traceId, store } = event
|
|
111
134
|
const htmlProps = props as HTMLAttributes<unknown>
|
|
112
135
|
|
|
@@ -132,25 +155,24 @@ export function createPropTrackerExtractor(): {
|
|
|
132
155
|
const entry = stores.propUsages.get(path)!
|
|
133
156
|
|
|
134
157
|
entry.elements.set(traceId, { tag, elementId: traceId })
|
|
135
|
-
const
|
|
136
|
-
entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated:
|
|
158
|
+
const isConcatenatedAttribute = spies.length > 1 || spy.embedded
|
|
159
|
+
entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated: isConcatenatedAttribute })
|
|
137
160
|
|
|
138
|
-
|
|
139
|
-
if (isConcat) {
|
|
161
|
+
if (isConcatenatedAttribute) {
|
|
140
162
|
concatenatedAttrs.set(key, spy.propName)
|
|
141
163
|
}
|
|
142
164
|
})
|
|
143
165
|
})
|
|
144
166
|
|
|
145
|
-
|
|
146
|
-
const data: PropTrackerData = {
|
|
167
|
+
store.set(traceId, 'prop-tracker', {
|
|
147
168
|
tag,
|
|
148
169
|
role: htmlProps.role,
|
|
149
170
|
boundProps: [...boundProps],
|
|
150
171
|
concatenatedAttrs,
|
|
151
172
|
eventHandlers,
|
|
152
|
-
}
|
|
153
|
-
|
|
173
|
+
} satisfies PropTrackerData)
|
|
174
|
+
|
|
175
|
+
attachMatchedRefElement(traceId, props, store)
|
|
154
176
|
},
|
|
155
177
|
}
|
|
156
178
|
|
|
@@ -158,4 +180,46 @@ export function createPropTrackerExtractor(): {
|
|
|
158
180
|
extractor,
|
|
159
181
|
state: { stores },
|
|
160
182
|
}
|
|
183
|
+
|
|
184
|
+
function queuePendingRefElement(idValue: unknown, refComponentType: string | undefined): void {
|
|
185
|
+
const elementPropsPath = parseRefElementMarker(idValue)
|
|
186
|
+
if (!elementPropsPath || !refComponentType) return
|
|
187
|
+
if (eligiblePaths.size > 0 && !eligiblePaths.has(elementPropsPath)) return
|
|
188
|
+
|
|
189
|
+
const pendingRefElements = pendingRefElementsByPath.get(elementPropsPath) ?? []
|
|
190
|
+
pendingRefElements.push({ elementPropsPath, refComponentType })
|
|
191
|
+
pendingRefElementsByPath.set(elementPropsPath, pendingRefElements)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function attachMatchedRefElement(
|
|
195
|
+
traceId: string,
|
|
196
|
+
props: Record<string, unknown>,
|
|
197
|
+
store: CreateElementEvent['store'],
|
|
198
|
+
): void {
|
|
199
|
+
const elementPropsPath = parseRefElementMarker(props.id)
|
|
200
|
+
if (!elementPropsPath) return
|
|
201
|
+
|
|
202
|
+
const selector = getRefElementSelector(props.className)
|
|
203
|
+
if (!selector) return
|
|
204
|
+
|
|
205
|
+
const pendingRefElements = pendingRefElementsByPath.get(elementPropsPath)
|
|
206
|
+
const pendingRefElement = pendingRefElements?.shift()
|
|
207
|
+
if (!pendingRefElement) return
|
|
208
|
+
if (pendingRefElements?.length === 0) {
|
|
209
|
+
pendingRefElementsByPath.delete(elementPropsPath)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const refElementMatch: RefElementMatch = {
|
|
213
|
+
elementPropsPath: pendingRefElement.elementPropsPath,
|
|
214
|
+
refComponentType: pendingRefElement.refComponentType,
|
|
215
|
+
selector,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
store.set(traceId, 'ref-element', refElementMatch)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getRefElementSelector(classNameValue: unknown): string | undefined {
|
|
223
|
+
const semanticClassName = findPreferredSemanticClass(normalizeClassNames(classNameValue))
|
|
224
|
+
return semanticClassName ? `.${semanticClassName}` : undefined
|
|
161
225
|
}
|
|
@@ -49,6 +49,6 @@ export interface CoupledComponentInfo {
|
|
|
49
49
|
componentName: string
|
|
50
50
|
props: Record<string, CoupledProp>
|
|
51
51
|
elements: ExtractedElement[]
|
|
52
|
-
innerElementProps?: Map<string, Record<string, CoupledProp
|
|
52
|
+
innerElementProps?: Map<string, { elementPropsPath: string; props: Record<string, CoupledProp> }>
|
|
53
53
|
propUsages: TrackingStores['propUsages']
|
|
54
54
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { createRefElementMarker, parseRefElementMarker } from '../../../ref-elements/path-utils'
|
|
3
|
+
import type { ComponentInfo } from '../../ts/types'
|
|
4
|
+
import { generateMockProps, resetMockCounter } from './mock-generator'
|
|
5
|
+
|
|
6
|
+
describe('generateMockProps', () => {
|
|
7
|
+
it('injects a dedicated ref-element id marker into each eligible elementProps branch', () => {
|
|
8
|
+
resetMockCounter()
|
|
9
|
+
|
|
10
|
+
const componentInfo: ComponentInfo = {
|
|
11
|
+
componentName: 'TestComponent',
|
|
12
|
+
props: {
|
|
13
|
+
elementProps: {
|
|
14
|
+
name: 'elementProps',
|
|
15
|
+
type: 'ElementProps',
|
|
16
|
+
required: true,
|
|
17
|
+
resolvedType: {
|
|
18
|
+
kind: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
actionButton: {
|
|
21
|
+
name: 'actionButton',
|
|
22
|
+
type: '{ className?: string; label?: string }',
|
|
23
|
+
required: true,
|
|
24
|
+
resolvedType: {
|
|
25
|
+
kind: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
className: {
|
|
28
|
+
name: 'className',
|
|
29
|
+
type: 'string',
|
|
30
|
+
required: false,
|
|
31
|
+
resolvedType: { kind: 'primitive', value: 'string' },
|
|
32
|
+
},
|
|
33
|
+
label: {
|
|
34
|
+
name: 'label',
|
|
35
|
+
type: 'string',
|
|
36
|
+
required: false,
|
|
37
|
+
resolvedType: { kind: 'primitive', value: 'string' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mockProps = generateMockProps(componentInfo, undefined, {
|
|
49
|
+
eligibleRefElementPaths: new Set(['actionButton']),
|
|
50
|
+
})
|
|
51
|
+
expect(mockProps).toMatchObject({
|
|
52
|
+
elementProps: {
|
|
53
|
+
actionButton: {
|
|
54
|
+
id: createRefElementMarker('actionButton'),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const actionButtonProps = (mockProps.elementProps as Record<string, unknown>).actionButton as Record<
|
|
60
|
+
string,
|
|
61
|
+
unknown
|
|
62
|
+
>
|
|
63
|
+
expect(parseRefElementMarker(actionButtonProps.id)).toBe('actionButton')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('injects nested ref-element markers using the full elementProps path', () => {
|
|
67
|
+
resetMockCounter()
|
|
68
|
+
|
|
69
|
+
const componentInfo: ComponentInfo = {
|
|
70
|
+
componentName: 'NestedTestComponent',
|
|
71
|
+
props: {
|
|
72
|
+
elementProps: {
|
|
73
|
+
name: 'elementProps',
|
|
74
|
+
type: 'ElementProps',
|
|
75
|
+
required: true,
|
|
76
|
+
resolvedType: {
|
|
77
|
+
kind: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
card: {
|
|
80
|
+
name: 'card',
|
|
81
|
+
type: '{ elementProps: { cta: { className?: string } } }',
|
|
82
|
+
required: true,
|
|
83
|
+
resolvedType: {
|
|
84
|
+
kind: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
elementProps: {
|
|
87
|
+
name: 'elementProps',
|
|
88
|
+
type: '{ cta: { className?: string } }',
|
|
89
|
+
required: true,
|
|
90
|
+
resolvedType: {
|
|
91
|
+
kind: 'object',
|
|
92
|
+
properties: {
|
|
93
|
+
cta: {
|
|
94
|
+
name: 'cta',
|
|
95
|
+
type: '{ className?: string }',
|
|
96
|
+
required: true,
|
|
97
|
+
resolvedType: {
|
|
98
|
+
kind: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
className: {
|
|
101
|
+
name: 'className',
|
|
102
|
+
type: 'string',
|
|
103
|
+
required: false,
|
|
104
|
+
resolvedType: { kind: 'primitive', value: 'string' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const mockProps = generateMockProps(componentInfo, undefined, {
|
|
122
|
+
eligibleRefElementPaths: new Set(['card.elementProps.cta']),
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const cardProps = (mockProps.elementProps as Record<string, unknown>).card as Record<string, unknown>
|
|
126
|
+
const nestedElementProps = cardProps.elementProps as Record<string, unknown>
|
|
127
|
+
const ctaProps = nestedElementProps.cta as Record<string, unknown>
|
|
128
|
+
|
|
129
|
+
expect(parseRefElementMarker(ctaProps.id)).toBe('card.elementProps.cta')
|
|
130
|
+
})
|
|
131
|
+
})
|