@pyreon/rocketstyle 0.24.4 → 0.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/rocketstyle",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.6",
|
|
4
4
|
"description": "Multi-dimensional style composition for Pyreon components",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
"!lib/**/*.map",
|
|
14
14
|
"!lib/analysis",
|
|
15
15
|
"README.md",
|
|
16
|
-
"LICENSE"
|
|
17
|
-
"src"
|
|
16
|
+
"LICENSE"
|
|
18
17
|
],
|
|
19
18
|
"type": "module",
|
|
20
19
|
"sideEffects": false,
|
|
@@ -22,7 +21,6 @@
|
|
|
22
21
|
"types": "./lib/index.d.ts",
|
|
23
22
|
"exports": {
|
|
24
23
|
".": {
|
|
25
|
-
"bun": "./src/index.ts",
|
|
26
24
|
"import": "./lib/index.js",
|
|
27
25
|
"types": "./lib/index.d.ts"
|
|
28
26
|
}
|
|
@@ -43,8 +41,8 @@
|
|
|
43
41
|
},
|
|
44
42
|
"devDependencies": {
|
|
45
43
|
"@pyreon/test-utils": "^0.13.11",
|
|
46
|
-
"@pyreon/typescript": "^0.24.
|
|
47
|
-
"@pyreon/ui-core": "^0.24.
|
|
44
|
+
"@pyreon/typescript": "^0.24.6",
|
|
45
|
+
"@pyreon/ui-core": "^0.24.6",
|
|
48
46
|
"@vitest/browser-playwright": "^4.1.4",
|
|
49
47
|
"@vitus-labs/tools-rolldown": "^2.4.0"
|
|
50
48
|
},
|
|
@@ -52,9 +50,9 @@
|
|
|
52
50
|
"node": ">= 22"
|
|
53
51
|
},
|
|
54
52
|
"dependencies": {
|
|
55
|
-
"@pyreon/core": "^0.24.
|
|
56
|
-
"@pyreon/reactivity": "^0.24.
|
|
57
|
-
"@pyreon/styler": "^0.24.
|
|
58
|
-
"@pyreon/ui-core": "^0.24.
|
|
53
|
+
"@pyreon/core": "^0.24.6",
|
|
54
|
+
"@pyreon/reactivity": "^0.24.6",
|
|
55
|
+
"@pyreon/styler": "^0.24.6",
|
|
56
|
+
"@pyreon/ui-core": "^0.24.6"
|
|
59
57
|
}
|
|
60
58
|
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import type { ComponentFn } from '@pyreon/core'
|
|
2
|
-
import rocketstyle from '../init'
|
|
3
|
-
|
|
4
|
-
// Type-level regression tests for the post-#225/#227 `.attrs()` overload
|
|
5
|
-
// split: (a) DFP widening makes `.attrs(obj)` keys optional at JSX call site,
|
|
6
|
-
// (b) callback overload preserves Pyreon's loose-return convention so
|
|
7
|
-
// `_documentProps` / `tag: 'a'` runtime extras still typecheck without
|
|
8
|
-
// per-callsite `as any` casts.
|
|
9
|
-
//
|
|
10
|
-
// These are not bisect-load-bearing at runtime — they're type-level
|
|
11
|
-
// assertions exercised by `tsc --noEmit`. Including them in the suite
|
|
12
|
-
// makes failures show up in the test report (vitest treats type errors
|
|
13
|
-
// as compile failures).
|
|
14
|
-
describe('attrs overloads — type-level contract', () => {
|
|
15
|
-
// A minimal base component standing in for Text / Button / etc.
|
|
16
|
-
// We only care about the type-level surface here.
|
|
17
|
-
type BaseProps = {
|
|
18
|
-
tag?: 'div' | 'span' | 'p' | 'h1' | 'h2' | 'h3'
|
|
19
|
-
role?: string
|
|
20
|
-
}
|
|
21
|
-
const Base: ComponentFn<BaseProps> = () => null
|
|
22
|
-
|
|
23
|
-
describe('object overload — keys become optional at JSX site (PR #225)', () => {
|
|
24
|
-
it('accepts object with default values', () => {
|
|
25
|
-
const Comp = rocketstyle()({ name: 'Comp', component: Base }).attrs({
|
|
26
|
-
tag: 'div',
|
|
27
|
-
})
|
|
28
|
-
// The component is callable — at the JSX call site, `tag` is now
|
|
29
|
-
// optional because `.attrs({ tag: 'div' })` provides a default.
|
|
30
|
-
// Pre-#225 the type would have required `tag` at the JSX site.
|
|
31
|
-
// We can't directly assert via `expectTypeOf` without the dep, but
|
|
32
|
-
// the smoke is: the chain compiles without errors.
|
|
33
|
-
expect(typeof Comp).toBe('function')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('accepts new keys in attrs object', () => {
|
|
37
|
-
// The attrs object can introduce new keys beyond the base component's
|
|
38
|
-
// props (here: `customField`). The keys flow into the returned
|
|
39
|
-
// component's extended-attrs `EA` and become typed props.
|
|
40
|
-
const Comp = rocketstyle()({ name: 'Comp', component: Base }).attrs({
|
|
41
|
-
customField: 'hello',
|
|
42
|
-
})
|
|
43
|
-
expect(typeof Comp).toBe('function')
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('callback overload — Pyreon convention for runtime extras', () => {
|
|
48
|
-
it('accepts callback returning fields outside the base prop union (no as-cast needed)', () => {
|
|
49
|
-
// This is the canonical document-primitive pattern: a Text-based
|
|
50
|
-
// rocketstyle that overrides `tag` to a value outside Text's strict
|
|
51
|
-
// `tag` union AND adds a runtime-only `_documentProps` marker. The
|
|
52
|
-
// callback's return type intentionally allows `Record<string, unknown>`
|
|
53
|
-
// for keys outside the user's explicit `<P>` generic, matching
|
|
54
|
-
// Pyreon's pre-#225 convention.
|
|
55
|
-
const Comp = rocketstyle()({ name: 'DocLink', component: Base }).attrs<{
|
|
56
|
-
href?: string
|
|
57
|
-
}>((props) => ({
|
|
58
|
-
tag: 'a', // 'a' is NOT in BaseProps['tag'] — falls through Record<string, unknown>
|
|
59
|
-
_documentProps: { href: props.href ?? '#' },
|
|
60
|
-
}))
|
|
61
|
-
expect(typeof Comp).toBe('function')
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('accepts callback returning literal values (contextual narrowing via <P>)', () => {
|
|
65
|
-
// When the user passes an explicit `<P>` generic, the callback's
|
|
66
|
-
// return is contextually typed against `Partial<P>`. Writing
|
|
67
|
-
// `tag: 'h1'` stays narrow at literal `'h1'` — no `as const` needed.
|
|
68
|
-
// Note: tag is in BaseProps['tag'] union so this typechecks against
|
|
69
|
-
// BOTH the wildcard arm AND the explicit P-key arm.
|
|
70
|
-
const Comp = rocketstyle()({ name: 'Heading', component: Base }).attrs<{
|
|
71
|
-
level?: number
|
|
72
|
-
}>((props) => ({
|
|
73
|
-
tag: `h${props.level ?? 1}` as 'h1' | 'h2' | 'h3',
|
|
74
|
-
}))
|
|
75
|
-
expect(typeof Comp).toBe('function')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('callback receives full DFP-typed props for narrow reads', () => {
|
|
79
|
-
// The `props` arg passed to the callback IS strictly typed as
|
|
80
|
-
// `Partial<DFP & P>` — so reading `props.tag` narrows against the
|
|
81
|
-
// wrapped component's full surface. This is the "props narrow,
|
|
82
|
-
// return loose" asymmetry Pyreon settled on.
|
|
83
|
-
const Comp = rocketstyle()({ name: 'Probe', component: Base }).attrs<{
|
|
84
|
-
scale?: number
|
|
85
|
-
}>((props) => {
|
|
86
|
-
// Type check: `props.scale` is `number | undefined`, `props.tag`
|
|
87
|
-
// is the narrow BaseProps['tag'] union | undefined.
|
|
88
|
-
const _scale: number | undefined = props.scale
|
|
89
|
-
const _tag: 'div' | 'span' | 'p' | 'h1' | 'h2' | 'h3' | undefined = props.tag
|
|
90
|
-
expect(_scale).toBeUndefined()
|
|
91
|
-
expect(_tag).toBeUndefined()
|
|
92
|
-
return { scale: 1 }
|
|
93
|
-
})
|
|
94
|
-
expect(typeof Comp).toBe('function')
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
})
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
calculateChainOptions,
|
|
3
|
-
calculateStylingAttrs,
|
|
4
|
-
pickStyledAttrs,
|
|
5
|
-
removeUndefinedProps,
|
|
6
|
-
} from '../utils/attrs'
|
|
7
|
-
|
|
8
|
-
describe('removeUndefinedProps', () => {
|
|
9
|
-
it('removes keys with undefined values', () => {
|
|
10
|
-
const result = removeUndefinedProps({ a: 1, b: undefined, c: 'hello' })
|
|
11
|
-
expect(result).toEqual({ a: 1, c: 'hello' })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('keeps null values', () => {
|
|
15
|
-
const result = removeUndefinedProps({ a: null, b: 0 })
|
|
16
|
-
expect(result).toEqual({ a: null, b: 0 })
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('keeps all falsy non-undefined values', () => {
|
|
20
|
-
const result = removeUndefinedProps({ a: 0, b: '', c: false, d: null })
|
|
21
|
-
expect(result).toEqual({ a: 0, b: '', c: false, d: null })
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('returns empty for all undefined', () => {
|
|
25
|
-
expect(removeUndefinedProps({ a: undefined, b: undefined })).toEqual({})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('returns empty for empty input', () => {
|
|
29
|
-
expect(removeUndefinedProps({})).toEqual({})
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('pickStyledAttrs', () => {
|
|
34
|
-
it('picks keys that exist in keywords with truthy values', () => {
|
|
35
|
-
const result = pickStyledAttrs(
|
|
36
|
-
{ state: 'primary', size: 'large', label: 'hello' },
|
|
37
|
-
{ state: true, size: true },
|
|
38
|
-
)
|
|
39
|
-
expect(result).toEqual({ state: 'primary', size: 'large' })
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('ignores falsy prop values', () => {
|
|
43
|
-
const result = pickStyledAttrs({ state: '', size: 'large' }, { state: true, size: true })
|
|
44
|
-
expect(result).toEqual({ size: 'large' })
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('returns empty when no keywords match', () => {
|
|
48
|
-
const result = pickStyledAttrs({ label: 'hello' } as any, { state: true })
|
|
49
|
-
expect(result).toEqual({})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
it('returns empty for empty props', () => {
|
|
53
|
-
const result = pickStyledAttrs({}, { state: true })
|
|
54
|
-
expect(result).toEqual({})
|
|
55
|
-
})
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
describe('calculateChainOptions', () => {
|
|
59
|
-
it('returns empty object when options is empty array', () => {
|
|
60
|
-
const calc = calculateChainOptions([])
|
|
61
|
-
expect(calc([])).toEqual({})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it('returns empty object when options is undefined', () => {
|
|
65
|
-
const calc = calculateChainOptions(undefined)
|
|
66
|
-
expect(calc([])).toEqual({})
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('evaluates chain of functions and merges via Object.assign', () => {
|
|
70
|
-
const fn1 = (props: any) => ({ a: 1, ...props })
|
|
71
|
-
const fn2 = (_props: any) => ({ b: 2 })
|
|
72
|
-
const calc = calculateChainOptions([fn1, fn2])
|
|
73
|
-
expect(calc([{ c: 3 }])).toEqual({ a: 1, b: 2, c: 3 })
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('later functions override earlier ones (shallow)', () => {
|
|
77
|
-
const fn1 = () => ({ a: 1 })
|
|
78
|
-
const fn2 = () => ({ a: 2 })
|
|
79
|
-
const calc = calculateChainOptions([fn1, fn2])
|
|
80
|
-
expect(calc([])).toEqual({ a: 2 })
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('passes all args to each function', () => {
|
|
84
|
-
const fn = vi.fn(() => ({}))
|
|
85
|
-
const calc = calculateChainOptions([fn])
|
|
86
|
-
calc(['arg1', 'arg2'] as any)
|
|
87
|
-
expect(fn).toHaveBeenCalledWith('arg1', 'arg2')
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('calculateStylingAttrs', () => {
|
|
92
|
-
it('picks string values from props for dimensions', () => {
|
|
93
|
-
const calc = calculateStylingAttrs({ useBooleans: false, multiKeys: {} })
|
|
94
|
-
const result = calc({
|
|
95
|
-
props: { state: 'primary', size: 'large' },
|
|
96
|
-
dimensions: { state: {}, size: {} },
|
|
97
|
-
})
|
|
98
|
-
expect(result).toEqual({ state: 'primary', size: 'large' })
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
it('picks number values from props', () => {
|
|
102
|
-
const calc = calculateStylingAttrs({ useBooleans: false, multiKeys: {} })
|
|
103
|
-
const result = calc({
|
|
104
|
-
props: { state: 0 },
|
|
105
|
-
dimensions: { state: {} },
|
|
106
|
-
})
|
|
107
|
-
expect(result).toEqual({ state: 0 })
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
it('sets undefined for non-string/non-number values when booleans disabled', () => {
|
|
111
|
-
const calc = calculateStylingAttrs({ useBooleans: false, multiKeys: {} })
|
|
112
|
-
const result = calc({
|
|
113
|
-
props: { state: true },
|
|
114
|
-
dimensions: { state: {} },
|
|
115
|
-
})
|
|
116
|
-
expect(result).toEqual({ state: undefined })
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
it('allows arrays for multi-key dimensions', () => {
|
|
120
|
-
const calc = calculateStylingAttrs({
|
|
121
|
-
useBooleans: false,
|
|
122
|
-
multiKeys: { multiple: true },
|
|
123
|
-
})
|
|
124
|
-
const result = calc({
|
|
125
|
-
props: { multiple: ['a', 'b'] },
|
|
126
|
-
dimensions: { multiple: {} },
|
|
127
|
-
})
|
|
128
|
-
expect(result).toEqual({ multiple: ['a', 'b'] })
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('resolves boolean props when useBooleans is true (single key)', () => {
|
|
132
|
-
const calc = calculateStylingAttrs({
|
|
133
|
-
useBooleans: true,
|
|
134
|
-
multiKeys: {},
|
|
135
|
-
})
|
|
136
|
-
const result = calc({
|
|
137
|
-
props: { primary: true },
|
|
138
|
-
dimensions: { state: { primary: true, secondary: true } },
|
|
139
|
-
})
|
|
140
|
-
expect(result).toEqual({ state: 'primary' })
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('resolves multi-key boolean props as array', () => {
|
|
144
|
-
const calc = calculateStylingAttrs({
|
|
145
|
-
useBooleans: true,
|
|
146
|
-
multiKeys: { multiple: true },
|
|
147
|
-
})
|
|
148
|
-
const result = calc({
|
|
149
|
-
props: { a: true, b: true },
|
|
150
|
-
dimensions: { multiple: { a: true, b: true, c: true } },
|
|
151
|
-
})
|
|
152
|
-
expect(result.multiple).toEqual(expect.arrayContaining(['a', 'b']))
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
it('prefers explicit string prop over boolean shorthand', () => {
|
|
156
|
-
const calc = calculateStylingAttrs({
|
|
157
|
-
useBooleans: true,
|
|
158
|
-
multiKeys: {},
|
|
159
|
-
})
|
|
160
|
-
const result = calc({
|
|
161
|
-
props: { state: 'secondary', primary: true },
|
|
162
|
-
dimensions: { state: { primary: true, secondary: true } },
|
|
163
|
-
})
|
|
164
|
-
expect(result).toEqual({ state: 'secondary' })
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
it('skips boolean keyword when prop value is falsy', () => {
|
|
168
|
-
const calc = calculateStylingAttrs({
|
|
169
|
-
useBooleans: true,
|
|
170
|
-
multiKeys: {},
|
|
171
|
-
})
|
|
172
|
-
const result = calc({
|
|
173
|
-
props: { primary: false },
|
|
174
|
-
dimensions: { state: { primary: true, secondary: true } },
|
|
175
|
-
})
|
|
176
|
-
expect(result.state).toBeUndefined()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('skips boolean resolution when value is already set', () => {
|
|
180
|
-
const calc = calculateStylingAttrs({
|
|
181
|
-
useBooleans: true,
|
|
182
|
-
multiKeys: {},
|
|
183
|
-
})
|
|
184
|
-
const result = calc({
|
|
185
|
-
props: { state: 'primary', secondary: true },
|
|
186
|
-
dimensions: { state: { primary: true, secondary: true } },
|
|
187
|
-
})
|
|
188
|
-
expect(result.state).toBe('primary')
|
|
189
|
-
})
|
|
190
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bug reproduction: under `useBooleans: true`, `_resolveRsEntry`'s cache
|
|
3
|
-
* key reads `propsRec[dimName]` directly (e.g. `propsRec.state`). Boolean
|
|
4
|
-
* shorthand props like `<X primary />` populate `propsRec.primary` (NOT
|
|
5
|
-
* `propsRec.state`), so the cache key for the `state` slot is `undefined`
|
|
6
|
-
* → `''` regardless of which boolean variant was passed.
|
|
7
|
-
*
|
|
8
|
-
* Result: `<X primary />` and `<X secondary />` produce identical cache
|
|
9
|
-
* keys and share the cached entry. The first-resolved variant's
|
|
10
|
-
* `$rocketstyle` wins for all subsequent renders.
|
|
11
|
-
*/
|
|
12
|
-
import { initTestConfig, withThemeContext } from '@pyreon/test-utils'
|
|
13
|
-
import rocketstyle from '../init'
|
|
14
|
-
|
|
15
|
-
let cleanup: () => void
|
|
16
|
-
beforeAll(() => {
|
|
17
|
-
cleanup = initTestConfig()
|
|
18
|
-
})
|
|
19
|
-
afterAll(() => cleanup())
|
|
20
|
-
|
|
21
|
-
const ThemeCapture: any = ({ $rocketstyle, $rocketstate, ...rest }: any) => ({
|
|
22
|
-
type: 'div',
|
|
23
|
-
props: rest,
|
|
24
|
-
$rocketstyle: typeof $rocketstyle === 'function' ? $rocketstyle() : $rocketstyle,
|
|
25
|
-
$rocketstate: typeof $rocketstate === 'function' ? $rocketstate() : $rocketstate,
|
|
26
|
-
})
|
|
27
|
-
ThemeCapture.displayName = 'ThemeCapture'
|
|
28
|
-
|
|
29
|
-
describe('rocketstyle — cache-key collision under useBooleans:true', () => {
|
|
30
|
-
it('different boolean variants produce different $rocketstyle (NOT collide)', () => {
|
|
31
|
-
const Button: any = rocketstyle({ useBooleans: true })({
|
|
32
|
-
name: 'BoolButton',
|
|
33
|
-
component: ThemeCapture,
|
|
34
|
-
}).states(() => ({
|
|
35
|
-
primary: { color: 'red' },
|
|
36
|
-
secondary: { color: 'blue' },
|
|
37
|
-
}))
|
|
38
|
-
|
|
39
|
-
// Render with primary=true. Captures the $rocketstyle resolved
|
|
40
|
-
// for state='primary'.
|
|
41
|
-
const a = withThemeContext(() => Button({ primary: true }))
|
|
42
|
-
|
|
43
|
-
// Render with secondary=true. Should resolve to state='secondary'
|
|
44
|
-
// and produce DIFFERENT $rocketstyle.
|
|
45
|
-
const b = withThemeContext(() => Button({ secondary: true }))
|
|
46
|
-
|
|
47
|
-
// Bug: a.$rocketstyle === b.$rocketstyle (same cached entry).
|
|
48
|
-
// Fix: a.$rocketstyle.color === 'red', b.$rocketstyle.color === 'blue'.
|
|
49
|
-
expect(a.$rocketstate.state).toBe('primary')
|
|
50
|
-
expect(b.$rocketstate.state).toBe('secondary')
|
|
51
|
-
expect(a.$rocketstyle.color).toBe('red')
|
|
52
|
-
expect(b.$rocketstyle.color).toBe('blue')
|
|
53
|
-
})
|
|
54
|
-
})
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { chainOptions, chainOrOptions, chainReservedKeyOptions } from '../utils/chaining'
|
|
2
|
-
|
|
3
|
-
describe('chainOptions', () => {
|
|
4
|
-
it('appends function to defaults', () => {
|
|
5
|
-
const fn1 = () => ({ a: 1 })
|
|
6
|
-
const fn2 = () => ({ b: 2 })
|
|
7
|
-
const result = chainOptions(fn2, [fn1])
|
|
8
|
-
expect(result).toHaveLength(2)
|
|
9
|
-
expect(result[0]).toBe(fn1)
|
|
10
|
-
expect(result[1]).toBe(fn2)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
it('wraps object in function and appends', () => {
|
|
14
|
-
const obj = { a: 1 }
|
|
15
|
-
const result = chainOptions(obj, [])
|
|
16
|
-
expect(result).toHaveLength(1)
|
|
17
|
-
expect(result[0]?.()).toEqual({ a: 1 })
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('returns defaults when opts is undefined', () => {
|
|
21
|
-
const fn1 = () => ({ a: 1 })
|
|
22
|
-
const result = chainOptions(undefined, [fn1])
|
|
23
|
-
expect(result).toEqual([fn1])
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
it('handles empty defaults', () => {
|
|
27
|
-
const fn = () => ({ a: 1 })
|
|
28
|
-
const result = chainOptions(fn, [])
|
|
29
|
-
expect(result).toHaveLength(1)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('defaults to empty array when defaultOpts missing', () => {
|
|
33
|
-
const fn = () => ({ a: 1 })
|
|
34
|
-
// @ts-expect-error testing with undefined defaults
|
|
35
|
-
const result = chainOptions(fn, undefined)
|
|
36
|
-
expect(result).toHaveLength(1)
|
|
37
|
-
})
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
describe('chainOrOptions', () => {
|
|
41
|
-
it('merges opts with defaults using keys', () => {
|
|
42
|
-
const keys = ['a', 'b', 'c'] as const
|
|
43
|
-
const opts = { a: 'new', c: 'also' }
|
|
44
|
-
const defaults = { a: 'old', b: 'default', c: 'orig' }
|
|
45
|
-
const result = chainOrOptions(keys, opts, defaults)
|
|
46
|
-
expect(result).toEqual({ a: 'new', b: 'default', c: 'also' })
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it('uses default when opt is falsy', () => {
|
|
50
|
-
const keys = ['a'] as const
|
|
51
|
-
const opts = { a: '' }
|
|
52
|
-
const defaults = { a: 'default' }
|
|
53
|
-
const result = chainOrOptions(keys, opts, defaults)
|
|
54
|
-
expect(result).toEqual({ a: 'default' })
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it('handles missing keys in both', () => {
|
|
58
|
-
const keys = ['x'] as const
|
|
59
|
-
const result = chainOrOptions(keys, {}, {})
|
|
60
|
-
expect(result).toEqual({ x: undefined })
|
|
61
|
-
})
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
describe('chainReservedKeyOptions', () => {
|
|
65
|
-
it('chains options for each reserved key', () => {
|
|
66
|
-
const keys = ['theme', 'styles'] as const
|
|
67
|
-
const fn1 = () => ({ a: 1 })
|
|
68
|
-
const fn2 = () => ({ b: 2 })
|
|
69
|
-
const opts = { theme: fn2 }
|
|
70
|
-
const defaults = { theme: [fn1], styles: [] }
|
|
71
|
-
|
|
72
|
-
const result = chainReservedKeyOptions(keys, opts, defaults)
|
|
73
|
-
expect(result.theme).toHaveLength(2)
|
|
74
|
-
expect(result.styles).toHaveLength(0)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('wraps object opts into functions', () => {
|
|
78
|
-
const keys = ['theme'] as const
|
|
79
|
-
const opts = { theme: { color: 'red' } }
|
|
80
|
-
const defaults = { theme: [] }
|
|
81
|
-
|
|
82
|
-
const result = chainReservedKeyOptions(keys, opts, defaults)
|
|
83
|
-
expect(result.theme).toHaveLength(1)
|
|
84
|
-
expect(result.theme?.[0]?.()).toEqual({ color: 'red' })
|
|
85
|
-
})
|
|
86
|
-
})
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { removeNullableValues } from '../utils/collection'
|
|
2
|
-
|
|
3
|
-
describe('removeNullableValues', () => {
|
|
4
|
-
it('removes null values', () => {
|
|
5
|
-
expect(removeNullableValues({ a: 1, b: null })).toEqual({ a: 1 })
|
|
6
|
-
})
|
|
7
|
-
|
|
8
|
-
it('removes undefined values', () => {
|
|
9
|
-
expect(removeNullableValues({ a: 1, b: undefined })).toEqual({ a: 1 })
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('removes false values', () => {
|
|
13
|
-
expect(removeNullableValues({ a: 1, b: false })).toEqual({ a: 1 })
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
it('keeps truthy values', () => {
|
|
17
|
-
expect(removeNullableValues({ a: 1, b: 'hello', c: true })).toEqual({
|
|
18
|
-
a: 1,
|
|
19
|
-
b: 'hello',
|
|
20
|
-
c: true,
|
|
21
|
-
})
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it('keeps zero and empty string', () => {
|
|
25
|
-
expect(removeNullableValues({ a: 0, b: '' })).toEqual({ a: 0, b: '' })
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it('returns empty object for all nullable', () => {
|
|
29
|
-
expect(removeNullableValues({ a: null, b: undefined, c: false })).toEqual({})
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('handles empty object', () => {
|
|
33
|
-
expect(removeNullableValues({})).toEqual({})
|
|
34
|
-
})
|
|
35
|
-
})
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { calculateHocsFuncs } from '../utils/compose'
|
|
2
|
-
|
|
3
|
-
describe('calculateHocsFuncs', () => {
|
|
4
|
-
it('extracts functions from object values', () => {
|
|
5
|
-
const fn1 = (x: any) => x
|
|
6
|
-
const fn2 = (x: any) => x
|
|
7
|
-
const options = { a: fn1, b: fn2 }
|
|
8
|
-
const result = calculateHocsFuncs(options)
|
|
9
|
-
expect(result).toHaveLength(2)
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
it('filters out non-function values', () => {
|
|
13
|
-
const fn = (x: any) => x
|
|
14
|
-
const options = { a: fn, b: 'string', c: 42, d: null }
|
|
15
|
-
const result = calculateHocsFuncs(options)
|
|
16
|
-
expect(result).toHaveLength(1)
|
|
17
|
-
expect(result[0]).toBe(fn)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
it('reverses the order', () => {
|
|
21
|
-
const fn1 = () => 'first'
|
|
22
|
-
const fn2 = () => 'second'
|
|
23
|
-
const options = { a: fn1, b: fn2 }
|
|
24
|
-
const result = calculateHocsFuncs(options)
|
|
25
|
-
expect(result[0]).toBe(fn2)
|
|
26
|
-
expect(result[1]).toBe(fn1)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('returns empty array for empty options', () => {
|
|
30
|
-
expect(calculateHocsFuncs({})).toEqual([])
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it('handles undefined options', () => {
|
|
34
|
-
expect(calculateHocsFuncs(undefined as any)).toEqual([])
|
|
35
|
-
})
|
|
36
|
-
})
|