@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.
- package/package.json +14 -12
- package/src/__tests__/attrs.test.ts +190 -0
- package/src/__tests__/chaining.test.ts +86 -0
- package/src/__tests__/collection.test.ts +35 -0
- package/src/__tests__/compose.test.ts +36 -0
- package/src/__tests__/context.test.ts +200 -0
- package/src/__tests__/createLocalProvider.test.ts +248 -0
- package/src/__tests__/dimensions.test.ts +183 -0
- package/src/__tests__/e2e-styler.test.ts +291 -0
- package/src/__tests__/hooks.test.ts +207 -0
- package/src/__tests__/isRocketComponent.test.ts +48 -0
- package/src/__tests__/misc.test.ts +204 -0
- package/src/__tests__/providerConsumer.test.ts +248 -0
- package/src/__tests__/rocketstyleIntegration.test.ts +615 -0
- package/src/__tests__/themeUtils.test.ts +463 -0
- package/src/cache/LocalThemeManager.ts +14 -0
- package/src/cache/index.ts +3 -0
- package/src/constants/booleanTags.ts +32 -0
- package/src/constants/defaultDimensions.ts +23 -0
- package/src/constants/index.ts +44 -0
- package/src/context/context.ts +51 -0
- package/src/context/createLocalProvider.ts +84 -0
- package/src/context/localContext.ts +37 -0
- package/src/hoc/index.ts +3 -0
- package/src/hoc/rocketstyleAttrsHoc.ts +63 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/usePseudoState.ts +79 -0
- package/src/hooks/useTheme.ts +36 -0
- package/src/index.ts +77 -0
- package/src/init.ts +93 -0
- package/src/isRocketComponent.ts +16 -0
- package/src/rocketstyle.ts +320 -0
- package/src/types/attrs.ts +13 -0
- package/src/types/config.ts +48 -0
- package/src/types/configuration.ts +69 -0
- package/src/types/dimensions.ts +106 -0
- package/src/types/hoc.ts +5 -0
- package/src/types/pseudo.ts +19 -0
- package/src/types/rocketComponent.ts +24 -0
- package/src/types/rocketstyle.ts +156 -0
- package/src/types/styles.ts +46 -0
- package/src/types/theme.ts +19 -0
- package/src/types/utils.ts +55 -0
- package/src/utils/attrs.ts +134 -0
- package/src/utils/chaining.ts +58 -0
- package/src/utils/collection.ts +9 -0
- package/src/utils/compose.ts +11 -0
- package/src/utils/dimensions.ts +126 -0
- package/src/utils/statics.ts +44 -0
- package/src/utils/styles.ts +18 -0
- package/src/utils/theme.ts +196 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-end test: rocketstyle theme computation pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Tests that rocketstyle components correctly compute theme values
|
|
5
|
+
* through the full chain: base theme → dimension themes → transform modifiers.
|
|
6
|
+
*
|
|
7
|
+
* Unlike the React version which tested CSS injection in the DOM,
|
|
8
|
+
* this Pyreon version tests the computed $rocketstyle output directly.
|
|
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
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
config.init({
|
|
28
|
+
css: mockCss as any,
|
|
29
|
+
styled: mockStyled as any,
|
|
30
|
+
component: "div",
|
|
31
|
+
textComponent: "span",
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
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
|
+
const vnode = Component(props) as any
|
|
68
|
+
return vnode.$rocketstyle
|
|
69
|
+
} finally {
|
|
70
|
+
popContext()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
describe("e2e: rocketstyle theme computation", () => {
|
|
75
|
+
it("base theme values are passed through", () => {
|
|
76
|
+
const Comp: any = rocketstyle()({
|
|
77
|
+
name: "BaseComp",
|
|
78
|
+
component: ThemeCapture,
|
|
79
|
+
}).theme({
|
|
80
|
+
backgroundColor: "#0070f3",
|
|
81
|
+
color: "#fff",
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const theme = getComputedTheme(Comp)
|
|
85
|
+
expect(theme.backgroundColor).toBe("#0070f3")
|
|
86
|
+
expect(theme.color).toBe("#fff")
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("state dimension overrides base theme values", () => {
|
|
90
|
+
const Comp: any = rocketstyle()({
|
|
91
|
+
name: "StateComp",
|
|
92
|
+
component: ThemeCapture,
|
|
93
|
+
})
|
|
94
|
+
.theme({
|
|
95
|
+
backgroundColor: "#0070f3",
|
|
96
|
+
color: "#fff",
|
|
97
|
+
})
|
|
98
|
+
.states({
|
|
99
|
+
danger: {
|
|
100
|
+
backgroundColor: "#dc3545",
|
|
101
|
+
color: "#fff",
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const theme = getComputedTheme(Comp, { state: "danger" })
|
|
106
|
+
expect(theme.backgroundColor).toBe("#dc3545")
|
|
107
|
+
expect(theme.color).toBe("#fff")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("modifier transform derives styles from accumulated state theme", () => {
|
|
111
|
+
const Comp: any = rocketstyle()({
|
|
112
|
+
name: "ModifierComp",
|
|
113
|
+
component: ThemeCapture,
|
|
114
|
+
})
|
|
115
|
+
.theme({
|
|
116
|
+
backgroundColor: "#0070f3",
|
|
117
|
+
color: "#fff",
|
|
118
|
+
})
|
|
119
|
+
.states({
|
|
120
|
+
danger: {
|
|
121
|
+
backgroundColor: "#dc3545",
|
|
122
|
+
color: "#fff",
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
.modifiers({
|
|
126
|
+
outlined: (accTheme: any) => ({
|
|
127
|
+
color: accTheme.backgroundColor,
|
|
128
|
+
backgroundColor: "transparent",
|
|
129
|
+
}),
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// danger state + outlined modifier
|
|
133
|
+
const theme = getComputedTheme(Comp, {
|
|
134
|
+
state: "danger",
|
|
135
|
+
modifier: "outlined",
|
|
136
|
+
})
|
|
137
|
+
// outlined should flip: color becomes the danger backgroundColor
|
|
138
|
+
expect(theme.color).toBe("#dc3545")
|
|
139
|
+
expect(theme.backgroundColor).toBe("transparent")
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it("modifier without active state uses base theme only", () => {
|
|
143
|
+
const Comp: any = rocketstyle()({
|
|
144
|
+
name: "ModifierBaseComp",
|
|
145
|
+
component: ThemeCapture,
|
|
146
|
+
})
|
|
147
|
+
.theme({
|
|
148
|
+
backgroundColor: "#0070f3",
|
|
149
|
+
color: "#fff",
|
|
150
|
+
})
|
|
151
|
+
.modifiers({
|
|
152
|
+
outlined: (accTheme: any) => ({
|
|
153
|
+
color: accTheme.backgroundColor,
|
|
154
|
+
backgroundColor: "transparent",
|
|
155
|
+
}),
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// just outlined modifier, no state — derive from base theme
|
|
159
|
+
const theme = getComputedTheme(Comp, { modifier: "outlined" })
|
|
160
|
+
expect(theme.color).toBe("#0070f3")
|
|
161
|
+
expect(theme.backgroundColor).toBe("transparent")
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it("variant dimension values are applied correctly", () => {
|
|
165
|
+
const Comp: any = rocketstyle()({
|
|
166
|
+
name: "VariantComp",
|
|
167
|
+
component: ThemeCapture,
|
|
168
|
+
})
|
|
169
|
+
.theme({
|
|
170
|
+
backgroundColor: "#FFFFFF",
|
|
171
|
+
borderRadius: 8,
|
|
172
|
+
})
|
|
173
|
+
.variants({
|
|
174
|
+
box: {
|
|
175
|
+
height: 64,
|
|
176
|
+
padding: 8,
|
|
177
|
+
backgroundColor: "transparent",
|
|
178
|
+
},
|
|
179
|
+
circle: {
|
|
180
|
+
width: 72,
|
|
181
|
+
height: 72,
|
|
182
|
+
padding: 4,
|
|
183
|
+
backgroundColor: "#F0F0F0",
|
|
184
|
+
borderRadius: 180,
|
|
185
|
+
},
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const theme = getComputedTheme(Comp, { variant: "circle" })
|
|
189
|
+
expect(theme.width).toBe(72)
|
|
190
|
+
expect(theme.height).toBe(72)
|
|
191
|
+
expect(theme.backgroundColor).toBe("#F0F0F0")
|
|
192
|
+
expect(theme.borderRadius).toBe(180)
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it("variant box values override base theme", () => {
|
|
196
|
+
const Comp: any = rocketstyle()({
|
|
197
|
+
name: "VariantBoxComp",
|
|
198
|
+
component: ThemeCapture,
|
|
199
|
+
})
|
|
200
|
+
.theme({
|
|
201
|
+
backgroundColor: "#FFFFFF",
|
|
202
|
+
borderRadius: 8,
|
|
203
|
+
})
|
|
204
|
+
.variants({
|
|
205
|
+
box: {
|
|
206
|
+
height: 64,
|
|
207
|
+
padding: 8,
|
|
208
|
+
backgroundColor: "transparent",
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
const theme = getComputedTheme(Comp, { variant: "box" })
|
|
213
|
+
expect(theme.backgroundColor).toBe("transparent")
|
|
214
|
+
expect(theme.borderRadius).toBe(8) // inherited from base
|
|
215
|
+
expect(theme.height).toBe(64)
|
|
216
|
+
expect(theme.padding).toBe(8)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it("size dimension values are applied", () => {
|
|
220
|
+
const Comp: any = rocketstyle()({
|
|
221
|
+
name: "SizeComp",
|
|
222
|
+
component: ThemeCapture,
|
|
223
|
+
})
|
|
224
|
+
.theme({ fontSize: 14 })
|
|
225
|
+
.sizes({
|
|
226
|
+
small: { fontSize: 12, padding: 4 },
|
|
227
|
+
large: { fontSize: 18, padding: 8 },
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const theme = getComputedTheme(Comp, { size: "large" })
|
|
231
|
+
expect(theme.fontSize).toBe(18)
|
|
232
|
+
expect(theme.padding).toBe(8)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("multiple dimensions combine", () => {
|
|
236
|
+
const Comp: any = rocketstyle()({
|
|
237
|
+
name: "MultiDimComp",
|
|
238
|
+
component: ThemeCapture,
|
|
239
|
+
})
|
|
240
|
+
.theme({ color: "black" })
|
|
241
|
+
.states({ primary: { color: "blue" } })
|
|
242
|
+
.sizes({ large: { fontSize: 18 } })
|
|
243
|
+
|
|
244
|
+
const theme = getComputedTheme(Comp, {
|
|
245
|
+
state: "primary",
|
|
246
|
+
size: "large",
|
|
247
|
+
})
|
|
248
|
+
expect(theme.color).toBe("blue")
|
|
249
|
+
expect(theme.fontSize).toBe(18)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it("multiple modifier transforms compose sequentially", () => {
|
|
253
|
+
const Comp: any = rocketstyle()({
|
|
254
|
+
name: "MultiModComp",
|
|
255
|
+
component: ThemeCapture,
|
|
256
|
+
})
|
|
257
|
+
.theme({ backgroundColor: "blue", color: "white" })
|
|
258
|
+
.modifiers({
|
|
259
|
+
outlined: (accTheme: any) => ({
|
|
260
|
+
color: accTheme.backgroundColor,
|
|
261
|
+
backgroundColor: "transparent",
|
|
262
|
+
}),
|
|
263
|
+
rounded: () => ({ borderRadius: "999px" }),
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const theme = getComputedTheme(Comp, {
|
|
267
|
+
modifier: ["outlined", "rounded"],
|
|
268
|
+
})
|
|
269
|
+
expect(theme.color).toBe("blue")
|
|
270
|
+
expect(theme.backgroundColor).toBe("transparent")
|
|
271
|
+
expect(theme.borderRadius).toBe("999px")
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it("later transform sees earlier transform results", () => {
|
|
275
|
+
const Comp: any = rocketstyle()({
|
|
276
|
+
name: "ChainedModComp",
|
|
277
|
+
component: ThemeCapture,
|
|
278
|
+
})
|
|
279
|
+
.theme({})
|
|
280
|
+
.modifiers({
|
|
281
|
+
first: () => ({ step: "one" }),
|
|
282
|
+
second: (accTheme: any) => ({ sawStep: accTheme.step }),
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
const theme = getComputedTheme(Comp, {
|
|
286
|
+
modifier: ["first", "second"],
|
|
287
|
+
})
|
|
288
|
+
expect(theme.step).toBe("one")
|
|
289
|
+
expect(theme.sawStep).toBe("one")
|
|
290
|
+
})
|
|
291
|
+
})
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { popContext, pushContext } from "@pyreon/core"
|
|
2
|
+
import { context } from "../context/context"
|
|
3
|
+
import { localContext, useLocalContext } from "../context/localContext"
|
|
4
|
+
import usePseudoState from "../hooks/usePseudoState"
|
|
5
|
+
import useThemeAttrs from "../hooks/useTheme"
|
|
6
|
+
|
|
7
|
+
describe("usePseudoState", () => {
|
|
8
|
+
it("returns initial state with all false", () => {
|
|
9
|
+
const { state } = usePseudoState({})
|
|
10
|
+
expect(state.hover).toBe(false)
|
|
11
|
+
expect(state.focus).toBe(false)
|
|
12
|
+
expect(state.pressed).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("returns event handlers", () => {
|
|
16
|
+
const { events } = usePseudoState({})
|
|
17
|
+
expect(typeof events.onMouseEnter).toBe("function")
|
|
18
|
+
expect(typeof events.onMouseLeave).toBe("function")
|
|
19
|
+
expect(typeof events.onMouseDown).toBe("function")
|
|
20
|
+
expect(typeof events.onMouseUp).toBe("function")
|
|
21
|
+
expect(typeof events.onFocus).toBe("function")
|
|
22
|
+
expect(typeof events.onBlur).toBe("function")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("sets hover on mouseEnter", () => {
|
|
26
|
+
const { state, events } = usePseudoState({})
|
|
27
|
+
events.onMouseEnter({} as any)
|
|
28
|
+
expect(state.hover).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("clears hover and pressed on mouseLeave", () => {
|
|
32
|
+
const { state, events } = usePseudoState({})
|
|
33
|
+
events.onMouseEnter({} as any)
|
|
34
|
+
events.onMouseDown({} as any)
|
|
35
|
+
expect(state.hover).toBe(true)
|
|
36
|
+
expect(state.pressed).toBe(true)
|
|
37
|
+
|
|
38
|
+
events.onMouseLeave({} as any)
|
|
39
|
+
expect(state.hover).toBe(false)
|
|
40
|
+
expect(state.pressed).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("sets pressed on mouseDown, clears on mouseUp", () => {
|
|
44
|
+
const { state, events } = usePseudoState({})
|
|
45
|
+
events.onMouseDown({} as any)
|
|
46
|
+
expect(state.pressed).toBe(true)
|
|
47
|
+
|
|
48
|
+
events.onMouseUp({} as any)
|
|
49
|
+
expect(state.pressed).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("sets focus on focus, clears on blur", () => {
|
|
53
|
+
const { state, events } = usePseudoState({})
|
|
54
|
+
events.onFocus({} as any)
|
|
55
|
+
expect(state.focus).toBe(true)
|
|
56
|
+
|
|
57
|
+
events.onBlur({} as any)
|
|
58
|
+
expect(state.focus).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("calls user-provided event handlers", () => {
|
|
62
|
+
const onMouseEnter = vi.fn()
|
|
63
|
+
const onMouseLeave = vi.fn()
|
|
64
|
+
const onMouseDown = vi.fn()
|
|
65
|
+
const onMouseUp = vi.fn()
|
|
66
|
+
const onFocus = vi.fn()
|
|
67
|
+
const onBlur = vi.fn()
|
|
68
|
+
|
|
69
|
+
const { events } = usePseudoState({
|
|
70
|
+
onMouseEnter,
|
|
71
|
+
onMouseLeave,
|
|
72
|
+
onMouseDown,
|
|
73
|
+
onMouseUp,
|
|
74
|
+
onFocus,
|
|
75
|
+
onBlur,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const mockEvent = {} as any
|
|
79
|
+
events.onMouseEnter(mockEvent)
|
|
80
|
+
events.onMouseLeave(mockEvent)
|
|
81
|
+
events.onMouseDown(mockEvent)
|
|
82
|
+
events.onMouseUp(mockEvent)
|
|
83
|
+
events.onFocus(mockEvent)
|
|
84
|
+
events.onBlur(mockEvent)
|
|
85
|
+
|
|
86
|
+
expect(onMouseEnter).toHaveBeenCalledWith(mockEvent)
|
|
87
|
+
expect(onMouseLeave).toHaveBeenCalledWith(mockEvent)
|
|
88
|
+
expect(onMouseDown).toHaveBeenCalledWith(mockEvent)
|
|
89
|
+
expect(onMouseUp).toHaveBeenCalledWith(mockEvent)
|
|
90
|
+
expect(onFocus).toHaveBeenCalledWith(mockEvent)
|
|
91
|
+
expect(onBlur).toHaveBeenCalledWith(mockEvent)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe("useThemeAttrs", () => {
|
|
96
|
+
let pushed = false
|
|
97
|
+
|
|
98
|
+
afterEach(() => {
|
|
99
|
+
if (pushed) {
|
|
100
|
+
popContext()
|
|
101
|
+
pushed = false
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it("returns default values when no context", () => {
|
|
106
|
+
const result = useThemeAttrs({ inversed: false })
|
|
107
|
+
expect(result.theme).toEqual({})
|
|
108
|
+
expect(result.mode).toBe("light")
|
|
109
|
+
expect(result.isLight).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("reads theme from context", () => {
|
|
113
|
+
pushContext(
|
|
114
|
+
new Map([
|
|
115
|
+
[
|
|
116
|
+
context.id,
|
|
117
|
+
{
|
|
118
|
+
theme: { rootSize: 16 },
|
|
119
|
+
mode: "light",
|
|
120
|
+
isDark: false,
|
|
121
|
+
isLight: true,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
]),
|
|
125
|
+
)
|
|
126
|
+
pushed = true
|
|
127
|
+
|
|
128
|
+
const result = useThemeAttrs({ inversed: false })
|
|
129
|
+
expect(result.theme).toEqual({ rootSize: 16 })
|
|
130
|
+
expect(result.mode).toBe("light")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("inverts mode when inversed is true", () => {
|
|
134
|
+
pushContext(
|
|
135
|
+
new Map([
|
|
136
|
+
[
|
|
137
|
+
context.id,
|
|
138
|
+
{
|
|
139
|
+
theme: { rootSize: 16 },
|
|
140
|
+
mode: "light",
|
|
141
|
+
isDark: false,
|
|
142
|
+
isLight: true,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
]),
|
|
146
|
+
)
|
|
147
|
+
pushed = true
|
|
148
|
+
|
|
149
|
+
const result = useThemeAttrs({ inversed: true })
|
|
150
|
+
expect(result.mode).toBe("dark")
|
|
151
|
+
expect(result.isDark).toBe(true)
|
|
152
|
+
expect(result.isLight).toBe(false)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it("inverts dark to light", () => {
|
|
156
|
+
pushContext(
|
|
157
|
+
new Map([
|
|
158
|
+
[
|
|
159
|
+
context.id,
|
|
160
|
+
{
|
|
161
|
+
theme: {},
|
|
162
|
+
mode: "dark",
|
|
163
|
+
isDark: true,
|
|
164
|
+
isLight: false,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
]),
|
|
168
|
+
)
|
|
169
|
+
pushed = true
|
|
170
|
+
|
|
171
|
+
const result = useThemeAttrs({ inversed: true })
|
|
172
|
+
expect(result.mode).toBe("light")
|
|
173
|
+
expect(result.isDark).toBe(false)
|
|
174
|
+
expect(result.isLight).toBe(true)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
describe("useLocalContext", () => {
|
|
179
|
+
let pushed = false
|
|
180
|
+
|
|
181
|
+
afterEach(() => {
|
|
182
|
+
if (pushed) {
|
|
183
|
+
popContext()
|
|
184
|
+
pushed = false
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it("returns default pseudo when no consumer", () => {
|
|
189
|
+
const result = useLocalContext(null)
|
|
190
|
+
expect(result).toEqual({ pseudo: {} })
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it("returns default pseudo when consumer is undefined", () => {
|
|
194
|
+
const result = useLocalContext(undefined)
|
|
195
|
+
expect(result).toEqual({ pseudo: {} })
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it("calls consumer with getter function", () => {
|
|
199
|
+
pushContext(new Map([[localContext.id, { pseudo: { hover: true } }]]))
|
|
200
|
+
pushed = true
|
|
201
|
+
|
|
202
|
+
const consumer = (getter: any) => getter((ctx: any) => ({ myPseudo: ctx.pseudo }))
|
|
203
|
+
|
|
204
|
+
const result = useLocalContext(consumer)
|
|
205
|
+
expect(result.myPseudo).toEqual({ hover: true })
|
|
206
|
+
})
|
|
207
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import isRocketComponent from "../isRocketComponent"
|
|
2
|
+
|
|
3
|
+
describe("isRocketComponent", () => {
|
|
4
|
+
it("returns true for object with IS_ROCKETSTYLE property", () => {
|
|
5
|
+
const component = { IS_ROCKETSTYLE: true }
|
|
6
|
+
expect(isRocketComponent(component)).toBe(true)
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it("returns false for plain object without IS_ROCKETSTYLE", () => {
|
|
10
|
+
expect(isRocketComponent({})).toBe(false)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it("returns false for null", () => {
|
|
14
|
+
expect(isRocketComponent(null)).toBe(false)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it("returns false for undefined", () => {
|
|
18
|
+
expect(isRocketComponent(undefined)).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it("returns false for primitives", () => {
|
|
22
|
+
expect(isRocketComponent("string")).toBe(false)
|
|
23
|
+
expect(isRocketComponent(42)).toBe(false)
|
|
24
|
+
expect(isRocketComponent(true)).toBe(false)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("returns false for plain functions without IS_ROCKETSTYLE", () => {
|
|
28
|
+
expect(
|
|
29
|
+
isRocketComponent(() => {
|
|
30
|
+
/* no-op */
|
|
31
|
+
}),
|
|
32
|
+
).toBe(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("returns true for functions with IS_ROCKETSTYLE", () => {
|
|
36
|
+
const fn = () => {
|
|
37
|
+
/* no-op */
|
|
38
|
+
}
|
|
39
|
+
;(fn as any).IS_ROCKETSTYLE = true
|
|
40
|
+
expect(isRocketComponent(fn)).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it("returns true even if IS_ROCKETSTYLE is falsy", () => {
|
|
44
|
+
// hasOwnProperty check only, doesn't check truthiness
|
|
45
|
+
const component = { IS_ROCKETSTYLE: false }
|
|
46
|
+
expect(isRocketComponent(component)).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
})
|