@yak/react 0.0.0-stage → 10.0.0-rc.0

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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -2
  3. package/dist/context/baseContext.cjs +9 -0
  4. package/dist/context/baseContext.cjs.map +1 -0
  5. package/dist/context/baseContext.d.cts +6 -0
  6. package/dist/context/baseContext.d.ts +6 -0
  7. package/dist/context/baseContext.js +8 -0
  8. package/dist/context/baseContext.js.map +1 -0
  9. package/dist/context/index.cjs +45 -0
  10. package/dist/context/index.cjs.map +1 -0
  11. package/dist/context/index.d.cts +21 -0
  12. package/dist/context/index.d.ts +21 -0
  13. package/dist/context/index.js +15 -0
  14. package/dist/context/index.js.map +1 -0
  15. package/dist/context/index.server.cjs +47 -0
  16. package/dist/context/index.server.cjs.map +1 -0
  17. package/dist/context/index.server.d.cts +10 -0
  18. package/dist/context/index.server.d.ts +10 -0
  19. package/dist/context/index.server.js +17 -0
  20. package/dist/context/index.server.js.map +1 -0
  21. package/dist/cross-file-resolver/index.d.ts +199 -0
  22. package/dist/cross-file-resolver/index.js +535 -0
  23. package/dist/cross-file-resolver/index.js.map +1 -0
  24. package/dist/index.cjs +2 -0
  25. package/dist/index.cjs.map +1 -0
  26. package/dist/index.d.cts +320 -0
  27. package/dist/index.d.ts +320 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/internal.cjs +544 -0
  31. package/dist/internal.cjs.map +1 -0
  32. package/dist/internal.d.cts +452 -0
  33. package/dist/internal.d.ts +452 -0
  34. package/dist/internal.js +363 -0
  35. package/dist/internal.js.map +1 -0
  36. package/dist/isolated-source-eval/index.d.ts +30 -0
  37. package/dist/isolated-source-eval/index.js +317 -0
  38. package/dist/isolated-source-eval/index.js.map +1 -0
  39. package/dist/isolated-source-eval/worker.js +59 -0
  40. package/dist/isolated-source-eval/worker.js.map +1 -0
  41. package/dist/jsx-dev-runtime.cjs +2 -0
  42. package/dist/jsx-dev-runtime.cjs.map +1 -0
  43. package/dist/jsx-dev-runtime.d.cts +7 -0
  44. package/dist/jsx-dev-runtime.d.ts +7 -0
  45. package/dist/jsx-dev-runtime.js +2 -0
  46. package/dist/jsx-dev-runtime.js.map +1 -0
  47. package/dist/jsx-runtime.cjs +2 -0
  48. package/dist/jsx-runtime.cjs.map +1 -0
  49. package/dist/jsx-runtime.d.cts +38 -0
  50. package/dist/jsx-runtime.d.ts +38 -0
  51. package/dist/jsx-runtime.js +2 -0
  52. package/dist/jsx-runtime.js.map +1 -0
  53. package/dist/loaders/turbo-loader.cjs +803 -0
  54. package/dist/loaders/turbo-loader.cjs.map +1 -0
  55. package/dist/loaders/turbo-loader.d.cts +15 -0
  56. package/dist/loaders/vite-plugin.d.ts +111 -0
  57. package/dist/loaders/vite-plugin.js +878 -0
  58. package/dist/loaders/vite-plugin.js.map +1 -0
  59. package/dist/loaders/webpack-loader.cjs +697 -0
  60. package/dist/loaders/webpack-loader.cjs.map +1 -0
  61. package/dist/loaders/webpack-loader.d.cts +12 -0
  62. package/dist/rsbuild/index.d.ts +29 -0
  63. package/dist/rsbuild/index.js +52 -0
  64. package/dist/rsbuild/index.js.map +1 -0
  65. package/dist/static/chunk-DK3Fl9T5.js +1 -0
  66. package/dist/static/index.cjs +2 -0
  67. package/dist/static/index.cjs.map +1 -0
  68. package/dist/static/index.d.cts +28 -0
  69. package/dist/static/index.d.ts +28 -0
  70. package/dist/static/index.js +2 -0
  71. package/dist/static/index.js.map +1 -0
  72. package/dist/withYak/index.cjs +208 -0
  73. package/dist/withYak/index.cjs.map +1 -0
  74. package/dist/withYak/index.d.cts +121 -0
  75. package/dist/withYak/index.d.ts +121 -0
  76. package/dist/withYak/index.js +177 -0
  77. package/dist/withYak/index.js.map +1 -0
  78. package/loaders/lib/resolveCrossFileSelectors.ts +162 -0
  79. package/loaders/turbo-evaluator.ts +134 -0
  80. package/loaders/turbo-loader.ts +180 -0
  81. package/loaders/vite-plugin.ts +28 -0
  82. package/loaders/webpack-loader.ts +44 -0
  83. package/package.json +214 -3
  84. package/rsbuild/index.ts +92 -0
  85. package/runtime/__tests__/atoms.test.tsx +48 -0
  86. package/runtime/__tests__/attrs.test.tsx +490 -0
  87. package/runtime/__tests__/cssLiteral.test.tsx +503 -0
  88. package/runtime/__tests__/cssProp.test.tsx +392 -0
  89. package/runtime/__tests__/cssPropTest.tsx +227 -0
  90. package/runtime/__tests__/foldedJsxParity.test.tsx +117 -0
  91. package/runtime/__tests__/styled.test.tsx +653 -0
  92. package/runtime/__tests__/tsconfig.json +18 -0
  93. package/runtime/__tests__/typeTest.tsx +374 -0
  94. package/runtime/atoms.tsx +36 -0
  95. package/runtime/context/README.md +84 -0
  96. package/runtime/context/baseContext.tsx +5 -0
  97. package/runtime/context/index.server.tsx +22 -0
  98. package/runtime/context/index.tsx +36 -0
  99. package/runtime/cssLiteral.tsx +214 -0
  100. package/runtime/globalStyle.tsx +54 -0
  101. package/runtime/index.ts +37 -0
  102. package/runtime/internal.ts +42 -0
  103. package/runtime/internals/mergeClassNames.ts +23 -0
  104. package/runtime/internals/mergeCssProp.ts +69 -0
  105. package/runtime/internals/propMarkers.ts +20 -0
  106. package/runtime/internals/unitPostFix.ts +34 -0
  107. package/runtime/jsx-dev-runtime.ts +6 -0
  108. package/runtime/jsx-runtime.ts +48 -0
  109. package/runtime/keyframes.tsx +30 -0
  110. package/runtime/mocks/cssLiteral.ts +74 -0
  111. package/runtime/mocks/globalStyle.ts +14 -0
  112. package/runtime/mocks/keyframes.ts +29 -0
  113. package/runtime/mocks/styled.ts +8 -0
  114. package/runtime/publicStyledApi.ts +159 -0
  115. package/runtime/styled.tsx +372 -0
  116. package/runtime/styledDom.tsx +139 -0
  117. package/withYak/allow-global-css.ts +94 -0
  118. package/withYak/index.ts +282 -0
