@pyreon/rocketstyle 0.11.5 → 0.11.7

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/README.md +32 -32
  2. package/lib/index.d.ts +25 -6
  3. package/lib/index.js +122 -97
  4. package/package.json +25 -24
  5. package/src/__tests__/attrs.test.ts +49 -49
  6. package/src/__tests__/chaining.test.ts +27 -27
  7. package/src/__tests__/collection.test.ts +12 -12
  8. package/src/__tests__/compose.test.ts +10 -10
  9. package/src/__tests__/context.test.ts +65 -65
  10. package/src/__tests__/createLocalProvider.test.ts +53 -53
  11. package/src/__tests__/dimensions.test.ts +54 -54
  12. package/src/__tests__/e2e-styler.test.ts +142 -136
  13. package/src/__tests__/hooks.test.ts +41 -70
  14. package/src/__tests__/isRocketComponent.test.ts +11 -11
  15. package/src/__tests__/misc.test.ts +91 -91
  16. package/src/__tests__/providerConsumer.test.ts +54 -126
  17. package/src/__tests__/rocketstyleIntegration.test.ts +182 -255
  18. package/src/__tests__/themeUtils.test.ts +173 -173
  19. package/src/cache/index.ts +1 -1
  20. package/src/constants/booleanTags.ts +25 -25
  21. package/src/constants/defaultDimensions.ts +5 -5
  22. package/src/constants/index.ts +16 -16
  23. package/src/context/context.ts +13 -10
  24. package/src/context/createLocalProvider.ts +26 -13
  25. package/src/context/localContext.ts +2 -2
  26. package/src/hoc/index.ts +1 -1
  27. package/src/hoc/rocketstyleAttrsHoc.ts +26 -29
  28. package/src/hooks/index.ts +2 -2
  29. package/src/hooks/usePseudoState.ts +3 -3
  30. package/src/hooks/useTheme.ts +14 -17
  31. package/src/index.ts +32 -15
  32. package/src/init.ts +12 -12
  33. package/src/isRocketComponent.ts +2 -2
  34. package/src/rocketstyle.ts +125 -112
  35. package/src/types/attrs.ts +2 -2
  36. package/src/types/config.ts +4 -4
  37. package/src/types/configuration.ts +5 -5
  38. package/src/types/dimensions.ts +5 -5
  39. package/src/types/hoc.ts +1 -1
  40. package/src/types/rocketComponent.ts +4 -4
  41. package/src/types/rocketstyle.ts +10 -10
  42. package/src/types/styles.ts +9 -4
  43. package/src/types/theme.ts +4 -4
  44. package/src/types/utils.ts +1 -1
  45. package/src/utils/attrs.ts +2 -2
  46. package/src/utils/chaining.ts +2 -2
  47. package/src/utils/compose.ts +1 -1
  48. package/src/utils/dimensions.ts +6 -6
  49. package/src/utils/statics.ts +2 -2
  50. package/src/utils/styles.ts +2 -2
  51. package/src/utils/theme.ts +10 -10
@@ -7,89 +7,89 @@ import {
7
7
  getValues,
8
8
  isMultiKey,
9
9
  isValidKey,
10
- } from "../utils/dimensions"
10
+ } from '../utils/dimensions'
11
11
 
