@pyreon/rocketstyle 0.11.0 → 0.11.2

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 (51) hide show
  1. package/package.json +14 -12
  2. package/src/__tests__/attrs.test.ts +190 -0
  3. package/src/__tests__/chaining.test.ts +86 -0
  4. package/src/__tests__/collection.test.ts +35 -0
  5. package/src/__tests__/compose.test.ts +36 -0
  6. package/src/__tests__/context.test.ts +200 -0
  7. package/src/__tests__/createLocalProvider.test.ts +248 -0
  8. package/src/__tests__/dimensions.test.ts +183 -0
  9. package/src/__tests__/e2e-styler.test.ts +291 -0
  10. package/src/__tests__/hooks.test.ts +207 -0
  11. package/src/__tests__/isRocketComponent.test.ts +48 -0
  12. package/src/__tests__/misc.test.ts +204 -0
  13. package/src/__tests__/providerConsumer.test.ts +248 -0
  14. package/src/__tests__/rocketstyleIntegration.test.ts +615 -0
  15. package/src/__tests__/themeUtils.test.ts +463 -0
  16. package/src/cache/LocalThemeManager.ts +14 -0
  17. package/src/cache/index.ts +3 -0
  18. package/src/constants/booleanTags.ts +32 -0
  19. package/src/constants/defaultDimensions.ts +23 -0
  20. package/src/constants/index.ts +44 -0
  21. package/src/context/context.ts +51 -0
  22. package/src/context/createLocalProvider.ts +84 -0
  23. package/src/context/localContext.ts +37 -0
  24. package/src/hoc/index.ts +3 -0
  25. package/src/hoc/rocketstyleAttrsHoc.ts +63 -0
  26. package/src/hooks/index.ts +4 -0
  27. package/src/hooks/usePseudoState.ts +79 -0
  28. package/src/hooks/useTheme.ts +36 -0
  29. package/src/index.ts +77 -0
  30. package/src/init.ts +93 -0
  31. package/src/isRocketComponent.ts +16 -0
  32. package/src/rocketstyle.ts +320 -0
  33. package/src/types/attrs.ts +13 -0
  34. package/src/types/config.ts +48 -0
  35. package/src/types/configuration.ts +69 -0
  36. package/src/types/dimensions.ts +106 -0
  37. package/src/types/hoc.ts +5 -0
  38. package/src/types/pseudo.ts +19 -0
  39. package/src/types/rocketComponent.ts +24 -0
  40. package/src/types/rocketstyle.ts +156 -0
  41. package/src/types/styles.ts +46 -0
  42. package/src/types/theme.ts +19 -0
  43. package/src/types/utils.ts +55 -0
  44. package/src/utils/attrs.ts +134 -0
  45. package/src/utils/chaining.ts +58 -0
  46. package/src/utils/collection.ts +9 -0
  47. package/src/utils/compose.ts +11 -0
  48. package/src/utils/dimensions.ts +126 -0
  49. package/src/utils/statics.ts +44 -0
  50. package/src/utils/styles.ts +18 -0
  51. package/src/utils/theme.ts +196 -0