@@ -0,0 +1,503 @@
1
+ // @ts-nocheck These are runtime tests and the external API isn't the runtime (after compile) API
2
+ import type { YakTheme } from "../context";
3
+ import { css, ClassNames } from "../cssLiteral";
4
+
5
+ describe("cssLiteral css function", () => {
6
+ describe("static CSS class names", () => {
7
+ it("should add static class name to classNames set", () => {
8
+ const classNames = new Set<string>();
9
+ const style: React.CSSProperties = {};
10
+ const props = { theme: {} };
11
+
12
+ const processor = css("static-class-name");
13
+ processor(props, classNames, style);
14
+
15
+ expect(classNames.has("static-class-name")).toBe(true);
16
+ });
17
+
18
+ it("should handle empty class name", () => {
19
+ const classNames = new Set<string>();
20
+ const style: React.CSSProperties = {};
21
+ const props = { theme: {} };
22
+
23
+ const processor = css("");
24
+ processor(props, classNames, style);
25
+
26
+ expect(classNames.size).toBe(0);
27
+ });
28
+ });
29
+
30
+ describe("dynamic CSS functions", () => {
31
+ it("should execute dynamic CSS functions", () => {
32
+ const classNames = new Set<string>();
33
+ const style: React.CSSProperties = {};
34
+ const props = { theme: {} };
35
+
36
+ const dynamicFn = (props, classNames, style) => {
37
+ classNames.add("dynamic-class");
38
+ style.color = "red";
39
+ };
40
+
41
+ const processor = css("base-class", dynamicFn);
42
+ processor(props, classNames, style);
43
+
44
+ expect(classNames.has("base-class")).toBe(true);
45
+ expect(classNames.has("dynamic-class")).toBe(true);
46
+ expect(style.color).toBe("red");
47
+ });
48
+
49
+ it("should handle multiple dynamic functions", () => {
50
+ const classNames = new Set<string>();
51
+ const style: React.CSSProperties = {};
52
+ const props = { theme: {} };
53
+
54
+ const fn1 = (props, classNames, style) => {
55
+ classNames.add("class1");
56
+ style.margin = "10px";
57
+ };
58
+ const fn2 = (props, classNames, style) => {
59
+ classNames.add("class2");
60
+ style.padding = "5px";
61
+ };
62
+
63
+ const processor = css("base", fn1, fn2);
64
+ processor(props, classNames, style);
65
+
66
+ expect(classNames.has("base")).toBe(true);
67
+ expect(classNames.has("class1")).toBe(true);
68
+ expect(classNames.has("class2")).toBe(true);
69
+ expect(style.margin).toBe("10px");
70
+ expect(style.padding).toBe("5px");
71
+ });
72
+ });
73
+
74
+ describe("CSS variables with style objects", () => {
75
+ it("should handle static CSS variables", () => {
76
+ const classNames = new Set<string>();
77
+ const style: React.CSSProperties = {};
78
+ const props = { theme: {} };
79
+
80
+ const styleObj = {
81
+ style: {
82
+ "--css-var": "static-value",
83
+ "--another-var": "42px",
84
+ },
85
+ };
86
+
87
+ const processor = css("class-name", styleObj);
88
+ processor(props, classNames, style);
89
+
90
+ expect(classNames.has("class-name")).toBe(true);
91
+ expect(style["--css-var"]).toBe("static-value");
92
+ expect(style["--another-var"]).toBe("42px");
93
+ });
94
+
95
+ it("should handle dynamic CSS variables with functions", () => {
96
+ const classNames = new Set<string>();
97
+ const style: React.CSSProperties = {};
98
+ const props = {
99
+ theme: {},
100
+ $width: 100,
101
+ $responsiveWidth: false,
102
+ };
103
+
104
+ const styleObj = {
105
+ style: {
106
+ "--width": (props) => (props.$responsiveWidth ? "100%" : `${props.$width}px`),
107
+ "--static-var": "static-value",
108
+ },
109
+ };
110
+
111
+ const processor = css("class-name", styleObj);
112
+ processor(props, classNames, style);
113
+
114
+ expect(classNames.has("class-name")).toBe(true);
115
+ expect(style["--width"]).toBe("100px");
116
+ expect(style["--static-var"]).toBe("static-value");
117
+ });
118
+
119
+ it("should handle mixed static and dynamic CSS variables (the bug fix)", () => {
120
+ const classNames = new Set<string>();
121
+ const style: React.CSSProperties = {};
122
+ const props = {
123
+ theme: {},
124
+ $width: 200,
125
+ $responsiveWidth: true,
126
+ $aspectRatio: "16/9",
127
+ };
128
+
129
+ const styleObj = {
130
+ style: {
131
+ "--page_StyledImageWithRatioInternal__width_zWYXKE": (props) =>
132
+ props.$responsiveWidth ? "100%" : `${props.$width}px`,
133
+ "--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE": (props) => props.$aspectRatio,
134
+ },
135
+ };
136
+
137
+ const processor = css("page_StyledImageWithRatioInternal_zWYXKE", styleObj);
138
+ processor(props, classNames, style);
139
+
140
+ expect(classNames.has("page_StyledImageWithRatioInternal_zWYXKE")).toBe(true);
141
+ expect(style["--page_StyledImageWithRatioInternal__width_zWYXKE"]).toBe("100%");
142
+ expect(style["--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE"]).toBe("16/9");
143
+ });
144
+
145
+ it("should handle multiple style objects", () => {
146
+ const classNames = new Set<string>();
147
+ const style: React.CSSProperties = {};
148
+ const props = {
149
+ theme: {},
150
+ $width: 150,
151
+ $aspectRatio: "4/3",
152
+ };
153
+
154
+ const styleObj1 = {
155
+ style: {
156
+ "--var1": "value1",
157
+ "--var2": (props) => (props.$width ? `${props.$width}px` : "0"),
158
+ },
159
+ };
160
+
161
+ const styleObj2 = {
162
+ style: {
163
+ "--var3": "value3",
164
+ "--var4": (props) => props.$aspectRatio || "auto",
165
+ },
166
+ };
167
+
168
+ const processor = css("base-class", styleObj1, styleObj2);
169
+ processor(props, classNames, style);
170
+
171
+ expect(classNames.has("base-class")).toBe(true);
172
+ expect(style["--var1"]).toBe("value1");
173
+ expect(style["--var2"]).toBe("150px");
174
+ expect(style["--var3"]).toBe("value3");
175
+ expect(style["--var4"]).toBe("4/3");
176
+ });
177
+ });
178
+
179
+ describe("complex combinations", () => {
180
+ it("should handle mix of static class, dynamic functions, and CSS variables", () => {
181
+ const classNames = new Set<string>();
182
+ const style: React.CSSProperties = {};
183
+ const props = {
184
+ theme: {},
185
+ $width: 300,
186
+ $responsiveWidth: true,
187
+ };
188
+
189
+ const dynamicFn = (props, classNames, style) => {
190
+ if (props.$responsiveWidth) {
191
+ classNames.add("responsive");
192
+ }
193
+ style.display = "block";
194
+ };
195
+
196
+ const styleObj = {
197
+ style: {
198
+ "--width": (props) => (props.$responsiveWidth ? "100%" : `${props.$width}px`),
199
+ "--static-height": "200px",
200
+ },
201
+ };
202
+
203
+ const processor = css("base-class", dynamicFn, styleObj);
204
+ processor(props, classNames, style);
205
+
206
+ expect(classNames.has("base-class")).toBe(true);
207
+ expect(classNames.has("responsive")).toBe(true);
208
+ expect(style.display).toBe("block");
209
+ expect(style["--width"]).toBe("100%");
210
+ expect(style["--static-height"]).toBe("200px");
211
+ });
212
+
213
+ it("should handle empty inputs gracefully", () => {
214
+ const classNames = new Set<string>();
215
+ const style: React.CSSProperties = {};
216
+ const props = { theme: {} };
217
+
218
+ const processor = css();
219
+ processor(props, classNames, style);
220
+
221
+ expect(classNames.size).toBe(0);
222
+ expect(Object.keys(style).length).toBe(0);
223
+ });
224
+ });
225
+
226
+ describe("optimization for static-only CSS", () => {
227
+ it("should optimize when no dynamic functions are present", () => {
228
+ const classNames = new Set<string>();
229
+ const style: React.CSSProperties = {};
230
+ const props = { theme: {} };
231
+
232
+ const processor = css("static-only-class");
233
+ const result = processor(props, classNames, style);
234
+
235
+ expect(classNames.has("static-only-class")).toBe(true);
236
+ // static processors are marked so the styled runtime can skip theme
237
+ // lookup and style-object allocation; they return nothing
238
+ expect(processor.$dynamic).toBe(false);
239
+ expect(result).toBeUndefined();
240
+ });
241
+ });
242
+
243
+ describe("type conversion", () => {
244
+ it("should convert non-string values to strings for CSS variables", () => {
245
+ const classNames = new Set<string>();
246
+ const style: React.CSSProperties = {};
247
+ const props = {
248
+ theme: {},
249
+ $width: 42,
250
+ $responsiveWidth: true,
251
+ };
252
+
253
+ const styleObj = {
254
+ style: {
255
+ "--number-var": (props) => props.$width || 0,
256
+ "--boolean-var": (props) => props.$responsiveWidth || false,
257
+ },
258
+ };
259
+
260
+ const processor = css("class", styleObj);
261
+ processor(props, classNames, style);
262
+
263
+ expect(style["--number-var"]).toBe("42");
264
+ expect(style["--boolean-var"]).toBe("true");
265
+ });
266
+
267
+ it("should handle null and undefined values", () => {
268
+ const classNames = new Set<string>();
269
+ const style: React.CSSProperties = {};
270
+ const props = { theme: {} };
271
+
272
+ const styleObj = {
273
+ style: {
274
+ "--null-var": () => null,
275
+ "--undefined-var": () => undefined,
276
+ "--empty-var": () => "",
277
+ },
278
+ };
279
+
280
+ const processor = css("class", styleObj);
281
+ processor(props, classNames, style);
282
+
283
+ expect(style["--null-var"]).toBe("null");
284
+ expect(style["--undefined-var"]).toBe("undefined");
285
+ expect(style["--empty-var"]).toBe("");
286
+ });
287
+ });
288
+
289
+ describe("error conditions", () => {
290
+ it("should handle invalid function types gracefully", () => {
291
+ const classNames = new Set<string>();
292
+ const style: React.CSSProperties = {};
293
+ const props = { theme: {} };
294
+
295
+ const invalidArg = { notAValidProperty: "value" };
296
+
297
+ expect(() => {
298
+ const processor = css("class", invalidArg);
299
+ processor(props, classNames, style);
300
+ }).not.toThrow();
301
+ });
302
+
303
+ it("should handle functions that throw errors", () => {
304
+ const classNames = new Set<string>();
305
+ const style: React.CSSProperties = {};
306
+ const props = { theme: {} };
307
+
308
+ const throwingFunction = () => {
309
+ throw new Error("Test error");
310
+ };
311
+
312
+ expect(() => {
313
+ const processor = css("class", throwingFunction);
314
+ processor(props, classNames, style);
315
+ }).toThrow("Test error");
316
+ });
317
+ });
318
+
319
+ describe("real-world scenario from the bug report", () => {
320
+ it("should handle the exact scenario from StyledImageWithRatioInternal", () => {
321
+ const classNames = new Set<string>();
322
+ const style: React.CSSProperties = {};
323
+ const props = {
324
+ theme: {},
325
+ $responsiveWidth: false,
326
+ $width: 250,
327
+ $aspectRatio: "16/9",
328
+ };
329
+
330
+ // Simulate the exact compiled output from the bug report
331
+ const dynamicWidthFn = (props, classNames, style) => {
332
+ style["--page_StyledImageWithRatioInternal__width_zWYXKE"] = props.$responsiveWidth
333
+ ? "100%"
334
+ : `${props.$width}px`;
335
+ classNames.add("page_StyledImageWithRatioInternal___zWYXKE");
336
+ };
337
+
338
+ const aspectRatioStyle = {
339
+ style: {
340
+ "--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE": (props) => props.$aspectRatio,
341
+ },
342
+ };
343
+
344
+ const processor = css(
345
+ "page_StyledImageWithRatioInternal_zWYXKE",
346
+ dynamicWidthFn,
347
+ aspectRatioStyle,
348
+ );
349
+
350
+ processor(props, classNames, style);
351
+
352
+ expect(classNames.has("page_StyledImageWithRatioInternal_zWYXKE")).toBe(true);
353
+ expect(classNames.has("page_StyledImageWithRatioInternal___zWYXKE")).toBe(true);
354
+ expect(style["--page_StyledImageWithRatioInternal__width_zWYXKE"]).toBe("250px");
355
+ expect(style["--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE"]).toBe("16/9");
356
+ });
357
+
358
+ it("should handle responsive width scenario", () => {
359
+ const classNames = new Set<string>();
360
+ const style: React.CSSProperties = {};
361
+ const props = {
362
+ theme: {},
363
+ $responsiveWidth: true,
364
+ $width: 250,
365
+ $aspectRatio: "4/3",
366
+ };
367
+
368
+ const dynamicWidthFn = (props, classNames, style) => {
369
+ style["--page_StyledImageWithRatioInternal__width_zWYXKE"] = props.$responsiveWidth
370
+ ? "100%"
371
+ : `${props.$width}px`;
372
+ classNames.add("page_StyledImageWithRatioInternal___zWYXKE");
373
+ };
374
+
375
+ const aspectRatioStyle = {
376
+ style: {
377
+ "--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE": (props) => props.$aspectRatio,
378
+ },
379
+ };
380
+
381
+ const processor = css(
382
+ "page_StyledImageWithRatioInternal_zWYXKE",
383
+ dynamicWidthFn,
384
+ aspectRatioStyle,
385
+ );
386
+
387
+ processor(props, classNames, style);
388
+
389
+ expect(style["--page_StyledImageWithRatioInternal__width_zWYXKE"]).toBe("100%");
390
+ expect(style["--page_StyledImageWithRatioInternal__aspect-ratio_zWYXKE"]).toBe("4/3");
391
+ });
392
+ });
393
+
394
+ describe("edge cases for CSS variable handling", () => {
395
+ it("should handle mixed function and static values in same style object", () => {
396
+ const classNames = new Set<string>();
397
+ const style: React.CSSProperties = {};
398
+ const props = {
399
+ theme: {},
400
+ $width: 123,
401
+ $aspectRatio: "1/1",
402
+ };
403
+
404
+ const styleObj = {
405
+ style: {
406
+ "--dynamic": (props) => `${props.$width}px`,
407
+ "--static": "static-value",
408
+ "--another-dynamic": (props) => props.$aspectRatio,
409
+ "--another-static": "100%",
410
+ },
411
+ };
412
+
413
+ const processor = css("test-class", styleObj);
414
+ processor(props, classNames, style);
415
+
416
+ expect(style["--dynamic"]).toBe("123px");
417
+ expect(style["--static"]).toBe("static-value");
418
+ expect(style["--another-dynamic"]).toBe("1/1");
419
+ expect(style["--another-static"]).toBe("100%");
420
+ });
421
+
422
+ it("should handle empty style objects", () => {
423
+ const classNames = new Set<string>();
424
+ const style: React.CSSProperties = {};
425
+ const props = { theme: {} };
426
+
427
+ const emptyStyleObj = { style: {} };
428
+
429
+ const processor = css("test-class", emptyStyleObj);
430
+ processor(props, classNames, style);
431
+
432
+ expect(classNames.has("test-class")).toBe(true);
433
+ expect(Object.keys(style).length).toBe(0);
434
+ });
435
+
436
+ it("should handle only static CSS variables", () => {
437
+ const classNames = new Set<string>();
438
+ const style: React.CSSProperties = {};
439
+ const props = { theme: {} };
440
+
441
+ const styleObj = {
442
+ style: {
443
+ "--static1": "value1",
444
+ "--static2": "value2",
445
+ },
446
+ };
447
+
448
+ const processor = css("test-class", styleObj);
449
+ processor(props, classNames, style);
450
+
451
+ expect(style["--static1"]).toBe("value1");
452
+ expect(style["--static2"]).toBe("value2");
453
+ });
454
+
455
+ it("should handle only dynamic CSS variables", () => {
456
+ const classNames = new Set<string>();
457
+ const style: React.CSSProperties = {};
458
+ const props = {
459
+ theme: {},
460
+ $value1: "dynamic1",
461
+ $value2: "dynamic2",
462
+ };
463
+
464
+ const styleObj = {
465
+ style: {
466
+ "--dynamic1": (props) => props.$value1,
467
+ "--dynamic2": (props) => props.$value2,
468
+ },
469
+ };
470
+
471
+ const processor = css("test-class", styleObj);
472
+ processor(props, classNames, style);
473
+
474
+ expect(style["--dynamic1"]).toBe("dynamic1");
475
+ expect(style["--dynamic2"]).toBe("dynamic2");
476
+ });
477
+ });
478
+ });
479
+
480
+ describe("ClassNames collector", () => {
481
+ it("does not deduplicate added class names", () => {
482
+ const classNames = new ClassNames();
483
+ classNames.add("a");
484
+ classNames.add("b");
485
+ classNames.add("a");
486
+ expect(classNames.value).toBe("a b a");
487
+ });
488
+
489
+ it("does not deduplicate against the incoming className", () => {
490
+ const classNames = new ClassNames("a");
491
+ classNames.add("a");
492
+ expect(classNames.value).toBe("a a");
493
+ });
494
+
495
+ it("deletes every occurrence of a duplicated class", () => {
496
+ const classNames = new ClassNames();
497
+ classNames.add("a");
498
+ classNames.add("b");
499
+ classNames.add("a");
500
+ classNames.delete("a");
501
+ expect(classNames.value).toBe("b");
502
+ });
503
+ });