@pyreon/rocketstyle 0.24.5 → 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.
- package/package.json +8 -10
- package/src/__tests__/attrs-overloads.test.ts +0 -97
- package/src/__tests__/attrs.test.ts +0 -190
- package/src/__tests__/cache-key-boolean-collision.test.ts +0 -54
- package/src/__tests__/chaining.test.ts +0 -86
- package/src/__tests__/collection.test.ts +0 -35
- package/src/__tests__/compose.test.ts +0 -36
- package/src/__tests__/context.test.ts +0 -200
- package/src/__tests__/createLocalProvider.test.ts +0 -280
- package/src/__tests__/dimensions.test.ts +0 -183
- package/src/__tests__/e2e-styler.test.ts +0 -299
- package/src/__tests__/hooks.test.ts +0 -178
- package/src/__tests__/isRocketComponent.test.ts +0 -48
- package/src/__tests__/memo-cap.test.ts +0 -174
- package/src/__tests__/minimal-theme.test.ts +0 -62
- package/src/__tests__/misc.test.ts +0 -204
- package/src/__tests__/native-marker.test.ts +0 -9
- package/src/__tests__/providerConsumer.test.ts +0 -183
- package/src/__tests__/reactive-props-preservation.test.ts +0 -195
- package/src/__tests__/rocketstyle.browser.test.tsx +0 -481
- package/src/__tests__/rocketstyleIntegration.test.ts +0 -711
- package/src/__tests__/theme-integration.test.tsx +0 -254
- package/src/__tests__/themeUtils.test.ts +0 -463
- package/src/cache/LocalThemeManager.ts +0 -14
- package/src/cache/index.ts +0 -3
- package/src/constants/booleanTags.ts +0 -32
- package/src/constants/defaultDimensions.ts +0 -23
- package/src/constants/index.ts +0 -59
- package/src/context/context.ts +0 -70
- package/src/context/createLocalProvider.ts +0 -97
- package/src/context/localContext.ts +0 -37
- package/src/env.d.ts +0 -6
- package/src/hoc/index.ts +0 -3
- package/src/hoc/rocketstyleAttrsHoc.ts +0 -76
- package/src/hooks/index.ts +0 -4
- package/src/hooks/usePseudoState.ts +0 -79
- package/src/hooks/useTheme.ts +0 -48
- package/src/index.ts +0 -95
- package/src/init.ts +0 -93
- package/src/isRocketComponent.ts +0 -16
- package/src/rocketstyle.ts +0 -640
- package/src/types/attrs.ts +0 -23
- package/src/types/config.ts +0 -48
- package/src/types/configuration.ts +0 -69
- package/src/types/dimensions.ts +0 -109
- package/src/types/hoc.ts +0 -5
- package/src/types/pseudo.ts +0 -19
- package/src/types/rocketComponent.ts +0 -24
- package/src/types/rocketstyle.ts +0 -220
- package/src/types/styles.ts +0 -61
- package/src/types/theme.ts +0 -18
- package/src/types/utils.ts +0 -98
- package/src/utils/attrs.ts +0 -181
- package/src/utils/chaining.ts +0 -58
- package/src/utils/collection.ts +0 -9
- package/src/utils/compose.ts +0 -11
- package/src/utils/dimensions.ts +0 -126
- package/src/utils/statics.ts +0 -44
- package/src/utils/styles.ts +0 -18
- 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
|
-
})
|