@pyreon/rocketstyle 0.24.4 → 0.24.6

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.
Files changed (60) hide show
  1. package/package.json +8 -10
  2. package/src/__tests__/attrs-overloads.test.ts +0 -97
  3. package/src/__tests__/attrs.test.ts +0 -190
  4. package/src/__tests__/cache-key-boolean-collision.test.ts +0 -54
  5. package/src/__tests__/chaining.test.ts +0 -86
  6. package/src/__tests__/collection.test.ts +0 -35
  7. package/src/__tests__/compose.test.ts +0 -36
  8. package/src/__tests__/context.test.ts +0 -200
  9. package/src/__tests__/createLocalProvider.test.ts +0 -280
  10. package/src/__tests__/dimensions.test.ts +0 -183
  11. package/src/__tests__/e2e-styler.test.ts +0 -299
  12. package/src/__tests__/hooks.test.ts +0 -178
  13. package/src/__tests__/isRocketComponent.test.ts +0 -48
  14. package/src/__tests__/memo-cap.test.ts +0 -174
  15. package/src/__tests__/minimal-theme.test.ts +0 -62
  16. package/src/__tests__/misc.test.ts +0 -204
  17. package/src/__tests__/native-marker.test.ts +0 -9
  18. package/src/__tests__/providerConsumer.test.ts +0 -183
  19. package/src/__tests__/reactive-props-preservation.test.ts +0 -195
  20. package/src/__tests__/rocketstyle.browser.test.tsx +0 -481
  21. package/src/__tests__/rocketstyleIntegration.test.ts +0 -711
  22. package/src/__tests__/theme-integration.test.tsx +0 -254
  23. package/src/__tests__/themeUtils.test.ts +0 -463
  24. package/src/cache/LocalThemeManager.ts +0 -14
  25. package/src/cache/index.ts +0 -3
  26. package/src/constants/booleanTags.ts +0 -32
  27. package/src/constants/defaultDimensions.ts +0 -23
  28. package/src/constants/index.ts +0 -59
  29. package/src/context/context.ts +0 -70
  30. package/src/context/createLocalProvider.ts +0 -97
  31. package/src/context/localContext.ts +0 -37
  32. package/src/env.d.ts +0 -6
  33. package/src/hoc/index.ts +0 -3
  34. package/src/hoc/rocketstyleAttrsHoc.ts +0 -76
  35. package/src/hooks/index.ts +0 -4
  36. package/src/hooks/usePseudoState.ts +0 -79
  37. package/src/hooks/useTheme.ts +0 -48
  38. package/src/index.ts +0 -95
  39. package/src/init.ts +0 -93
  40. package/src/isRocketComponent.ts +0 -16
  41. package/src/rocketstyle.ts +0 -640
  42. package/src/types/attrs.ts +0 -23
  43. package/src/types/config.ts +0 -48
  44. package/src/types/configuration.ts +0 -69
  45. package/src/types/dimensions.ts +0 -109
  46. package/src/types/hoc.ts +0 -5
  47. package/src/types/pseudo.ts +0 -19
  48. package/src/types/rocketComponent.ts +0 -24
  49. package/src/types/rocketstyle.ts +0 -220
  50. package/src/types/styles.ts +0 -61
  51. package/src/types/theme.ts +0 -18
  52. package/src/types/utils.ts +0 -98
  53. package/src/utils/attrs.ts +0 -181
  54. package/src/utils/chaining.ts +0 -58
  55. package/src/utils/collection.ts +0 -9
  56. package/src/utils/compose.ts +0 -11
  57. package/src/utils/dimensions.ts +0 -126
  58. package/src/utils/statics.ts +0 -44
  59. package/src/utils/styles.ts +0 -18
  60. package/src/utils/theme.ts +0 -211
