clava 0.4.0 → 0.4.1

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/tests/_utils.ts CHANGED
@@ -87,12 +87,16 @@ export function getModeComponent<
87
87
  V extends Variants = {},
88
88
  const E extends AnyComponent[] = [],
89
89
  >(mode: M, component: CVComponent<V, E>) {
90
- if (!mode) return component;
90
+ if (!mode) {
91
+ return component;
92
+ }
91
93
  return component[mode];
92
94
  }
93
95
 
94
96
  function getClass(props: ComponentResult) {
95
- if ("class" in props) return props.class;
97
+ if ("class" in props) {
98
+ return props.class;
99
+ }
96
100
  return props.className;
97
101
  }
98
102
 
@@ -1,18 +1,92 @@
1
1
  import { execFile } from "node:child_process";
2
- import { readFile } from "node:fs/promises";
2
+ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
3
4
  import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { promisify } from "node:util";
7
+ import { withPackageBuildLock } from "test-utils/build-lock";
8
+ import { build } from "vite";
6
9
  import { expect, test } from "vitest";
7
10
 
8
11
  const root = join(dirname(fileURLToPath(import.meta.url)), "..");
9
12
  const exec = promisify(execFile);
13
+ let buildPromise: Promise<unknown> | undefined;
14
+
15
+ function buildPackage() {
16
+ buildPromise ??= exec("pnpm", ["--dir", root, "build"]);
17
+ return buildPromise;
18
+ }
10
19
 
