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/CHANGELOG.md +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +57 -16
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
- package/src/index.ts +252 -75
- package/src/types.ts +2 -2
- package/src/utils.ts +31 -10
- package/tests/_utils.ts +6 -2
- package/tests/build.test.ts +81 -7
- package/tests/extend.test.ts +7 -2
- package/tests/refine.test.ts +149 -0
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)
|
|
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)
|
|
97
|
+
if ("class" in props) {
|
|
98
|
+
return props.class;
|
|
99
|
+
}
|
|
96
100
|
return props.className;
|
|
97
101
|
}
|
|
98
102
|
|
package/tests/build.test.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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);
|
package/tests/extend.test.ts
CHANGED
|
@@ -333,8 +333,13 @@ describe("non-idempotent transformClass", () => {
|
|
|
333
333
|
});
|
|
334
334
|
});
|
|
335
335
|
|
|
336
|
-
|
|
337
|
-
|
|
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
|
package/tests/refine.test.ts
CHANGED
|
@@ -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,
|