e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
801 lines
22 KiB
JavaScript
801 lines
22 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import "tsx/esm";
|
|
|
|
const {
|
|
createBaseRegistryItem,
|
|
createRadixRegistryItem,
|
|
getBaseVariantSourcePath,
|
|
validateBaseTreeRadixImports,
|
|
validateBaseVariantContent,
|
|
validateEmittedSpecifierHygiene,
|
|
validateRadixPassDidNotReadBaseSources,
|
|
validateStyleScopedDependencies,
|
|
validateVariantExportParity,
|
|
validateVariantSlotParity,
|
|
validateVariantTreesDiffer,
|
|
} = await import("./build-registry.ts");
|
|
|
|
const createBuilt = (
|
|
name,
|
|
files,
|
|
{ readPaths = [], baseVariantOutputPaths, sourceContentsByOutputPath } = {},
|
|
) => ({
|
|
payload: {
|
|
$schema: "https://ui.shadcn.com/schema/registry-item.json",
|
|
name,
|
|
type: "registry:ui",
|
|
files: files.map(([filePath, content]) => ({
|
|
path: filePath,
|
|
type: "registry:ui",
|
|
content,
|
|
})),
|
|
},
|
|
readPaths,
|
|
baseVariantOutputPaths:
|
|
baseVariantOutputPaths ?? files.map(([filePath]) => filePath),
|
|
sourceContentsByOutputPath:
|
|
sourceContentsByOutputPath ??
|
|
new Map(files.map(([filePath, content]) => [filePath, content])),
|
|
});
|
|
|
|
test("base registry item merges, rewrites, and deduplicates dependencies in order", () => {
|
|
const item = {
|
|
name: "example",
|
|
type: "registry:ui",
|
|
registryDependencies: [
|
|
"https://r.assistant-ui.com/thread.json",
|
|
"tooltip",
|
|
"https://example.com/foreign.json",
|
|
"https://r.assistant-ui.com/base/message.json",
|
|
],
|
|
baseRegistryDependencies: [
|
|
"https://r.assistant-ui.com/thread.json",
|
|
"popover",
|
|
"https://r.assistant-ui.com/message.json",
|
|
],
|
|
};
|
|
|
|
assert.deepEqual(createBaseRegistryItem(item), {
|
|
name: "example",
|
|
type: "registry:ui",
|
|
registryDependencies: [
|
|
"https://r.assistant-ui.com/base/thread.json",
|
|
"tooltip",
|
|
"https://example.com/foreign.json",
|
|
"https://r.assistant-ui.com/base/message.json",
|
|
"popover",
|
|
],
|
|
});
|
|
});
|
|
|
|
test("base registry item rewriting is idempotent", () => {
|
|
const once = createBaseRegistryItem({
|
|
name: "example",
|
|
type: "registry:ui",
|
|
registryDependencies: ["https://r.assistant-ui.com/base/thread.json"],
|
|
baseRegistryDependencies: ["https://r.assistant-ui.com/thread.json"],
|
|
});
|
|
const twice = createBaseRegistryItem(once);
|
|
|
|
assert.deepEqual(twice, once);
|
|
assert.deepEqual(once.registryDependencies, [
|
|
"https://r.assistant-ui.com/base/thread.json",
|
|
]);
|
|
});
|
|
|
|
test("radix registry item removes base-only dependencies without rewriting", () => {
|
|
assert.deepEqual(
|
|
createRadixRegistryItem({
|
|
name: "example",
|
|
type: "registry:ui",
|
|
registryDependencies: [
|
|
"https://r.assistant-ui.com/thread.json",
|
|
"tooltip",
|
|
],
|
|
baseRegistryDependencies: ["https://r.assistant-ui.com/popover.json"],
|
|
}),
|
|
{
|
|
name: "example",
|
|
type: "registry:ui",
|
|
registryDependencies: [
|
|
"https://r.assistant-ui.com/thread.json",
|
|
"tooltip",
|
|
],
|
|
},
|
|
);
|
|
});
|
|
|
|
test("base variant source path replaces only the .tsx suffix", () => {
|
|
assert.equal(
|
|
getBaseVariantSourcePath("components/ui/button.tsx"),
|
|
"components/ui/button.base.tsx",
|
|
);
|
|
assert.equal(getBaseVariantSourcePath("components/ui/button.ts"), null);
|
|
assert.equal(getBaseVariantSourcePath("components/ui/button.jsx"), null);
|
|
assert.equal(getBaseVariantSourcePath("components/ui/button"), null);
|
|
});
|
|
|
|
test("base variant content validation accepts clean content", () => {
|
|
assert.doesNotThrow(() =>
|
|
validateBaseVariantContent([
|
|
createBuilt("clean", [
|
|
["components/clean.tsx", "export const clean = true;"],
|
|
]),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("emitted specifier hygiene aggregates marked UI specifiers", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateEmittedSpecifierHygiene([
|
|
createBuilt("radix", [
|
|
["components/radix.tsx", 'import "@/components/ui/radix/button";'],
|
|
]),
|
|
createBuilt("base", [
|
|
["components/base.tsx", 'import "@/components/ui/base/button";'],
|
|
]),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid emitted UI specifiers:/);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- radix: components/radix.tsx contains @/components/ui/radix/",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- base: components/base.tsx contains @/components/ui/base/",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateEmittedSpecifierHygiene([
|
|
createBuilt("clean", [
|
|
["components/clean.tsx", 'import "@/components/ui/button";'],
|
|
]),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("base tree radix import validation catches fallback payloads", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateBaseTreeRadixImports([
|
|
createBuilt(
|
|
"fallback",
|
|
[["components/fallback.tsx", 'import { Tooltip } from "radix-ui";']],
|
|
{ baseVariantOutputPaths: [] },
|
|
),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid base tree imports:/);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- fallback: base tree file components/fallback.tsx imports radix",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateBaseTreeRadixImports([
|
|
createBuilt(
|
|
"scoped",
|
|
[
|
|
[
|
|
"components/scoped.tsx",
|
|
'import { Tooltip } from "@radix-ui/react-tooltip";',
|
|
],
|
|
],
|
|
{ baseVariantOutputPaths: [] },
|
|
),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- scoped: base tree file components/scoped.tsx imports radix",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateBaseTreeRadixImports([
|
|
createBuilt(
|
|
"side-effect",
|
|
[
|
|
[
|
|
"components/side-effect.tsx",
|
|
'import "@radix-ui/themes/styles.css";',
|
|
],
|
|
],
|
|
{ baseVariantOutputPaths: [] },
|
|
),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- side-effect: base tree file components/side-effect.tsx imports radix",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateBaseTreeRadixImports([
|
|
createBuilt(
|
|
"clean",
|
|
[
|
|
[
|
|
"components/clean.tsx",
|
|
'export const styles = "data-radix-thing"; export const clean = true;',
|
|
],
|
|
],
|
|
{ baseVariantOutputPaths: [] },
|
|
),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("base variant content validation reports plain and scoped radix imports", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateBaseVariantContent([
|
|
createBuilt("plain", [
|
|
["components/plain.tsx", 'import { Tooltip } from "radix-ui";'],
|
|
]),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- plain: base variant for components/plain.tsx contains forbidden radix import",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateBaseVariantContent([
|
|
createBuilt("scoped", [
|
|
[
|
|
"components/scoped.tsx",
|
|
'import { Tooltip } from "@radix-ui/react-tooltip";',
|
|
],
|
|
]),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- scoped: base variant for components/scoped.tsx contains forbidden radix import",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateBaseVariantContent([
|
|
createBuilt("clean", [
|
|
["components/clean.tsx", "export const clean = true;"],
|
|
]),
|
|
]),
|
|
);
|
|
});
|
|
|
|
test("base variant content validation aggregates forbidden tokens across files", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateBaseVariantContent([
|
|
createBuilt("first", [
|
|
["components/first.tsx", "const trigger = <Button asChild />;"],
|
|
]),
|
|
createBuilt("second", [
|
|
[
|
|
"components/second.tsx",
|
|
'import { Tooltip } from "radix-ui"; const styles = "delayDuration data-[state=open]";',
|
|
],
|
|
]),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid base variant content:/);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- first: base variant for components/first.tsx contains forbidden asChild",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- second: base variant for components/second.tsx contains forbidden delayDuration",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- second: base variant for components/second.tsx contains forbidden radix import",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- second: base variant for components/second.tsx contains forbidden data-[state=",
|
|
),
|
|
);
|
|
assert.equal(
|
|
error.message.split("\n").filter((line) => line.startsWith("- "))
|
|
.length,
|
|
4,
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("radix source validation aggregates every base variant path read", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateRadixPassDidNotReadBaseSources([
|
|
createBuilt("first", [], {
|
|
readPaths: ["components/first.base.tsx"],
|
|
}),
|
|
createBuilt("second", [], {
|
|
readPaths: ["components/second.tsx", "components/second.base.tsx"],
|
|
}),
|
|
]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- first: radix registry pass read base variant path components/first.base.tsx",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- second: radix registry pass read base variant path components/second.base.tsx",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("variant tree validation aggregates identical radix and base sources", () => {
|
|
const radixBuilt = [
|
|
createBuilt("first", [["components/first.tsx", "same first"]]),
|
|
createBuilt("second", [["components/second.tsx", "same second"]]),
|
|
];
|
|
const baseBuilt = [
|
|
createBuilt("first", [["components/first.tsx", "same first"]]),
|
|
createBuilt("second", [["components/second.tsx", "same second"]]),
|
|
];
|
|
|
|
assert.throws(
|
|
() => validateVariantTreesDiffer(radixBuilt, baseBuilt),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- first: radix and base sources for components/first.tsx are identical despite a .base.tsx variant",
|
|
),
|
|
);
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- second: radix and base sources for components/second.tsx are identical despite a .base.tsx variant",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("variant tree validation accepts identical emitted content when sources differ", () => {
|
|
const radixBuilt = [
|
|
createBuilt("widget", [["components/widget.tsx", "shared emitted"]], {
|
|
sourceContentsByOutputPath: new Map([
|
|
["components/widget.tsx", 'import "@/components/ui/collapsible";'],
|
|
]),
|
|
}),
|
|
];
|
|
const baseBuilt = [
|
|
createBuilt("widget", [["components/widget.tsx", "shared emitted"]], {
|
|
sourceContentsByOutputPath: new Map([
|
|
["components/widget.tsx", 'import "@/components/ui-base/collapsible";'],
|
|
]),
|
|
}),
|
|
];
|
|
|
|
assert.doesNotThrow(() => validateVariantTreesDiffer(radixBuilt, baseBuilt));
|
|
});
|
|
|
|
test("radix registry item merges and dedupes radixDependencies into dependencies", () => {
|
|
assert.deepEqual(
|
|
createRadixRegistryItem({
|
|
name: "example",
|
|
type: "registry:ui",
|
|
dependencies: ["lucide-react", "radix-ui"],
|
|
radixDependencies: ["radix-ui", "class-variance-authority"],
|
|
baseDependencies: ["@base-ui/react"],
|
|
baseRegistryDependencies: ["popover"],
|
|
}),
|
|
{
|
|
name: "example",
|
|
type: "registry:ui",
|
|
dependencies: ["lucide-react", "radix-ui", "class-variance-authority"],
|
|
},
|
|
);
|
|
});
|
|
|
|
test("radix registry item omits dependencies when neither source list exists", () => {
|
|
assert.deepEqual(
|
|
createRadixRegistryItem({
|
|
name: "example",
|
|
type: "registry:ui",
|
|
baseDependencies: ["@base-ui/react"],
|
|
baseRegistryDependencies: ["popover"],
|
|
}),
|
|
{
|
|
name: "example",
|
|
type: "registry:ui",
|
|
},
|
|
);
|
|
});
|
|
|
|
test("base registry item merges baseDependencies and drops radixDependencies", () => {
|
|
assert.deepEqual(
|
|
createBaseRegistryItem({
|
|
name: "example",
|
|
type: "registry:ui",
|
|
dependencies: ["lucide-react", "@base-ui/react"],
|
|
baseDependencies: ["@base-ui/react", "clsx"],
|
|
radixDependencies: ["radix-ui"],
|
|
}),
|
|
{
|
|
name: "example",
|
|
type: "registry:ui",
|
|
dependencies: ["lucide-react", "@base-ui/react", "clsx"],
|
|
},
|
|
);
|
|
});
|
|
|
|
test("slot parity reports mismatched data-slot attributes", () => {
|
|
const radixBuilt = [
|
|
createBuilt("button", [
|
|
[
|
|
"components/button.tsx",
|
|
'<button data-slot="button" data-slot="button-icon" />',
|
|
],
|
|
]),
|
|
];
|
|
const baseBuilt = [
|
|
createBuilt("button", [
|
|
["components/button.tsx", '<button data-slot="button" />'],
|
|
]),
|
|
];
|
|
|
|
assert.throws(
|
|
() => validateVariantSlotParity(radixBuilt, baseBuilt),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid variant slot parity:/);
|
|
assert.ok(error.message.includes("button"));
|
|
assert.ok(error.message.includes("components/button.tsx"));
|
|
assert.ok(error.message.includes("button-icon"));
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- button: data-slot attributes differ for components/button.tsx (radix-only: button-icon)",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("slot parity accepts identical slot sets and skips empty base variants", () => {
|
|
assert.doesNotThrow(() =>
|
|
validateVariantSlotParity(
|
|
[
|
|
createBuilt("button", [
|
|
["components/button.tsx", '<button data-slot="button" />'],
|
|
]),
|
|
createBuilt("plain", [["components/plain.tsx", "export const x = 1;"]]),
|
|
],
|
|
[
|
|
createBuilt("button", [
|
|
["components/button.tsx", '<div data-slot="button" />'],
|
|
]),
|
|
createBuilt(
|
|
"plain",
|
|
[["components/plain.tsx", "export const y = 2;"]],
|
|
{
|
|
baseVariantOutputPaths: [],
|
|
},
|
|
),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test("slot parity counts object-prop slots the same as jsx-attribute slots", () => {
|
|
assert.doesNotThrow(() =>
|
|
validateVariantSlotParity(
|
|
[
|
|
createBuilt("badge", [
|
|
["components/badge.tsx", '<span data-slot="badge" />'],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("badge", [
|
|
[
|
|
"components/badge.tsx",
|
|
'useRender({ props: { "data-slot": "badge" } });',
|
|
],
|
|
]),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test("export parity reports exports present only in the radix content", () => {
|
|
const radixBuilt = [
|
|
createBuilt("widget", [
|
|
[
|
|
"components/widget.tsx",
|
|
"export function Widget() {}\nexport function Helper() {}",
|
|
],
|
|
]),
|
|
];
|
|
const baseBuilt = [
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export function Widget() {}"],
|
|
]),
|
|
];
|
|
|
|
assert.throws(
|
|
() => validateVariantExportParity(radixBuilt, baseBuilt),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid variant export parity:/);
|
|
assert.ok(error.message.includes("widget"));
|
|
assert.ok(error.message.includes("components/widget.tsx"));
|
|
assert.ok(error.message.includes("Helper"));
|
|
assert.ok(
|
|
error.message.includes(
|
|
"- widget: exported symbols differ for components/widget.tsx (radix-only: Helper)",
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("export parity treats export { A as B } as B and accepts identical sets", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("alias", [
|
|
["components/alias.tsx", "const A = 1;\nexport { A as B };"],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("alias", [
|
|
["components/alias.tsx", "export function Other() {}"],
|
|
]),
|
|
],
|
|
),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(error.message.includes("radix-only: B"));
|
|
assert.ok(error.message.includes("base-only: Other"));
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("same", [
|
|
[
|
|
"components/same.tsx",
|
|
"export function Same() {}\nconst A = 1;\nexport { A as B };",
|
|
],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("same", [
|
|
[
|
|
"components/same.tsx",
|
|
"export function Same() {}\nexport function B() {}",
|
|
],
|
|
]),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test("export parity records default exports as default regardless of local name", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export default function Widget() {}"],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export function Widget() {}"],
|
|
]),
|
|
],
|
|
),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(error.message.includes("radix-only: default"));
|
|
assert.ok(error.message.includes("base-only: Widget"));
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export default function RadixWidget() {}"],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export default function BaseWidget() {}"],
|
|
]),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
test("export parity tracks star and namespace re-exports", () => {
|
|
assert.throws(
|
|
() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("widget", [
|
|
[
|
|
"components/widget.tsx",
|
|
'export function Widget() {}\nexport * from "./extra";',
|
|
],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export function Widget() {}"],
|
|
]),
|
|
],
|
|
),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(error.message.includes("radix-only: *:./extra"));
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateVariantExportParity(
|
|
[
|
|
createBuilt("widget", [
|
|
[
|
|
"components/widget.tsx",
|
|
'export * as Helpers from "./extra";\nexport function Widget() {}',
|
|
],
|
|
]),
|
|
],
|
|
[
|
|
createBuilt("widget", [
|
|
["components/widget.tsx", "export function Widget() {}"],
|
|
]),
|
|
],
|
|
),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(error.message.includes("radix-only: Helpers"));
|
|
return true;
|
|
},
|
|
);
|
|
});
|
|
|
|
test("style-scoped dependencies flag deps used only by the opposite tree", () => {
|
|
const radixOnlyImport = createBuilt("tooltip", [
|
|
[
|
|
"components/tooltip.tsx",
|
|
'import { Tooltip } from "radix-ui";\nexport const TooltipButton = Tooltip;',
|
|
],
|
|
]);
|
|
const baseOnlyImport = createBuilt("tooltip", [
|
|
[
|
|
"components/tooltip.tsx",
|
|
'import { Tooltip } from "@base-ui/react";\nexport const TooltipButton = Tooltip;',
|
|
],
|
|
]);
|
|
const bothImport = createBuilt("shared", [
|
|
[
|
|
"components/shared.tsx",
|
|
'import { clsx } from "clsx";\nexport const cx = clsx;',
|
|
],
|
|
]);
|
|
const neitherImport = createBuilt("unused", [
|
|
["components/unused.tsx", "export const value = 1;"],
|
|
]);
|
|
|
|
radixOnlyImport.payload.dependencies = ["radix-ui"];
|
|
baseOnlyImport.payload.dependencies = ["radix-ui"];
|
|
bothImport.payload.dependencies = ["clsx"];
|
|
neitherImport.payload.dependencies = ["lodash"];
|
|
|
|
assert.throws(
|
|
() => validateStyleScopedDependencies([radixOnlyImport], [baseOnlyImport]),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.match(error.message, /^Invalid style-scoped dependencies:/);
|
|
assert.ok(error.message.includes("radixDependencies"));
|
|
assert.ok(
|
|
error.message.includes(
|
|
'- tooltip: dependency "radix-ui" is declared for the base tree but only used by the radix tree; move it to radixDependencies',
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
const radixDeclaresBaseOnly = createBuilt("panel", [
|
|
["components/panel.tsx", "export const P = true;"],
|
|
]);
|
|
const baseUsesBaseOnly = createBuilt("panel", [
|
|
[
|
|
"components/panel.tsx",
|
|
'import { Panel } from "@base-ui/react";\nexport const P = Panel;',
|
|
],
|
|
]);
|
|
radixDeclaresBaseOnly.payload.dependencies = ["@base-ui/react"];
|
|
|
|
assert.throws(
|
|
() =>
|
|
validateStyleScopedDependencies(
|
|
[radixDeclaresBaseOnly],
|
|
[baseUsesBaseOnly],
|
|
),
|
|
(error) => {
|
|
assert.equal(error instanceof Error, true);
|
|
assert.ok(error.message.includes("baseDependencies"));
|
|
assert.ok(
|
|
error.message.includes(
|
|
'- panel: dependency "@base-ui/react" is declared for the radix tree but only used by the base tree; move it to baseDependencies',
|
|
),
|
|
);
|
|
return true;
|
|
},
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateStyleScopedDependencies([bothImport], [bothImport]),
|
|
);
|
|
|
|
assert.doesNotThrow(() =>
|
|
validateStyleScopedDependencies([neitherImport], [neitherImport]),
|
|
);
|
|
});
|