12
- describe("isValidKey", () => {
13
- it("returns true for truthy values", () => {
14
- expect(isValidKey("a")).toBe(true)
12
+ describe('isValidKey', () => {
13
+ it('returns true for truthy values', () => {
14
+ expect(isValidKey('a')).toBe(true)
15
15
  expect(isValidKey(1)).toBe(true)
16
16
  expect(isValidKey(true)).toBe(true)
17
17
  expect(isValidKey(0)).toBe(true)
18
- expect(isValidKey("")).toBe(true)
18
+ expect(isValidKey('')).toBe(true)
19
19
  })
20
20
 
21
- it("returns false for undefined, null, false", () => {
21
+ it('returns false for undefined, null, false', () => {
22
22
  expect(isValidKey(undefined)).toBe(false)
23
23
  expect(isValidKey(null)).toBe(false)
24
24
  expect(isValidKey(false)).toBe(false)
25
25
  })
26
26
  })
27
27
 
28
- describe("isMultiKey", () => {
29
- it("returns [true, propName] for object with propName", () => {
30
- expect(isMultiKey({ propName: "tags", multi: true })).toEqual([true, "tags"])
28
+ describe('isMultiKey', () => {
29
+ it('returns [true, propName] for object with propName', () => {
30
+ expect(isMultiKey({ propName: 'tags', multi: true })).toEqual([true, 'tags'])
31
31
  })
32
32
 
33
- it("returns [false, value] for string", () => {
34
- expect(isMultiKey("variant")).toEqual([false, "variant"])
33
+ it('returns [false, value] for string', () => {
34
+ expect(isMultiKey('variant')).toEqual([false, 'variant'])
35
35
  })
36
36
  })
37
37
 
38
- describe("getKeys", () => {
39
- it("returns object keys", () => {
40
- expect(getKeys({ a: 1, b: 2 })).toEqual(["a", "b"])
38
+ describe('getKeys', () => {
39
+ it('returns object keys', () => {
40
+ expect(getKeys({ a: 1, b: 2 })).toEqual(['a', 'b'])
41
41
  })
42
42
 
43
- it("returns empty array for empty object", () => {
43
+ it('returns empty array for empty object', () => {
44
44
  expect(getKeys({})).toEqual([])
45
45
  })
46
46
  })
47
47
 
48
- describe("getValues", () => {
49
- it("returns object values", () => {
48
+ describe('getValues', () => {
49
+ it('returns object values', () => {
50
50
  expect(getValues({ a: 1, b: 2 })).toEqual([1, 2])
51
51
  })
52
52
  })
53
53
 
54
- describe("getDimensionsValues", () => {
55
- it("extracts string dimension values", () => {
56
- const dimensions = { size: "size", variant: "variant" }
57
- expect(getDimensionsValues(dimensions)).toEqual(["size", "variant"])
54
+ describe('getDimensionsValues', () => {
55
+ it('extracts string dimension values', () => {
56
+ const dimensions = { size: 'size', variant: 'variant' }
57
+ expect(getDimensionsValues(dimensions)).toEqual(['size', 'variant'])
58
58
  })
59
59
 
60
- it("extracts propName from object dimensions", () => {
60
+ it('extracts propName from object dimensions', () => {
61
61
  const dimensions = {
62
- size: "size",
63
- tags: { propName: "tags", multi: true },
62
+ size: 'size',
63
+ tags: { propName: 'tags', multi: true },
64
64
  }
65
- expect(getDimensionsValues(dimensions)).toEqual(["size", "tags"])
65
+ expect(getDimensionsValues(dimensions)).toEqual(['size', 'tags'])
66
66
  })
67
67
  })
68
68
 
69
- describe("getMultipleDimensions", () => {
70
- it("identifies multi-key dimensions", () => {
69
+ describe('getMultipleDimensions', () => {
70
+ it('identifies multi-key dimensions', () => {
71
71
  const dimensions = {
72
- size: "size",
73
- tags: { propName: "tags", multi: true },
72
+ size: 'size',
73
+ tags: { propName: 'tags', multi: true },
74
74
  }
75
75
  expect(getMultipleDimensions(dimensions)).toEqual({ tags: true })
76
76
  })
77
77
 
78
- it("returns empty for no multi dimensions", () => {
79
- const dimensions = { size: "size", variant: "variant" }
78
+ it('returns empty for no multi dimensions', () => {
79
+ const dimensions = { size: 'size', variant: 'variant' }
80
80
  expect(getMultipleDimensions(dimensions)).toEqual({})
81
81
  })
82
82
 
83
- it("skips multi=false", () => {
83
+ it('skips multi=false', () => {
84
84
  const dimensions = {
85
- tags: { propName: "tags", multi: false },
85
+ tags: { propName: 'tags', multi: false },
86
86
  }
87
87
  expect(getMultipleDimensions(dimensions)).toEqual({})
88
88
  })
89
89
  })
90
90
 
91
- describe("getDimensionsMap", () => {
92
- it("builds keysMap and keywords from themes", () => {
91
+ describe('getDimensionsMap', () => {
92
+ it('builds keysMap and keywords from themes', () => {
93
93
  const themes = {
94
94
  size: { small: { fontSize: 12 }, large: { fontSize: 18 } },
95
95
  }
@@ -100,7 +100,7 @@ describe("getDimensionsMap", () => {
100
100
  expect(result.keywords.size).toBe(true)
101
101
  })
102
102
 
103
- it("adds dimension keys to keywords when useBooleans", () => {
103
+ it('adds dimension keys to keywords when useBooleans', () => {
104
104
  const themes = {
105
105
  size: { small: { fontSize: 12 }, large: { fontSize: 18 } },
106
106
  }
@@ -109,7 +109,7 @@ describe("getDimensionsMap", () => {
109
109
  expect(result.keywords.large).toBe(true)
110
110
  })
111
111
 
112
- it("skips invalid values (false, null, undefined)", () => {
112
+ it('skips invalid values (false, null, undefined)', () => {
113
113
  const themes = {
114
114
  size: { small: { fontSize: 12 }, disabled: false, hidden: null },
115
115
  }
@@ -117,17 +117,17 @@ describe("getDimensionsMap", () => {
117
117
  expect(result.keysMap.size).toEqual({ small: true })
118
118
  })
119
119
 
120
- it("returns empty for empty themes", () => {
120
+ it('returns empty for empty themes', () => {
121
121
  const result = getDimensionsMap({ themes: {} })
122
122
  expect(result.keysMap).toEqual({})
123
123
  expect(result.keywords).toEqual({})
124
124
  })
125
125
 
126
- it("includes function values (transform modifiers) as valid keys", () => {
126
+ it('includes function values (transform modifiers) as valid keys', () => {
127
127
  const themes = {
128
128
  modifier: {
129
129
  outlined: (theme: any) => ({ color: theme.bg }),
130
- ghost: () => ({ bg: "transparent" }),
130
+ ghost: () => ({ bg: 'transparent' }),
131
131
  },
132
132
  }
133
133
  const result = getDimensionsMap({ themes })
@@ -137,7 +137,7 @@ describe("getDimensionsMap", () => {
137
137
  expect(result.keywords.modifier).toBe(true)
138
138
  })
139
139
 
140
- it("includes function values as boolean keywords when useBooleans", () => {
140
+ it('includes function values as boolean keywords when useBooleans', () => {
141
141
  const themes = {
142
142
  modifier: {
143
143
  outlined: (theme: any) => ({ color: theme.bg }),
@@ -148,35 +148,35 @@ describe("getDimensionsMap", () => {
148
148
  })
149
149
  })
150
150
 
151
- describe("getTransformDimensions", () => {
152
- it("identifies transform dimensions", () => {
151
+ describe('getTransformDimensions', () => {
152
+ it('identifies transform dimensions', () => {
153
153
  const dimensions = {
154
- states: "state",
155
- modifiers: { propName: "modifier", multi: true, transform: true },
154
+ states: 'state',
155
+ modifiers: { propName: 'modifier', multi: true, transform: true },
156
156
  }
157
157
  expect(getTransformDimensions(dimensions)).toEqual({ modifier: true })
158
158
  })
159
159
 
160
- it("returns empty when no transform dimensions exist", () => {
160
+ it('returns empty when no transform dimensions exist', () => {
161
161
  const dimensions = {
162
- states: "state",
163
- sizes: "size",
164
- multiple: { propName: "multiple", multi: true },
162
+ states: 'state',
163
+ sizes: 'size',
164
+ multiple: { propName: 'multiple', multi: true },
165
165
  }
166
166
  expect(getTransformDimensions(dimensions)).toEqual({})
167
167
  })
168
168
 
169
- it("skips dimensions without transform flag", () => {
169
+ it('skips dimensions without transform flag', () => {
170
170
  const dimensions = {
171
- states: "state",
172
- tags: { propName: "tags", multi: true },
171
+ states: 'state',
172
+ tags: { propName: 'tags', multi: true },
173
173
  }
174
174
  expect(getTransformDimensions(dimensions)).toEqual({})
175
175
  })
176
176
 
177
- it("skips transform=false", () => {
177
+ it('skips transform=false', () => {
178
178
  const dimensions = {
179
- modifiers: { propName: "modifier", multi: true, transform: false },
179
+ modifiers: { propName: 'modifier', multi: true, transform: false },
180
180
  }
181
181
  expect(getTransformDimensions(dimensions)).toEqual({})
182
182
  })
@@ -7,221 +7,163 @@
7
7
  * Unlike the React version which tested CSS injection in the DOM,
8
8
  * this Pyreon version tests the computed $rocketstyle output directly.
9
9
  */
10
- import { popContext, pushContext } from "@pyreon/core"
11
- import { config } from "@pyreon/ui-core"
12
- import { context } from "../context/context"
13
- import rocketstyle from "../init"
14
-
15
- // Mock styled that passes through the component unchanged
16
- const mockStyled = (component: any) => {
17
- const taggedTemplate = (_strings: any, ..._args: any[]) => component
18
- return taggedTemplate
19
- }
20
-
21
- const mockCss = (_strings: any, ..._args: any[]) => ""
22
-
23
- const originalStyled = config.styled
24
- const originalCss = config.css
10
+ import { ThemeCapture, getComputedTheme, initTestConfig, withThemeContext } from '@pyreon/test-utils'
11
+ import rocketstyle from '../init'
25
12
 
13
+ let cleanup: () => void
26
14
  beforeAll(() => {
27
- config.init({
28
- css: mockCss as any,
29
- styled: mockStyled as any,
30
- component: "div",
31
- textComponent: "span",
32
- })
15
+ cleanup = initTestConfig()
33
16
  })
17
+ afterAll(() => cleanup())
34
18
 
35
- afterAll(() => {
36
- config.styled = originalStyled
37
- config.css = originalCss
38
- })
39
-
40
- /** Component that captures $rocketstyle for inspection */
41
- const ThemeCapture: any = ({ $rocketstyle, $rocketstate, ...rest }: any) => ({
42
- type: "div",
43
- props: rest,
44
- children: [],
45
- key: null,
46
- $rocketstyle,
47
- $rocketstate,
48
- })
49
- ThemeCapture.displayName = "ThemeCapture"
50
-
51
- /** Helper to render within theme context and return $rocketstyle */
52
- const getComputedTheme = (Component: any, props: Record<string, any> = {}) => {
53
- pushContext(
54
- new Map([
55
- [
56
- context.id,
57
- {
58
- theme: { rootSize: 16 },
59
- mode: "light",
60
- isDark: false,
61
- isLight: true,
62
- },
63
- ],
64
- ]),
65
- )
66
- try {
67
- let vnode = Component(props) as any
68
- // EnhancedComponent returns a reactive accessor (function) for mode switching.
69
- // In tests we evaluate it directly to get the VNode.
70
- while (typeof vnode === "function") vnode = vnode()
71
- return vnode.$rocketstyle
72
- } finally {
73
- popContext()
74
- }
75
- }
76
-
77
- describe("e2e: rocketstyle theme computation", () => {
78
- it("base theme values are passed through", () => {
19
+ describe('e2e: rocketstyle theme computation', () => {
20
+ it('base theme values are passed through', () => {
79
21
  const Comp: any = rocketstyle()({
80
- name: "BaseComp",
22
+ name: 'BaseComp',
81
23
  component: ThemeCapture,
82
24
  }).theme({
83
- backgroundColor: "#0070f3",
84
- color: "#fff",
25
+ backgroundColor: '#0070f3',
26
+ color: '#fff',
85
27
  })
86
28
 
87
29
  const theme = getComputedTheme(Comp)
88
- expect(theme.backgroundColor).toBe("#0070f3")
89
- expect(theme.color).toBe("#fff")
30
+ expect(theme.backgroundColor).toBe('#0070f3')
31
+ expect(theme.color).toBe('#fff')
90
32
  })
91
33
 
92
- it("state dimension overrides base theme values", () => {
34
+ it('state dimension overrides base theme values', () => {
93
35
  const Comp: any = rocketstyle()({
94
- name: "StateComp",
36
+ name: 'StateComp',
95
37
  component: ThemeCapture,
96
38
  })
97
39
  .theme({
98
- backgroundColor: "#0070f3",
99
- color: "#fff",
40
+ backgroundColor: '#0070f3',
41
+ color: '#fff',
100
42
  })
101
43
  .states({
102
44
  danger: {
103
- backgroundColor: "#dc3545",
104
- color: "#fff",
45
+ backgroundColor: '#dc3545',
46
+ color: '#fff',
105
47
  },
106
48
  })
107
49
 
108
- const theme = getComputedTheme(Comp, { state: "danger" })
109
- expect(theme.backgroundColor).toBe("#dc3545")
110
- expect(theme.color).toBe("#fff")
50
+ const theme = getComputedTheme(Comp, { state: 'danger' })
51
+ expect(theme.backgroundColor).toBe('#dc3545')
52
+ expect(theme.color).toBe('#fff')
111
53
  })
112
54
 
113
- it("modifier transform derives styles from accumulated state theme", () => {
55
+ it('modifier transform derives styles from accumulated state theme', () => {
114
56
  const Comp: any = rocketstyle()({
115
- name: "ModifierComp",
57
+ name: 'ModifierComp',
116
58
  component: ThemeCapture,
117
59
  })
118
60
  .theme({
119
- backgroundColor: "#0070f3",
120
- color: "#fff",
61
+ backgroundColor: '#0070f3',
62
+ color: '#fff',
121
63
  })
122
64
  .states({
123
65
  danger: {
124
- backgroundColor: "#dc3545",
125
- color: "#fff",
66
+ backgroundColor: '#dc3545',
67
+ color: '#fff',
126
68
  },
127
69
  })
128
70
  .modifiers({
129
71
  outlined: (accTheme: any) => ({
130
72
  color: accTheme.backgroundColor,
131
- backgroundColor: "transparent",
73
+ backgroundColor: 'transparent',
132
74
  }),
133
75
  })
134
76
 
135
77
  // danger state + outlined modifier
136
78
  const theme = getComputedTheme(Comp, {
137
- state: "danger",
138
- modifier: "outlined",
79
+ state: 'danger',
80
+ modifier: 'outlined',
139
81
  })
140
82
  // outlined should flip: color becomes the danger backgroundColor
141
- expect(theme.color).toBe("#dc3545")
142
- expect(theme.backgroundColor).toBe("transparent")
83
+ expect(theme.color).toBe('#dc3545')
84
+ expect(theme.backgroundColor).toBe('transparent')
143
85
  })
144
86
 
145
- it("modifier without active state uses base theme only", () => {
87
+ it('modifier without active state uses base theme only', () => {
146
88
  const Comp: any = rocketstyle()({
147
- name: "ModifierBaseComp",
89
+ name: 'ModifierBaseComp',
148
90
  component: ThemeCapture,
149
91
  })
150
92
  .theme({
151
- backgroundColor: "#0070f3",
152
- color: "#fff",
93
+ backgroundColor: '#0070f3',
94
+ color: '#fff',
153
95
  })
154
96
  .modifiers({
155
97
  outlined: (accTheme: any) => ({
156
98
  color: accTheme.backgroundColor,
157
- backgroundColor: "transparent",
99
+ backgroundColor: 'transparent',
158
100
  }),
159
101
  })
160
102
 
161
103
  // just outlined modifier, no state — derive from base theme
162
- const theme = getComputedTheme(Comp, { modifier: "outlined" })
163
- expect(theme.color).toBe("#0070f3")
164
- expect(theme.backgroundColor).toBe("transparent")
104
+ const theme = getComputedTheme(Comp, { modifier: 'outlined' })
105
+ expect(theme.color).toBe('#0070f3')
106
+ expect(theme.backgroundColor).toBe('transparent')
165
107
  })
166
108
 
167
- it("variant dimension values are applied correctly", () => {
109
+ it('variant dimension values are applied correctly', () => {
168
110
  const Comp: any = rocketstyle()({
169
- name: "VariantComp",
111
+ name: 'VariantComp',
170
112
  component: ThemeCapture,
171
113
  })
172
114
  .theme({
173
- backgroundColor: "#FFFFFF",
115
+ backgroundColor: '#FFFFFF',
174
116
  borderRadius: 8,
175
117
  })
176
118
  .variants({
177
119
  box: {
178
120
  height: 64,
179
121
  padding: 8,
180
- backgroundColor: "transparent",
122
+ backgroundColor: 'transparent',
181
123
  },
182
124
  circle: {
183
125
  width: 72,
184
126
  height: 72,
185
127
  padding: 4,
186
- backgroundColor: "#F0F0F0",
128
+ backgroundColor: '#F0F0F0',
187
129
  borderRadius: 180,
188
130
  },
189
131
  })
190
132
 
191
- const theme = getComputedTheme(Comp, { variant: "circle" })
133
+ const theme = getComputedTheme(Comp, { variant: 'circle' })
192
134
  expect(theme.width).toBe(72)
193
135
  expect(theme.height).toBe(72)
194
- expect(theme.backgroundColor).toBe("#F0F0F0")
136
+ expect(theme.backgroundColor).toBe('#F0F0F0')
195
137
  expect(theme.borderRadius).toBe(180)
196
138
  })
197
139
 
198
- it("variant box values override base theme", () => {
140
+ it('variant box values override base theme', () => {
199
141
  const Comp: any = rocketstyle()({
200
- name: "VariantBoxComp",
142
+ name: 'VariantBoxComp',
201
143
  component: ThemeCapture,
202
144
  })
203
145
  .theme({
204
- backgroundColor: "#FFFFFF",
146
+ backgroundColor: '#FFFFFF',
205
147
  borderRadius: 8,
206
148
  })
207
149
  .variants({
208
150
  box: {
209
151
  height: 64,
210
152
  padding: 8,
211
- backgroundColor: "transparent",
153
+ backgroundColor: 'transparent',
212
154
  },
213
155
  })
214
156
 
215
- const theme = getComputedTheme(Comp, { variant: "box" })
216
- expect(theme.backgroundColor).toBe("transparent")
157
+ const theme = getComputedTheme(Comp, { variant: 'box' })
158
+ expect(theme.backgroundColor).toBe('transparent')
217
159
  expect(theme.borderRadius).toBe(8) // inherited from base
218
160
  expect(theme.height).toBe(64)
219
161
  expect(theme.padding).toBe(8)
220
162
  })
221
163
 
222
- it("size dimension values are applied", () => {
164
+ it('size dimension values are applied', () => {
223
165
  const Comp: any = rocketstyle()({
224
- name: "SizeComp",
166
+ name: 'SizeComp',
225
167
  component: ThemeCapture,
226
168
  })
227
169
  .theme({ fontSize: 14 })
@@ -230,65 +172,129 @@ describe("e2e: rocketstyle theme computation", () => {
230
172
  large: { fontSize: 18, padding: 8 },
231
173
  })
232
174
 
233
- const theme = getComputedTheme(Comp, { size: "large" })
175
+ const theme = getComputedTheme(Comp, { size: 'large' })
234
176
  expect(theme.fontSize).toBe(18)
235
177
  expect(theme.padding).toBe(8)
236
178
  })
237
179
 
238
- it("multiple dimensions combine", () => {
180
+ it('multiple dimensions combine', () => {
239
181
  const Comp: any = rocketstyle()({
240
- name: "MultiDimComp",
182
+ name: 'MultiDimComp',
241
183
  component: ThemeCapture,
242
184
  })
243
- .theme({ color: "black" })
244
- .states({ primary: { color: "blue" } })
185
+ .theme({ color: 'black' })
186
+ .states({ primary: { color: 'blue' } })
245
187
  .sizes({ large: { fontSize: 18 } })
246
188
 
247
189
  const theme = getComputedTheme(Comp, {
248
- state: "primary",
249
- size: "large",
190
+ state: 'primary',
191
+ size: 'large',
250
192
  })
251
- expect(theme.color).toBe("blue")
193
+ expect(theme.color).toBe('blue')
252
194
  expect(theme.fontSize).toBe(18)
253
195
  })
254
196
 
255
- it("multiple modifier transforms compose sequentially", () => {
197
+ it('multiple modifier transforms compose sequentially', () => {
256
198
  const Comp: any = rocketstyle()({
257
- name: "MultiModComp",
199
+ name: 'MultiModComp',
258
200
  component: ThemeCapture,
259
201
  })
260
- .theme({ backgroundColor: "blue", color: "white" })
202
+ .theme({ backgroundColor: 'blue', color: 'white' })
261
203
  .modifiers({
262
204
  outlined: (accTheme: any) => ({
263
205
  color: accTheme.backgroundColor,
264
- backgroundColor: "transparent",
206
+ backgroundColor: 'transparent',
265
207
  }),
266
- rounded: () => ({ borderRadius: "999px" }),
208
+ rounded: () => ({ borderRadius: '999px' }),
267
209
  })
268
210
 
269
211
  const theme = getComputedTheme(Comp, {
270
- modifier: ["outlined", "rounded"],
212
+ modifier: ['outlined', 'rounded'],
271
213
  })
272
- expect(theme.color).toBe("blue")
273
- expect(theme.backgroundColor).toBe("transparent")
274
- expect(theme.borderRadius).toBe("999px")
214
+ expect(theme.color).toBe('blue')
215
+ expect(theme.backgroundColor).toBe('transparent')
216
+ expect(theme.borderRadius).toBe('999px')
275
217
  })
276
218
 
277
- it("later transform sees earlier transform results", () => {
219
+ it('later transform sees earlier transform results', () => {
278
220
  const Comp: any = rocketstyle()({
279
- name: "ChainedModComp",
221
+ name: 'ChainedModComp',
280
222
  component: ThemeCapture,
281
223
  })
282
224
  .theme({})
283
225
  .modifiers({
284
- first: () => ({ step: "one" }),
226
+ first: () => ({ step: 'one' }),
285
227
  second: (accTheme: any) => ({ sawStep: accTheme.step }),
286
228
  })
287
229
 
288
230
  const theme = getComputedTheme(Comp, {
289
- modifier: ["first", "second"],
231
+ modifier: ['first', 'second'],
232
+ })
233
+ expect(theme.step).toBe('one')
234
+ expect(theme.sawStep).toBe('one')
235
+ })
236
+ })
237
+
238
+ // ── Reactive dimension props ──────────────────────────────────────────────────
239
+
240
+ describe('reactive $rocketstyle accessor', () => {
241
+ it('$rocketstyleAccessor resolves different themes for different dimension props', () => {
242
+ const Comp: any = rocketstyle()({
243
+ name: 'ReactiveComp',
244
+ component: ThemeCapture,
245
+ })
246
+ .theme({ color: 'black', bg: 'white' })
247
+ .states({
248
+ primary: { color: 'blue' },
249
+ secondary: { color: 'green' },
250
+ })
251
+
252
+ // First call with state=primary
253
+ const theme1 = getComputedTheme(Comp, { state: 'primary' })
254
+ expect(theme1.color).toBe('blue')
255
+
256
+ // Second call with state=secondary — should produce different theme
257
+ const theme2 = getComputedTheme(Comp, { state: 'secondary' })
258
+ expect(theme2.color).toBe('green')
259
+ })
260
+
261
+ it('$rocketstyleAccessor is a function, not a plain object', () => {
262
+ const Comp: any = rocketstyle()({
263
+ name: 'AccessorComp',
264
+ component: ThemeCapture,
265
+ }).theme({ color: 'red' })
266
+
267
+ const vnode = withThemeContext(() => Comp({}))
268
+ // ThemeCapture resolves the accessor — result should be the theme object
269
+ expect(vnode.$rocketstyle).toBeDefined()
270
+ expect(vnode.$rocketstyle.color).toBe('red')
271
+ })
272
+
273
+ it('$rocketstateAccessor resolves active dimensions', () => {
274
+ const Comp: any = rocketstyle()({
275
+ name: 'StateAccessorComp',
276
+ component: ThemeCapture,
277
+ }).states({
278
+ primary: { color: 'blue' },
290
279
  })
291
- expect(theme.step).toBe("one")
292
- expect(theme.sawStep).toBe("one")
280
+
281
+ const vnode = withThemeContext(() => Comp({ state: 'primary' }))
282
+ expect(vnode.$rocketstate).toBeDefined()
283
+ expect(vnode.$rocketstate.state).toBe('primary')
284
+ })
285
+
286
+ it('mode change produces different theme via accessor', () => {
287
+ const Comp: any = rocketstyle()({
288
+ name: 'ModeReactiveComp',
289
+ component: ThemeCapture,
290
+ }).theme((t: any, m: any) => ({
291
+ color: m('light-color', 'dark-color'),
292
+ }))
293
+
294
+ const lightTheme = getComputedTheme(Comp, {}, { mode: 'light' })
295
+ expect(lightTheme.color).toBe('light-color')
296
+
297
+ const darkTheme = getComputedTheme(Comp, {}, { mode: 'dark' })
298
+ expect(darkTheme.color).toBe('dark-color')
293
299
  })
294
300
  })