@nordcraft/core 1.0.77 → 1.0.79
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/api/ToddleApiV2.d.ts +3 -3
- package/dist/api/apiTypes.d.ts +2 -2
- package/dist/component/component.types.d.ts +8 -1
- package/dist/component/component.types.js.map +1 -1
- package/dist/component/schemas/action-schema.d.ts +3 -0
- package/dist/component/schemas/action-schema.js +169 -0
- package/dist/component/schemas/action-schema.js.map +1 -0
- package/dist/component/schemas/api-schema.d.ts +3 -0
- package/dist/component/schemas/api-schema.js +174 -0
- package/dist/component/schemas/api-schema.js.map +1 -0
- package/dist/component/schemas/attribute-schema.d.ts +3 -0
- package/dist/component/schemas/attribute-schema.js +12 -0
- package/dist/component/schemas/attribute-schema.js.map +1 -0
- package/dist/component/schemas/component-schema.d.ts +6 -0
- package/dist/component/schemas/component-schema.js +132 -0
- package/dist/component/schemas/component-schema.js.map +1 -0
- package/dist/component/schemas/context-schema.d.ts +3 -0
- package/dist/component/schemas/context-schema.js +20 -0
- package/dist/component/schemas/context-schema.js.map +1 -0
- package/dist/component/schemas/event-schema.d.ts +4 -0
- package/dist/component/schemas/event-schema.js +25 -0
- package/dist/component/schemas/event-schema.js.map +1 -0
- package/dist/component/schemas/formula-schema.d.ts +5 -0
- package/dist/component/schemas/formula-schema.js +203 -0
- package/dist/component/schemas/formula-schema.js.map +1 -0
- package/dist/component/schemas/node-schema.d.ts +3 -0
- package/dist/component/schemas/node-schema.js +159 -0
- package/dist/component/schemas/node-schema.js.map +1 -0
- package/dist/component/schemas/route-schema.d.ts +3 -0
- package/dist/component/schemas/route-schema.js +88 -0
- package/dist/component/schemas/route-schema.js.map +1 -0
- package/dist/component/schemas/variable-schema.d.ts +3 -0
- package/dist/component/schemas/variable-schema.js +8 -0
- package/dist/component/schemas/variable-schema.js.map +1 -0
- package/dist/component/schemas/workflow-schema.d.ts +3 -0
- package/dist/component/schemas/workflow-schema.js +23 -0
- package/dist/component/schemas/workflow-schema.js.map +1 -0
- package/dist/component/schemas/zod-schemas.d.ts +26 -3
- package/dist/component/schemas/zod-schemas.js +4 -1077
- package/dist/component/schemas/zod-schemas.js.map +1 -1
- package/dist/styling/style.css.d.ts +3 -1
- package/dist/styling/style.css.js +124 -128
- package/dist/styling/style.css.js.map +1 -1
- package/dist/styling/theme.const.d.ts +2 -0
- package/dist/styling/theme.const.js +2 -0
- package/dist/styling/theme.const.js.map +1 -1
- package/dist/styling/theme.js +2 -2
- package/dist/styling/theme.js.map +1 -1
- package/dist/styling/variantSelector.d.ts +1 -8
- package/dist/styling/variantSelector.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/collections.d.ts +1 -1
- package/dist/utils/collections.js.map +1 -1
- package/package.json +6 -1
- package/src/api/apiTypes.ts +2 -2
- package/src/component/component.types.ts +9 -1
- package/src/component/schemas/action-schema.ts +258 -0
- package/src/component/schemas/api-schema.ts +255 -0
- package/src/component/schemas/attribute-schema.ts +15 -0
- package/src/component/schemas/component-schema.ts +174 -0
- package/src/component/schemas/context-schema.ts +21 -0
- package/src/component/schemas/event-schema.ts +35 -0
- package/src/component/schemas/formula-schema.ts +299 -0
- package/src/component/schemas/node-schema.ts +259 -0
- package/src/component/schemas/route-schema.ts +135 -0
- package/src/component/schemas/variable-schema.ts +11 -0
- package/src/component/schemas/workflow-schema.ts +30 -0
- package/src/component/schemas/zod-schemas.ts +4 -1489
- package/src/styling/style.css.test.ts +929 -0
- package/src/styling/style.css.ts +148 -152
- package/src/styling/theme.const.ts +3 -0
- package/src/styling/theme.test.ts +4 -4
- package/src/styling/theme.ts +2 -2
- package/src/styling/variantSelector.ts +1 -7
- package/src/types.ts +1 -1
- package/src/utils/collections.ts +4 -1
|
@@ -0,0 +1,929 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test'
|
|
2
|
+
import type { Component } from '../component/component.types'
|
|
3
|
+
import { getClassName } from './className'
|
|
4
|
+
import {
|
|
5
|
+
createStylesheet,
|
|
6
|
+
getNodeStyles,
|
|
7
|
+
kebabCase,
|
|
8
|
+
styleToCss,
|
|
9
|
+
} from './style.css'
|
|
10
|
+
import { theme as defaultTheme } from './theme.const'
|
|
11
|
+
|
|
12
|
+
describe('styleToCss()', () => {
|
|
13
|
+
test('it converts style object to CSS', () => {
|
|
14
|
+
const style = {
|
|
15
|
+
color: 'red',
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const css = styleToCss(style)
|
|
19
|
+
|
|
20
|
+
expect(css).toBe(`color:red;`)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('it converts camelCase properties to kebab-case', () => {
|
|
24
|
+
const style = {
|
|
25
|
+
backgroundColor: 'blue',
|
|
26
|
+
marginTop: '10px',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const css = styleToCss(style)
|
|
30
|
+
|
|
31
|
+
expect(css).toBe(`background-color:blue;
|
|
32
|
+
margin-top:10px;`)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('it filters out undefined and null values', () => {
|
|
36
|
+
const style: any = {
|
|
37
|
+
color: 'red',
|
|
38
|
+
backgroundColor: undefined,
|
|
39
|
+
marginTop: null,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const css = styleToCss(style)
|
|
43
|
+
|
|
44
|
+
expect(css).toBe(`color:red;`)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('kebabCase()', () => {
|
|
49
|
+
test('it converts camelCase to kebab-case', () => {
|
|
50
|
+
expect(kebabCase('backgroundColor')).toBe('background-color')
|
|
51
|
+
expect(kebabCase('marginTop')).toBe('margin-top')
|
|
52
|
+
expect(kebabCase('fontSize')).toBe('font-size')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('it converts PascalCase to kebab-case', () => {
|
|
56
|
+
expect(kebabCase('BackgroundColor')).toBe('background-color')
|
|
57
|
+
expect(kebabCase('MarginTop')).toBe('margin-top')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('it handles strings with multiple uppercase letters', () => {
|
|
61
|
+
expect(kebabCase('marginTopLeft')).toBe('margin-top-left')
|
|
62
|
+
expect(kebabCase('backgroundColorRed')).toBe('background-color-red')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('it handles already kebab-case strings', () => {
|
|
66
|
+
expect(kebabCase('background-color')).toBe('background-color')
|
|
67
|
+
expect(kebabCase('margin-top')).toBe('margin-top')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('it handles empty string', () => {
|
|
71
|
+
expect(kebabCase('')).toBe('')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('it handles single character', () => {
|
|
75
|
+
expect(kebabCase('a')).toBe('a')
|
|
76
|
+
expect(kebabCase('A')).toBe('a')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('getNodeStyles()', () => {
|
|
81
|
+
test('it generates CSS for a simple node', () => {
|
|
82
|
+
const node = {
|
|
83
|
+
type: 'element' as const,
|
|
84
|
+
tag: 'div',
|
|
85
|
+
style: {
|
|
86
|
+
color: 'red',
|
|
87
|
+
backgroundColor: 'blue',
|
|
88
|
+
},
|
|
89
|
+
variants: undefined,
|
|
90
|
+
children: [],
|
|
91
|
+
attrs: {},
|
|
92
|
+
events: {},
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const classHash = 'test-hash'
|
|
96
|
+
const css = getNodeStyles(node, classHash)
|
|
97
|
+
|
|
98
|
+
expect(css).toBe(`
|
|
99
|
+
|
|
100
|
+
.${classHash} {
|
|
101
|
+
color:red;
|
|
102
|
+
background-color:blue;
|
|
103
|
+
}`)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('it generates CSS for node with hover variant', () => {
|
|
107
|
+
const node = {
|
|
108
|
+
type: 'element' as const,
|
|
109
|
+
tag: 'div',
|
|
110
|
+
style: {
|
|
111
|
+
color: 'blue',
|
|
112
|
+
},
|
|
113
|
+
variants: [
|
|
114
|
+
{
|
|
115
|
+
hover: true,
|
|
116
|
+
style: {
|
|
117
|
+
color: 'red',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
children: [],
|
|
122
|
+
attrs: {},
|
|
123
|
+
events: {},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const classHash = 'test-hash'
|
|
127
|
+
const css = getNodeStyles(node, classHash)
|
|
128
|
+
|
|
129
|
+
expect(css).toBe(`
|
|
130
|
+
|
|
131
|
+
.${classHash} {
|
|
132
|
+
color:blue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.${classHash}:hover {
|
|
136
|
+
color:red;
|
|
137
|
+
}`)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('it generates CSS for node with multiple variants', () => {
|
|
141
|
+
const node = {
|
|
142
|
+
type: 'element' as const,
|
|
143
|
+
tag: 'button',
|
|
144
|
+
style: {
|
|
145
|
+
color: 'black',
|
|
146
|
+
},
|
|
147
|
+
variants: [
|
|
148
|
+
{
|
|
149
|
+
hover: true,
|
|
150
|
+
style: {
|
|
151
|
+
color: 'blue',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
focus: true,
|
|
156
|
+
style: {
|
|
157
|
+
color: 'green',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
active: true,
|
|
162
|
+
style: {
|
|
163
|
+
color: 'red',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
children: [],
|
|
168
|
+
attrs: {},
|
|
169
|
+
events: {},
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const classHash = 'test-hash'
|
|
173
|
+
const css = getNodeStyles(node, classHash)
|
|
174
|
+
|
|
175
|
+
expect(css).toBe(`
|
|
176
|
+
|
|
177
|
+
.${classHash} {
|
|
178
|
+
color:black;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.${classHash}:hover {
|
|
182
|
+
color:blue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.${classHash}:focus {
|
|
186
|
+
color:green;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.${classHash}:active {
|
|
190
|
+
color:red;
|
|
191
|
+
}`)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
test('it generates CSS for node with media query variant', () => {
|
|
195
|
+
const node = {
|
|
196
|
+
type: 'element' as const,
|
|
197
|
+
tag: 'div',
|
|
198
|
+
style: {
|
|
199
|
+
fontSize: '16px',
|
|
200
|
+
},
|
|
201
|
+
variants: [
|
|
202
|
+
{
|
|
203
|
+
style: {
|
|
204
|
+
fontSize: '14px',
|
|
205
|
+
},
|
|
206
|
+
mediaQuery: {
|
|
207
|
+
'max-width': '768px',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
children: [],
|
|
212
|
+
attrs: {},
|
|
213
|
+
events: {},
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const classHash = 'test-hash'
|
|
217
|
+
const css = getNodeStyles(node, classHash)
|
|
218
|
+
|
|
219
|
+
expect(css).toBe(`
|
|
220
|
+
|
|
221
|
+
.${classHash} {
|
|
222
|
+
font-size:16px;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
@media (max-width: 768px) {
|
|
226
|
+
|
|
227
|
+
.${classHash} {
|
|
228
|
+
font-size:14px;
|
|
229
|
+
}
|
|
230
|
+
}`)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('it generates CSS for node with prefers-reduced-motion reduce variant', () => {
|
|
234
|
+
const node = {
|
|
235
|
+
type: 'element' as const,
|
|
236
|
+
tag: 'div',
|
|
237
|
+
style: {
|
|
238
|
+
color: 'blue',
|
|
239
|
+
},
|
|
240
|
+
variants: [
|
|
241
|
+
{
|
|
242
|
+
style: {
|
|
243
|
+
transition: 'none',
|
|
244
|
+
animation: 'none',
|
|
245
|
+
},
|
|
246
|
+
mediaQuery: {
|
|
247
|
+
'prefers-reduced-motion': 'reduce' as const,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
children: [],
|
|
252
|
+
attrs: {},
|
|
253
|
+
events: {},
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const classHash = 'test-hash'
|
|
257
|
+
const css = getNodeStyles(node, classHash)
|
|
258
|
+
|
|
259
|
+
expect(css).toBe(`
|
|
260
|
+
|
|
261
|
+
.${classHash} {
|
|
262
|
+
color:blue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@media (prefers-reduced-motion: reduce) {
|
|
266
|
+
|
|
267
|
+
.${classHash} {
|
|
268
|
+
transition:none;
|
|
269
|
+
animation:none;
|
|
270
|
+
}
|
|
271
|
+
}`)
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
test('it generates CSS for node with prefers-reduced-motion no-preference variant', () => {
|
|
275
|
+
const node = {
|
|
276
|
+
type: 'element' as const,
|
|
277
|
+
tag: 'div',
|
|
278
|
+
style: {
|
|
279
|
+
color: 'blue',
|
|
280
|
+
},
|
|
281
|
+
variants: [
|
|
282
|
+
{
|
|
283
|
+
style: {
|
|
284
|
+
animation: 'fadeIn 1s',
|
|
285
|
+
},
|
|
286
|
+
mediaQuery: {
|
|
287
|
+
'prefers-reduced-motion': 'no-preference' as const,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
],
|
|
291
|
+
children: [],
|
|
292
|
+
attrs: {},
|
|
293
|
+
events: {},
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const classHash = 'test-hash'
|
|
297
|
+
const css = getNodeStyles(node, classHash)
|
|
298
|
+
|
|
299
|
+
expect(css).toBe(`
|
|
300
|
+
|
|
301
|
+
.${classHash} {
|
|
302
|
+
color:blue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
306
|
+
|
|
307
|
+
.${classHash} {
|
|
308
|
+
animation:fadeIn 1s;
|
|
309
|
+
}
|
|
310
|
+
}`)
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
test('it generates CSS for node with prefers-reduced-motion combined with other media queries', () => {
|
|
314
|
+
const node = {
|
|
315
|
+
type: 'element' as const,
|
|
316
|
+
tag: 'div',
|
|
317
|
+
style: {
|
|
318
|
+
fontSize: '16px',
|
|
319
|
+
},
|
|
320
|
+
variants: [
|
|
321
|
+
{
|
|
322
|
+
style: {
|
|
323
|
+
fontSize: '14px',
|
|
324
|
+
},
|
|
325
|
+
mediaQuery: {
|
|
326
|
+
'prefers-reduced-motion': 'reduce' as const,
|
|
327
|
+
'max-width': '768px',
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
children: [],
|
|
332
|
+
attrs: {},
|
|
333
|
+
events: {},
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const classHash = 'test-hash'
|
|
337
|
+
const css = getNodeStyles(node, classHash)
|
|
338
|
+
|
|
339
|
+
expect(css).toBe(`
|
|
340
|
+
|
|
341
|
+
.${classHash} {
|
|
342
|
+
font-size:16px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
@media (prefers-reduced-motion: reduce) and (max-width: 768px) {
|
|
346
|
+
|
|
347
|
+
.${classHash} {
|
|
348
|
+
font-size:14px;
|
|
349
|
+
}
|
|
350
|
+
}`)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
test('it generates CSS for node with breakpoint variant', () => {
|
|
354
|
+
const node = {
|
|
355
|
+
type: 'element' as const,
|
|
356
|
+
tag: 'div',
|
|
357
|
+
style: {
|
|
358
|
+
fontSize: '16px',
|
|
359
|
+
},
|
|
360
|
+
variants: [
|
|
361
|
+
{
|
|
362
|
+
style: {
|
|
363
|
+
fontSize: '18px',
|
|
364
|
+
},
|
|
365
|
+
breakpoint: 'medium' as const,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
children: [],
|
|
369
|
+
attrs: {},
|
|
370
|
+
events: {},
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const classHash = 'test-hash'
|
|
374
|
+
const css = getNodeStyles(node, classHash)
|
|
375
|
+
|
|
376
|
+
expect(css).toBe(`
|
|
377
|
+
|
|
378
|
+
.${classHash} {
|
|
379
|
+
font-size:16px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@media (min-width: 960px) {
|
|
383
|
+
|
|
384
|
+
.${classHash} {
|
|
385
|
+
font-size:18px;
|
|
386
|
+
}
|
|
387
|
+
}`)
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test('it generates CSS for node with animations', () => {
|
|
391
|
+
const node = {
|
|
392
|
+
type: 'element' as const,
|
|
393
|
+
tag: 'div',
|
|
394
|
+
style: {
|
|
395
|
+
color: 'red',
|
|
396
|
+
},
|
|
397
|
+
variants: undefined,
|
|
398
|
+
animations: {
|
|
399
|
+
'fade-in': {
|
|
400
|
+
'0': {
|
|
401
|
+
key: 'opacity',
|
|
402
|
+
position: 0,
|
|
403
|
+
value: '0',
|
|
404
|
+
},
|
|
405
|
+
'1': {
|
|
406
|
+
key: 'opacity',
|
|
407
|
+
position: 1,
|
|
408
|
+
value: '1',
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
children: [],
|
|
413
|
+
attrs: {},
|
|
414
|
+
events: {},
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const classHash = 'test-hash'
|
|
418
|
+
const animationHashes = new Set<string>()
|
|
419
|
+
const css = getNodeStyles(node, classHash, animationHashes)
|
|
420
|
+
|
|
421
|
+
expect(css).toBe(`
|
|
422
|
+
|
|
423
|
+
.${classHash} {
|
|
424
|
+
color:red;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
@keyframes fade-in {
|
|
428
|
+
0% {
|
|
429
|
+
opacity: 0;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
100% {
|
|
433
|
+
opacity: 1;
|
|
434
|
+
}
|
|
435
|
+
}`)
|
|
436
|
+
expect(animationHashes.has('fade-in')).toBe(true)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
test('it tracks animation hashes to prevent duplicates', () => {
|
|
440
|
+
const node1 = {
|
|
441
|
+
type: 'element' as const,
|
|
442
|
+
tag: 'div',
|
|
443
|
+
style: { color: 'red' },
|
|
444
|
+
variants: undefined,
|
|
445
|
+
animations: {
|
|
446
|
+
'fade-in': {
|
|
447
|
+
'0': {
|
|
448
|
+
key: 'opacity',
|
|
449
|
+
position: 0,
|
|
450
|
+
value: '0',
|
|
451
|
+
},
|
|
452
|
+
'1': {
|
|
453
|
+
key: 'opacity',
|
|
454
|
+
position: 1,
|
|
455
|
+
value: '1',
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
children: [],
|
|
460
|
+
attrs: {},
|
|
461
|
+
events: {},
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const node2 = {
|
|
465
|
+
type: 'element' as const,
|
|
466
|
+
tag: 'span',
|
|
467
|
+
style: { color: 'blue' },
|
|
468
|
+
variants: undefined,
|
|
469
|
+
animations: {
|
|
470
|
+
'fade-in': {
|
|
471
|
+
'0': {
|
|
472
|
+
key: 'opacity',
|
|
473
|
+
position: 0,
|
|
474
|
+
value: '0',
|
|
475
|
+
},
|
|
476
|
+
'1': {
|
|
477
|
+
key: 'opacity',
|
|
478
|
+
position: 1,
|
|
479
|
+
value: '1',
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
children: [],
|
|
484
|
+
attrs: {},
|
|
485
|
+
events: {},
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const animationHashes = new Set<string>()
|
|
489
|
+
const css1 = getNodeStyles(node1, 'hash1', animationHashes)
|
|
490
|
+
const css2 = getNodeStyles(node2, 'hash2', animationHashes)
|
|
491
|
+
|
|
492
|
+
expect(css1).toBe(`
|
|
493
|
+
|
|
494
|
+
.hash1 {
|
|
495
|
+
color:red;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
@keyframes fade-in {
|
|
499
|
+
0% {
|
|
500
|
+
opacity: 0;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
100% {
|
|
504
|
+
opacity: 1;
|
|
505
|
+
}
|
|
506
|
+
}`)
|
|
507
|
+
expect(css2).toBe(`
|
|
508
|
+
|
|
509
|
+
.hash2 {
|
|
510
|
+
color:blue;
|
|
511
|
+
}`)
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
test('it generates CSS for node with scrollbar-width', () => {
|
|
515
|
+
const node = {
|
|
516
|
+
type: 'element' as const,
|
|
517
|
+
tag: 'div',
|
|
518
|
+
style: {
|
|
519
|
+
color: 'red',
|
|
520
|
+
'scrollbar-width': 'thin' as const,
|
|
521
|
+
},
|
|
522
|
+
variants: undefined,
|
|
523
|
+
children: [],
|
|
524
|
+
attrs: {},
|
|
525
|
+
events: {},
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const classHash = 'test-hash'
|
|
529
|
+
const css = getNodeStyles(node, classHash)
|
|
530
|
+
|
|
531
|
+
expect(css).toBe(`
|
|
532
|
+
|
|
533
|
+
.${classHash} {
|
|
534
|
+
color:red;
|
|
535
|
+
scrollbar-width:thin;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.${classHash}::-webkit-scrollbar {
|
|
539
|
+
width: 4px;
|
|
540
|
+
}`)
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('it handles scrollbar-width none', () => {
|
|
544
|
+
const node = {
|
|
545
|
+
type: 'element' as const,
|
|
546
|
+
tag: 'div',
|
|
547
|
+
style: {
|
|
548
|
+
color: 'red',
|
|
549
|
+
'scrollbar-width': 'none' as const,
|
|
550
|
+
},
|
|
551
|
+
variants: undefined,
|
|
552
|
+
children: [],
|
|
553
|
+
attrs: {},
|
|
554
|
+
events: {},
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const classHash = 'test-hash'
|
|
558
|
+
const css = getNodeStyles(node, classHash)
|
|
559
|
+
|
|
560
|
+
expect(css).toBe(`
|
|
561
|
+
|
|
562
|
+
.${classHash} {
|
|
563
|
+
color:red;
|
|
564
|
+
scrollbar-width:none;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.${classHash}::-webkit-scrollbar {
|
|
568
|
+
width: 0;
|
|
569
|
+
}`)
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
test('it generates CSS for node with startingStyle variant', () => {
|
|
573
|
+
const node = {
|
|
574
|
+
type: 'element' as const,
|
|
575
|
+
tag: 'div',
|
|
576
|
+
style: {
|
|
577
|
+
color: 'blue',
|
|
578
|
+
},
|
|
579
|
+
variants: [
|
|
580
|
+
{
|
|
581
|
+
style: {
|
|
582
|
+
color: 'red',
|
|
583
|
+
},
|
|
584
|
+
startingStyle: true,
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
children: [],
|
|
588
|
+
attrs: {},
|
|
589
|
+
events: {},
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const classHash = 'test-hash'
|
|
593
|
+
const css = getNodeStyles(node, classHash)
|
|
594
|
+
|
|
595
|
+
expect(css).toBe(`
|
|
596
|
+
|
|
597
|
+
.${classHash} {
|
|
598
|
+
color:blue;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.${classHash} {
|
|
602
|
+
|
|
603
|
+
@starting-style {
|
|
604
|
+
color:red;
|
|
605
|
+
}
|
|
606
|
+
}`)
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
test('it handles node with empty style', () => {
|
|
610
|
+
const node = {
|
|
611
|
+
type: 'element' as const,
|
|
612
|
+
tag: 'div',
|
|
613
|
+
style: {},
|
|
614
|
+
variants: undefined,
|
|
615
|
+
children: [],
|
|
616
|
+
attrs: {},
|
|
617
|
+
events: {},
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const classHash = 'test-hash'
|
|
621
|
+
const css = getNodeStyles(node, classHash)
|
|
622
|
+
|
|
623
|
+
expect(css).toBe(``)
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
test('it handles node with undefined style', () => {
|
|
627
|
+
const node = {
|
|
628
|
+
type: 'element' as const,
|
|
629
|
+
tag: 'div',
|
|
630
|
+
style: undefined,
|
|
631
|
+
variants: undefined,
|
|
632
|
+
children: [],
|
|
633
|
+
attrs: {},
|
|
634
|
+
events: {},
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const classHash = 'test-hash'
|
|
638
|
+
const css = getNodeStyles(node, classHash)
|
|
639
|
+
|
|
640
|
+
expect(css).toBe(``)
|
|
641
|
+
})
|
|
642
|
+
|
|
643
|
+
test('it handles legacy variants stored in style object', () => {
|
|
644
|
+
const node = {
|
|
645
|
+
type: 'element' as const,
|
|
646
|
+
tag: 'div',
|
|
647
|
+
style: {
|
|
648
|
+
color: 'blue',
|
|
649
|
+
variants: [
|
|
650
|
+
{
|
|
651
|
+
hover: true,
|
|
652
|
+
style: {
|
|
653
|
+
color: 'red',
|
|
654
|
+
},
|
|
655
|
+
},
|
|
656
|
+
],
|
|
657
|
+
} as any,
|
|
658
|
+
variants: undefined,
|
|
659
|
+
children: [],
|
|
660
|
+
attrs: {},
|
|
661
|
+
events: {},
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const classHash = 'test-hash'
|
|
665
|
+
const css = getNodeStyles(node, classHash)
|
|
666
|
+
|
|
667
|
+
expect(css).toBe(`
|
|
668
|
+
|
|
669
|
+
.${classHash} {
|
|
670
|
+
color:blue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.${classHash}:hover {
|
|
674
|
+
color:red;
|
|
675
|
+
}`)
|
|
676
|
+
})
|
|
677
|
+
|
|
678
|
+
test('it omits variants, breakpoints, and shadows from style', () => {
|
|
679
|
+
const node = {
|
|
680
|
+
type: 'element' as const,
|
|
681
|
+
tag: 'div',
|
|
682
|
+
style: {
|
|
683
|
+
color: 'red',
|
|
684
|
+
variants: [{ hover: true, style: { color: 'blue' } }],
|
|
685
|
+
breakpoints: {},
|
|
686
|
+
shadows: [],
|
|
687
|
+
} as any,
|
|
688
|
+
variants: undefined,
|
|
689
|
+
children: [],
|
|
690
|
+
attrs: {},
|
|
691
|
+
events: {},
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const classHash = 'test-hash'
|
|
695
|
+
const css = getNodeStyles(node, classHash)
|
|
696
|
+
|
|
697
|
+
// Note: variants in style object are still rendered (legacy support)
|
|
698
|
+
// but breakpoints and shadows are omitted
|
|
699
|
+
expect(css).toBe(`
|
|
700
|
+
|
|
701
|
+
.${classHash} {
|
|
702
|
+
color:red;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.${classHash}:hover {
|
|
706
|
+
color:blue;
|
|
707
|
+
}`)
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
test('it handles ComponentNodeModel', () => {
|
|
711
|
+
const node = {
|
|
712
|
+
type: 'component' as const,
|
|
713
|
+
name: 'TestComponent',
|
|
714
|
+
style: {
|
|
715
|
+
color: 'purple',
|
|
716
|
+
},
|
|
717
|
+
variants: undefined,
|
|
718
|
+
children: [],
|
|
719
|
+
attrs: {},
|
|
720
|
+
events: {},
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const classHash = 'component-hash'
|
|
724
|
+
const css = getNodeStyles(node, classHash)
|
|
725
|
+
|
|
726
|
+
expect(css).toBe(`
|
|
727
|
+
|
|
728
|
+
.${classHash} {
|
|
729
|
+
color:purple;
|
|
730
|
+
}`)
|
|
731
|
+
})
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
describe('createStylesheet()', () => {
|
|
735
|
+
test('it generates CSS for a simple component', () => {
|
|
736
|
+
const node = {
|
|
737
|
+
type: 'element' as const,
|
|
738
|
+
tag: 'div',
|
|
739
|
+
style: {
|
|
740
|
+
color: 'red',
|
|
741
|
+
},
|
|
742
|
+
variants: undefined,
|
|
743
|
+
children: [],
|
|
744
|
+
attrs: {},
|
|
745
|
+
events: {},
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const component: Component = {
|
|
749
|
+
name: 'TestComponent',
|
|
750
|
+
nodes: {
|
|
751
|
+
'0': node,
|
|
752
|
+
},
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const classHash = getClassName([node.style, node.variants])
|
|
756
|
+
const stylesheet = createStylesheet(
|
|
757
|
+
component,
|
|
758
|
+
[],
|
|
759
|
+
{ defaultTheme },
|
|
760
|
+
{ includeResetStyle: false, createFontFaces: false },
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
expect(stylesheet).toContain(` .${classHash} {
|
|
764
|
+
color:red;
|
|
765
|
+
}`)
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
test('it generates CSS for component with variants', () => {
|
|
769
|
+
const node = {
|
|
770
|
+
type: 'element' as const,
|
|
771
|
+
tag: 'div',
|
|
772
|
+
style: {
|
|
773
|
+
color: 'blue',
|
|
774
|
+
},
|
|
775
|
+
variants: [
|
|
776
|
+
{
|
|
777
|
+
hover: true,
|
|
778
|
+
style: {
|
|
779
|
+
color: 'red',
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
],
|
|
783
|
+
children: [],
|
|
784
|
+
attrs: {},
|
|
785
|
+
events: {},
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const component: Component = {
|
|
789
|
+
name: 'TestComponent',
|
|
790
|
+
nodes: {
|
|
791
|
+
'0': node,
|
|
792
|
+
},
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const classHash = getClassName([node.style, node.variants])
|
|
796
|
+
const stylesheet = createStylesheet(
|
|
797
|
+
component,
|
|
798
|
+
[],
|
|
799
|
+
{ defaultTheme },
|
|
800
|
+
{ includeResetStyle: false, createFontFaces: false },
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
expect(stylesheet).toContain(`
|
|
804
|
+
.${classHash} {
|
|
805
|
+
color:blue;
|
|
806
|
+
}`)
|
|
807
|
+
expect(stylesheet).toContain(`
|
|
808
|
+
.${classHash}:hover {
|
|
809
|
+
color:red;
|
|
810
|
+
}`)
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
test('it generates CSS for component with media query variants', () => {
|
|
814
|
+
const node = {
|
|
815
|
+
type: 'element' as const,
|
|
816
|
+
tag: 'div',
|
|
817
|
+
style: {
|
|
818
|
+
fontSize: '16px',
|
|
819
|
+
},
|
|
820
|
+
variants: [
|
|
821
|
+
{
|
|
822
|
+
style: {
|
|
823
|
+
fontSize: '14px',
|
|
824
|
+
},
|
|
825
|
+
mediaQuery: {
|
|
826
|
+
'max-width': '768px',
|
|
827
|
+
},
|
|
828
|
+
},
|
|
829
|
+
],
|
|
830
|
+
children: [],
|
|
831
|
+
attrs: {},
|
|
832
|
+
events: {},
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const component: Component = {
|
|
836
|
+
name: 'TestComponent',
|
|
837
|
+
nodes: {
|
|
838
|
+
'0': node,
|
|
839
|
+
},
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
const classHash = getClassName([node.style, node.variants])
|
|
843
|
+
const stylesheet = createStylesheet(
|
|
844
|
+
component,
|
|
845
|
+
[],
|
|
846
|
+
{ defaultTheme },
|
|
847
|
+
{ includeResetStyle: false, createFontFaces: false },
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
expect(stylesheet).toContain(`
|
|
851
|
+
.${classHash} {
|
|
852
|
+
font-size:16px;
|
|
853
|
+
}`)
|
|
854
|
+
// Check for media query with the variant styles
|
|
855
|
+
expect(stylesheet).toContain('@media (max-width: 768px)')
|
|
856
|
+
expect(stylesheet).toContain(`
|
|
857
|
+
.${classHash} {
|
|
858
|
+
font-size:14px;
|
|
859
|
+
}`)
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
test('it handles empty component', () => {
|
|
863
|
+
const component: Component = {
|
|
864
|
+
name: 'EmptyComponent',
|
|
865
|
+
nodes: {},
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const stylesheet = createStylesheet(
|
|
869
|
+
component,
|
|
870
|
+
[],
|
|
871
|
+
{ defaultTheme },
|
|
872
|
+
{ includeResetStyle: false, createFontFaces: false },
|
|
873
|
+
)
|
|
874
|
+
|
|
875
|
+
expect(stylesheet).toContain('body, :host {')
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
test('it handles component with multiple nodes', () => {
|
|
879
|
+
const node0 = {
|
|
880
|
+
type: 'element' as const,
|
|
881
|
+
tag: 'div',
|
|
882
|
+
style: {
|
|
883
|
+
color: 'red',
|
|
884
|
+
},
|
|
885
|
+
variants: undefined,
|
|
886
|
+
children: [],
|
|
887
|
+
attrs: {},
|
|
888
|
+
events: {},
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
const node1 = {
|
|
892
|
+
type: 'element' as const,
|
|
893
|
+
tag: 'span',
|
|
894
|
+
style: {
|
|
895
|
+
color: 'blue',
|
|
896
|
+
},
|
|
897
|
+
variants: undefined,
|
|
898
|
+
children: [],
|
|
899
|
+
attrs: {},
|
|
900
|
+
events: {},
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const component: Component = {
|
|
904
|
+
name: 'MultiNodeComponent',
|
|
905
|
+
nodes: {
|
|
906
|
+
'0': node0,
|
|
907
|
+
'1': node1,
|
|
908
|
+
},
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const classHash0 = getClassName([node0.style, node0.variants])
|
|
912
|
+
const classHash1 = getClassName([node1.style, node1.variants])
|
|
913
|
+
const stylesheet = createStylesheet(
|
|
914
|
+
component,
|
|
915
|
+
[],
|
|
916
|
+
{ defaultTheme },
|
|
917
|
+
{ includeResetStyle: false, createFontFaces: false },
|
|
918
|
+
)
|
|
919
|
+
|
|
920
|
+
expect(stylesheet).toContain(`
|
|
921
|
+
.${classHash0} {
|
|
922
|
+
color:red;
|
|
923
|
+
}`)
|
|
924
|
+
expect(stylesheet).toContain(`
|
|
925
|
+
.${classHash1} {
|
|
926
|
+
color:blue;
|
|
927
|
+
}`)
|
|
928
|
+
})
|
|
929
|
+
})
|