@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,653 @@
1
+ // @ts-nocheck
2
+ // We are testing internal functionality which does not match
3
+ // 1:1 the API exposed to the user before compilation.
4
+ // Therefore types are not matching and need to be ignored.
5
+ import { render } from "@testing-library/react";
6
+ import React from "react";
7
+ import { expect, it } from "vitest";
8
+ import { YakThemeProvider } from "../context";
9
+ import { css } from "../cssLiteral";
10
+ import { styled as styledFn } from "../styled";
11
+ import { atoms } from "../atoms";
12
+
13
+ // This transform is usually done by the SWC plugin.
14
+ // However this `styled.test.tsx` does not compile
15
+ // the code before testing
16
+ const styled = Object.assign(styledFn, {
17
+ div: styledFn("div"),
18
+ input: styledFn("input"),
19
+ a: styledFn("a"),
20
+ }) as typeof styledFn;
21
+
22
+ it("should render a literal element", () => {
23
+ const Component = styled.input``;
24
+
25
+ const { container } = render(<Component />);
26
+
27
+ expect(container).toMatchInlineSnapshot(`
28
+ <div>
29
+ <input />
30
+ </div>
31
+ `);
32
+ });
33
+
34
+ it("should render a literal element with styles", () => {
35
+ const Component = styled.input("cssClass");
36
+
37
+ const { container } = render(<Component />);
38
+
39
+ expect(container).toMatchInlineSnapshot(`
40
+ <div>
41
+ <input
42
+ class="cssClass"
43
+ />
44
+ </div>
45
+ `);
46
+ });
47
+
48
+ it("should forward properties", () => {
49
+ const Component = styled.input("cssClass");
50
+
51
+ const { container } = render(<Component defaultValue="forwarded" />);
52
+
53
+ expect(container).toMatchInlineSnapshot(`
54
+ <div>
55
+ <input
56
+ class="cssClass"
57
+ value="forwarded"
58
+ />
59
+ </div>
60
+ `);
61
+ });
62
+
63
+ it("should forward children", () => {
64
+ const Component = styled.div``;
65
+
66
+ const { container } = render(
67
+ <Component>
68
+ <button>Click me!</button>
69
+ </Component>,
70
+ );
71
+
72
+ expect(container).toMatchInlineSnapshot(`
73
+ <div>
74
+ <div>
75
+ <button>
76
+ Click me!
77
+ </button>
78
+ </div>
79
+ </div>
80
+ `);
81
+ });
82
+
83
+ it("should filter out properties starting with $", () => {
84
+ const Component = styled.input``;
85
+
86
+ const { container } = render(<Component $forwardedProp="notForwarded" />);
87
+
88
+ expect(container).toMatchInlineSnapshot(`
89
+ <div>
90
+ <input />
91
+ </div>
92
+ `);
93
+ });
94
+
95
+ it("should filter out properties starting with $ when passing to custom", () => {
96
+ let forwardedProps = null;
97
+ const Component = ({ className, style, ...props }) => {
98
+ forwardedProps = props;
99
+ return null;
100
+ };
101
+ const StyledComponent = styled(Component)``;
102
+ render(<StyledComponent $forwardedProp="notForwarded" />);
103
+
104
+ expect(forwardedProps).toEqual({});
105
+ });
106
+
107
+ it("should forward properties to the next yak component", () => {
108
+ const Component = styled.input.attrs(({ $text }) => ({
109
+ "aria-label": $text,
110
+ }))``;
111
+ const StyledComponent = styled(Component)``;
112
+ const { container } = render(<StyledComponent $text="hello world" />);
113
+
114
+ expect(container).toMatchInlineSnapshot(`
115
+ <div>
116
+ <input
117
+ aria-label="hello world"
118
+ />
119
+ </div>
120
+ `);
121
+ });
122
+
123
+ it("should concatenate classNames", () => {
124
+ const Component = styled.input("className1");
125
+
126
+ const { container } = render(<Component className="className2" />);
127
+
128
+ expect(container).toMatchInlineSnapshot(`
129
+ <div>
130
+ <input
131
+ class="className2 className1"
132
+ />
133
+ </div>
134
+ `);
135
+ });
136
+
137
+ it("should concatenate styles", () => {
138
+ const Component = styled.input``;
139
+
140
+ const { container } = render(<Component style={{ color: "red" }} />);
141
+
142
+ expect(container).toMatchInlineSnapshot(`
143
+ <div>
144
+ <input
145
+ style="color: red;"
146
+ />
147
+ </div>
148
+ `);
149
+ });
150
+
151
+ it("should not inject a default style prop into a static wrapped component", () => {
152
+ let receivedProps = null;
153
+ const Component = (props) => {
154
+ receivedProps = props;
155
+ return null;
156
+ };
157
+ const StyledComponent = styled(Component)("staticClass");
158
+
159
+ render(<StyledComponent />);
160
+
161
+ expect("style" in receivedProps).toBe(false);
162
+ });
163
+
164
+ it("should forward a style prop by reference to a static wrapped component", () => {
165
+ let receivedProps = null;
166
+ const Component = (props) => {
167
+ receivedProps = props;
168
+ return null;
169
+ };
170
+ const StyledComponent = styled(Component)("staticClass");
171
+ const style = { color: "red" };
172
+
173
+ render(<StyledComponent style={style} />);
174
+
175
+ // no per-render clone — keeps React.memo/PureComponent on the target working
176
+ expect(receivedProps.style).toBe(style);
177
+ });
178
+
179
+ it("should not add class if prop is not set", () => {
180
+ const Component = styled.input(({ testProp }) => testProp && css("test"));
181
+
182
+ const { container } = render(<Component />);
183
+
184
+ expect(container).toMatchInlineSnapshot(`
185
+ <div>
186
+ <input />
187
+ </div>
188
+ `);
189
+ });
190
+
191
+ it("should add class if prop is set", () => {
192
+ const Component = styled.input(({ $testProp }) => $testProp && css("test"));
193
+
194
+ const { container } = render(<Component $testProp />);
195
+
196
+ expect(container).toMatchInlineSnapshot(`
197
+ <div>
198
+ <input
199
+ class="test"
200
+ />
201
+ </div>
202
+ `);
203
+ });
204
+
205
+ it("should allow falsy values", () => {
206
+ const Component = styled.input(({ $testProp }) => $testProp && css("test"));
207
+
208
+ const { container } = render(
209
+ <>
210
+ <Component $testProp={null} />
211
+ <Component $testProp={false} />
212
+ <Component $testProp={undefined} />
213
+ </>,
214
+ );
215
+
216
+ expect(container).toMatchInlineSnapshot(`
217
+ <div>
218
+ <input />
219
+ <input />
220
+ <input />
221
+ </div>
222
+ `);
223
+ });
224
+
225
+ it("should execute runtime styles recursively", () => {
226
+ const Component = styled.input<{
227
+ $testProp: boolean;
228
+ }>(
229
+ ({ $testProp }) =>
230
+ $testProp &&
231
+ css(
232
+ ({ $testProp }) =>
233
+ $testProp && css(({ $testProp }) => $testProp && css("recursive-test-class")),
234
+ ),
235
+ );
236
+
237
+ const { container } = render(<Component $testProp />);
238
+
239
+ expect(container).toMatchInlineSnapshot(`
240
+ <div>
241
+ <input
242
+ class="recursive-test-class"
243
+ />
244
+ </div>
245
+ `);
246
+ });
247
+
248
+ it("should allow using refs", () => {
249
+ const Component = styled.input();
250
+
251
+ let elementFromRef: HTMLInputElement | null = null;
252
+ render(
253
+ <Component
254
+ ref={(element) => {
255
+ elementFromRef = element;
256
+ }}
257
+ />,
258
+ );
259
+
260
+ expect(elementFromRef).toBeInstanceOf(HTMLInputElement);
261
+ });
262
+
263
+ it("should allow using nested refs", () => {
264
+ const BaseComponent = styled.input();
265
+ const Component = styled(BaseComponent)();
266
+
267
+ let elementFromRef: HTMLInputElement | null = null;
268
+ render(
269
+ <Component
270
+ ref={(element) => {
271
+ elementFromRef = element;
272
+ }}
273
+ />,
274
+ );
275
+
276
+ expect(elementFromRef).toBeInstanceOf(HTMLInputElement);
277
+ });
278
+
279
+ it("should remove theme if styled element", () => {
280
+ const Link = styled.a((p) => p && css("test"));
281
+
282
+ const { container } = render(
283
+ <YakThemeProvider theme={{ color: "red" }}>
284
+ <Link />
285
+ </YakThemeProvider>,
286
+ );
287
+
288
+ expect(container).toMatchInlineSnapshot(`
289
+ <div>
290
+ <a
291
+ class="test"
292
+ />
293
+ </div>
294
+ `);
295
+ });
296
+
297
+ it("should keep theme if theme is passed to element", () => {
298
+ const ThemePrinter = ({ theme, ...props }: { theme?: unknown }) => (
299
+ <pre {...props}>{JSON.stringify(theme)}</pre>
300
+ );
301
+ const Link = styled(ThemePrinter)((p) => p && css("test"));
302
+
303
+ const { container } = render(
304
+ <YakThemeProvider theme={{ color: "red" }}>
305
+ <Link theme={{ anything: "test" }} />
306
+ </YakThemeProvider>,
307
+ );
308
+
309
+ expect(container).toMatchInlineSnapshot(`
310
+ <div>
311
+ <pre
312
+ class="test"
313
+ >
314
+ {"anything":"test"}
315
+ </pre>
316
+ </div>
317
+ `);
318
+ });
319
+
320
+ it("should remove theme on wrapped element", () => {
321
+ const BaseComponent = styled.input((p) => p && css("test"));
322
+ const Component = styled(BaseComponent)((p) => p && css("test-wrapper"));
323
+
324
+ const { container } = render(
325
+ <YakThemeProvider theme={{ color: "red" }}>
326
+ <Component />
327
+ </YakThemeProvider>,
328
+ );
329
+
330
+ expect(container).toMatchInlineSnapshot(`
331
+ <div>
332
+ <input
333
+ class="test test-wrapper"
334
+ />
335
+ </div>
336
+ `);
337
+ });
338
+
339
+ it("should not remove theme if theme is passed to wrapped element", () => {
340
+ const ThemePrinter = ({ theme, ...props }: { theme?: unknown }) => (
341
+ <pre {...props}>{JSON.stringify(theme)}</pre>
342
+ );
343
+
344
+ const BaseComponent = styled(ThemePrinter)((p) => p && css("test"));
345
+ const Component = styled(BaseComponent)((p) => p && css("test-wrapper"));
346
+
347
+ const { container } = render(
348
+ <YakThemeProvider theme={{ color: "red" }}>
349
+ <Component theme={{ anything: "test" }} />
350
+ </YakThemeProvider>,
351
+ );
352
+
353
+ expect(container).toMatchInlineSnapshot(`
354
+ <div>
355
+ <pre
356
+ class="test test-wrapper"
357
+ >
358
+ {"anything":"test"}
359
+ </pre>
360
+ </div>
361
+ `);
362
+ });
363
+
364
+ describe("dev mode - error tests", () => {
365
+ const originalNodeEnv = process.env.NODE_ENV;
366
+ const originalError = console.error;
367
+
368
+ beforeEach(() => {
369
+ // errors are only thrown in development mode
370
+ process.env.NODE_ENV = "development";
371
+ // prevent console.error from printing the error a second time
372
+ console.error = () => {};
373
+ });
374
+
375
+ afterEach(() => {
376
+ process.env.NODE_ENV = originalNodeEnv;
377
+ console.error = originalError;
378
+ });
379
+
380
+ it("should show the function body in error message when dynamic css function returns invalid value", () => {
381
+ const Component = styled.div("cssClass", {
382
+ style: {
383
+ "--bar": ({ $groupColor }) => $groupColor,
384
+ },
385
+ });
386
+
387
+ let error = null;
388
+ try {
389
+ render(<Component />);
390
+ } catch (e) {
391
+ error = e;
392
+ }
393
+ expect(error).toMatchInlineSnapshot(`
394
+ [Error: Dynamic CSS functions must return a string or number but returned undefined
395
+
396
+ Dynamic CSS function: ({ $groupColor }) => $groupColor
397
+ ]
398
+ `);
399
+ });
400
+ });
401
+
402
+ describe("dynamic runtime function processing", () => {
403
+ let consoleSpy: any;
404
+
405
+ beforeEach(() => {
406
+ consoleSpy = {
407
+ log: [],
408
+ originalLog: console.log,
409
+ };
410
+ console.log = (...args) => {
411
+ consoleSpy.log.push(args.join(" "));
412
+ };
413
+ });
414
+
415
+ afterEach(() => {
416
+ console.log = consoleSpy.originalLog;
417
+ });
418
+
419
+ it("should execute atoms runtime functions", () => {
420
+ const Component = styled.div(
421
+ atoms("static-class", (props, classNames, style) => {
422
+ classNames.add("dynamic-class");
423
+ style.fontSize = "16px";
424
+ }),
425
+ );
426
+ const { container } = render(<Component />);
427
+ expect(container).toMatchInlineSnapshot(`
428
+ <div>
429
+ <div
430
+ class="static-class dynamic-class"
431
+ style="font-size: 16px;"
432
+ />
433
+ </div>
434
+ `);
435
+ });
436
+
437
+ it("should execute combined css and atoms runtime functions", () => {
438
+ const Component = styled.div(
439
+ ({ $primary }) => $primary && css("color-primary"),
440
+ atoms("atoms-class", (props, classNames, style) => {
441
+ classNames.add("atoms-dynamic");
442
+ style.padding = "10px";
443
+ }),
444
+ );
445
+ const { container } = render(<Component $primary />);
446
+ expect(container).toMatchInlineSnapshot(`
447
+ <div>
448
+ <div
449
+ class="color-primary atoms-class atoms-dynamic"
450
+ style="padding: 10px;"
451
+ />
452
+ </div>
453
+ `);
454
+ });
455
+
456
+ it("should not duplicate runtime processing in nested components", () => {
457
+ const BaseComponent = styled.div(
458
+ atoms("base-class", () => console.log("Base runtime executed")),
459
+ );
460
+
461
+ const ExtendedComponent = styled(BaseComponent)(
462
+ atoms("extended-class", () => console.log("Extended runtime executed")),
463
+ );
464
+
465
+ render(<ExtendedComponent />);
466
+
467
+ expect(consoleSpy.log).toEqual(["Base runtime executed", "Extended runtime executed"]);
468
+ expect(consoleSpy.log).toHaveLength(2);
469
+ });
470
+
471
+ it("should execute nested runtime functions in correct order", () => {
472
+ const executionOrder = [];
473
+
474
+ const Level1 = styled.div(atoms(() => executionOrder.push("level1")));
475
+
476
+ const Level2 = styled(Level1)(atoms(() => executionOrder.push("level2")));
477
+
478
+ const Level3 = styled(Level2)(atoms(() => executionOrder.push("level3")));
479
+
480
+ render(<Level3 />);
481
+
482
+ expect(executionOrder).toEqual(["level1", "level2", "level3"]);
483
+ });
484
+
485
+ it("should allow runtime functions to see previous modifications", () => {
486
+ const Component = styled.div(
487
+ atoms("first-class", (props, classNames, style) => {
488
+ classNames.add("first-dynamic");
489
+ style.color = "red";
490
+ }),
491
+ atoms((props, classNames, style) => {
492
+ // Should see modifications from first atoms call
493
+ expect(classNames.has("first-class")).toBe(true);
494
+ expect(classNames.has("first-dynamic")).toBe(true);
495
+ expect(style.color).toBe("red");
496
+
497
+ classNames.add("second-dynamic");
498
+ style.backgroundColor = "blue";
499
+ }),
500
+ );
501
+
502
+ const { container } = render(<Component />);
503
+ expect(container).toMatchInlineSnapshot(`
504
+ <div>
505
+ <div
506
+ class="first-class first-dynamic second-dynamic"
507
+ style="color: red; background-color: blue;"
508
+ />
509
+ </div>
510
+ `);
511
+ });
512
+
513
+ it("should provide theme to runtime functions", () => {
514
+ const Component = styled.div(
515
+ atoms((props, classNames, style) => {
516
+ style.color = props.theme?.primaryColor || "black";
517
+ classNames.add("themed");
518
+ }),
519
+ );
520
+
521
+ const { container } = render(
522
+ <YakThemeProvider theme={{ primaryColor: "purple" }}>
523
+ <Component />
524
+ </YakThemeProvider>,
525
+ );
526
+
527
+ expect(container).toMatchInlineSnapshot(`
528
+ <div>
529
+ <div
530
+ class="themed"
531
+ style="color: purple;"
532
+ />
533
+ </div>
534
+ `);
535
+ });
536
+
537
+ it("should merge external props with runtime modifications", () => {
538
+ const Component = styled.div(
539
+ atoms("runtime-class", (props, classNames, style) => {
540
+ style.fontSize = "18px";
541
+ }),
542
+ );
543
+
544
+ const { container } = render(
545
+ <Component className="external-class" style={{ color: "green", fontSize: "14px" }} />,
546
+ );
547
+
548
+ expect(container).toMatchInlineSnapshot(`
549
+ <div>
550
+ <div
551
+ class="external-class runtime-class"
552
+ style="color: green; font-size: 18px;"
553
+ />
554
+ </div>
555
+ `);
556
+ });
557
+
558
+ it("should handle conditional runtime functions", () => {
559
+ const Component = styled.div(
560
+ ({ $variant }) => $variant === "primary" && css("bg-blue"),
561
+ atoms(
562
+ ({ $size }, classNames) => {
563
+ if ($size === "large") {
564
+ classNames.add("large-class");
565
+ }
566
+ },
567
+ (props, classNames, style) => {
568
+ if (props.$active) {
569
+ classNames.add("active");
570
+ style.borderWidth = "2px";
571
+ }
572
+ },
573
+ ),
574
+ );
575
+
576
+ const { container } = render(
577
+ <>
578
+ <Component $variant="primary" $size="large" $active />
579
+ <Component $variant="secondary" $size="small" />
580
+ </>,
581
+ );
582
+
583
+ expect(container).toMatchInlineSnapshot(`
584
+ <div>
585
+ <div
586
+ class="bg-blue large-class active"
587
+ style="border-width: 2px;"
588
+ />
589
+ <div />
590
+ </div>
591
+ `);
592
+ });
593
+
594
+ it("should filter $ props but still use them in runtime functions", () => {
595
+ const Component = styled.div(
596
+ atoms((props, classNames, style) => {
597
+ if (props.$variant) {
598
+ classNames.add(`variant-${props.$variant}`);
599
+ style.padding = props.$spacing || "8px";
600
+ }
601
+ }),
602
+ );
603
+
604
+ const { container } = render(<Component $variant="primary" $spacing="16px" />);
605
+
606
+ const element = container.querySelector("div");
607
+ // Should not have $ props as DOM attributes
608
+ expect(element.getAttribute("$variant")).toBeNull();
609
+ expect(element.getAttribute("$spacing")).toBeNull();
610
+
611
+ // But should have styles/classes generated from them
612
+ expect(container).toMatchInlineSnapshot(`
613
+ <div>
614
+ <div
615
+ class="variant-primary"
616
+ style="padding: 16px;"
617
+ />
618
+ </div>
619
+ `);
620
+ });
621
+
622
+ it("should handle runtime functions that return falsy values", () => {
623
+ const Component = styled.div(
624
+ ({ $show }) => $show && css("block"),
625
+ atoms(
626
+ ({ $variant }, classNames) => {
627
+ if ($variant === "primary") {
628
+ classNames.add(`variant-${$variant}`);
629
+ }
630
+ },
631
+ null,
632
+ undefined,
633
+ false && "never-added",
634
+ ),
635
+ );
636
+
637
+ const { container } = render(
638
+ <>
639
+ <Component $show $variant="primary" />
640
+ <Component />
641
+ </>,
642
+ );
643
+
644
+ expect(container).toMatchInlineSnapshot(`
645
+ <div>
646
+ <div
647
+ class="block variant-primary"
648
+ />
649
+ <div />
650
+ </div>
651
+ `);
652
+ });
653
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2019",
4
+ "lib": ["DOM"],
5
+ "types": ["node"],
6
+ "jsx": "react",
7
+ "moduleResolution": "bundler",
8
+ "allowJs": true,
9
+ "checkJs": true,
10
+ "maxNodeModuleJsDepth": 0,
11
+ "noEmit": true,
12
+ "esModuleInterop": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "strict": true,
15
+ "skipLibCheck": true
16
+ },
17
+ "include": ["./*"]
18
+ }