@pyreon/core 0.11.2 → 0.11.4

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.
@@ -0,0 +1,157 @@
1
+ import { _resetIdCounter, createUniqueId, mergeProps, splitProps } from "../props"
2
+
3
+ describe("createUniqueId — extended", () => {
4
+ test("returns pyreon- prefixed string", () => {
5
+ const id = createUniqueId()
6
+ expect(id).toMatch(/^pyreon-\d+$/)
7
+ })
8
+
9
+ test("returns incrementing values", () => {
10
+ const id1 = createUniqueId()
11
+ const id2 = createUniqueId()
12
+ const id3 = createUniqueId()
13
+ const num1 = Number.parseInt(id1.replace("pyreon-", ""), 10)
14
+ const num2 = Number.parseInt(id2.replace("pyreon-", ""), 10)
15
+ const num3 = Number.parseInt(id3.replace("pyreon-", ""), 10)
16
+ expect(num2).toBe(num1 + 1)
17
+ expect(num3).toBe(num2 + 1)
18
+ })
19
+
20
+ test("all IDs are unique", () => {
21
+ const ids = new Set<string>()
22
+ for (let i = 0; i < 100; i++) {
23
+ ids.add(createUniqueId())
24
+ }
25
+ expect(ids.size).toBe(100)
26
+ })
27
+ })
28
+
29
+ describe("_resetIdCounter", () => {
30
+ test("resets the counter so IDs restart", () => {
31
+ // Generate some IDs to advance counter
32
+ createUniqueId()
33
+ createUniqueId()
34
+
35
+ _resetIdCounter()
36
+
37
+ const id = createUniqueId()
38
+ expect(id).toBe("pyreon-1")
39
+ })
40
+
41
+ test("subsequent calls after reset increment from 1", () => {
42
+ _resetIdCounter()
43
+ expect(createUniqueId()).toBe("pyreon-1")
44
+ expect(createUniqueId()).toBe("pyreon-2")
45
+ expect(createUniqueId()).toBe("pyreon-3")
46
+ })
47
+ })
48
+
49
+ describe("splitProps — extended", () => {
50
+ test("non-existent keys produce empty picked object", () => {
51
+ const props = { a: 1, b: 2 }
52
+ const [own, rest] = splitProps(props, ["c" as keyof typeof props])
53
+ expect(Object.keys(own)).toEqual([])
54
+ expect(rest).toEqual({ a: 1, b: 2 })
55
+ })
56
+
57
+ test("all keys in picked leaves rest empty", () => {
58
+ const props = { x: 10, y: 20 }
59
+ const [own, rest] = splitProps(props, ["x", "y"])
60
+ expect(own).toEqual({ x: 10, y: 20 })
61
+ expect(Object.keys(rest)).toEqual([])
62
+ })
63
+
64
+ test("preserves getter on rest side", () => {
65
+ let count = 0
66
+ const props = {} as Record<string, unknown>
67
+ Object.defineProperty(props, "reactive", {
68
+ get: () => ++count,
69
+ enumerable: true,
70
+ configurable: true,
71
+ })
72
+ Object.defineProperty(props, "other", {
73
+ value: "static",
74
+ enumerable: true,
75
+ configurable: true,
76
+ })
77
+
78
+ const [_own, rest] = splitProps(props, ["other"])
79
+ expect((rest as Record<string, unknown>).reactive).toBe(1)
80
+ expect((rest as Record<string, unknown>).reactive).toBe(2) // getter called again
81
+ })
82
+
83
+ test("handles object with undefined values", () => {
84
+ const props = { a: undefined, b: "defined" }
85
+ const [own, rest] = splitProps(props, ["a"])
86
+ expect(own.a).toBeUndefined()
87
+ expect((rest as Record<string, unknown>).b).toBe("defined")
88
+ })
89
+ })
90
+
91
+ describe("mergeProps — extended", () => {
92
+ test("single source returns copy", () => {
93
+ const src = { a: 1, b: 2 }
94
+ const result = mergeProps(src)
95
+ expect(result).toEqual({ a: 1, b: 2 })
96
+ expect(result).not.toBe(src) // should be a new object
97
+ })
98
+
99
+ test("three sources merge correctly", () => {
100
+ const result = mergeProps({ a: 1 }, { b: 2 }, { c: 3 })
101
+ expect(result).toEqual({ a: 1, b: 2, c: 3 })
102
+ })
103
+
104
+ test("later defined value overrides earlier", () => {
105
+ const result = mergeProps({ x: "first" }, { x: "second" }, { x: "third" })
106
+ expect(result.x).toBe("third")
107
+ })
108
+
109
+ test("undefined in later source does not override earlier defined value", () => {
110
+ const result = mergeProps({ x: "keep" }, { x: undefined as string | undefined })
111
+ expect(result.x).toBe("keep")
112
+ })
113
+
114
+ test("getter merging: later getter overrides earlier static when defined", () => {
115
+ let dynamic: string | undefined = "from-getter"
116
+ const getterSrc = {} as Record<string, unknown>
117
+ Object.defineProperty(getterSrc, "val", {
118
+ get: () => dynamic,
119
+ enumerable: true,
120
+ configurable: true,
121
+ })
122
+ const result = mergeProps({ val: "static" }, getterSrc)
123
+ expect(result.val).toBe("from-getter")
124
+
125
+ // When getter returns undefined, falls back to static
126
+ dynamic = undefined
127
+ expect(result.val).toBe("static")
128
+ })
129
+
130
+ test("two getters: later getter wins when defined, falls to earlier getter", () => {
131
+ let g1val: string | undefined = "g1"
132
+ let g2val: string | undefined = "g2"
133
+
134
+ const src1 = {} as Record<string, unknown>
135
+ Object.defineProperty(src1, "x", {
136
+ get: () => g1val,
137
+ enumerable: true,
138
+ configurable: true,
139
+ })
140
+
141
+ const src2 = {} as Record<string, unknown>
142
+ Object.defineProperty(src2, "x", {
143
+ get: () => g2val,
144
+ enumerable: true,
145
+ configurable: true,
146
+ })
147
+
148
+ const result = mergeProps(src1, src2)
149
+ expect(result.x).toBe("g2")
150
+
151
+ g2val = undefined
152
+ expect(result.x).toBe("g1")
153
+
154
+ g1val = undefined
155
+ expect(result.x).toBeUndefined()
156
+ })
157
+ })
@@ -0,0 +1,70 @@
1
+ import type { Ref, RefCallback, RefProp } from "../ref"
2
+ import { createRef } from "../ref"
3
+
4
+ describe("createRef", () => {
5
+ test("returns object with current = null", () => {
6
+ const ref = createRef()
7
+ expect(ref.current).toBeNull()
8
+ })
9
+
10
+ test("current is mutable", () => {
11
+ const ref = createRef<number>()
12
+ ref.current = 42
13
+ expect(ref.current).toBe(42)
14
+ })
15
+
16
+ test("typed ref — HTMLElement", () => {
17
+ const ref = createRef<HTMLDivElement>()
18
+ expect(ref.current).toBeNull()
19
+ // In real code, runtime-dom sets this after mount
20
+ ref.current = {} as HTMLDivElement
21
+ expect(ref.current).not.toBeNull()
22
+ })
23
+
24
+ test("typed ref — string", () => {
25
+ const ref = createRef<string>()
26
+ ref.current = "hello"
27
+ expect(ref.current).toBe("hello")
28
+ })
29
+
30
+ test("can be reset to null", () => {
31
+ const ref = createRef<number>()
32
+ ref.current = 99
33
+ expect(ref.current).toBe(99)
34
+ ref.current = null
35
+ expect(ref.current).toBeNull()
36
+ })
37
+
38
+ test("each createRef returns a unique object", () => {
39
+ const ref1 = createRef()
40
+ const ref2 = createRef()
41
+ expect(ref1).not.toBe(ref2)
42
+ })
43
+
44
+ test("ref object has exactly one property", () => {
45
+ const ref = createRef()
46
+ expect(Object.keys(ref)).toEqual(["current"])
47
+ })
48
+
49
+ test("ref object shape matches Ref interface", () => {
50
+ const ref: Ref<number> = createRef<number>()
51
+ expect("current" in ref).toBe(true)
52
+ expect(ref.current).toBeNull()
53
+ })
54
+ })
55
+
56
+ describe("RefCallback type (type-level verification)", () => {
57
+ test("callback ref can be assigned to RefProp", () => {
58
+ const callback: RefCallback<HTMLElement> = (_el) => {}
59
+ // Type-level test: RefProp accepts both object ref and callback ref
60
+ const prop: RefProp<HTMLElement> = callback
61
+ expect(typeof prop).toBe("function")
62
+ })
63
+
64
+ test("object ref can be assigned to RefProp", () => {
65
+ const ref = createRef<HTMLElement>()
66
+ const prop: RefProp<HTMLElement> = ref
67
+ expect(typeof prop).toBe("object")
68
+ expect(prop).toBe(ref)
69
+ })
70
+ })
@@ -0,0 +1,238 @@
1
+ import { h } from "../h"
2
+ import { Match, MatchSymbol, Show, Switch } from "../show"
3
+ import type { VNodeChild } from "../types"
4
+
5
+ describe("Show", () => {
6
+ test("returns a reactive getter (function)", () => {
7
+ const result = Show({ when: () => true, children: "visible" })
8
+ expect(typeof result).toBe("function")
9
+ })
10
+
11
+ test("getter returns children when condition is truthy", () => {
12
+ const getter = Show({ when: () => true, children: "visible" }) as unknown as () => VNodeChild
13
+ expect(getter()).toBe("visible")
14
+ })
15
+
16
+ test("getter returns null when condition is falsy and no fallback", () => {
17
+ const getter = Show({ when: () => false, children: "hidden" }) as unknown as () => VNodeChild
18
+ expect(getter()).toBeNull()
19
+ })
20
+
21
+ test("getter returns fallback when condition is falsy", () => {
22
+ const fb = h("span", null, "fallback")
23
+ const getter = Show({
24
+ when: () => false,
25
+ fallback: fb,
26
+ children: "main",
27
+ }) as unknown as () => VNodeChild
28
+ expect(getter()).toBe(fb)
29
+ })
30
+
31
+ test("reacts to condition changes", () => {
32
+ let flag = true
33
+ const getter = Show({
34
+ when: () => flag,
35
+ children: "yes",
36
+ fallback: "no",
37
+ }) as unknown as () => VNodeChild
38
+ expect(getter()).toBe("yes")
39
+ flag = false
40
+ expect(getter()).toBe("no")
41
+ flag = true
42
+ expect(getter()).toBe("yes")
43
+ })
44
+
45
+ test("returns null for undefined children when truthy", () => {
46
+ const getter = Show({ when: () => true }) as unknown as () => VNodeChild
47
+ expect(getter()).toBeNull()
48
+ })
49
+
50
+ test("returns null for undefined fallback when falsy", () => {
51
+ const getter = Show({ when: () => false, children: "x" }) as unknown as () => VNodeChild
52
+ expect(getter()).toBeNull()
53
+ })
54
+
55
+ test("VNode children are preserved as-is", () => {
56
+ const child = h("div", null, "content")
57
+ const getter = Show({ when: () => true, children: child }) as unknown as () => VNodeChild
58
+ expect(getter()).toBe(child)
59
+ })
60
+
61
+ test("truthiness: non-empty string", () => {
62
+ const getter = Show({
63
+ when: () => "truthy-string",
64
+ children: "shown",
65
+ }) as unknown as () => VNodeChild
66
+ expect(getter()).toBe("shown")
67
+ })
68
+
69
+ test("truthiness: 0 is falsy", () => {
70
+ const getter = Show({
71
+ when: () => 0,
72
+ children: "shown",
73
+ fallback: "hidden",
74
+ }) as unknown as () => VNodeChild
75
+ expect(getter()).toBe("hidden")
76
+ })
77
+
78
+ test("truthiness: empty string is falsy", () => {
79
+ const getter = Show({
80
+ when: () => "",
81
+ children: "shown",
82
+ fallback: "hidden",
83
+ }) as unknown as () => VNodeChild
84
+ expect(getter()).toBe("hidden")
85
+ })
86
+
87
+ test("truthiness: null is falsy", () => {
88
+ const getter = Show({
89
+ when: () => null,
90
+ children: "shown",
91
+ fallback: "hidden",
92
+ }) as unknown as () => VNodeChild
93
+ expect(getter()).toBe("hidden")
94
+ })
95
+
96
+ test("truthiness: object is truthy", () => {
97
+ const getter = Show({
98
+ when: () => ({ a: 1 }),
99
+ children: "shown",
100
+ }) as unknown as () => VNodeChild
101
+ expect(getter()).toBe("shown")
102
+ })
103
+ })
104
+
105
+ describe("Match", () => {
106
+ test("returns null (marker-only component)", () => {
107
+ const result = Match({ when: () => true, children: "content" })
108
+ expect(result).toBeNull()
109
+ })
110
+
111
+ test("MatchSymbol is a unique symbol", () => {
112
+ expect(typeof MatchSymbol).toBe("symbol")
113
+ expect(MatchSymbol.toString()).toContain("pyreon.Match")
114
+ })
115
+ })
116
+
117
+ describe("Switch", () => {
118
+ test("renders first truthy Match branch", () => {
119
+ const result = Switch({
120
+ children: [
121
+ h(Match, { when: () => false }, "first"),
122
+ h(Match, { when: () => true }, "second"),
123
+ h(Match, { when: () => true }, "third"),
124
+ ],
125
+ })
126
+ const getter = result as unknown as () => VNodeChild
127
+ expect(getter()).toBe("second")
128
+ })
129
+
130
+ test("renders fallback when no match", () => {
131
+ const fb = h("p", null, "404")
132
+ const result = Switch({
133
+ fallback: fb,
134
+ children: [h(Match, { when: () => false }, "a"), h(Match, { when: () => false }, "b")],
135
+ })
136
+ const getter = result as unknown as () => VNodeChild
137
+ expect(getter()).toBe(fb)
138
+ })
139
+
140
+ test("returns null when no match and no fallback", () => {
141
+ const result = Switch({
142
+ children: [h(Match, { when: () => false }, "a")],
143
+ })
144
+ const getter = result as unknown as () => VNodeChild
145
+ expect(getter()).toBeNull()
146
+ })
147
+
148
+ test("handles single child (not array)", () => {
149
+ const result = Switch({
150
+ children: h(Match, { when: () => true }, "only"),
151
+ })
152
+ const getter = result as unknown as () => VNodeChild
153
+ expect(getter()).toBe("only")
154
+ })
155
+
156
+ test("handles no children", () => {
157
+ const result = Switch({})
158
+ const getter = result as unknown as () => VNodeChild
159
+ expect(getter()).toBeNull()
160
+ })
161
+
162
+ test("handles null/undefined children", () => {
163
+ const result = Switch({ children: null as unknown as VNodeChild })
164
+ const getter = result as unknown as () => VNodeChild
165
+ expect(getter()).toBeNull()
166
+ })
167
+
168
+ test("skips non-Match VNode children", () => {
169
+ const result = Switch({
170
+ fallback: "default",
171
+ children: [h("div", null, "not-a-match"), h(Match, { when: () => true }, "found")],
172
+ })
173
+ const getter = result as unknown as () => VNodeChild
174
+ expect(getter()).toBe("found")
175
+ })
176
+
177
+ test("skips non-object children (strings, null)", () => {
178
+ const result = Switch({
179
+ fallback: "default",
180
+ children: [
181
+ null as unknown as VNodeChild,
182
+ "string-child" as unknown as VNodeChild,
183
+ h(Match, { when: () => true }, "found"),
184
+ ],
185
+ })
186
+ const getter = result as unknown as () => VNodeChild
187
+ expect(getter()).toBe("found")
188
+ })
189
+
190
+ test("reacts to condition changes", () => {
191
+ let a = false
192
+ let b = false
193
+ const result = Switch({
194
+ fallback: "none",
195
+ children: [h(Match, { when: () => a }, "A"), h(Match, { when: () => b }, "B")],
196
+ })
197
+ const getter = result as unknown as () => VNodeChild
198
+ expect(getter()).toBe("none")
199
+ b = true
200
+ expect(getter()).toBe("B")
201
+ a = true
202
+ expect(getter()).toBe("A") // first match wins
203
+ b = false
204
+ expect(getter()).toBe("A")
205
+ a = false
206
+ expect(getter()).toBe("none")
207
+ })
208
+
209
+ test("Match with multiple children returns array", () => {
210
+ const result = Switch({
211
+ children: [h(Match, { when: () => true }, "child1", "child2")],
212
+ })
213
+ const getter = result as unknown as () => VNodeChild
214
+ const value = getter()
215
+ expect(Array.isArray(value)).toBe(true)
216
+ expect(value).toEqual(["child1", "child2"])
217
+ })
218
+
219
+ test("Match with zero vnode.children falls back to props.children", () => {
220
+ const matchVNode = {
221
+ type: Match,
222
+ props: { when: () => true, children: "from-props" },
223
+ children: [],
224
+ key: null,
225
+ } as unknown as VNodeChild
226
+ const result = Switch({ children: [matchVNode] })
227
+ const getter = result as unknown as () => VNodeChild
228
+ expect(getter()).toBe("from-props")
229
+ })
230
+
231
+ test("Match with single vnode.children returns it directly (not array)", () => {
232
+ const result = Switch({
233
+ children: [h(Match, { when: () => true }, "single")],
234
+ })
235
+ const getter = result as unknown as () => VNodeChild
236
+ expect(getter()).toBe("single")
237
+ })
238
+ })
@@ -0,0 +1,157 @@
1
+ import { CSS_UNITLESS, cx, normalizeStyleValue, toKebabCase } from "../style"
2
+
3
+ // cx() is extensively tested in cx.test.ts — these tests cover toKebabCase,
4
+ // normalizeStyleValue, and CSS_UNITLESS which are used by runtime-dom/runtime-server.
5
+
6
+ describe("toKebabCase", () => {
7
+ test("converts camelCase to kebab-case", () => {
8
+ expect(toKebabCase("backgroundColor")).toBe("background-color")
9
+ })
10
+
11
+ test("handles single uppercase letter", () => {
12
+ expect(toKebabCase("zIndex")).toBe("z-index")
13
+ })
14
+
15
+ test("handles multiple uppercase letters", () => {
16
+ expect(toKebabCase("borderTopLeftRadius")).toBe("border-top-left-radius")
17
+ })
18
+
19
+ test("returns lowercase string unchanged", () => {
20
+ expect(toKebabCase("color")).toBe("color")
21
+ })
22
+
23
+ test("handles empty string", () => {
24
+ expect(toKebabCase("")).toBe("")
25
+ })
26
+
27
+ test("handles consecutive uppercase (treated individually)", () => {
28
+ expect(toKebabCase("MSTransform")).toBe("-m-s-transform")
29
+ })
30
+
31
+ test("handles leading lowercase with single word", () => {
32
+ expect(toKebabCase("opacity")).toBe("opacity")
33
+ })
34
+ })
35
+
36
+ describe("normalizeStyleValue", () => {
37
+ test("appends px to numbers for non-unitless properties", () => {
38
+ expect(normalizeStyleValue("width", 100)).toBe("100px")
39
+ expect(normalizeStyleValue("height", 50)).toBe("50px")
40
+ expect(normalizeStyleValue("padding", 0)).toBe("0px")
41
+ expect(normalizeStyleValue("marginTop", 20)).toBe("20px")
42
+ })
43
+
44
+ test("does not append px to unitless properties", () => {
45
+ expect(normalizeStyleValue("opacity", 0.5)).toBe("0.5")
46
+ expect(normalizeStyleValue("zIndex", 10)).toBe("10")
47
+ expect(normalizeStyleValue("flexGrow", 1)).toBe("1")
48
+ expect(normalizeStyleValue("fontWeight", 700)).toBe("700")
49
+ expect(normalizeStyleValue("lineHeight", 1.5)).toBe("1.5")
50
+ expect(normalizeStyleValue("order", 3)).toBe("3")
51
+ expect(normalizeStyleValue("columns", 2)).toBe("2")
52
+ expect(normalizeStyleValue("flex", 1)).toBe("1")
53
+ expect(normalizeStyleValue("scale", 2)).toBe("2")
54
+ expect(normalizeStyleValue("widows", 2)).toBe("2")
55
+ expect(normalizeStyleValue("orphans", 3)).toBe("3")
56
+ })
57
+
58
+ test("passes through string values unchanged", () => {
59
+ expect(normalizeStyleValue("width", "100%")).toBe("100%")
60
+ expect(normalizeStyleValue("color", "red")).toBe("red")
61
+ expect(normalizeStyleValue("display", "flex")).toBe("flex")
62
+ })
63
+
64
+ test("converts non-string/non-number to string", () => {
65
+ expect(normalizeStyleValue("display", null)).toBe("null")
66
+ expect(normalizeStyleValue("display", undefined)).toBe("undefined")
67
+ expect(normalizeStyleValue("display", true)).toBe("true")
68
+ })
69
+
70
+ test("handles zero correctly for non-unitless props", () => {
71
+ expect(normalizeStyleValue("margin", 0)).toBe("0px")
72
+ })
73
+
74
+ test("handles negative numbers", () => {
75
+ expect(normalizeStyleValue("marginLeft", -10)).toBe("-10px")
76
+ expect(normalizeStyleValue("zIndex", -1)).toBe("-1")
77
+ })
78
+ })
79
+
80
+ describe("CSS_UNITLESS", () => {
81
+ test("is a Set", () => {
82
+ expect(CSS_UNITLESS).toBeInstanceOf(Set)
83
+ })
84
+
85
+ test("contains common unitless properties", () => {
86
+ expect(CSS_UNITLESS.has("opacity")).toBe(true)
87
+ expect(CSS_UNITLESS.has("zIndex")).toBe(true)
88
+ expect(CSS_UNITLESS.has("fontWeight")).toBe(true)
89
+ expect(CSS_UNITLESS.has("lineHeight")).toBe(true)
90
+ expect(CSS_UNITLESS.has("flex")).toBe(true)
91
+ expect(CSS_UNITLESS.has("flexGrow")).toBe(true)
92
+ expect(CSS_UNITLESS.has("flexShrink")).toBe(true)
93
+ expect(CSS_UNITLESS.has("order")).toBe(true)
94
+ expect(CSS_UNITLESS.has("columnCount")).toBe(true)
95
+ expect(CSS_UNITLESS.has("animationIterationCount")).toBe(true)
96
+ })
97
+
98
+ test("contains SVG unitless properties", () => {
99
+ expect(CSS_UNITLESS.has("fillOpacity")).toBe(true)
100
+ expect(CSS_UNITLESS.has("floodOpacity")).toBe(true)
101
+ expect(CSS_UNITLESS.has("stopOpacity")).toBe(true)
102
+ expect(CSS_UNITLESS.has("strokeOpacity")).toBe(true)
103
+ expect(CSS_UNITLESS.has("strokeWidth")).toBe(true)
104
+ expect(CSS_UNITLESS.has("strokeMiterlimit")).toBe(true)
105
+ expect(CSS_UNITLESS.has("strokeDasharray")).toBe(true)
106
+ expect(CSS_UNITLESS.has("strokeDashoffset")).toBe(true)
107
+ })
108
+
109
+ test("does not contain properties that need units", () => {
110
+ expect(CSS_UNITLESS.has("width")).toBe(false)
111
+ expect(CSS_UNITLESS.has("height")).toBe(false)
112
+ expect(CSS_UNITLESS.has("margin")).toBe(false)
113
+ expect(CSS_UNITLESS.has("padding")).toBe(false)
114
+ expect(CSS_UNITLESS.has("fontSize")).toBe(false)
115
+ expect(CSS_UNITLESS.has("borderWidth")).toBe(false)
116
+ expect(CSS_UNITLESS.has("top")).toBe(false)
117
+ expect(CSS_UNITLESS.has("left")).toBe(false)
118
+ })
119
+ })
120
+
121
+ describe("cx — additional edge cases", () => {
122
+ test("object with all false values", () => {
123
+ expect(cx({ a: false, b: false, c: false })).toBe("")
124
+ })
125
+
126
+ test("object with null and undefined values", () => {
127
+ expect(cx({ a: null, b: undefined, c: true })).toBe("c")
128
+ })
129
+
130
+ test("mixed array of numbers, strings, and objects", () => {
131
+ expect(cx([1, "two", { three: true, four: false }])).toBe("1 two three")
132
+ })
133
+
134
+ test("single-element array", () => {
135
+ expect(cx(["only"])).toBe("only")
136
+ })
137
+
138
+ test("number 0 in an array", () => {
139
+ expect(cx([0, "one"])).toBe("0 one")
140
+ })
141
+
142
+ test("boolean true in array is filtered", () => {
143
+ expect(cx([true, "visible"])).toBe("visible")
144
+ })
145
+
146
+ test("nested empty arrays", () => {
147
+ expect(cx([[], [[]], [[[]]]])).toBe("")
148
+ })
149
+
150
+ test("object with function returning false", () => {
151
+ expect(cx({ hidden: () => false })).toBe("")
152
+ })
153
+
154
+ test("single key object", () => {
155
+ expect(cx({ active: true })).toBe("active")
156
+ })
157
+ })