@primitiv-ui/react 0.1.18 → 0.1.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/react",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Headless, accessible React components built on the WAI-ARIA authoring patterns. Zero styles ship with this package — bring your own (CSS, Tailwind, CSS-in-JS, design tokens).",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -0,0 +1,66 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, resolve } from "node:path";
4
+
5
+ import { render, screen } from "@testing-library/react";
6
+
7
+ import { Divider } from "../Divider";
8
+
9
+ /**
10
+ * Auto-verification of the Divider styling contract (RFC 0004 §3.4 / D15). The
11
+ * `data-*` half of `registry/components/divider/contract.json` is *derived from
12
+ * and asserted against the rendered headless component* so it can never drift
13
+ * from what the component actually emits. Divider has **no** data-* surface — it
14
+ * styles off `aria-orientation` — so the guard also pins that hook: the registry
15
+ * stylesheet's `[aria-orientation="…"]` selectors depend on it.
16
+ */
17
+ const contractPath = resolve(
18
+ dirname(fileURLToPath(import.meta.url)),
19
+ "../../../../../registry/components/divider/contract.json",
20
+ );
21
+
22
+ const contract = JSON.parse(readFileSync(contractPath, "utf8"));
23
+
24
+ /** Every `data-*` attribute name present on an element. */
25
+ function dataAttributeNames(element: Element): string[] {
26
+ return [...element.attributes]
27
+ .map((attribute) => attribute.name)
28
+ .filter((name) => name.startsWith("data-"))
29
+ .sort();
30
+ }
31
+
32
+ describe("Divider styling contract", () => {
33
+ const autoAttributes = contract.dataAttributes.filter(
34
+ (attribute: { source: string }) => attribute.source === "auto",
35
+ );
36
+
37
+ it("declares every data-* attribute as auto-verified against the component", () => {
38
+ expect(autoAttributes).toHaveLength(contract.dataAttributes.length);
39
+ });
40
+
41
+ it("emits no data-* attributes in either orientation", () => {
42
+ const { rerender } = render(<Divider />);
43
+ const emitted = new Set(dataAttributeNames(screen.getByRole("separator")));
44
+
45
+ rerender(<Divider orientation="vertical" />);
46
+ for (const name of dataAttributeNames(screen.getByRole("separator"))) {
47
+ emitted.add(name);
48
+ }
49
+
50
+ expect([...emitted]).toEqual([]);
51
+ });
52
+
53
+ it("exposes the orientation through aria-orientation — the styling hook", () => {
54
+ const { rerender } = render(<Divider />);
55
+ expect(screen.getByRole("separator")).toHaveAttribute(
56
+ "aria-orientation",
57
+ "horizontal",
58
+ );
59
+
60
+ rerender(<Divider orientation="vertical" />);
61
+ expect(screen.getByRole("separator")).toHaveAttribute(
62
+ "aria-orientation",
63
+ "vertical",
64
+ );
65
+ });
66
+ });
@@ -0,0 +1,80 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, resolve } from "node:path";
4
+
5
+ import { render } from "@testing-library/react";
6
+
7
+ import { Table } from "../Table";
8
+
9
+ /**
10
+ * Auto-verification of the Table styling contract (RFC 0004 §3.4 / D15). The
11
+ * `data-*` half of `registry/components/table/contract.json` is *derived from and
12
+ * asserted against the rendered headless component* so it can never drift from
13
+ * what the component actually emits. Table is a **static** structural compound:
14
+ * it carries no state, so neither the root nor any part emits a `data-*` — the
15
+ * guard pins that emptiness across every part.
16
+ */
17
+ const contractPath = resolve(
18
+ dirname(fileURLToPath(import.meta.url)),
19
+ "../../../../../registry/components/table/contract.json",
20
+ );
21
+
22
+ const contract = JSON.parse(readFileSync(contractPath, "utf8"));
23
+
24
+ type DataAttribute = { name: string; value: string; when: string; source: string };
25
+
26
+ /** Every `data-*` attribute name present anywhere in a subtree. */
27
+ function dataAttributeNames(root: Element): string[] {
28
+ return [
29
+ ...new Set(
30
+ [root, ...root.querySelectorAll("*")].flatMap((element) =>
31
+ [...element.attributes]
32
+ .map((attribute) => attribute.name)
33
+ .filter((name) => name.startsWith("data-")),
34
+ ),
35
+ ),
36
+ ].sort();
37
+ }
38
+
39
+ function renderTable() {
40
+ return render(
41
+ <Table.Root>
42
+ <Table.Caption>Team</Table.Caption>
43
+ <Table.Head>
44
+ <Table.Row>
45
+ <Table.Header scope="col">Name</Table.Header>
46
+ <Table.Header scope="col">Role</Table.Header>
47
+ </Table.Row>
48
+ </Table.Head>
49
+ <Table.Body>
50
+ <Table.Row aria-selected="true">
51
+ <Table.Cell>Alice</Table.Cell>
52
+ <Table.Cell>Engineer</Table.Cell>
53
+ </Table.Row>
54
+ </Table.Body>
55
+ <Table.Footer>
56
+ <Table.Row>
57
+ <Table.Cell>Total</Table.Cell>
58
+ <Table.Cell>1</Table.Cell>
59
+ </Table.Row>
60
+ </Table.Footer>
61
+ </Table.Root>,
62
+ );
63
+ }
64
+
65
+ describe("Table styling contract", () => {
66
+ it("declares every data-* attribute on every part as auto-verified", () => {
67
+ const all: DataAttribute[] = [
68
+ ...contract.dataAttributes,
69
+ ...contract.subcomponents.flatMap(
70
+ (sub: { dataAttributes: DataAttribute[] }) => sub.dataAttributes,
71
+ ),
72
+ ];
73
+ expect(all.every((attribute) => attribute.source === "auto")).toBe(true);
74
+ });
75
+
76
+ it("emits no data-* attributes on the root or any part", () => {
77
+ const { container } = renderTable();
78
+ expect(dataAttributeNames(container.firstElementChild as Element)).toEqual([]);
79
+ });
80
+ });