@ryupold/vode 1.8.4 → 1.8.5

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,226 @@
1
+ import { expect } from "./helper";
2
+ import { app, ARTICLE, DIV, P, SPAN } from "../index";
3
+
4
+ export default {
5
+ "app(): successful initialization": () => {
6
+ const root = document.createElement("div");
7
+ const container = document.createElement("div");
8
+ root.appendChild(container);
9
+
10
+ const patch = expect(
11
+ () => app(container, {}, () =>
12
+ [DIV,
13
+ [ARTICLE,
14
+ [P, "foo", [SPAN, "bar"]]
15
+ ]
16
+ ]
17
+ )
18
+ ).toSucceed();
19
+
20
+ expect(patch).toBeA("function");
21
+
22
+ expect(container).toMatch(
23
+ [DIV,
24
+ [ARTICLE,
25
+ [P, "foo", [SPAN, "bar"]]
26
+ ]
27
+ ]
28
+ );
29
+ },
30
+
31
+ //=== FAILURE CASES ===
32
+
33
+ "app(): fails when the container has no parent": () => {
34
+ const container = document.createElement("div");
35
+ const err = expect(() => app(container, {}, () => [DIV]))
36
+ .toFail();
37
+
38
+ expect(err.message).toEqual("first argument to app() must be a valid HTMLElement inside the <html></html> document");
39
+ },
40
+
41
+ "app(): fails when the state is not an object": () => {
42
+ const root = document.createElement("div");
43
+ const container = document.createElement("div");
44
+ root.appendChild(container);
45
+
46
+ const err = expect(() => app(container, "oops", () => [DIV]))
47
+ .toFail();
48
+
49
+ expect(err.message).toEqual("second argument to app() must be a state object");
50
+ },
51
+
52
+ "app(): fails when the dom factory is not a function": () => {
53
+ const root = document.createElement("div");
54
+ const container = document.createElement("div");
55
+ root.appendChild(container);
56
+
57
+ const err = expect(() => app(container, {}, [DIV] as any))
58
+ .toFail();
59
+
60
+ expect(err.message).toEqual("third argument to app() must be a function that returns a vode");
61
+ },
62
+
63
+ //=== INITIAL PATCHES ===
64
+
65
+ "app(): executes initial patches after first render": () => {
66
+ const root = document.createElement("div");
67
+ const container = document.createElement("div");
68
+ root.appendChild(container);
69
+
70
+ const state = { count: 6, start: 1 };
71
+
72
+ app(container, state, () => [DIV],
73
+ { count: 7 },
74
+ () => ({ start: 2 })
75
+ );
76
+
77
+ expect(state).toEqual({ count: 7, start: 2 });
78
+ },
79
+
80
+ //=== STATE PATCHING ===
81
+
82
+ "app(): patch with object updates state and re-renders DOM": () => {
83
+ const root = document.createElement("div");
84
+ const container = document.createElement("div");
85
+ root.appendChild(container);
86
+
87
+ const state: any = { msg: "hello" };
88
+ app(container, state, (s: any) => [DIV, s.msg]);
89
+
90
+ expect(state.msg).toEqual("hello");
91
+ expect(container).toMatch([DIV, "hello"]);
92
+
93
+ state.patch({ msg: "world" });
94
+
95
+ expect(state.msg).toEqual("world");
96
+ expect(container).toMatch([DIV, "world"]);
97
+ },
98
+
99
+ "app(): patch with effect function executes and applies result": () => {
100
+ const root = document.createElement("div");
101
+ const container = document.createElement("div");
102
+ root.appendChild(container);
103
+
104
+ const state: any = { count: 0 };
105
+ app(container, state, (s: any) => [DIV, String(s.count)]);
106
+
107
+ state.patch(() => ({ count: 5 }));
108
+
109
+ expect(state.count).toEqual(5);
110
+ expect(container).toMatch([DIV, "5"]);
111
+ },
112
+
113
+ "app(): patch with array applies multiple patches in sequence": () => {
114
+ const root = document.createElement("div");
115
+ const container = document.createElement("div");
116
+ root.appendChild(container);
117
+
118
+ const state: any = { a: 1, b: 2 };
119
+ app(container, state, () => [DIV]);
120
+
121
+ state.patch([{ a: 10 }, { b: 20 }]);
122
+
123
+ expect(state.a).toEqual(10);
124
+ expect(state.b).toEqual(20);
125
+ },
126
+
127
+ "app(): multiple sequential patches both apply": () => {
128
+ const root = document.createElement("div");
129
+ const container = document.createElement("div");
130
+ root.appendChild(container);
131
+
132
+ const state: any = { x: 0, y: 0 };
133
+ app(container, state, () => [DIV]);
134
+
135
+ state.patch({ x: 1 });
136
+ state.patch({ y: 2 });
137
+
138
+ expect(state).toEqual({ x: 1, y: 2 });
139
+ },
140
+
141
+ //=== LIFECYCLE ===
142
+
143
+ "app(): onMount callback is called on newly created child elements": () => {
144
+ const root = document.createElement("div");
145
+ const container = document.createElement("div");
146
+ root.appendChild(container);
147
+
148
+ let mountCalled = false;
149
+ app(container, {}, () =>
150
+ [DIV,
151
+ [SPAN, { onMount: () => { mountCalled = true; return {}; } }, "text"]
152
+ ] as any
153
+ );
154
+
155
+ expect(mountCalled).toEqual(true);
156
+ },
157
+
158
+ //=== COMPONENTS ===
159
+
160
+ "app(): component function as child renders correctly": () => {
161
+ const root = document.createElement("div");
162
+ const container = document.createElement("div");
163
+ root.appendChild(container);
164
+
165
+ app(container, {}, () =>
166
+ [DIV,
167
+ ((s: any) => [SPAN, "component rendered"]) as any
168
+ ]
169
+ );
170
+
171
+ expect(container).toMatch(
172
+ [DIV, [SPAN, "component rendered"]]
173
+ );
174
+ },
175
+
176
+ "app(): component accesses state and renders dynamic content": () => {
177
+ const root = document.createElement("div");
178
+ const container = document.createElement("div");
179
+ root.appendChild(container);
180
+
181
+ const state: any = { label: "dynamic" };
182
+ app(container, state, (s) =>
183
+ [DIV,
184
+ ((st: any) => [SPAN, st.label]) as any
185
+ ]
186
+ );
187
+
188
+ expect(container).toMatch([DIV, [SPAN, "dynamic"]]);
189
+ },
190
+
191
+ //=== DEEP STATE ===
192
+
193
+ "app(): deep nested state merges correctly via patch": () => {
194
+ const root = document.createElement("div");
195
+ const container = document.createElement("div");
196
+ root.appendChild(container);
197
+
198
+ const state: any = { nested: { value: 1, other: "keep" } };
199
+ app(container, state, (s: any) => [DIV, String(s.nested.value)]);
200
+
201
+ state.patch({ nested: { value: 2 } });
202
+
203
+ expect(state.nested.value).toEqual(2);
204
+ expect(state.nested.other).toEqual("keep");
205
+ expect(container).toMatch([DIV, "2"]);
206
+ },
207
+
208
+ //=== IGNORED PATCHES ===
209
+
210
+ "app(): patching with ignored types is a no-op": () => {
211
+ const root = document.createElement("div");
212
+ const container = document.createElement("div");
213
+ root.appendChild(container);
214
+
215
+ const state: any = { x: 1 };
216
+ app(container, state, () => [DIV]);
217
+
218
+ state.patch(null);
219
+ state.patch(undefined);
220
+ state.patch(42);
221
+ state.patch("ignored");
222
+ state.patch(true);
223
+
224
+ expect(state.x).toEqual(1);
225
+ },
226
+ };
@@ -0,0 +1,69 @@
1
+ import { children, child, childCount, childrenStart, DIV, SPAN, P, Vode } from "../index";
2
+ import { expect } from "./helper";
3
+
4
+ export default {
5
+ "children(): tag+props+children returns children array": () => {
6
+ const v: Vode = [DIV, { class: "x" }, [SPAN, "a"], [P, "b"]];
7
+ const c = children(v);
8
+ expect(Array.isArray(c)).toEqual(true);
9
+ expect(c!.length).toEqual(2);
10
+ },
11
+
12
+ "children(): tag+children (no props) returns children array": () => {
13
+ const v: Vode = [DIV, [SPAN, "a"], [P, "b"]];
14
+ const c = children(v);
15
+ expect(Array.isArray(c)).toEqual(true);
16
+ expect(c!.length).toEqual(2);
17
+ },
18
+
19
+ "children(): just-tag vode returns null": () => {
20
+ expect(children([DIV])).toEqual(null);
21
+ },
22
+
23
+ "children(): text vode returns null": () => {
24
+ expect(children("hello")).toEqual(null);
25
+ },
26
+
27
+ "childrenStart(): with props+children returns 2": () => {
28
+ expect(childrenStart([DIV, { class: "x" }, [SPAN]])).toEqual(2);
29
+ },
30
+
31
+ "childrenStart(): without props but with children returns 1": () => {
32
+ expect(childrenStart([DIV, [SPAN]])).toEqual(1);
33
+ },
34
+
35
+ "childrenStart(): just-tag returns -1": () => {
36
+ expect(childrenStart([DIV])).toEqual(-1);
37
+ },
38
+
39
+ "childrenStart(): text vode returns -1": () => {
40
+ expect(childrenStart("hello")).toEqual(-1);
41
+ },
42
+
43
+ "childCount(): matches actual child count": () => {
44
+ expect(childCount([DIV, { class: "x" }, [SPAN, "a"], [P, "b"]])).toEqual(2);
45
+ expect(childCount([DIV, [SPAN]])).toEqual(1);
46
+ },
47
+
48
+ "childCount(): returns 0 for no-children vode": () => {
49
+ expect(childCount([DIV])).toEqual(0);
50
+ expect(childCount("hello" as any)).toEqual(0);
51
+ },
52
+
53
+ "child(): returns correct child at index": () => {
54
+ const v: Vode = [DIV, { class: "x" }, [SPAN, "a"], [P, "b"]];
55
+
56
+ expect(child(v, 0)).toEqual([SPAN, "a"]);
57
+ expect(child(v, 1)).toEqual([P, "b"]);
58
+ },
59
+
60
+ "child(): returns undefined for out-of-bounds": () => {
61
+ expect(child([DIV, { class: "x" }, [SPAN]], 5))
62
+ .toEqual(undefined);
63
+ },
64
+
65
+ "child(): returns undefined for text vode": () => {
66
+ expect(child("hello" as any, 0))
67
+ .toEqual(undefined);
68
+ }
69
+ };
@@ -0,0 +1,28 @@
1
+ import { createPatch } from "../src/vode";
2
+ import { expect } from "./helper";
3
+
4
+ export default {
5
+ "createPatch(): just returns the input": () => {
6
+ const p = { a: 123 };
7
+ expect(createPatch(p) === p).toEqual(true);
8
+ },
9
+
10
+ "createPatch(): returns undefined as-is": () => {
11
+ expect(createPatch(undefined)).toEqual(undefined);
12
+ },
13
+
14
+ "createPatch(): returns null as-is": () => {
15
+ expect(createPatch(null)).toEqual(null);
16
+ },
17
+
18
+ "createPatch(): returns function as-is": () => {
19
+ const fn = () => ({});
20
+ expect(createPatch(fn) === fn).toEqual(true);
21
+ },
22
+
23
+ "createPatch(): returns primitive as-is": () => {
24
+ expect(createPatch(42)).toEqual(42);
25
+ expect(createPatch("ignored")).toEqual("ignored");
26
+ expect(createPatch(false)).toEqual(false);
27
+ }
28
+ };
@@ -0,0 +1,43 @@
1
+ import { expect } from "./helper";
2
+ import { createState, app, DIV } from "../index";
3
+
4
+ export default {
5
+ "createState(): throws when state is not an object": () => {
6
+ const err = expect(() => createState(null as any)).toFail();
7
+ expect(err.message)
8
+ .toEqual("createState() must be called with a state object");
9
+ },
10
+
11
+ "createState(): adds patch function to state": () => {
12
+ const state = createState({ x: 1 });
13
+ expect(typeof (state as any).patch).toEqual("function");
14
+ expect((state)).toEqual({ x: 1, patch: (state as any).patch });
15
+ },
16
+
17
+ "createState(): patch is non-enumerable": () => {
18
+ const state = createState({ x: 1 });
19
+ expect(Object.keys(state)).toEqual(["x"]);
20
+ },
21
+
22
+ "createState(): app picks up queued patches": () => {
23
+ const state: any = createState({ count: 0 });
24
+ state.patch({ count: 1 });
25
+ state.patch({ count: 2 });
26
+ const root = document.createElement("div");
27
+ const container = document.createElement("div");
28
+ root.appendChild(container);
29
+ app(container, state, () => [DIV]);
30
+
31
+ expect(state.count)
32
+ .toEqual(2);
33
+ },
34
+
35
+ "createState(): already-patchable state is kept as-is": () => {
36
+ const existingPatch = (action: any) => { };
37
+ const state: any = { value: 5, patch: existingPatch };
38
+ const result = createState(state);
39
+
40
+ expect(result.patch === existingPatch)
41
+ .toEqual(true);
42
+ },
43
+ };
@@ -0,0 +1,74 @@
1
+ import { expect } from "./helper";
2
+ import { app, DIV, defuse } from "../index";
3
+
4
+ export default {
5
+ "defuse(): on a container without _vode is a no-op": () => {
6
+ const container = document.createElement("div");
7
+
8
+ expect(() => defuse(container as any))
9
+ .toSucceed();
10
+ },
11
+
12
+ "defuse(): removes _vode from container": () => {
13
+ const root = document.createElement("div");
14
+ const container = document.createElement("div");
15
+ root.appendChild(container);
16
+ app(container, {}, () => [DIV]);
17
+ expect(typeof (container as any)._vode).toEqual("object");
18
+ defuse(container as any);
19
+
20
+ expect((container as any)._vode)
21
+ .toEqual(undefined);
22
+ },
23
+
24
+ "defuse(): removes patch function from state": () => {
25
+ const root = document.createElement("div");
26
+ const container = document.createElement("div");
27
+ root.appendChild(container);
28
+ const state: any = {};
29
+ app(container, state, () => [DIV]);
30
+ expect(typeof state.patch).toEqual("function");
31
+ defuse(container as any);
32
+
33
+ expect(state.patch)
34
+ .toEqual(undefined);
35
+ },
36
+
37
+ "defuse(): disables renderSync and renderAsync": () => {
38
+ const root = document.createElement("div");
39
+ const container = document.createElement("div");
40
+ root.appendChild(container);
41
+ app(container, {}, () => [DIV]);
42
+ defuse(container as any);
43
+
44
+ expect((container as any)._vode)
45
+ .toEqual(undefined);
46
+ },
47
+
48
+ "defuse(): clears event listeners from rendered elements": () => {
49
+ const root = document.createElement("div");
50
+ const container = document.createElement("div");
51
+ root.appendChild(container);
52
+ app(container, {}, () => [DIV, { onclick: () => ({}) }] as any);
53
+ const node = (container as any)._vode.vode.node;
54
+ expect(typeof node.onclick).toEqual("function");
55
+ defuse(container as any);
56
+
57
+ expect(node.onclick)
58
+ .toEqual(null);
59
+ },
60
+
61
+ "defuse(): recurses into child containers": () => {
62
+ const root = document.createElement("div");
63
+ const outer = document.createElement("div");
64
+ const inner = document.createElement("div");
65
+ root.appendChild(outer);
66
+ outer.appendChild(inner);
67
+ const state: any = {};
68
+ app(inner, state, () => [DIV]);
69
+ defuse(outer as any);
70
+
71
+ expect(state.patch)
72
+ .toEqual(undefined);
73
+ }
74
+ };
@@ -0,0 +1,68 @@
1
+ import { expect } from "./helper";
2
+ import { hydrate, DIV, SPAN, P } from "../index";
3
+ import { MockElement, MockText } from "./mocks";
4
+
5
+ export default {
6
+ "hydrate(): text node returns its text content": () => {
7
+ const text = new MockText("hello world");
8
+
9
+ expect(hydrate(text as any))
10
+ .toMatch("hello world");
11
+ },
12
+
13
+ "hydrate(): empty element returns a vode": () => {
14
+ const el = new MockElement("div");
15
+ const result = hydrate(el as any);
16
+
17
+ expect(result)
18
+ .toMatch([DIV]);
19
+ },
20
+
21
+ "hydrate(): element with children returns full vode tree": () => {
22
+ const parent = new MockElement("div");
23
+ const child = new MockElement("span");
24
+ parent.appendChild(child);
25
+
26
+ expect(hydrate(parent as any))
27
+ .toMatch([DIV, [SPAN]]);
28
+ },
29
+
30
+ "hydrate(): element with text child": () => {
31
+ const parent = new MockElement("p");
32
+ const text = new MockText("hello");
33
+ parent.appendChild(text);
34
+
35
+ expect(hydrate(parent as any))
36
+ .toMatch([P, "hello"]);
37
+ },
38
+
39
+ "hydrate(): element with attributes reads them into props": () => {
40
+ const el = new MockElement("div");
41
+ el.setAttribute("class", "foo");
42
+ el.setAttribute("id", "bar");
43
+
44
+ expect(hydrate(el as any))
45
+ .toMatch([DIV, { class: "foo", id: "bar" }]);
46
+ },
47
+
48
+ "hydrate(): unknown node type returns undefined": () => {
49
+ const frag = { nodeType: 999 } as any;
50
+
51
+ expect(hydrate(frag))
52
+ .toEqual(undefined);
53
+ },
54
+
55
+ "hydrate(): empty text node returns undefined": () => {
56
+ const text = new MockText(" ");
57
+
58
+ expect(hydrate(text as any))
59
+ .toEqual(undefined);
60
+ },
61
+
62
+ "hydrate(): only element and text nodes are supported": () => {
63
+ const comment = { nodeType: Node.COMMENT_NODE } as any;
64
+
65
+ expect(hydrate(comment))
66
+ .toEqual(undefined);
67
+ },
68
+ };
@@ -0,0 +1,119 @@
1
+ import { expect } from "./helper";
2
+ import { memo, DIV, app, createState, SPAN } from "../index";
3
+
4
+ export default {
5
+ "memo(): returns the given function": () => {
6
+ const fn = (s: any) => [DIV];
7
+ const result = memo([1, 2], fn);
8
+ expect(result === fn).toEqual(true);
9
+ },
10
+
11
+ "memo(): throws when compare is not an array": () => {
12
+ const err = expect(() => memo(null as any, (s: any) => [DIV]))
13
+ .toFail();
14
+ expect(err.message)
15
+ .toEqual("first argument to memo() must be an array of values to compare");
16
+ },
17
+
18
+ "memo(): throws when componentOrProps is not a function": () => {
19
+ const err = expect(() => memo([1], null as any))
20
+ .toFail();
21
+ expect(err.message)
22
+ .toEqual("second argument to memo() must be a function that returns a vode or props object");
23
+ },
24
+
25
+ "memo(): integration with app prevents re-render when deps match": () => {
26
+ const state = createState({ count: 12 });
27
+ const root = document.createElement("div");
28
+ const container = document.createElement("div");
29
+ root.appendChild(container);
30
+
31
+ let callCount = 0;
32
+ app<typeof state>(container, state, (s) => [DIV, memo(
33
+ [s.count],
34
+ (s) => {
35
+ callCount++;
36
+ return [DIV, [SPAN, `${s.count}`]];
37
+ }
38
+ )]);
39
+
40
+
41
+ expect(callCount).toEqual(1);
42
+
43
+ state.patch({ count: 12 }); //same value, should not re-render
44
+ expect(callCount).toEqual(1);
45
+ state.patch({ count: 13 }); //different value, should re-render
46
+ expect(callCount).toEqual(2);
47
+ expect(container).toMatch(
48
+ [DIV,
49
+ [DIV,
50
+ [SPAN, "13"]
51
+ ]
52
+ ]
53
+ );
54
+ },
55
+
56
+ "memo(): works also with props factory": () => {
57
+ const state = createState({ count: 12, prefix: "Count is: " });
58
+ const root = document.createElement("div");
59
+ const container = document.createElement("div");
60
+ root.appendChild(container);
61
+
62
+ let callCount = 0;
63
+ app<typeof state>(container, state, (s) => [DIV,
64
+ [DIV,
65
+ memo(
66
+ [s.count],
67
+ (s) => {
68
+ callCount++;
69
+ return {
70
+ class: {
71
+ low: s.count < 10,
72
+ high: s.count >= 10,
73
+ }
74
+ };
75
+ }
76
+ ),
77
+ [SPAN, `${s.prefix}${s.count}`]
78
+ ],
79
+ ]);
80
+
81
+
82
+ expect(callCount).toEqual(1);
83
+ state.patch({ count: 12 });
84
+ expect(callCount).toEqual(1); // unchanged count should not cause re-render
85
+ state.patch({ count: 13 });
86
+ expect(callCount).toEqual(2);
87
+ state.patch({ prefix: "count: " });
88
+ expect(callCount).toEqual(3);
89
+ expect(container).toMatch(
90
+ [DIV,
91
+ [DIV, { class: { low: false, high: true } },
92
+ [SPAN, "count: 13"]
93
+ ]
94
+ ]
95
+ );
96
+ },
97
+
98
+ "memo(): can be a nested component function": () => {
99
+ const state = createState({ count: 12 });
100
+ const root = document.createElement("div");
101
+ const container = document.createElement("div");
102
+ root.appendChild(container);
103
+
104
+ let callCount = 0;
105
+ app<typeof state>(container, state, (s) => [DIV,
106
+ () => memo(
107
+ [s.count],
108
+ (s) => {
109
+ callCount++;
110
+ return [DIV, [SPAN, `${s.count}`]];
111
+ }
112
+ )]);
113
+
114
+
115
+ expect(callCount).toEqual(1);
116
+ state.patch({ count: 12 }); //same value, should not re-render
117
+ expect(callCount).toEqual(1);
118
+ },
119
+ };
@@ -0,0 +1,63 @@
1
+ import { mergeClass } from "../index";
2
+ import { expect } from "./helper";
3
+
4
+ export default {
5
+ "mergeClass(): no args returns null": () => {
6
+ expect(mergeClass()).toEqual(null);
7
+ },
8
+
9
+ "mergeClass(): single string returns it": () => {
10
+ expect(mergeClass("foo")).toEqual("foo");
11
+ },
12
+
13
+ "mergeClass(): two strings are joined and deduplicated": () => {
14
+ expect(mergeClass("foo", "bar")).toEqual("foo bar");
15
+ expect(mergeClass("foo bar", "bar baz")).toEqual("foo bar baz");
16
+ },
17
+
18
+ "mergeClass(): string and array": () => {
19
+ expect(mergeClass("foo", ["bar", "baz"])).toEqual("bar baz foo");
20
+ },
21
+
22
+ "mergeClass(): array and string": () => {
23
+ expect(mergeClass(["foo", "bar"], "baz")).toEqual("foo bar baz");
24
+ },
25
+
26
+ "mergeClass(): two arrays": () => {
27
+ expect(mergeClass(["foo", "bar"], ["baz", "qux"])).toEqual("foo bar baz qux");
28
+ },
29
+
30
+ "mergeClass(): two string arrays with duplicates": () => {
31
+ expect(mergeClass(["foo", "bar"], ["bar", "baz"])).toEqual("foo bar baz");
32
+ },
33
+
34
+ "mergeClass(): string and object": () => {
35
+ expect(mergeClass("foo", { bar: true, baz: false })).toEqual({ foo: true, bar: true, baz: false });
36
+ },
37
+
38
+ "mergeClass(): object and string": () => {
39
+ expect(mergeClass({ foo: true, bar: false }, "baz")).toEqual({ foo: true, bar: false, baz: true });
40
+ },
41
+
42
+ "mergeClass(): two objects": () => {
43
+ expect(mergeClass({ foo: true, bar: true }, { bar: false, baz: true })).toEqual({ foo: true, bar: false, baz: true });
44
+ },
45
+
46
+ "mergeClass(): object and array": () => {
47
+ expect(mergeClass({ foo: true }, ["bar", "baz"])).toEqual({ foo: true, 0: "bar", 1: "baz" });
48
+ },
49
+
50
+ "mergeClass(): array and object": () => {
51
+ expect(mergeClass(["foo", "bar"], { baz: true, qux: false })).toEqual({ 0: "foo", 1: "bar", baz: true, qux: false });
52
+ },
53
+
54
+ "mergeClass(): falsy entries are skipped": () => {
55
+ expect(mergeClass("foo", null, "bar")).toEqual("foo bar");
56
+ expect(mergeClass(null, "foo", undefined, "bar")).toEqual("foo bar");
57
+ },
58
+
59
+ "mergeClass(): multiple args (3+)": () => {
60
+ expect(mergeClass("a", "b", "c")).toEqual("a b c");
61
+ expect(mergeClass("x", null, ["y", "z"], "w")).toEqual("y z x w");
62
+ }
63
+ };