@@ -0,0 +1,615 @@
1
+ import { popContext, pushContext } from "@pyreon/core"
2
+ import { config } from "@pyreon/ui-core"
3
+ import { context } from "../context/context"
4
+ import rocketstyle from "../init"
5
+ import isRocketComponent from "../isRocketComponent"
6
+
7
+ // Mock styled function that returns the component unchanged
8
+ const mockStyled = (component: any) => {
9
+ const taggedTemplate = (_strings: any, ..._args: any[]) => component
10
+ return taggedTemplate
11
+ }
12
+
13
+ const mockCss = (_strings: any, ..._args: any[]) => ""
14
+
15
+ // Store originals to restore later
16
+ const originalStyled = config.styled
17
+ const originalCss = config.css
18
+
19
+ beforeAll(() => {
20
+ config.init({
21
+ css: mockCss as any,
22
+ styled: mockStyled as any,
23
+ component: "div",
24
+ textComponent: "span",
25
+ })
26
+ })
27
+
28
+ afterAll(() => {
29
+ config.styled = originalStyled
30
+ config.css = originalCss
31
+ })
32
+
33
+ /**
34
+ * Base component that filters internal props and returns a VNode-like object.
35
+ * In Pyreon, components are plain functions — no forwardRef needed.
36
+ */
37
+ const BaseComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
38
+ type: "div",
39
+ props: rest,
40
+ children,
41
+ key: null,
42
+ $rocketstyle,
43
+ $rocketstate,
44
+ })
45
+ BaseComponent.displayName = "BaseComponent"
46
+
47
+ /** Helper to push a theme context for testing */
48
+ const withThemeContext = (fn: () => any) => {
49
+ pushContext(
50
+ new Map([
51
+ [
52
+ context.id,
53
+ {
54
+ theme: { rootSize: 16 },
55
+ mode: "light",
56
+ isDark: false,
57
+ isLight: true,
58
+ },
59
+ ],
60
+ ]),
61
+ )
62
+ try {
63
+ return fn()
64
+ } finally {
65
+ popContext()
66
+ }
67
+ }
68
+
69
+ /** Helper: call the component and return its output for inspection. */
70
+ const renderProps = (Component: any, props: Record<string, any> = {}) => {
71
+ return withThemeContext(() => {
72
+ const vnode = Component(props) as any
73
+ return vnode?.props ?? vnode
74
+ })
75
+ }
76
+
77
+ // --------------------------------------------------------
78
+ // rocketstyle factory
79
+ // --------------------------------------------------------
80
+ describe("rocketstyle factory", () => {
81
+ it("creates a component from factory", () => {
82
+ const Button = rocketstyle()({
83
+ name: "TestButton",
84
+ component: BaseComponent,
85
+ })
86
+ expect(Button).toBeDefined()
87
+ expect(typeof Button).toBe("function")
88
+ })
89
+
90
+ it("sets IS_ROCKETSTYLE on the component", () => {
91
+ const Button = rocketstyle()({
92
+ name: "TestButton",
93
+ component: BaseComponent,
94
+ })
95
+ expect(Button.IS_ROCKETSTYLE).toBe(true)
96
+ expect(isRocketComponent(Button)).toBe(true)
97
+ })
98
+
99
+ it("sets displayName on the component", () => {
100
+ const Button = rocketstyle()({
101
+ name: "MyButton",
102
+ component: BaseComponent,
103
+ })
104
+ expect(Button.displayName).toBe("MyButton")
105
+ })
106
+
107
+ it("throws when component is missing", () => {
108
+ expect(() => {
109
+ rocketstyle()({ name: "Test", component: undefined as any })
110
+ }).toThrow("component")
111
+ })
112
+
113
+ it("throws when name is missing", () => {
114
+ expect(() => {
115
+ rocketstyle()({ name: "", component: BaseComponent })
116
+ }).toThrow("name")
117
+ })
118
+
119
+ it("throws when dimension uses reserved key", () => {
120
+ expect(() => {
121
+ rocketstyle({ dimensions: { attrs: "attrs" } as any })({
122
+ name: "Test",
123
+ component: BaseComponent,
124
+ })
125
+ }).toThrow("invalid")
126
+ })
127
+
128
+ it("allows custom dimensions", () => {
129
+ const Button = rocketstyle({
130
+ dimensions: { colors: "color", shapes: "shape" },
131
+ })({ name: "CustomButton", component: BaseComponent })
132
+ expect(Button).toBeDefined()
133
+ expect(Button.IS_ROCKETSTYLE).toBe(true)
134
+ })
135
+
136
+ it("defaults useBooleans to true", () => {
137
+ const Button = rocketstyle()({
138
+ name: "Test",
139
+ component: BaseComponent,
140
+ })
141
+ expect(Button).toBeDefined()
142
+ })
143
+ })
144
+
145
+ // --------------------------------------------------------
146
+ // chaining methods
147
+ // --------------------------------------------------------
148
+ describe("chaining methods", () => {
149
+ const Button: any = rocketstyle()({
150
+ name: "ChainButton",
151
+ component: BaseComponent,
152
+ })
153
+
154
+ it(".attrs() returns a new component", () => {
155
+ const Enhanced = Button.attrs(() => ({ label: "test" }))
156
+ expect(Enhanced).toBeDefined()
157
+ expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
158
+ expect(Enhanced).not.toBe(Button)
159
+ })
160
+
161
+ it(".attrs() with priority option", () => {
162
+ const Enhanced = Button.attrs(() => ({ label: "priority" }), {
163
+ priority: true,
164
+ })
165
+ expect(Enhanced).toBeDefined()
166
+ expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
167
+ })
168
+
169
+ it(".attrs() with filter option", () => {
170
+ const Enhanced = Button.attrs(() => ({ label: "filtered" }), {
171
+ filter: ["internal"],
172
+ })
173
+ expect(Enhanced).toBeDefined()
174
+ })
175
+
176
+ it(".config() returns a new component", () => {
177
+ const Enhanced = Button.config({ DEBUG: true })
178
+ expect(Enhanced).toBeDefined()
179
+ expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
180
+ })
181
+
182
+ it(".statics() returns a new component", () => {
183
+ const Enhanced = Button.statics({ customMeta: "value" })
184
+ expect(Enhanced).toBeDefined()
185
+ expect(Enhanced.meta.customMeta).toBe("value")
186
+ })
187
+
188
+ it(".theme() returns a new component", () => {
189
+ const Enhanced = Button.theme(() => ({ color: "blue" }))
190
+ expect(Enhanced).toBeDefined()
191
+ expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
192
+ })
193
+
194
+ it(".styles() returns a new component", () => {
195
+ const Enhanced = Button.styles(() => "color: red;")
196
+ expect(Enhanced).toBeDefined()
197
+ })
198
+
199
+ it(".compose() returns a new component", () => {
200
+ const hoc = (C: any) => C
201
+ const Enhanced = Button.compose({ myHoc: hoc })
202
+ expect(Enhanced).toBeDefined()
203
+ })
204
+
205
+ it("supports chaining multiple methods", () => {
206
+ const Enhanced = Button.theme(() => ({ color: "blue" }))
207
+ .attrs(() => ({ label: "test" }))
208
+ .config({ name: "EnhancedButton" })
209
+ .statics({ version: "1.0" })
210
+
211
+ expect(Enhanced.IS_ROCKETSTYLE).toBe(true)
212
+ expect(Enhanced.meta.version).toBe("1.0")
213
+ })
214
+
215
+ it(".getStaticDimensions() returns dimension info", () => {
216
+ const Themed = Button.states(() => ({
217
+ primary: { color: "red" },
218
+ secondary: { color: "blue" },
219
+ }))
220
+
221
+ const info = Themed.getStaticDimensions({ rootSize: 16 })
222
+ expect(info.dimensions).toBeDefined()
223
+ expect(info.useBooleans).toBe(true)
224
+ expect(info.multiKeys).toBeDefined()
225
+ })
226
+
227
+ it(".getDefaultAttrs() evaluates attrs chain", () => {
228
+ const WithAttrs = Button.attrs((props: any) => ({
229
+ label: "default",
230
+ ...props,
231
+ }))
232
+ const result = WithAttrs.getDefaultAttrs({}, {}, "light")
233
+ expect(result.label).toBe("default")
234
+ })
235
+ })
236
+
237
+ // --------------------------------------------------------
238
+ // rendering
239
+ // --------------------------------------------------------
240
+ describe("rendering", () => {
241
+ it("renders a basic rocketstyle component", () => {
242
+ const Button: any = rocketstyle()({
243
+ name: "RenderButton",
244
+ component: BaseComponent,
245
+ }).config({})
246
+
247
+ const result = renderProps(Button, { children: "Hello" })
248
+ expect(result).toBeDefined()
249
+ })
250
+
251
+ it("adds data-rocketstyle attribute in dev mode", () => {
252
+ const Button: any = rocketstyle()({
253
+ name: "DevButton",
254
+ component: BaseComponent,
255
+ }).config({})
256
+
257
+ const result = renderProps(Button)
258
+ expect(result["data-rocketstyle"]).toBe("DevButton")
259
+ })
260
+
261
+ it("renders with attrs defaults", () => {
262
+ const Button: any = rocketstyle()({
263
+ name: "AttrsButton",
264
+ component: BaseComponent,
265
+ }).attrs((() => ({ "data-default": "yes" })) as any)
266
+
267
+ const result = renderProps(Button)
268
+ expect(result["data-default"]).toBe("yes")
269
+ })
270
+
271
+ it("explicit props override attrs", () => {
272
+ const Button: any = rocketstyle()({
273
+ name: "OverrideButton",
274
+ component: BaseComponent,
275
+ }).attrs((() => ({ "data-val": "from-attrs" })) as any)
276
+
277
+ const result = renderProps(Button, { "data-val": "from-props" })
278
+ expect(result["data-val"]).toBe("from-props")
279
+ })
280
+
281
+ it("renders with theme", () => {
282
+ const Button: any = rocketstyle()({
283
+ name: "ThemedButton",
284
+ component: BaseComponent,
285
+ }).theme(() => ({ fontSize: 14 }))
286
+
287
+ const result = renderProps(Button)
288
+ expect(result).toBeDefined()
289
+ })
290
+
291
+ it("renders with dimension states", () => {
292
+ const Button: any = rocketstyle()({
293
+ name: "StatesButton",
294
+ component: BaseComponent,
295
+ })
296
+ .theme(() => ({ color: "default" }))
297
+ .states(() => ({
298
+ primary: { color: "blue" },
299
+ secondary: { color: "green" },
300
+ }))
301
+
302
+ const result = renderProps(Button, { state: "primary" })
303
+ expect(result).toBeDefined()
304
+ })
305
+
306
+ it("renders with boolean dimension props", () => {
307
+ const Button: any = rocketstyle()({
308
+ name: "BoolButton",
309
+ component: BaseComponent,
310
+ }).states(() => ({
311
+ primary: { color: "blue" },
312
+ }))
313
+
314
+ // boolean prop 'primary' should map to state='primary'
315
+ const result = renderProps(Button, { primary: true })
316
+ expect(result).toBeDefined()
317
+ })
318
+
319
+ it("renders with priority attrs", () => {
320
+ const Button: any = rocketstyle()({
321
+ name: "PriorityButton",
322
+ component: BaseComponent,
323
+ }).attrs((() => ({ "data-priority": "yes" })) as any, { priority: true })
324
+
325
+ const result = renderProps(Button)
326
+ expect(result["data-priority"]).toBe("yes")
327
+ })
328
+ })
329
+
330
+ // --------------------------------------------------------
331
+ // DEBUG option
332
+ // --------------------------------------------------------
333
+ describe("DEBUG option", () => {
334
+ it("calls console.debug when DEBUG is enabled", () => {
335
+ const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {
336
+ /* no-op */
337
+ })
338
+
339
+ const Button: any = rocketstyle()({
340
+ name: "DebugButton",
341
+ component: BaseComponent,
342
+ }).config({ DEBUG: true })
343
+
344
+ renderProps(Button)
345
+ expect(debugSpy).toHaveBeenCalledWith(
346
+ "[rocketstyle] DebugButton render:",
347
+ expect.objectContaining({
348
+ component: "DebugButton",
349
+ rocketstate: expect.any(Object),
350
+ rocketstyle: expect.any(Object),
351
+ dimensions: expect.any(Object),
352
+ mode: expect.any(String),
353
+ }),
354
+ )
355
+
356
+ debugSpy.mockRestore()
357
+ })
358
+
359
+ it("does not call console.debug when DEBUG is not set", () => {
360
+ const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => {
361
+ /* no-op */
362
+ })
363
+
364
+ const Button: any = rocketstyle()({
365
+ name: "NoDebugButton",
366
+ component: BaseComponent,
367
+ }).config({})
368
+
369
+ renderProps(Button)
370
+ expect(debugSpy).not.toHaveBeenCalled()
371
+
372
+ debugSpy.mockRestore()
373
+ })
374
+ })
375
+
376
+ // --------------------------------------------------------
377
+ // passProps option
378
+ // --------------------------------------------------------
379
+ describe("passProps option", () => {
380
+ it("passes styling props through when passProps is configured", () => {
381
+ const PassPropsComponent: any = ({
382
+ children,
383
+ $rocketstyle,
384
+ $rocketstate,
385
+ state,
386
+ ...rest
387
+ }: any) => ({
388
+ type: "div",
389
+ props: { ...rest, "data-state": state },
390
+ children,
391
+ key: null,
392
+ })
393
+ PassPropsComponent.displayName = "PassPropsComponent"
394
+
395
+ const Button: any = rocketstyle()({
396
+ name: "PassPropsButton",
397
+ component: PassPropsComponent,
398
+ })
399
+ .states(() => ({
400
+ primary: { color: "blue" },
401
+ secondary: { color: "green" },
402
+ }))
403
+ .config({ passProps: ["state"] } as any)
404
+
405
+ const result = renderProps(Button, { state: "primary" })
406
+ expect(result["data-state"]).toBe("primary")
407
+ })
408
+
409
+ it("does not pass styling props without passProps", () => {
410
+ const PassPropsComponent: any = ({
411
+ children,
412
+ $rocketstyle,
413
+ $rocketstate,
414
+ state,
415
+ ...rest
416
+ }: any) => ({
417
+ type: "div",
418
+ props: { ...rest, "data-state": state ?? "none" },
419
+ children,
420
+ key: null,
421
+ })
422
+ PassPropsComponent.displayName = "NoPassPropsComponent"
423
+
424
+ const Button: any = rocketstyle()({
425
+ name: "NoPassPropsButton",
426
+ component: PassPropsComponent,
427
+ }).states(() => ({
428
+ primary: { color: "blue" },
429
+ }))
430
+
431
+ const result = renderProps(Button, { state: "primary" })
432
+ // Without passProps, the state prop should be filtered out
433
+ expect(result["data-state"]).toBe("none")
434
+ })
435
+ })
436
+
437
+ // --------------------------------------------------------
438
+ // IS_ROCKETSTYLE component wrapping
439
+ // --------------------------------------------------------
440
+ describe("IS_ROCKETSTYLE component wrapping", () => {
441
+ it("skips styled() wrapping when component already has IS_ROCKETSTYLE", () => {
442
+ const MarkedComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
443
+ type: "div",
444
+ props: rest,
445
+ children,
446
+ key: null,
447
+ })
448
+ MarkedComponent.IS_ROCKETSTYLE = true
449
+ MarkedComponent.displayName = "MarkedComponent"
450
+
451
+ const Outer: any = rocketstyle()({
452
+ name: "OuterComponent",
453
+ component: MarkedComponent,
454
+ })
455
+
456
+ expect(Outer).toBeDefined()
457
+ expect(Outer.IS_ROCKETSTYLE).toBe(true)
458
+ expect(Outer.displayName).toBe("OuterComponent")
459
+ })
460
+
461
+ it("renders IS_ROCKETSTYLE component when chained with config", () => {
462
+ const MarkedComponent: any = ({ children, $rocketstyle, $rocketstate, ...rest }: any) => ({
463
+ type: "div",
464
+ props: rest,
465
+ children,
466
+ key: null,
467
+ })
468
+ MarkedComponent.IS_ROCKETSTYLE = true
469
+ MarkedComponent.displayName = "MarkedComponent"
470
+
471
+ const Outer: any = rocketstyle()({
472
+ name: "OuterChained",
473
+ component: MarkedComponent,
474
+ }).config({})
475
+
476
+ const result = renderProps(Outer, { children: "Wrapped" })
477
+ expect(result).toBeDefined()
478
+ })
479
+ })
480
+
481
+ // --------------------------------------------------------
482
+ // empty dimensions validation
483
+ // --------------------------------------------------------
484
+ describe("empty dimensions validation", () => {
485
+ it("throws when dimensions is an empty object", () => {
486
+ expect(() => {
487
+ rocketstyle({ dimensions: {} as any })({
488
+ name: "EmptyDimensions",
489
+ component: BaseComponent,
490
+ })
491
+ }).toThrow("dimensions")
492
+ })
493
+ })
494
+
495
+ // --------------------------------------------------------
496
+ // multiple dimension values
497
+ // --------------------------------------------------------
498
+ describe("multiple dimension values", () => {
499
+ it("renders with array values for multi-key dimensions", () => {
500
+ const Button: any = rocketstyle()({
501
+ name: "MultiButton",
502
+ component: BaseComponent,
503
+ }).multiple(() => ({
504
+ bold: { fontWeight: "bold" },
505
+ italic: { fontStyle: "italic" },
506
+ underline: { textDecoration: "underline" },
507
+ }))
508
+
509
+ const result = renderProps(Button, { multiple: ["bold", "italic"] })
510
+ expect(result).toBeDefined()
511
+ })
512
+
513
+ it("renders with single value for non-multi dimensions", () => {
514
+ const Button: any = rocketstyle()({
515
+ name: "SingleDimButton",
516
+ component: BaseComponent,
517
+ })
518
+ .states(() => ({
519
+ primary: { color: "blue" },
520
+ secondary: { color: "green" },
521
+ }))
522
+ .sizes(() => ({
523
+ small: { fontSize: 12 },
524
+ large: { fontSize: 18 },
525
+ }))
526
+
527
+ const result = renderProps(Button, { state: "primary", size: "large" })
528
+ expect(result).toBeDefined()
529
+ })
530
+
531
+ it("renders with boolean shorthand for multi-key dimensions", () => {
532
+ const Button: any = rocketstyle()({
533
+ name: "MultiBoolButton",
534
+ component: BaseComponent,
535
+ }).multiple(() => ({
536
+ bold: { fontWeight: "bold" },
537
+ italic: { fontStyle: "italic" },
538
+ }))
539
+
540
+ // Boolean shorthand for multi-key: both bold and italic as boolean props
541
+ const result = renderProps(Button, { bold: true, italic: true })
542
+ expect(result).toBeDefined()
543
+ })
544
+ })
545
+
546
+ // --------------------------------------------------------
547
+ // rendering without Provider context
548
+ // --------------------------------------------------------
549
+ describe("rendering without Provider context", () => {
550
+ it("renders component without any Provider (useContext returns default)", () => {
551
+ const Button: any = rocketstyle()({
552
+ name: "NoProviderButton",
553
+ component: BaseComponent,
554
+ }).config({})
555
+
556
+ // Call without any context pushed
557
+ const vnode = Button({ children: "NoCtx" }) as any
558
+ const result = vnode?.props ?? vnode
559
+ expect(result).toBeDefined()
560
+ })
561
+ })
562
+
563
+ // --------------------------------------------------------
564
+ // $rocketstyle and $rocketstate are passed to inner component
565
+ // --------------------------------------------------------
566
+ describe("theme and state injection", () => {
567
+ it("passes $rocketstyle theme to inner component", () => {
568
+ const Receiver: any = ({ $rocketstyle, $rocketstate, ...rest }: any) => ({
569
+ type: "div",
570
+ props: rest,
571
+ children: [],
572
+ key: null,
573
+ $rocketstyle,
574
+ $rocketstate,
575
+ })
576
+ Receiver.displayName = "Receiver"
577
+
578
+ const Button: any = rocketstyle()({
579
+ name: "ThemeInjButton",
580
+ component: Receiver,
581
+ })
582
+ .theme(() => ({ color: "blue", bg: "white" }))
583
+ .states(() => ({
584
+ primary: { color: "red" },
585
+ }))
586
+
587
+ const vnode = withThemeContext(() => Button({ state: "primary" }))
588
+ expect(vnode.$rocketstyle).toBeDefined()
589
+ expect(vnode.$rocketstyle.color).toBe("red")
590
+ expect(vnode.$rocketstyle.bg).toBe("white")
591
+ })
592
+
593
+ it("passes $rocketstate with active dimensions to inner component", () => {
594
+ const Receiver: any = ({ $rocketstyle, $rocketstate, ...rest }: any) => ({
595
+ type: "div",
596
+ props: rest,
597
+ children: [],
598
+ key: null,
599
+ $rocketstyle,
600
+ $rocketstate,
601
+ })
602
+ Receiver.displayName = "Receiver"
603
+
604
+ const Button: any = rocketstyle()({
605
+ name: "StateInjButton",
606
+ component: Receiver,
607
+ }).states(() => ({
608
+ primary: { color: "blue" },
609
+ }))
610
+
611
+ const vnode = withThemeContext(() => Button({ state: "primary" }))
612
+ expect(vnode.$rocketstate).toBeDefined()
613
+ expect(vnode.$rocketstate.state).toBe("primary")
614
+ })
615
+ })