@wix/zero-config-implementation 1.27.0 → 1.29.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/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.29.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",
|
|
@@ -83,5 +83,5 @@
|
|
|
83
83
|
]
|
|
84
84
|
}
|
|
85
85
|
},
|
|
86
|
-
"falconPackageHash": "
|
|
86
|
+
"falconPackageHash": "9f12ded214e8668f280831d8bb3e03f08b14e2a9a46a97c8a423c29b"
|
|
87
87
|
}
|
|
@@ -13,4 +13,28 @@ describe('formatDisplayName', () => {
|
|
|
13
13
|
it('formats camelCase', () => {
|
|
14
14
|
expect(formatDisplayName('camelCaseExample')).toBe('Camel Case Example')
|
|
15
15
|
})
|
|
16
|
+
|
|
17
|
+
it('returns name unchanged when exactly 50 characters', () => {
|
|
18
|
+
// 10 words of 4 chars + 9 spaces = 49 chars; add one more char to hit 50
|
|
19
|
+
const input = 'Word One Two Three Four Five Six Seven Eight Nine'
|
|
20
|
+
expect(input.length).toBe(49)
|
|
21
|
+
expect(formatDisplayName(input)).toBe(input)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('drops leading words when formatted name exceeds 50 characters', () => {
|
|
25
|
+
// capitalCase('myVeryLongComponentWithAnExtremelyLongDisplayName')
|
|
26
|
+
// => "My Very Long Component With An Extremely Long Display Name" (58 chars)
|
|
27
|
+
// Drop "My" => 55 chars, drop "Very" => "Long Component With An Extremely Long Display Name" (50 chars)
|
|
28
|
+
expect(formatDisplayName('myVeryLongComponentWithAnExtremelyLongDisplayName')).toBe(
|
|
29
|
+
'Long Component With An Extremely Long Display Name',
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('truncates at 50 characters when a single word exceeds the limit', () => {
|
|
34
|
+
// capitalCase of 51 repeated 'a' characters is a single word starting with uppercase
|
|
35
|
+
const longSingleWord = 'a'.repeat(51)
|
|
36
|
+
const expected = `A${'a'.repeat(49)}` // 50 chars total
|
|
37
|
+
expect(formatDisplayName(longSingleWord)).toBe(expected)
|
|
38
|
+
expect(formatDisplayName(longSingleWord).length).toBe(50)
|
|
39
|
+
})
|
|
16
40
|
})
|
package/src/converters/utils.ts
CHANGED
|
@@ -8,10 +8,14 @@ const DISPLAY_NAME_OVERRIDES = {
|
|
|
8
8
|
a11y: 'Accessibility',
|
|
9
9
|
} as const
|
|
10
10
|
|
|
11
|
+
const DISPLAY_NAME_MAX_LENGTH = 50
|
|
12
|
+
|
|
11
13
|
/**
|
|
12
14
|
* Formats any string format to Title Case display name
|
|
13
15
|
* Handles: kebab-case, camelCase, PascalCase, snake_case, SCREAMING_SNAKE_CASE, and mixed formats
|
|
14
16
|
*
|
|
17
|
+
* If the result exceeds 50 characters, leading words are dropped (end words are more specific).
|
|
18
|
+
*
|
|
15
19
|
* Examples:
|
|
16
20
|
* "input-field-weight" -> "Input Field Weight"
|
|
17
21
|
* "camelCaseExample" -> "Camel Case Example"
|
|
@@ -20,10 +24,20 @@ const DISPLAY_NAME_OVERRIDES = {
|
|
|
20
24
|
* "SCREAMING_SNAKE_CASE" -> "Screaming Snake Case"
|
|
21
25
|
* "mixed-format_example" -> "Mixed Format Example"
|
|
22
26
|
* "a11y" -> "Accessibility"
|
|
27
|
+
* "myVeryLongComponentWithAnExtremelyLongDisplayName" -> "With An Extremely Long Display Name"
|
|
23
28
|
*/
|
|
24
29
|
export function formatDisplayName(input: string): string {
|
|
25
30
|
if (input in DISPLAY_NAME_OVERRIDES) {
|
|
26
31
|
return DISPLAY_NAME_OVERRIDES[input as keyof typeof DISPLAY_NAME_OVERRIDES]
|
|
27
32
|
}
|
|
28
|
-
|
|
33
|
+
const formatted = capitalCase(input, { keepSpecialCharacters: false })
|
|
34
|
+
if (formatted.length <= DISPLAY_NAME_MAX_LENGTH) {
|
|
35
|
+
return formatted
|
|
36
|
+
}
|
|
37
|
+
const words = formatted.split(' ')
|
|
38
|
+
while (words.length > 1 && words.join(' ').length > DISPLAY_NAME_MAX_LENGTH) {
|
|
39
|
+
words.shift()
|
|
40
|
+
}
|
|
41
|
+
const result = words.join(' ')
|
|
42
|
+
return result.slice(0, DISPLAY_NAME_MAX_LENGTH)
|
|
29
43
|
}
|
|
@@ -12,6 +12,7 @@ import { findPreferredSemanticClass } from '../../../../utils/css-class'
|
|
|
12
12
|
import { PRESETS_WRAPPER_CLASS_NAME } from '../../utils/mock-generator'
|
|
13
13
|
import type { CssPropertiesData } from '../css-properties'
|
|
14
14
|
import { addTextProperties } from '../css-properties'
|
|
15
|
+
import type { PropTrackerData } from '../prop-tracker'
|
|
15
16
|
import type { ExtractorStore } from './store'
|
|
16
17
|
|
|
17
18
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -150,7 +151,7 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
|
|
|
150
151
|
const id = getAttribute(element, 'id')
|
|
151
152
|
// Skip spy-instrumented ids (mock_propName_XXXXXX) — they are runtime mock values,
|
|
152
153
|
// not semantic identifiers, and would produce garbage element names.
|
|
153
|
-
if (id && !id.
|
|
154
|
+
if (id && !id.includes('mock_')) {
|
|
154
155
|
return pascalCase(id)
|
|
155
156
|
}
|
|
156
157
|
|
|
@@ -179,6 +180,28 @@ function getElementNamePart(element: Element, getElementById: (id: string) => El
|
|
|
179
180
|
return normalizeTagName(element.tagName)
|
|
180
181
|
}
|
|
181
182
|
|
|
183
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
184
|
+
// Element Filtering
|
|
185
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns true if the element should be included in the tree:
|
|
189
|
+
* - has a BEM-semantic CSS class, OR
|
|
190
|
+
* - has a className prop propagated from the component's props
|
|
191
|
+
*/
|
|
192
|
+
function hasClassNameCriteria(element: Element, extractorData: Map<string, unknown>): boolean {
|
|
193
|
+
const classAttr = getAttribute(element, 'class')
|
|
194
|
+
if (classAttr) {
|
|
195
|
+
const semanticClass = findPreferredSemanticClass(classAttr.split(' '))
|
|
196
|
+
if (semanticClass) return true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const propTrackerData = extractorData.get('prop-tracker') as PropTrackerData | undefined
|
|
200
|
+
if (propTrackerData?.boundProps.includes('className')) return true
|
|
201
|
+
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
182
205
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
183
206
|
// Tree Building
|
|
184
207
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -234,15 +257,20 @@ export function buildElementTree(html: string, store: ExtractorStore): Extracted
|
|
|
234
257
|
|
|
235
258
|
// If this element has a traceId, it's a traced element
|
|
236
259
|
if (traceId) {
|
|
260
|
+
// Get extractor data from store
|
|
261
|
+
const extractorData = store.getAll(traceId) ?? new Map()
|
|
262
|
+
|
|
263
|
+
// Filter: only include non-root elements with semantic or propagated className
|
|
264
|
+
if (!isRoot && !hasClassNameCriteria(node, extractorData)) {
|
|
265
|
+
return node.childNodes.flatMap((child) => walkTree(child, ancestorPath, false))
|
|
266
|
+
}
|
|
267
|
+
|
|
237
268
|
// Compute this element's name part
|
|
238
269
|
const namePart = isRoot ? 'root' : getElementNamePart(node, getElementById)
|
|
239
270
|
|
|
240
271
|
// Full name is ancestor path + this element's name (no separator)
|
|
241
272
|
const name = isRoot ? 'root' : ancestorPath + namePart
|
|
242
273
|
|
|
243
|
-
// Get extractor data from store
|
|
244
|
-
const extractorData = store.getAll(traceId) ?? new Map()
|
|
245
|
-
|
|
246
274
|
// Check for text content
|
|
247
275
|
const hasText = hasDirectTextContent(node)
|
|
248
276
|
|