11
20
  test("build preserves the production warning guard for consumers", async () => {
12
- await exec("pnpm", ["--dir", root, "build"]);
21
+ await withPackageBuildLock(async () => {
22
+ await buildPackage();
23
+
24
+ const code = await readFile(join(root, "dist/index.js"), "utf8");
25
+ expect(code).toMatch(
26
+ /warnRefineLimit[\s\S]*?process\.env\.NODE_ENV === "production"[\s\S]*?console\.warn\(/,
27
+ );
28
+ });
29
+ }, 60_000);
30
+
31
+ test("vite removes warning logic from the production bundle", async () => {
32
+ await withPackageBuildLock(async () => {
33
+ await buildPackage();
34
+
35
+ const tempDir = await mkdtemp(join(tmpdir(), "clava-vite-"));
36
+ try {
37
+ const entry = join(tempDir, "entry.js");
38
+ const bundle = join(tempDir, "dist/bundle.js");
39
+ const clavaUrl = pathToFileURL(join(root, "dist/index.js")).href;
40
+
41
+ await writeFile(
42
+ entry,
43
+ `
44
+ import { cv } from ${JSON.stringify(clavaUrl)};
45
+
46
+ export const button = cv({
47
+ variants: {
48
+ tone: {
49
+ primary: "primary",
50
+ },
51
+ },
52
+ refine({ setVariants }) {
53
+ setVariants({ tone: "primary" });
54
+ },
55
+ });
56
+
57
+ button();
58
+ `,
59
+ );
60
+
61
+ await build({
62
+ configFile: false,
63
+ logLevel: "silent",
64
+ root: tempDir,
65
+ mode: "production",
66
+ define: {
67
+ "process.env.NODE_ENV": JSON.stringify("production"),
68
+ },
69
+ build: {
70
+ emptyOutDir: true,
71
+ lib: {
72
+ entry,
73
+ fileName: () => "bundle.js",
74
+ formats: ["es"],
75
+ },
76
+ minify: true,
77
+ outDir: "dist",
78
+ },
79
+ });
13
80
 
14
- const code = await readFile(join(root, "dist/index.js"), "utf8");
15
- expect(code).toMatch(
16
- /process\.env\.NODE_ENV !== "production"[\s\S]*?console\.warn\(/,
17
- );
81
+ const code = await readFile(bundle, "utf8");
82
+ expect(code).not.toContain("console.warn");
83
+ expect(code).not.toContain("Clava: Maximum refine iterations exceeded");
84
+ expect(code).not.toContain("Variant(s) that did not stabilize");
85
+ expect(code).not.toContain("Component created at");
86
+ expect(code).not.toContain("captureStackTrace");
87
+ expect(code).not.toMatch(/\.warned\b|["']warned["']/);
88
+ } finally {
89
+ await rm(tempDir, { force: true, recursive: true });
90
+ }
91
+ });
18
92
  }, 60_000);
@@ -333,8 +333,13 @@ describe("non-idempotent transformClass", () => {
333
333
  });
334
334
  });
335
335
 
336
- const toUpperCase = (className: string) => className.toUpperCase();
337
- const toLowerCase = (className: string) => className.toLowerCase();
336
+ function toUpperCase(className: string) {
337
+ return className.toUpperCase();
338
+ }
339
+
340
+ function toLowerCase(className: string) {
341
+ return className.toLowerCase();
342
+ }
338
343
 
339
344
  describe("extend across `create()` factories", () => {
340
345
  // The extend's own `transformClass` must apply to its own classes even when
@@ -714,6 +714,14 @@ for (const config of Object.values(CONFIGS)) {
714
714
  expect(warn).toHaveBeenCalledWith(
715
715
  expect.stringContaining("Maximum refine iterations exceeded"),
716
716
  );
717
+ expect(warn).toHaveBeenCalledWith(
718
+ expect.stringMatching(
719
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
720
+ ),
721
+ );
722
+ expect(warn).toHaveBeenCalledWith(
723
+ expect.stringContaining("Component created at:"),
724
+ );
717
725
  } finally {
718
726
  warn.mockRestore();
719
727
  }
@@ -737,6 +745,14 @@ for (const config of Object.values(CONFIGS)) {
737
745
  expect(warn).toHaveBeenCalledWith(
738
746
  expect.stringContaining("Maximum refine iterations exceeded"),
739
747
  );
748
+ expect(warn).toHaveBeenCalledWith(
749
+ expect.stringMatching(
750
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
751
+ ),
752
+ );
753
+ expect(warn).toHaveBeenCalledWith(
754
+ expect.stringContaining("Component created at:"),
755
+ );
740
756
  } finally {
741
757
  warn.mockRestore();
742
758
  }
@@ -765,6 +781,139 @@ for (const config of Object.values(CONFIGS)) {
765
781
  }
766
782
  });
767
783
 
784
+ test("refine warning names the variant key that did not stabilize", () => {
785
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
786
+ const component = getModeComponent(
787
+ mode,
788
+ cv({
789
+ variants: {
790
+ size: { sm: "sm", lg: "lg" },
791
+ color: { red: "red", blue: "blue" },
792
+ },
793
+ defaultVariants: { size: "sm", color: "red" },
794
+ refine: ({ variants, setVariants }) => {
795
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
796
+ },
797
+ }),
798
+ );
799
+
800
+ try {
801
+ component();
802
+ expect(warn).toHaveBeenCalledWith(
803
+ expect.stringMatching(
804
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
805
+ ),
806
+ );
807
+ expect(warn).toHaveBeenCalledWith(
808
+ expect.not.stringMatching(
809
+ /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
810
+ ),
811
+ );
812
+ } finally {
813
+ warn.mockRestore();
814
+ }
815
+ });
816
+
817
+ test("refine warning reports keys that oscillate at different cadences", () => {
818
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
819
+ // `size` flips every iteration, `color` flips every other iteration, so
820
+ // the final pair may agree on one of them — the warning must still name
821
+ // both keys because each contributed to a transition.
822
+ const component = getModeComponent(
823
+ mode,
824
+ cv({
825
+ variants: {
826
+ size: { sm: "sm", lg: "lg" },
827
+ color: { red: "red", blue: "blue" },
828
+ },
829
+ defaultVariants: { size: "sm", color: "red" },
830
+ refine: ({ variants, setVariants }) => {
831
+ setVariants({
832
+ size: variants.size === "sm" ? "lg" : "sm",
833
+ color:
834
+ variants.size === "sm"
835
+ ? variants.color === "red"
836
+ ? "blue"
837
+ : "red"
838
+ : variants.color,
839
+ });
840
+ },
841
+ }),
842
+ );
843
+
844
+ try {
845
+ component();
846
+ expect(warn).toHaveBeenCalledWith(
847
+ expect.stringMatching(
848
+ /Variant\(s\) that did not stabilize: [^\n]*\bsize\b/,
849
+ ),
850
+ );
851
+ expect(warn).toHaveBeenCalledWith(
852
+ expect.stringMatching(
853
+ /Variant\(s\) that did not stabilize: [^\n]*\bcolor\b/,
854
+ ),
855
+ );
856
+ } finally {
857
+ warn.mockRestore();
858
+ }
859
+ });
860
+
861
+ test("refine warning includes the component creation stack", () => {
862
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
863
+ const component = getModeComponent(
864
+ mode,
865
+ cv({
866
+ variants: { size: { sm: "sm", lg: "lg" } },
867
+ defaultVariants: { size: "sm" },
868
+ refine: ({ variants, setVariants }) => {
869
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
870
+ },
871
+ }),
872
+ );
873
+
874
+ try {
875
+ component();
876
+ expect(warn).toHaveBeenCalledWith(
877
+ expect.stringContaining("Component created at:"),
878
+ );
879
+ expect(warn).toHaveBeenCalledWith(
880
+ expect.stringContaining("refine.test.ts"),
881
+ );
882
+ } finally {
883
+ warn.mockRestore();
884
+ }
885
+ });
886
+
887
+ test("refine warning omits the creation stack when cv ran in production", () => {
888
+ let captured = "";
889
+ const warn = vi
890
+ .spyOn(console, "warn")
891
+ .mockImplementation((message: unknown) => {
892
+ captured = String(message);
893
+ });
894
+ vi.stubEnv("NODE_ENV", "production");
895
+ const component = getModeComponent(
896
+ mode,
897
+ cv({
898
+ variants: { size: { sm: "sm", lg: "lg" } },
899
+ defaultVariants: { size: "sm" },
900
+ refine: ({ variants, setVariants }) => {
901
+ setVariants({ size: variants.size === "sm" ? "lg" : "sm" });
902
+ },
903
+ }),
904
+ );
905
+
906
+ try {
907
+ vi.unstubAllEnvs();
908
+ component();
909
+ expect(captured).toContain("Maximum refine iterations exceeded");
910
+ expect(captured).not.toContain("Component created at:");
911
+ } finally {
912
+ vi.unstubAllEnvs();
913
+ warn.mockRestore();
914
+ }
915
+ });
916
+
768
917
  test("refine setDefaultVariants when explicitly passing undefined", () => {
769
918
  const component = getModeComponent(
770
919
  mode,