@@ -1,711 +0,0 @@
1
- import {
2
- ThemeCapture,
3
- getComputedTheme,
4
- initTestConfig,
5
- renderProps,
6
- withThemeContext,
7
- } from '@pyreon/test-utils'
8
- import rocketstyle from '../init'
9
- import isRocketComponent from '../isRocketComponent'
10
-
11
- let cleanup: () => void
12
- beforeAll(() => {
13
- cleanup = initTestConfig()
14
- })
15
- afterAll(() => cleanup())
16
-
17
- /**
18
- * Base component that filters internal props and returns a VNode-like object.
19
- * In Pyreon, components are plain functions — no forwardRef needed.
20
- */
21
- const BaseComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
22
- type: 'div',
23
- props: rest,
24
- children,
25
- key: null,
26
- $rocketstyle: typeof $rocketstyle === 'function' ? $rocketstyle() : $rocketstyle,
27
- $rocketstate: typeof $rocketstate === 'function' ? $rocketstate() : $rocketstate,
28
- })
29
- BaseComponent.displayName = 'BaseComponent'
30
-
31
- // --------------------------------------------------------
32
- // rocketstyle factory
33
- // --------------------------------------------------------
34
- describe('rocketstyle factory', () => {
35
- it('creates a component from factory', () => {
36
- const Button = rocketstyle()({
37
- name: 'TestButton',
38
- component: BaseComponent,
39
- })
40
- expect(Button).toBeDefined()
41
- expect(typeof Button).toBe('function')
42
- })
43
-
44
- it('sets IS_ROCKETSTYLE on the component', () => {
45
- const Button = rocketstyle()({
46
- name: 'TestButton',
47
- component: BaseComponent,
48
- })
49
- expect(Button.IS_ROCKETSTYLE).toBe(true)
50
- expect(isRocketComponent(Button)).toBe(true)
51
- })
52
-
53
- it('sets displayName on the component', () => {
54
- const Button = rocketstyle()({
55
- name: 'MyButton',
56
- component: BaseComponent,
57
- })
58
- expect(Button.displayName).toBe('MyButton')
59
- })
60
-
61
- it('throws when component is missing', () => {
62
- expect(() => {
63
- rocketstyle()({ name: 'Test', component: undefined as any })
64
- }).toThrow('component')
65
- })
66
-
67
- it('throws when name is missing', () => {
68
- expect(() => {
69
- rocketstyle()({ name: '', component: BaseComponent })
70
- }).toThrow('name')
71
- })
72
-
73
- it('throws when dimension uses reserved key', () => {
74
- expect(() => {
75
- rocketstyle({ dimensions: { attrs: 'attrs' } as any })({
76
- name: 'Test',
77
- component: BaseComponent,
78
- })
79
- }).toThrow('invalid')
80
- })
81
-
82
- it('allows custom dimensions', () => {
83
- const Button = rocketstyle({
84
- dimensions: { colors: 'color', shapes: 'shape' },
85
- })({ name: 'CustomButton', component: BaseComponent })
86
- expect(Button).toBeDefined()
87
- expect(Button.IS_ROCKETSTYLE).toBe(true)
88
- })
89
-
90
- it('defaults useBooleans to false', () => {
91
- const Button = rocketstyle()({
92
- name: 'Test',
93
- component: BaseComponent,
94
- })
95
- expect(Button).toBeDefined()
96
- })
97
-
98
- // Regression: type default and runtime default must agree. When they
99
- // diverged (type default `true`, runtime default `false`), boolean
100
- // dimension props like `<Heading level3 />` typechecked but were silently
101
- // dropped at runtime — producing components with only base .theme() styles
102
- // and missing .sizes()/.variants()/.states() overrides.
103
- it('type default matches runtime default (both false)', () => {
104
- // Runtime: boolean shorthand is IGNORED when useBooleans is false
105
- const Button: any = rocketstyle()({ name: 'TypeTest', component: BaseComponent })
106
- .sizes(() => ({ level3: { fontSize: 24 } }))
107
- const ignored = getComputedTheme(Button, { level3: true })
108
- expect(ignored.fontSize).toBeUndefined()
109
-
110
- // Runtime: object form IS applied
111
- const applied = getComputedTheme(Button, { size: 'level3' })
112
- expect(applied.fontSize).toBe(24)
113
-
114
- // Type-level regression: when useBooleans is omitted, UB infers to
115
- // `false`, so boolean dimension props must NOT be on the public surface.
116
- // Before the fix (type default `true`), `level3: true` would typecheck —
117
- // silently matching the bokisch / ssr-showcase regression.
118
- const TypedComponent = (_props: { children?: unknown }): null => null
119
- const Typed = rocketstyle()({ name: 'T', component: TypedComponent })
120
- .sizes(() => ({ level3: { fontSize: 24 } }))
121
- type Props = (typeof Typed)['$$types']
122
- const _hasNoBooleanShorthand: 'level3' extends keyof Props ? false : true = true
123
- void _hasNoBooleanShorthand
124
- })
125
- })
126
-
127
- // --------------------------------------------------------
128
- // chaining methods
129
- // --------------------------------------------------------
130
- describe('chaining methods', () => {
131
- const Button: any = rocketstyle()({
132
- name: 'ChainButton',
133
- component: BaseComponent,
134
- })
135
-
136
- it('.attrs() returns a new component', () => {
137
- const Enhanced = Button.attrs(() => ({ label: 'test' }))
138
- expect(Enhanced).toBeDefined()
139
- expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
140
- expect(Enhanced).not.toBe(Button)
141
- })
142
-
143
- it('.attrs() with priority option', () => {
144
- const Enhanced = Button.attrs(() => ({ label: 'priority' }), {
145
- priority: true,
146
- })
147
- expect(Enhanced).toBeDefined()
148
- expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
149
- })
150
-
151
- it('.attrs() with filter option', () => {
152
- const Enhanced = Button.attrs(() => ({ label: 'filtered' }), {
153
- filter: ['internal'],
154
- })
155
- expect(Enhanced).toBeDefined()
156
- })
157
-
158
- it('.config() returns a new component', () => {
159
- const Enhanced = Button.config({ DEBUG: true })
160
- expect(Enhanced).toBeDefined()
161
- expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
162
- })
163
-
164
- it('.statics() returns a new component', () => {
165
- const Enhanced = Button.statics({ customMeta: 'value' })
166
- expect(Enhanced).toBeDefined()
167
- expect(Enhanced.meta.customMeta).toBe('value')
168
- })
169
-
170
- it('.theme() returns a new component', () => {
171
- const Enhanced = Button.theme(() => ({ color: 'blue' }))
172
- expect(Enhanced).toBeDefined()
173
- expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
174
- })
175
-
176
- it('.styles() returns a new component', () => {
177
- const Enhanced = Button.styles(() => 'color: red;')
178
- expect(Enhanced).toBeDefined()
179
- })
180
-
181
- it('.compose() returns a new component', () => {
182
- const hoc = (C: any) => C
183
- const Enhanced = Button.compose({ myHoc: hoc })
184
- expect(Enhanced).toBeDefined()
185
- })
186
-
187
- it('supports chaining multiple methods', () => {
188
- const Enhanced = Button.theme(() => ({ color: 'blue' }))
189
- .attrs(() => ({ label: 'test' }))
190
- .config({ name: 'EnhancedButton' })
191
- .statics({ version: '1.0' })
192
-
193
- expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
194
- expect(Enhanced.meta.version).toBe('1.0')
195
- })
196
-
197
- it('.getStaticDimensions() returns dimension info', () => {
198
- const Themed = Button.states(() => ({
199
- primary: { color: 'red' },
200
- secondary: { color: 'blue' },
201
- }))
202
-
203
- const info = Themed.getStaticDimensions({ rootSize: 16 })
204
- expect(info.dimensions).toBeDefined()
205
- expect(info.useBooleans).toBe(false)
206
- expect(info.multiKeys).toBeDefined()
207
- })
208
-
209
- it('.getDefaultAttrs() evaluates attrs chain', () => {
210
- const WithAttrs = Button.attrs((props: any) => ({
211
- label: 'default',
212
- ...props,
213
- }))
214
- const result = WithAttrs.getDefaultAttrs({}, {}, 'light')
215
- expect(result.label).toBe('default')
216
- })
217
-
218
- it('.getDefaultAttrs() passes isDark/isLight helpers matching the requested mode', () => {
219
- // Regression: pre-fix the helpers were inverted in `getDefaultAttrs`
220
- // (isDark: mode === 'light', isLight: mode === 'dark') so introspection
221
- // callers (rocketstories-style story generators, devtools) saw the
222
- // OPPOSITE of what the runtime renders. Runtime via `useTheme` derives
223
- // helpers correctly from context (and handles `inversed` by flipping
224
- // the mode at the Provider level — see context.test.ts), so the bug
225
- // only surfaced for callers that read the helpers via `getDefaultAttrs`.
226
- //
227
- // Contract: `mode` is the EFFECTIVE mode (post-`inversed` resolution).
228
- // Callers wanting "inversed light" pass `'dark'`; this function takes
229
- // the resolved value, so testing both light/dark covers both the
230
- // un-inversed and inversed cases.
231
- const captured: Array<{ mode: any; isDark: any; isLight: any }> = []
232
- const Probe = Button.attrs((_props: any, _theme: any, helpers: any) => {
233
- captured.push({
234
- mode: helpers.mode,
235
- isDark: helpers.isDark,
236
- isLight: helpers.isLight,
237
- })
238
- return {}
239
- })
240
-
241
- Probe.getDefaultAttrs({}, {}, 'light')
242
- Probe.getDefaultAttrs({}, {}, 'dark')
243
-
244
- expect(captured[0]).toEqual({ mode: 'light', isDark: false, isLight: true })
245
- expect(captured[1]).toEqual({ mode: 'dark', isDark: true, isLight: false })
246
- })
247
- })
248
-
249
- // --------------------------------------------------------
250
- // rendering
251
- // --------------------------------------------------------
252
- describe('rendering', () => {
253
- it('renders a basic rocketstyle component', () => {
254
- const Button: any = rocketstyle()({
255
- name: 'RenderButton',
256
- component: BaseComponent,
257
- }).config({})
258
-
259
- const result = renderProps(Button, { children: 'Hello' })
260
- expect(result).toBeDefined()
261
- })
262
-
263
- it('adds data-rocketstyle attribute in dev mode', () => {
264
- const Button: any = rocketstyle()({
265
- name: 'DevButton',
266
- component: BaseComponent,
267
- }).config({})
268
-
269
- const result = renderProps(Button)
270
- expect(result['data-rocketstyle']).toBe('DevButton')
271
- })
272
-
273
- it('renders with attrs defaults', () => {
274
- const Button: any = rocketstyle()({
275
- name: 'AttrsButton',
276
- component: BaseComponent,
277
- }).attrs((() => ({ 'data-default': 'yes' })) as any)
278
-
279
- const result = renderProps(Button)
280
- expect(result['data-default']).toBe('yes')
281
- })
282
-
283
- it('explicit props override attrs', () => {
284
- const Button: any = rocketstyle()({
285
- name: 'OverrideButton',
286
- component: BaseComponent,
287
- }).attrs((() => ({ 'data-val': 'from-attrs' })) as any)
288
-
289
- const result = renderProps(Button, { 'data-val': 'from-props' })
290
- expect(result['data-val']).toBe('from-props')
291
- })
292
-
293
- it('renders with theme', () => {
294
- const Button: any = rocketstyle()({
295
- name: 'ThemedButton',
296
- component: BaseComponent,
297
- }).theme(() => ({ fontSize: 14 }))
298
-
299
- const result = renderProps(Button)
300
- expect(result).toBeDefined()
301
- })
302
-
303
- it('renders with dimension states', () => {
304
- const Button: any = rocketstyle()({
305
- name: 'StatesButton',
306
- component: BaseComponent,
307
- })
308
- .theme(() => ({ color: 'default' }))
309
- .states(() => ({
310
- primary: { color: 'blue' },
311
- secondary: { color: 'green' },
312
- }))
313
-
314
- const result = renderProps(Button, { state: 'primary' })
315
- expect(result).toBeDefined()
316
- })
317
-
318
- it('renders with boolean dimension props', () => {
319
- const Button: any = rocketstyle()({
320
- name: 'BoolButton',
321
- component: BaseComponent,
322
- }).states(() => ({
323
- primary: { color: 'blue' },
324
- }))
325
-
326
- // boolean prop 'primary' should map to state='primary'
327
- const result = renderProps(Button, { primary: true })
328
- expect(result).toBeDefined()
329
- })
330
-
331
- it('renders with priority attrs', () => {
332
- const Button: any = rocketstyle()({
333
- name: 'PriorityButton',
334
- component: BaseComponent,
335
- }).attrs((() => ({ 'data-priority': 'yes' })) as any, { priority: true })
336
-
337
- const result = renderProps(Button)
338
- expect(result['data-priority']).toBe('yes')
339
- })
340
- })
341
-
342
- // --------------------------------------------------------
343
- // DEBUG option
344
- // --------------------------------------------------------
345
- describe('DEBUG option', () => {
346
- it('calls console.debug when DEBUG is enabled', () => {
347
- const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {
348
- /* no-op */
349
- })
350
-
351
- const Button: any = rocketstyle()({
352
- name: 'DebugButton',
353
- component: BaseComponent,
354
- }).config({ DEBUG: true })
355
-
356
- renderProps(Button)
357
- expect(debugSpy).toHaveBeenCalledWith(
358
- '[rocketstyle] DebugButton render:',
359
- expect.objectContaining({
360
- component: 'DebugButton',
361
- rocketstate: expect.any(Object),
362
- rocketstyle: expect.any(Object),
363
- dimensions: expect.any(Object),
364
- mode: expect.any(String),
365
- }),
366
- )
367
-
368
- debugSpy.mockRestore()
369
- })
370
-
371
- it('does not call console.debug when DEBUG is not set', () => {
372
- const debugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {
373
- /* no-op */
374
- })
375
-
376
- const Button: any = rocketstyle()({
377
- name: 'NoDebugButton',
378
- component: BaseComponent,
379
- }).config({})
380
-
381
- renderProps(Button)
382
- expect(debugSpy).not.toHaveBeenCalled()
383
-
384
- debugSpy.mockRestore()
385
- })
386
- })
387
-
388
- // --------------------------------------------------------
389
- // passProps option
390
- // --------------------------------------------------------
391
- describe('passProps option', () => {
392
- it('passes styling props through when passProps is configured', () => {
393
- const PassPropsComponent: any = ({
394
- children,
395
- $rocketstyle,
396
- $rocketstate,
397
- state,
398
- ...rest
399
- }: any) => ({
400
- type: 'div',
401
- props: { ...rest, 'data-state': state },
402
- children,
403
- key: null,
404
- })
405
- PassPropsComponent.displayName = 'PassPropsComponent'
406
-
407
- const Button: any = rocketstyle()({
408
- name: 'PassPropsButton',
409
- component: PassPropsComponent,
410
- })
411
- .states(() => ({
412
- primary: { color: 'blue' },
413
- secondary: { color: 'green' },
414
- }))
415
- .config({ passProps: ['state'] } as any)
416
-
417
- const result = renderProps(Button, { state: 'primary' })
418
- expect(result['data-state']).toBe('primary')
419
- })
420
-
421
- it('does not pass styling props without passProps', () => {
422
- const PassPropsComponent: any = ({
423
- children,
424
- $rocketstyle,
425
- $rocketstate,
426
- state,
427
- ...rest
428
- }: any) => ({
429
- type: 'div',
430
- props: { ...rest, 'data-state': state ?? 'none' },
431
- children,
432
- key: null,
433
- })
434
- PassPropsComponent.displayName = 'NoPassPropsComponent'
435
-
436
- const Button: any = rocketstyle()({
437
- name: 'NoPassPropsButton',
438
- component: PassPropsComponent,
439
- }).states(() => ({
440
- primary: { color: 'blue' },
441
- }))
442
-
443
- const result = renderProps(Button, { state: 'primary' })
444
- // Without passProps, the state prop should be filtered out
445
- expect(result['data-state']).toBe('none')
446
- })
447
- })
448
-
449
- // --------------------------------------------------------
450
- // IS_ROCKETSTYLE component wrapping
451
- // --------------------------------------------------------
452
- describe('IS_ROCKETSTYLE component wrapping', () => {
453
- it('skips styled() wrapping when component already has IS_ROCKETSTYLE', () => {
454
- const MarkedComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
455
- type: 'div',
456
- props: rest,
457
- children,
458
- key: null,
459
- })
460
- MarkedComponent.IS_ROCKETSTYLE = true
461
- MarkedComponent.displayName = 'MarkedComponent'
462
-
463
- const Outer: any = rocketstyle()({
464
- name: 'OuterComponent',
465
- component: MarkedComponent,
466
- })
467
-
468
- expect(Outer).toBeDefined()
469
- expect(Outer.IS_ROCKETSTYLE).toBe(true)
470
- expect(Outer.displayName).toBe('OuterComponent')
471
- })
472
-
473
- it('renders IS_ROCKETSTYLE component when chained with config', () => {
474
- const MarkedComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
475
- type: 'div',
476
- props: rest,
477
- children,
478
- key: null,
479
- })
480
- MarkedComponent.IS_ROCKETSTYLE = true
481
- MarkedComponent.displayName = 'MarkedComponent'
482
-
483
- const Outer: any = rocketstyle()({
484
- name: 'OuterChained',
485
- component: MarkedComponent,
486
- }).config({})
487
-
488
- const result = renderProps(Outer, { children: 'Wrapped' })
489
- expect(result).toBeDefined()
490
- })
491
- })
492
-
493
- // --------------------------------------------------------
494
- // empty dimensions validation
495
- // --------------------------------------------------------
496
- describe('empty dimensions validation', () => {
497
- it('throws when dimensions is an empty object', () => {
498
- expect(() => {
499
- rocketstyle({ dimensions: {} as any })({
500
- name: 'EmptyDimensions',
501
- component: BaseComponent,
502
- })
503
- }).toThrow('dimensions')
504
- })
505
- })
506
-
507
- // --------------------------------------------------------
508
- // multiple dimension values
509
- // --------------------------------------------------------
510
- describe('multiple dimension values', () => {
511
- it('renders with array values for multi-key dimensions', () => {
512
- const Button: any = rocketstyle()({
513
- name: 'MultiButton',
514
- component: BaseComponent,
515
- }).multiple(() => ({
516
- bold: { fontWeight: 'bold' },
517
- italic: { fontStyle: 'italic' },
518
- underline: { textDecoration: 'underline' },
519
- }))
520
-
521
- const result = renderProps(Button, { multiple: ['bold', 'italic'] })
522
- expect(result).toBeDefined()
523
- })
524
-
525
- it('renders with single value for non-multi dimensions', () => {
526
- const Button: any = rocketstyle()({
527
- name: 'SingleDimButton',
528
- component: BaseComponent,
529
- })
530
- .states(() => ({
531
- primary: { color: 'blue' },
532
- secondary: { color: 'green' },
533
- }))
534
- .sizes(() => ({
535
- small: { fontSize: 12 },
536
- large: { fontSize: 18 },
537
- }))
538
-
539
- const result = renderProps(Button, { state: 'primary', size: 'large' })
540
- expect(result).toBeDefined()
541
- })
542
-
543
- it('renders with boolean shorthand for multi-key dimensions', () => {
544
- const Button: any = rocketstyle()({
545
- name: 'MultiBoolButton',
546
- component: BaseComponent,
547
- }).multiple(() => ({
548
- bold: { fontWeight: 'bold' },
549
- italic: { fontStyle: 'italic' },
550
- }))
551
-
552
- // Boolean shorthand for multi-key: both bold and italic as boolean props
553
- const result = renderProps(Button, { bold: true, italic: true })
554
- expect(result).toBeDefined()
555
- })
556
- })
557
-
558
- // --------------------------------------------------------
559
- // rendering without Provider context
560
- // --------------------------------------------------------
561
- describe('rendering without Provider context', () => {
562
- it('renders component without any Provider (useContext returns default)', () => {
563
- const Button: any = rocketstyle()({
564
- name: 'NoProviderButton',
565
- component: BaseComponent,
566
- }).config({})
567
-
568
- // Call without any context pushed
569
- const vnode = Button({ children: 'NoCtx' }) as any
570
- const result = vnode?.props ?? vnode
571
- expect(result).toBeDefined()
572
- })
573
- })
574
-
575
- // --------------------------------------------------------
576
- // $rocketstyle and $rocketstate are passed to inner component
577
- // --------------------------------------------------------
578
- describe('theme and state injection', () => {
579
- it('passes $rocketstyle theme to inner component', () => {
580
- const Button: any = rocketstyle()({
581
- name: 'ThemeInjButton',
582
- component: ThemeCapture,
583
- })
584
- .theme(() => ({ color: 'blue', bg: 'white' }))
585
- .states(() => ({
586
- primary: { color: 'red' },
587
- }))
588
-
589
- const rs = getComputedTheme(Button, { state: 'primary' })
590
- expect(rs).toBeDefined()
591
- expect(rs.color).toBe('red')
592
- expect(rs.bg).toBe('white')
593
- })
594
-
595
- it('passes $rocketstate with active dimensions to inner component', () => {
596
- const Button: any = rocketstyle()({
597
- name: 'StateInjButton',
598
- component: ThemeCapture,
599
- }).states(() => ({
600
- primary: { color: 'blue' },
601
- }))
602
-
603
- const vnode = withThemeContext(() => Button({ state: 'primary' }))
604
- expect(vnode.$rocketstate).toBeDefined()
605
- expect(vnode.$rocketstate.state).toBe('primary')
606
- })
607
- })
608
-
609
- // --------------------------------------------------------
610
- // component-swap reset (cloneAndEnhance)
611
- // --------------------------------------------------------
612
- // `.config({ component: NewBase })` swaps the underlying renderable. The prior
613
- // .attrs() / .priorityAttrs() / .filterAttrs() / .compose() chains were
614
- // tailored to the previous component's prop shape — applying them to a
615
- // different component silently leaks invalid props through to the DOM (e.g.
616
- // `disabled` on an `<a>`). vitus-labs's rocketstyle drops those chains on
617
- // component swap; this regression test locks in matching behavior here.
618
- describe('component-swap reset', () => {
619
- it('drops .attrs() chain when component changes', () => {
620
- const ButtonBase: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
621
- type: 'button',
622
- props: rest,
623
- children,
624
- })
625
- ButtonBase.displayName = 'ButtonBase'
626
-
627
- const AnchorBase: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
628
- type: 'a',
629
- props: rest,
630
- children,
631
- })
632
- AnchorBase.displayName = 'AnchorBase'
633
-
634
- const Button: any = rocketstyle()({
635
- name: 'Button',
636
- component: ButtonBase,
637
- }).attrs((() => ({ 'data-button-attr': 'leaked' })) as any)
638
-
639
- const Link: any = Button.config({ component: AnchorBase })
640
-
641
- const result = renderProps(Link)
642
- expect(result['data-button-attr']).toBeUndefined()
643
- })
644
-
645
- it('preserves .attrs() chain when component is not changed', () => {
646
- const Base: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
647
- type: 'div',
648
- props: rest,
649
- children,
650
- })
651
- Base.displayName = 'Base'
652
-
653
- const Button: any = rocketstyle()({
654
- name: 'Button',
655
- component: Base,
656
- }).attrs((() => ({ 'data-keep': 'yes' })) as any)
657
-
658
- const Same: any = Button.config({ DEBUG: false })
659
-
660
- const result = renderProps(Same)
661
- expect(result['data-keep']).toBe('yes')
662
- })
663
-
664
- it('preserves .attrs() chain when same component is re-passed via config', () => {
665
- const Base: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
666
- type: 'div',
667
- props: rest,
668
- children,
669
- })
670
- Base.displayName = 'Base'
671
-
672
- const Button: any = rocketstyle()({
673
- name: 'Button',
674
- component: Base,
675
- }).attrs((() => ({ 'data-keep': 'yes' })) as any)
676
-
677
- const Same: any = Button.config({ component: Base })
678
-
679
- const result = renderProps(Same)
680
- expect(result['data-keep']).toBe('yes')
681
- })
682
-
683
- it('lets fresh attrs after component swap apply to the new component', () => {
684
- const ButtonBase: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
685
- type: 'button',
686
- props: rest,
687
- children,
688
- })
689
- ButtonBase.displayName = 'ButtonBase'
690
-
691
- const AnchorBase: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
692
- type: 'a',
693
- props: rest,
694
- children,
695
- })
696
- AnchorBase.displayName = 'AnchorBase'
697
-
698
- const Button: any = rocketstyle()({
699
- name: 'Button',
700
- component: ButtonBase,
701
- }).attrs((() => ({ 'data-from-button': 'original' })) as any)
702
-
703
- const Link: any = Button.config({ component: AnchorBase }).attrs(
704
- (() => ({ 'data-from-link': 'fresh' })) as any,
705
- )
706
-
707
- const result = renderProps(Link)
708
- expect(result['data-from-button']).toBeUndefined()
709
- expect(result['data-from-link']).toBe('fresh')
710
- })
711
- })