@wix/zero-config-implementation 1.99.0 → 1.101.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.js +9879 -9791
- package/package.json +2 -2
- package/src/converters/css-longhand-resolver.test.ts +35 -0
- package/src/converters/css-longhand-resolver.ts +22 -0
- package/src/converters/to-editor-component.test.ts +125 -0
- package/src/converters/to-editor-component.ts +38 -18
- package/src/information-extractors/react/extractors/css-properties.test.ts +136 -0
- package/src/information-extractors/react/extractors/css-properties.ts +146 -4
- package/src/information-extractors/react/extractors/index.ts +2 -0
- package/src/information-extractors/react/index.ts +2 -0
- package/src/manifest-pipeline.ts +3 -1
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.101.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",
|
|
@@ -106,5 +106,5 @@
|
|
|
106
106
|
]
|
|
107
107
|
}
|
|
108
108
|
},
|
|
109
|
-
"falconPackageHash": "
|
|
109
|
+
"falconPackageHash": "7aa5841bf5ff6a0fa0800fc3d71a267d6e96dbe084125f5fd56fc333"
|
|
110
110
|
}
|
|
@@ -131,6 +131,41 @@ describe('resolveLonghand', () => {
|
|
|
131
131
|
expect(resolveLonghand('paddingTop', new Map())).toBeUndefined()
|
|
132
132
|
})
|
|
133
133
|
|
|
134
|
+
describe('margin family', () => {
|
|
135
|
+
it('marginTop falls back through marginBlockStart → marginBlock → margin', () => {
|
|
136
|
+
expect(resolveLonghand('marginTop', new Map([['marginBlockStart', '1px']]))).toBe('1px')
|
|
137
|
+
expect(resolveLonghand('marginTop', new Map([['marginBlock', '4px 8px']]))).toBe('4px')
|
|
138
|
+
expect(resolveLonghand('marginTop', new Map([['margin', '1rem 2rem']]))).toBe('1rem')
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
it('marginBottom falls back through marginBlockEnd → marginBlock → margin', () => {
|
|
142
|
+
expect(resolveLonghand('marginBottom', new Map([['marginBlockEnd', '2px']]))).toBe('2px')
|
|
143
|
+
expect(resolveLonghand('marginBottom', new Map([['marginBlock', '4px 8px']]))).toBe('8px')
|
|
144
|
+
expect(resolveLonghand('marginBottom', new Map([['margin', '1rem 2rem']]))).toBe('1rem')
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('marginInlineStart falls back through marginLeft → marginInline → margin', () => {
|
|
148
|
+
expect(resolveLonghand('marginInlineStart', new Map([['marginLeft', '7px']]))).toBe('7px')
|
|
149
|
+
expect(resolveLonghand('marginInlineStart', new Map([['marginInline', '12px']]))).toBe('12px')
|
|
150
|
+
expect(resolveLonghand('marginInlineStart', new Map([['marginInline', '12px 24px']]))).toBe('12px')
|
|
151
|
+
expect(resolveLonghand('marginInlineStart', new Map([['margin', '1px 2px 3px 4px']]))).toBe('4px')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('marginInlineEnd falls back through marginRight → marginInline → margin', () => {
|
|
155
|
+
expect(resolveLonghand('marginInlineEnd', new Map([['marginRight', '9px']]))).toBe('9px')
|
|
156
|
+
expect(resolveLonghand('marginInlineEnd', new Map([['marginInline', '12px 24px']]))).toBe('24px')
|
|
157
|
+
expect(resolveLonghand('marginInlineEnd', new Map([['margin', '1px 2px 3px 4px']]))).toBe('2px')
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('prefers the literal longhand over any fallback', () => {
|
|
161
|
+
const values = new Map([
|
|
162
|
+
['marginTop', '5px'],
|
|
163
|
+
['margin', '10px'],
|
|
164
|
+
])
|
|
165
|
+
expect(resolveLonghand('marginTop', values)).toBe('5px')
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
134
169
|
describe('padding family', () => {
|
|
135
170
|
it('paddingTop falls back through paddingBlockStart → paddingBlock → padding', () => {
|
|
136
171
|
expect(resolveLonghand('paddingTop', new Map([['paddingBlockStart', '1px']]))).toBe('1px')
|
|
@@ -215,6 +215,18 @@ const fromTextDecorationLine: Fallback = (values) => {
|
|
|
215
215
|
return lineTokens.join(' ')
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* `flex-flow: <flex-direction> || <flex-wrap>` — order is free and either
|
|
220
|
+
* part may be omitted. Pick out just the direction keyword for the schema.
|
|
221
|
+
*/
|
|
222
|
+
const FLEX_DIRECTION_KEYWORDS = new Set(['row', 'row-reverse', 'column', 'column-reverse'])
|
|
223
|
+
|
|
224
|
+
const fromFlexFlowDirection: Fallback = (values) => {
|
|
225
|
+
const shorthand = values.get('flexFlow')
|
|
226
|
+
if (shorthand === undefined) return undefined
|
|
227
|
+
return splitTopLevelTokens(shorthand).find((token) => FLEX_DIRECTION_KEYWORDS.has(token.toLowerCase()))
|
|
228
|
+
}
|
|
229
|
+
|
|
218
230
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
219
231
|
// Resolution table
|
|
220
232
|
//
|
|
@@ -233,6 +245,12 @@ const FALLBACKS: Partial<Record<CssPropertyName, Fallback[]>> = {
|
|
|
233
245
|
paddingInlineStart: [fromKey('paddingLeft'), fromAxis('paddingInline', 'start'), fromBox('padding', 'left')],
|
|
234
246
|
paddingInlineEnd: [fromKey('paddingRight'), fromAxis('paddingInline', 'end'), fromBox('padding', 'right')],
|
|
235
247
|
|
|
248
|
+
// Margin — same block/inline convention as padding above.
|
|
249
|
+
marginTop: [fromKey('marginBlockStart'), fromAxis('marginBlock', 'start'), fromBox('margin', 'top')],
|
|
250
|
+
marginBottom: [fromKey('marginBlockEnd'), fromAxis('marginBlock', 'end'), fromBox('margin', 'bottom')],
|
|
251
|
+
marginInlineStart: [fromKey('marginLeft'), fromAxis('marginInline', 'start'), fromBox('margin', 'left')],
|
|
252
|
+
marginInlineEnd: [fromKey('marginRight'), fromAxis('marginInline', 'end'), fromBox('margin', 'right')],
|
|
253
|
+
|
|
236
254
|
// Border radius — schema uses all-logical corner names; physical names map under LTR/horizontal-tb.
|
|
237
255
|
borderStartStartRadius: [fromKey('borderTopLeftRadius'), fromCorner('borderRadius', 'tl')],
|
|
238
256
|
borderStartEndRadius: [fromKey('borderTopRightRadius'), fromCorner('borderRadius', 'tr')],
|
|
@@ -255,6 +273,10 @@ const FALLBACKS: Partial<Record<CssPropertyName, Fallback[]>> = {
|
|
|
255
273
|
// and color in one declaration. Pick out just the line tokens for the schema.
|
|
256
274
|
textDecorationLine: [fromTextDecorationLine],
|
|
257
275
|
|
|
276
|
+
// Flex — `flex-flow: column wrap` carries both direction and wrap; pick out
|
|
277
|
+
// just the direction keyword for the schema.
|
|
278
|
+
flexDirection: [fromFlexFlowDirection],
|
|
279
|
+
|
|
258
280
|
// Shorthand targets (e.g. `font`) live in css-shorthand-composer.ts and are
|
|
259
281
|
// merged in here — composer-driven, conceptually the inverse of the rows above.
|
|
260
282
|
...SHORTHAND_FALLBACKS,
|
|
@@ -346,6 +346,72 @@ describe('toEditorReactComponent — cssProperties defaultValue', () => {
|
|
|
346
346
|
defaultValue: 'var(--wst-primary-background-color)',
|
|
347
347
|
})
|
|
348
348
|
})
|
|
349
|
+
|
|
350
|
+
it('excludes width/height from the root element (sizing is handled via layout config)', () => {
|
|
351
|
+
const extractorData = new Map<string, unknown>()
|
|
352
|
+
extractorData.set('css-matcher', matcherDataFromCss('width: 100px; height: 50px; overflow: hidden;'))
|
|
353
|
+
extractorData.set('css-properties', {
|
|
354
|
+
relevant: [
|
|
355
|
+
CSS_PROPERTIES.CSS_PROPERTY_TYPE.width,
|
|
356
|
+
CSS_PROPERTIES.CSS_PROPERTY_TYPE.height,
|
|
357
|
+
CSS_PROPERTIES.CSS_PROPERTY_TYPE.overflow,
|
|
358
|
+
],
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
const rootElement: ExtractedElement = {
|
|
362
|
+
traceId: 'trace-root',
|
|
363
|
+
name: 'root',
|
|
364
|
+
tag: 'div',
|
|
365
|
+
attributes: {},
|
|
366
|
+
extractorData,
|
|
367
|
+
children: [],
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
371
|
+
|
|
372
|
+
expect(result.editorElement?.cssProperties?.width).toBeUndefined()
|
|
373
|
+
expect(result.editorElement?.cssProperties?.height).toBeUndefined()
|
|
374
|
+
expect(result.editorElement?.cssProperties?.overflow).toEqual({ defaultValue: 'hidden' })
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('keeps width/height on inner (non-root) elements', () => {
|
|
378
|
+
const rootExtractorData = new Map<string, unknown>()
|
|
379
|
+
rootExtractorData.set('css-matcher', matcherDataFromCss(''))
|
|
380
|
+
rootExtractorData.set('css-properties', { relevant: [] })
|
|
381
|
+
|
|
382
|
+
const childExtractorData = new Map<string, unknown>()
|
|
383
|
+
childExtractorData.set('css-matcher', matcherDataFromCss('width: 24px; height: 24px;'))
|
|
384
|
+
childExtractorData.set('css-properties', {
|
|
385
|
+
relevant: [CSS_PROPERTIES.CSS_PROPERTY_TYPE.width, CSS_PROPERTIES.CSS_PROPERTY_TYPE.height],
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
const childElement: ExtractedElement = {
|
|
389
|
+
traceId: 'trace-icon',
|
|
390
|
+
name: 'icon',
|
|
391
|
+
tag: 'img',
|
|
392
|
+
attributes: {},
|
|
393
|
+
extractorData: childExtractorData,
|
|
394
|
+
children: [],
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const rootElement: ExtractedElement = {
|
|
398
|
+
traceId: 'trace-root',
|
|
399
|
+
name: 'root',
|
|
400
|
+
tag: 'div',
|
|
401
|
+
attributes: {},
|
|
402
|
+
extractorData: rootExtractorData,
|
|
403
|
+
children: [childElement],
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const result = toEditorReactComponent(createComponent(rootElement))
|
|
407
|
+
|
|
408
|
+
expect(result.editorElement?.elements?.icon?.inlineElement?.cssProperties?.width).toEqual({
|
|
409
|
+
defaultValue: '24px',
|
|
410
|
+
})
|
|
411
|
+
expect(result.editorElement?.elements?.icon?.inlineElement?.cssProperties?.height).toEqual({
|
|
412
|
+
defaultValue: '24px',
|
|
413
|
+
})
|
|
414
|
+
})
|
|
349
415
|
})
|
|
350
416
|
|
|
351
417
|
describe('toEditorReactComponent selector conversion', () => {
|
|
@@ -659,6 +725,65 @@ describe('toEditorReactComponent — @property design tokens', () => {
|
|
|
659
725
|
expect(editorElement?.cssProperties?.['sdf-safelight-color']).toBeUndefined()
|
|
660
726
|
expect(editorElement?.cssProperties?.['--sdf-safelight-color']).toBeUndefined()
|
|
661
727
|
})
|
|
728
|
+
|
|
729
|
+
it('places a declared custom property on its declaring element even when its only tracked usage was pruned from the tree', () => {
|
|
730
|
+
const buttonElement: ExtractedElement = {
|
|
731
|
+
traceId: 'trace-button',
|
|
732
|
+
name: 'button',
|
|
733
|
+
tag: 'button',
|
|
734
|
+
attributes: { class: 'play-button' },
|
|
735
|
+
extractorData: new Map<string, unknown>([
|
|
736
|
+
['css-matcher', { matches: [], customProperties: { '--icon-color': 'red' } } as MatchedCssData],
|
|
737
|
+
]),
|
|
738
|
+
children: [],
|
|
739
|
+
}
|
|
740
|
+
const rootElement: ExtractedElement = {
|
|
741
|
+
traceId: 'trace-root',
|
|
742
|
+
name: 'root',
|
|
743
|
+
tag: 'div',
|
|
744
|
+
attributes: {},
|
|
745
|
+
extractorData: new Map<string, unknown>(),
|
|
746
|
+
children: [buttonElement],
|
|
747
|
+
}
|
|
748
|
+
const component: ComponentInfoWithCss = {
|
|
749
|
+
componentName: 'IconButton',
|
|
750
|
+
props: {},
|
|
751
|
+
elements: [rootElement],
|
|
752
|
+
propUsages: new Map(),
|
|
753
|
+
css: [],
|
|
754
|
+
// The icon that actually consumes --icon-color has no semantic class, so it was
|
|
755
|
+
// already filtered out of `elements` upstream — its trace id survives only here.
|
|
756
|
+
varUsedByTraceId: new Map([['--icon-color', new Set(['trace-pruned-icon'])]]),
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const editorElement = toEditorReactComponent(component).editorElement
|
|
760
|
+
|
|
761
|
+
expect(editorElement?.elements?.button?.inlineElement?.cssCustomProperties?.['icon-color']).toEqual({
|
|
762
|
+
displayName: 'Icon Color',
|
|
763
|
+
defaultValue: 'red',
|
|
764
|
+
cssPropertyType: 'color',
|
|
765
|
+
})
|
|
766
|
+
expect(editorElement?.cssCustomProperties?.['icon-color']).toBeUndefined()
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
it('falls back to the component root when a variable has no surviving declaration and only orphaned usage', () => {
|
|
770
|
+
const component: ComponentInfoWithCss = {
|
|
771
|
+
...createComponentWithCss(`
|
|
772
|
+
@property --gap { syntax: "<length>"; inherits: true; initial-value: 4px; }
|
|
773
|
+
`),
|
|
774
|
+
// No element declares --gap locally; its only recorded usage is a trace id that
|
|
775
|
+
// never made it into `elements` (simulating a pruned consumer).
|
|
776
|
+
varUsedByTraceId: new Map([['--gap', new Set(['trace-orphan'])]]),
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const customProps = toEditorReactComponent(component).editorElement?.cssCustomProperties
|
|
780
|
+
|
|
781
|
+
expect(customProps?.gap).toEqual({
|
|
782
|
+
displayName: 'Gap',
|
|
783
|
+
defaultValue: '4px',
|
|
784
|
+
cssPropertyType: 'length',
|
|
785
|
+
})
|
|
786
|
+
})
|
|
662
787
|
})
|
|
663
788
|
|
|
664
789
|
describe('toEditorReactComponent — ActiveItemIndex display groups', () => {
|
|
@@ -25,7 +25,11 @@ import type {
|
|
|
25
25
|
ExtractedElement,
|
|
26
26
|
TrackingStores,
|
|
27
27
|
} from '../information-extractors/react'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
ROOT_DISPLAY_CUSTOM_PROPERTY,
|
|
30
|
+
getDefaultDisplayForTag,
|
|
31
|
+
resolveDisplayValue,
|
|
32
|
+
} from '../information-extractors/react'
|
|
29
33
|
import { findPreferredSemanticClass, normalizeClassNames } from '../utils/css-class'
|
|
30
34
|
import { buildActiveItemDisplayGroups, buildActiveItemIndexDisplayFilters } from './active-item-index-builder'
|
|
31
35
|
import { resolveLonghand } from './css-longhand-resolver'
|
|
@@ -244,7 +248,7 @@ function buildElements(
|
|
|
244
248
|
...(data && Object.keys(data).length > 0 && { data }),
|
|
245
249
|
// CSS properties from heuristic + matched CSS files
|
|
246
250
|
...(Object.keys(cssProps).length > 0 && { cssProperties: cssProps }),
|
|
247
|
-
// CSS custom properties
|
|
251
|
+
// CSS custom properties for this element
|
|
248
252
|
...(Object.keys(cssCustomProps).length > 0 && { cssCustomProperties: cssCustomProps }),
|
|
249
253
|
...(states && { states }),
|
|
250
254
|
// Recursively build nested elements
|
|
@@ -273,8 +277,9 @@ function buildElements(
|
|
|
273
277
|
|
|
274
278
|
/**
|
|
275
279
|
* Pre-computes a map of traceId → custom property items to assign to each element.
|
|
276
|
-
*
|
|
277
|
-
*
|
|
280
|
+
* A variable is placed on the element that declares it; if none survived pruning, it
|
|
281
|
+
* falls back to the nearest common ancestor of its usages, or the root for a registered
|
|
282
|
+
* but undeclared @property.
|
|
278
283
|
*/
|
|
279
284
|
function buildNearestCommonAncestorCustomProps(
|
|
280
285
|
component: ComponentInfoWithCss,
|
|
@@ -307,23 +312,32 @@ function buildNearestCommonAncestorCustomProps(
|
|
|
307
312
|
const nearestCommonAncestorCustomProps = new Map<string, Record<string, CssCustomPropertyItem>>()
|
|
308
313
|
|
|
309
314
|
for (const varName of varNames) {
|
|
310
|
-
if (varName ===
|
|
315
|
+
if (varName === ROOT_DISPLAY_CUSTOM_PROPERTY) continue
|
|
311
316
|
|
|
312
317
|
const registration = registeredByVar.get(varName)
|
|
313
318
|
// Prefer a concrete declared value; fall back to the @property initial-value so a
|
|
314
319
|
// component doesn't need an element-level `--x: ...` declaration.
|
|
315
320
|
const defaultValue = allCustomPropertyValues.get(varName) ?? registration?.defaultValue
|
|
316
321
|
|
|
317
|
-
|
|
322
|
+
// Declaration wins over usage; multiple declaring elements resolve to their NCA.
|
|
323
|
+
const definingTraceIds = findDefiningElements(component.elements, varName)
|
|
318
324
|
|
|
319
325
|
let nearestCommonAncestorTraceId: string | undefined
|
|
320
|
-
if (
|
|
321
|
-
nearestCommonAncestorTraceId = findNearestCommonAncestor(
|
|
326
|
+
if (definingTraceIds.size > 0) {
|
|
327
|
+
nearestCommonAncestorTraceId = findNearestCommonAncestor(definingTraceIds, parentMap)
|
|
322
328
|
} else {
|
|
323
|
-
//
|
|
324
|
-
|
|
329
|
+
// No surviving declaration — fall back to usage NCA, restricted to trace IDs still in the tree.
|
|
330
|
+
const usedByTraceIds = component.varUsedByTraceId.get(varName) ?? new Set<string>()
|
|
331
|
+
const survivingUsedByTraceIds = new Set(
|
|
332
|
+
[...usedByTraceIds].filter((traceId) => traceId === rootTraceId || parentMap.has(traceId)),
|
|
333
|
+
)
|
|
334
|
+
|
|
325
335
|
nearestCommonAncestorTraceId =
|
|
326
|
-
|
|
336
|
+
survivingUsedByTraceIds.size > 0
|
|
337
|
+
? findNearestCommonAncestor(survivingUsedByTraceIds, parentMap)
|
|
338
|
+
: registration
|
|
339
|
+
? rootTraceId
|
|
340
|
+
: undefined
|
|
327
341
|
}
|
|
328
342
|
|
|
329
343
|
if (!nearestCommonAncestorTraceId) continue
|
|
@@ -502,17 +516,16 @@ function findNearestCommonAncestor(traceIds: Set<string>, parentMap: Map<string,
|
|
|
502
516
|
}
|
|
503
517
|
|
|
504
518
|
/**
|
|
505
|
-
* Finds
|
|
506
|
-
* define the given custom property (fallback when var() usage is not tracked).
|
|
519
|
+
* Finds every surviving element whose matched CSS declares the given custom property.
|
|
507
520
|
*/
|
|
508
|
-
function
|
|
521
|
+
function findDefiningElements(elements: ExtractedElement[], varName: string): Set<string> {
|
|
522
|
+
const definingTraceIds = new Set<string>()
|
|
509
523
|
for (const element of elements) {
|
|
510
524
|
const matcherData = element.extractorData.get('css-matcher') as MatchedCssData | undefined
|
|
511
|
-
if (matcherData?.customProperties[varName] !== undefined)
|
|
512
|
-
const
|
|
513
|
-
if (childResult !== undefined) return childResult
|
|
525
|
+
if (matcherData?.customProperties[varName] !== undefined) definingTraceIds.add(element.traceId)
|
|
526
|
+
for (const traceId of findDefiningElements(element.children, varName)) definingTraceIds.add(traceId)
|
|
514
527
|
}
|
|
515
|
-
return
|
|
528
|
+
return definingTraceIds
|
|
516
529
|
}
|
|
517
530
|
|
|
518
531
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -563,6 +576,10 @@ function toDisplayEnumValue(cssValue: string): DisplayEnumValue {
|
|
|
563
576
|
return (CSS_DISPLAY_TO_ENUM[cssValue] ?? cssValue) as DisplayEnumValue
|
|
564
577
|
}
|
|
565
578
|
|
|
579
|
+
// Root sizing is handled separately via layout config, so `width`/`height`
|
|
580
|
+
// are excluded from the root element's own `cssProperties`.
|
|
581
|
+
const ROOT_EXCLUDED_CSS_PROPERTIES = new Set<string>([CSS_PROPERTY_TYPE.width, CSS_PROPERTY_TYPE.height])
|
|
582
|
+
|
|
566
583
|
function buildCssProperties(
|
|
567
584
|
element: ExtractedElement | undefined,
|
|
568
585
|
isRootElement: boolean,
|
|
@@ -576,6 +593,9 @@ function buildCssProperties(
|
|
|
576
593
|
}
|
|
577
594
|
const cssPropertyValues = element ? getMatchedPropertyValues(element) : new Map<string, string>()
|
|
578
595
|
for (const propName of decidedProperties) {
|
|
596
|
+
if (isRootElement && ROOT_EXCLUDED_CSS_PROPERTIES.has(propName)) {
|
|
597
|
+
continue
|
|
598
|
+
}
|
|
579
599
|
if (propName === CSS_PROPERTY_TYPE.display && element) {
|
|
580
600
|
if (!isRootElement) {
|
|
581
601
|
result[propName] = buildDisplayProperty(element)
|
|
@@ -5,6 +5,7 @@ import type { MatchedCssData } from '../../css/types'
|
|
|
5
5
|
import type { ExtractedElement } from './core/tree-builder'
|
|
6
6
|
import {
|
|
7
7
|
enrichContainerProperties,
|
|
8
|
+
enrichFlexProperties,
|
|
8
9
|
getCssPropertiesForTag,
|
|
9
10
|
getDefaultDisplayForTag,
|
|
10
11
|
hasContainerLikeDisplay,
|
|
@@ -252,6 +253,131 @@ describe('getCssPropertiesForTag', () => {
|
|
|
252
253
|
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
253
254
|
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
254
255
|
})
|
|
256
|
+
|
|
257
|
+
it('gives block/replaced elements the full margin family regardless of box/text/media semantics', () => {
|
|
258
|
+
for (const tag of ['div', 'p', 'img']) {
|
|
259
|
+
const relevantProperties = getCssPropertiesForTag(tag)
|
|
260
|
+
|
|
261
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginTop)
|
|
262
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginBottom)
|
|
263
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineStart)
|
|
264
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineEnd)
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('gives non-replaced inline elements only the horizontal margins, per CSS margin-on-inline-boxes rules', () => {
|
|
269
|
+
for (const tag of ['span', 'a', 'em', 'b', 'i']) {
|
|
270
|
+
const relevantProperties = getCssPropertiesForTag(tag)
|
|
271
|
+
|
|
272
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineStart)
|
|
273
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineEnd)
|
|
274
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginTop)
|
|
275
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginBottom)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('gives internal table display elements no margin at all, per CSS 2.1 §17.6.1', () => {
|
|
280
|
+
for (const tag of ['tr', 'thead', 'tbody', 'tfoot', 'col', 'colgroup', 'td', 'th']) {
|
|
281
|
+
const relevantProperties = getCssPropertiesForTag(tag)
|
|
282
|
+
|
|
283
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginTop)
|
|
284
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginBottom)
|
|
285
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineStart)
|
|
286
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.marginInlineEnd)
|
|
287
|
+
}
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('gives box-generating elements width, height, overflow and mix-blend-mode', () => {
|
|
291
|
+
const relevantProperties = getCssPropertiesForTag('div')
|
|
292
|
+
|
|
293
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.width)
|
|
294
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.height)
|
|
295
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.overflow)
|
|
296
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.mixBlendMode)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
it('gives media elements sizing, object-fit/position and mix-blend-mode', () => {
|
|
300
|
+
const relevantProperties = getCssPropertiesForTag('img')
|
|
301
|
+
|
|
302
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.width)
|
|
303
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.height)
|
|
304
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.overflow)
|
|
305
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.objectFit)
|
|
306
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.objectPosition)
|
|
307
|
+
expect(relevantProperties).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.mixBlendMode)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
it('keeps text-only tags without width/height/overflow/object-fit', () => {
|
|
311
|
+
const relevantProperties = getCssPropertiesForTag('p')
|
|
312
|
+
|
|
313
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.width)
|
|
314
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.height)
|
|
315
|
+
expect(relevantProperties).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.objectFit)
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
describe('enrichFlexProperties', () => {
|
|
320
|
+
it('adds flexDirection/justifyContent/alignItems to a flex container', () => {
|
|
321
|
+
const elements = [createElementWithCssProperties('div', 'display: flex')]
|
|
322
|
+
|
|
323
|
+
const [enrichedElement] = enrichFlexProperties(elements)
|
|
324
|
+
const cssData = enrichedElement.extractorData.get('css-properties') as { relevant: string[] }
|
|
325
|
+
|
|
326
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.flexDirection)
|
|
327
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.justifyContent)
|
|
328
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignItems)
|
|
329
|
+
expect(cssData.relevant).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignSelf)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('does not add flex container properties to a non-flex/grid element', () => {
|
|
333
|
+
const elements = [createElementWithCssProperties('div', 'display: block')]
|
|
334
|
+
|
|
335
|
+
const [enrichedElement] = enrichFlexProperties(elements)
|
|
336
|
+
const cssData = enrichedElement.extractorData.get('css-properties') as { relevant: string[] }
|
|
337
|
+
|
|
338
|
+
expect(cssData.relevant).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.flexDirection)
|
|
339
|
+
expect(cssData.relevant).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.justifyContent)
|
|
340
|
+
expect(cssData.relevant).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignItems)
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it('adds alignSelf to direct children of a flex/grid container', () => {
|
|
344
|
+
const child = createElementWithCssProperties('span', '')
|
|
345
|
+
const parent: ExtractedElement = {
|
|
346
|
+
...createElementWithCssProperties('div', 'display: flex'),
|
|
347
|
+
children: [child],
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const [enrichedParent] = enrichFlexProperties([parent])
|
|
351
|
+
const enrichedChild = enrichedParent.children[0]
|
|
352
|
+
const childCssData = enrichedChild.extractorData.get('css-properties') as { relevant: string[] }
|
|
353
|
+
|
|
354
|
+
expect(childCssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignSelf)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('does not add alignSelf to children of a non-flex/grid container', () => {
|
|
358
|
+
const child = createElementWithCssProperties('span', '')
|
|
359
|
+
const parent: ExtractedElement = {
|
|
360
|
+
...createElementWithCssProperties('div', 'display: block'),
|
|
361
|
+
children: [child],
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const [enrichedParent] = enrichFlexProperties([parent])
|
|
365
|
+
const enrichedChild = enrichedParent.children[0]
|
|
366
|
+
const childCssData = enrichedChild.extractorData.get('css-properties') as { relevant: string[] }
|
|
367
|
+
|
|
368
|
+
expect(childCssData.relevant).not.toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignSelf)
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
it('adds flex container properties to a grid container as well', () => {
|
|
372
|
+
const elements = [createElementWithCssProperties('div', 'display: grid')]
|
|
373
|
+
|
|
374
|
+
const [enrichedElement] = enrichFlexProperties(elements)
|
|
375
|
+
const cssData = enrichedElement.extractorData.get('css-properties') as { relevant: string[] }
|
|
376
|
+
|
|
377
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.flexDirection)
|
|
378
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.justifyContent)
|
|
379
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.alignItems)
|
|
380
|
+
})
|
|
255
381
|
})
|
|
256
382
|
|
|
257
383
|
describe('getDefaultDisplayForTag', () => {
|
|
@@ -404,4 +530,14 @@ describe('enrichContainerProperties', () => {
|
|
|
404
530
|
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
405
531
|
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
406
532
|
})
|
|
533
|
+
|
|
534
|
+
it('adds box properties to span when display is signaled only via the --display root convention, not a literal display declaration', () => {
|
|
535
|
+
const elements = [createElementWithCssProperties('span', '--display: block')]
|
|
536
|
+
|
|
537
|
+
const [enrichedElement] = enrichContainerProperties(elements)
|
|
538
|
+
const cssData = enrichedElement.extractorData.get('css-properties') as { relevant: string[] }
|
|
539
|
+
|
|
540
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.font)
|
|
541
|
+
expect(cssData.relevant).toContain(CSS_PROPERTIES.CSS_PROPERTY_TYPE.paddingTop)
|
|
542
|
+
})
|
|
407
543
|
})
|