@stratum-hq/react 0.2.2 → 0.3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Crank
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @stratum-hq/react
2
+
3
+ React components and hooks for building multi-tenant administration UIs on top of [Stratum](https://github.com/stratum-hq/Stratum) — tenant switching, hierarchy visualization, and config/permission editing.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @stratum-hq/react @stratum-hq/core react react-dom
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ Wrap your app in `StratumProvider`, then drop in the components and hooks:
14
+
15
+ ```tsx
16
+ import {
17
+ StratumProvider,
18
+ useStratum,
19
+ TenantSwitcher,
20
+ TenantTree,
21
+ ConfigEditor,
22
+ PermissionEditor,
23
+ } from "@stratum-hq/react";
24
+
25
+ function App() {
26
+ return (
27
+ <StratumProvider controlPlaneUrl="http://localhost:3001" apiKey="sk_live_your_key">
28
+ <Layout />
29
+ </StratumProvider>
30
+ );
31
+ }
32
+
33
+ function Layout() {
34
+ const { currentTenant, tenantContext, loading } = useStratum();
35
+ if (loading) return <p>Loading...</p>;
36
+ if (!currentTenant) return <p>Select a tenant.</p>;
37
+
38
+ return (
39
+ <div style={{ display: "flex" }}>
40
+ <aside style={{ width: 240 }}>
41
+ <TenantSwitcher />
42
+ <TenantTree />
43
+ </aside>
44
+ <main style={{ flex: 1 }}>
45
+ <h1>{currentTenant.name}</h1>
46
+ <ConfigEditor />
47
+ <PermissionEditor />
48
+ </main>
49
+ </div>
50
+ );
51
+ }
52
+ ```
53
+
54
+ The provider creates a `StratumClient` internally and manages the current tenant state.
55
+
56
+ ## Components & Hooks
57
+
58
+ - **`StratumProvider`** — context provider; also exposes `TenantThemeProvider` for per-tenant theming.
59
+ - **`useStratum()`** — `{ currentTenant, tenantContext, loading, error, switchTenant, apiCall }`.
60
+ - **Data hooks** — `useTenant`, `useTenantTree`, `useConfig`, `usePermissions`, `useConfigCascade`, `useWebhooks`, `useAuditLogs`, `useToast`.
61
+ - **`TenantSwitcher`** — dropdown to select the active tenant.
62
+ - **`TenantTree` / `DraggableTenantTree`** — hierarchical tree view (drag-to-reparent in the draggable variant).
63
+ - **`ConfigEditor`** — edit resolved config with lock and inheritance indicators.
64
+ - **`PermissionEditor`** — edit permission policies with mode/revocation selection.
65
+ - **`ConfigInheritanceVisualizer`**, **`WebhookEditor`**, **`AuditLogViewer`**, **`TenantHealthCard`**, plus headless (`HeadlessTenantSwitcher`, …) variants for full styling control.
66
+
67
+ ## Scaffolding
68
+
69
+ Generate integration boilerplate (provider, guards, hooks) with the CLI:
70
+
71
+ ```bash
72
+ npx @stratum-hq/cli scaffold react --out src/components
73
+ ```
74
+
75
+ ## Links
76
+
77
+ - Documentation: https://docs.stratum-hq.org/packages/react/
78
+ - GitHub: https://github.com/stratum-hq/Stratum
79
+
80
+ ## License
81
+
82
+ MIT © Christian Crank
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ConfigEditor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigEditor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ConfigEditor.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, within, fireEvent, cleanup, waitFor } from "@testing-library/react";
3
+ import { describe, it, expect, vi, afterEach } from "vitest";
4
+ import { StratumContext } from "../provider.js";
5
+ import { ConfigEditor } from "../components/ConfigEditor.js";
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+ const mockTenant = {
10
+ id: "tenant-1",
11
+ name: "Acme Corp",
12
+ slug: "acme-corp",
13
+ status: "active",
14
+ parent_id: null,
15
+ ancestry_path: "tenant-1",
16
+ depth: 0,
17
+ isolation_strategy: "SHARED_RLS",
18
+ config: {},
19
+ metadata: {},
20
+ sort_order: 0,
21
+ deleted_at: null,
22
+ created_at: "2024-01-01T00:00:00Z",
23
+ updated_at: "2024-01-01T00:00:00Z",
24
+ };
25
+ const mockConfigResponse = {
26
+ "feature_flag": {
27
+ value: true,
28
+ source_tenant_id: "tenant-1",
29
+ inherited: false,
30
+ locked: false,
31
+ },
32
+ "max_users": {
33
+ value: 100,
34
+ source_tenant_id: "tenant-parent-1",
35
+ inherited: true,
36
+ locked: true,
37
+ },
38
+ };
39
+ const mockApiCall = vi.fn().mockResolvedValue(mockConfigResponse);
40
+ const mockContextValue = {
41
+ currentTenant: mockTenant,
42
+ tenantContext: null,
43
+ loading: false,
44
+ error: null,
45
+ switchTenant: vi.fn().mockResolvedValue(undefined),
46
+ apiCall: mockApiCall,
47
+ messages: {
48
+ "configEditor.error": "Error: {message}",
49
+ "configEditor.columnKey": "Key",
50
+ "configEditor.columnValue": "Value",
51
+ "configEditor.columnSource": "Source",
52
+ "configEditor.columnStatus": "Status",
53
+ "configEditor.columnActions": "Actions",
54
+ "configEditor.locked": "Locked",
55
+ "configEditor.inherited": "Inherited",
56
+ "configEditor.own": "Own",
57
+ "configEditor.editButton": "Edit",
58
+ "configEditor.removeButton": "Remove",
59
+ "configEditor.saveButton": "Save",
60
+ "configEditor.cancelButton": "Cancel",
61
+ "configEditor.editLabel": "Edit {key}",
62
+ "configEditor.keyPlaceholder": "Key",
63
+ "configEditor.valuePlaceholder": "Value",
64
+ "configEditor.keyLabel": "New key",
65
+ "configEditor.valueLabel": "New value",
66
+ "configEditor.addButton": "Add",
67
+ },
68
+ toast: {
69
+ success: vi.fn(),
70
+ error: vi.fn(),
71
+ warning: vi.fn(),
72
+ info: vi.fn(),
73
+ },
74
+ };
75
+ function renderWithContext(ui) {
76
+ return render(_jsx(StratumContext.Provider, { value: mockContextValue, children: ui }));
77
+ }
78
+ describe("ConfigEditor", () => {
79
+ it("renders without crashing", async () => {
80
+ const { container } = renderWithContext(_jsx(ConfigEditor, {}));
81
+ await waitFor(() => {
82
+ expect(within(container).getByRole("table")).toBeInTheDocument();
83
+ });
84
+ expect(container.querySelector(".stratum-config-editor")).toBeInTheDocument();
85
+ });
86
+ it("renders a table with config columns", async () => {
87
+ const { container } = renderWithContext(_jsx(ConfigEditor, {}));
88
+ await waitFor(() => {
89
+ expect(within(container).getByRole("table")).toBeInTheDocument();
90
+ });
91
+ });
92
+ it("renders add button disabled when key is empty", async () => {
93
+ const { container } = renderWithContext(_jsx(ConfigEditor, {}));
94
+ await waitFor(() => {
95
+ expect(within(container).getByRole("button", { name: /add/i })).toBeInTheDocument();
96
+ });
97
+ const addButton = within(container).getByRole("button", { name: /add/i });
98
+ expect(addButton).toBeDisabled();
99
+ });
100
+ it("enables add button when a key is typed", async () => {
101
+ const { container } = renderWithContext(_jsx(ConfigEditor, {}));
102
+ await waitFor(() => {
103
+ expect(within(container).getByRole("textbox", { name: /new key/i })).toBeInTheDocument();
104
+ });
105
+ const keyInput = within(container).getByRole("textbox", { name: /new key/i });
106
+ fireEvent.change(keyInput, { target: { value: "my_config_key" } });
107
+ const addButton = within(container).getByRole("button", { name: /add/i });
108
+ expect(addButton).not.toBeDisabled();
109
+ });
110
+ });
111
+ //# sourceMappingURL=ConfigEditor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigEditor.test.js","sourceRoot":"","sources":["../../src/__tests__/ConfigEditor.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=PermissionEditor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionEditor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/PermissionEditor.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, within, fireEvent, cleanup, waitFor } from "@testing-library/react";
3
+ import { describe, it, expect, vi, afterEach } from "vitest";
4
+ import { StratumContext } from "../provider.js";
5
+ import { PermissionEditor } from "../components/PermissionEditor.js";
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+ const mockTenant = {
10
+ id: "tenant-1",
11
+ name: "Acme Corp",
12
+ slug: "acme-corp",
13
+ status: "active",
14
+ parent_id: null,
15
+ ancestry_path: "tenant-1",
16
+ depth: 0,
17
+ isolation_strategy: "SHARED_RLS",
18
+ config: {},
19
+ metadata: {},
20
+ sort_order: 0,
21
+ deleted_at: null,
22
+ created_at: "2024-01-01T00:00:00Z",
23
+ updated_at: "2024-01-01T00:00:00Z",
24
+ };
25
+ const mockPermissionsResponse = {
26
+ "can_export": {
27
+ key: "can_export",
28
+ value: true,
29
+ mode: "INHERITED",
30
+ source_tenant_id: "tenant-parent-1",
31
+ inherited: true,
32
+ locked: false,
33
+ delegated: false,
34
+ revocation_mode: "CASCADE",
35
+ },
36
+ "can_delete": {
37
+ key: "can_delete",
38
+ value: false,
39
+ mode: "LOCKED",
40
+ source_tenant_id: "tenant-1",
41
+ inherited: false,
42
+ locked: true,
43
+ delegated: false,
44
+ revocation_mode: "PERMANENT",
45
+ },
46
+ };
47
+ const mockApiCall = vi.fn().mockResolvedValue(mockPermissionsResponse);
48
+ const mockContextValue = {
49
+ currentTenant: mockTenant,
50
+ tenantContext: null,
51
+ loading: false,
52
+ error: null,
53
+ switchTenant: vi.fn().mockResolvedValue(undefined),
54
+ apiCall: mockApiCall,
55
+ messages: {
56
+ "permissionEditor.error": "Error: {message}",
57
+ "permissionEditor.columnKey": "Key",
58
+ "permissionEditor.columnValue": "Value",
59
+ "permissionEditor.columnMode": "Mode",
60
+ "permissionEditor.columnSource": "Source",
61
+ "permissionEditor.columnStatus": "Status",
62
+ "permissionEditor.columnActions": "Actions",
63
+ "permissionEditor.locked": "Locked",
64
+ "permissionEditor.delegated": "Delegated",
65
+ "permissionEditor.removeButton": "Remove",
66
+ "permissionEditor.keyPlaceholder": "Permission key",
67
+ "permissionEditor.keyLabel": "New permission key",
68
+ "permissionEditor.modeLabel": "Mode",
69
+ "permissionEditor.revocationModeLabel": "Revocation mode",
70
+ "permissionEditor.addButton": "Add",
71
+ },
72
+ toast: {
73
+ success: vi.fn(),
74
+ error: vi.fn(),
75
+ warning: vi.fn(),
76
+ info: vi.fn(),
77
+ },
78
+ };
79
+ function renderWithContext(ui) {
80
+ return render(_jsx(StratumContext.Provider, { value: mockContextValue, children: ui }));
81
+ }
82
+ describe("PermissionEditor", () => {
83
+ it("renders without crashing", async () => {
84
+ const { container } = renderWithContext(_jsx(PermissionEditor, {}));
85
+ await waitFor(() => {
86
+ expect(within(container).getByRole("table")).toBeInTheDocument();
87
+ });
88
+ expect(container.querySelector(".stratum-permission-editor")).toBeInTheDocument();
89
+ });
90
+ it("renders a table with permission columns", async () => {
91
+ const { container } = renderWithContext(_jsx(PermissionEditor, {}));
92
+ await waitFor(() => {
93
+ expect(within(container).getByRole("table")).toBeInTheDocument();
94
+ });
95
+ });
96
+ it("renders mode select with default INHERITED option", async () => {
97
+ const { container } = renderWithContext(_jsx(PermissionEditor, {}));
98
+ await waitFor(() => {
99
+ expect(within(container).getByRole("combobox", { name: /^mode$/i })).toBeInTheDocument();
100
+ });
101
+ const modeSelect = within(container).getByRole("combobox", { name: /^mode$/i });
102
+ expect(modeSelect).toHaveValue("INHERITED");
103
+ });
104
+ it("renders add button disabled when key is empty", async () => {
105
+ const { container } = renderWithContext(_jsx(PermissionEditor, {}));
106
+ await waitFor(() => {
107
+ expect(within(container).getByRole("button", { name: /add/i })).toBeInTheDocument();
108
+ });
109
+ const addButton = within(container).getByRole("button", { name: /add/i });
110
+ expect(addButton).toBeDisabled();
111
+ });
112
+ it("enables add button when a key is typed", async () => {
113
+ const { container } = renderWithContext(_jsx(PermissionEditor, {}));
114
+ await waitFor(() => {
115
+ expect(within(container).getByRole("textbox", { name: /new permission key/i })).toBeInTheDocument();
116
+ });
117
+ const keyInput = within(container).getByRole("textbox", { name: /new permission key/i });
118
+ fireEvent.change(keyInput, { target: { value: "can_read" } });
119
+ const addButton = within(container).getByRole("button", { name: /add/i });
120
+ expect(addButton).not.toBeDisabled();
121
+ });
122
+ });
123
+ //# sourceMappingURL=PermissionEditor.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PermissionEditor.test.js","sourceRoot":"","sources":["../../src/__tests__/PermissionEditor.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TenantSwitcher.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantSwitcher.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/TenantSwitcher.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, within, fireEvent, cleanup, waitFor } from "@testing-library/react";
3
+ import { describe, it, expect, vi, afterEach } from "vitest";
4
+ import { StratumContext } from "../provider.js";
5
+ import { TenantSwitcher } from "../components/TenantSwitcher.js";
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+ const mockTenant = {
10
+ id: "tenant-1",
11
+ name: "Acme Corp",
12
+ slug: "acme-corp",
13
+ status: "active",
14
+ parent_id: null,
15
+ ancestry_path: "tenant-1",
16
+ depth: 0,
17
+ isolation_strategy: "SHARED_RLS",
18
+ config: {},
19
+ metadata: {},
20
+ sort_order: 0,
21
+ deleted_at: null,
22
+ created_at: "2024-01-01T00:00:00Z",
23
+ updated_at: "2024-01-01T00:00:00Z",
24
+ };
25
+ const mockApiCall = vi.fn().mockResolvedValue({ data: [mockTenant] });
26
+ const mockSwitchTenant = vi.fn().mockResolvedValue(undefined);
27
+ const mockContextValue = {
28
+ currentTenant: mockTenant,
29
+ tenantContext: null,
30
+ loading: false,
31
+ error: null,
32
+ switchTenant: mockSwitchTenant,
33
+ apiCall: mockApiCall,
34
+ messages: {
35
+ "tenantSwitcher.loading": "Loading...",
36
+ "tenantSwitcher.placeholder": "Select tenant",
37
+ "tenantSwitcher.searchPlaceholder": "Search tenants...",
38
+ "tenantSwitcher.searchLabel": "Search tenants",
39
+ },
40
+ toast: {
41
+ success: vi.fn(),
42
+ error: vi.fn(),
43
+ warning: vi.fn(),
44
+ info: vi.fn(),
45
+ },
46
+ };
47
+ function renderWithContext(ui) {
48
+ return render(_jsx(StratumContext.Provider, { value: mockContextValue, children: ui }));
49
+ }
50
+ describe("TenantSwitcher", () => {
51
+ it("renders without crashing", async () => {
52
+ const { container } = renderWithContext(_jsx(TenantSwitcher, {}));
53
+ // Wait for loading state to clear
54
+ await waitFor(() => {
55
+ expect(within(container).queryByText("Loading...")).not.toBeInTheDocument();
56
+ });
57
+ expect(container.querySelector(".stratum-tenant-switcher")).toBeInTheDocument();
58
+ });
59
+ it("displays current tenant name", async () => {
60
+ const { container } = renderWithContext(_jsx(TenantSwitcher, {}));
61
+ await waitFor(() => {
62
+ expect(within(container).queryByText("Loading...")).not.toBeInTheDocument();
63
+ });
64
+ expect(within(container).getByText(/Acme Corp/)).toBeInTheDocument();
65
+ });
66
+ it("opens dropdown when trigger button is clicked", async () => {
67
+ const { container } = renderWithContext(_jsx(TenantSwitcher, {}));
68
+ await waitFor(() => {
69
+ expect(within(container).queryByText("Loading...")).not.toBeInTheDocument();
70
+ });
71
+ const trigger = within(container).getByRole("button");
72
+ fireEvent.click(trigger);
73
+ expect(within(container).getByRole("listbox")).toBeInTheDocument();
74
+ });
75
+ });
76
+ //# sourceMappingURL=TenantSwitcher.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantSwitcher.test.js","sourceRoot":"","sources":["../../src/__tests__/TenantSwitcher.test.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TenantTree.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantTree.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/TenantTree.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,94 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, within, fireEvent, cleanup, waitFor } from "@testing-library/react";
3
+ import { describe, it, expect, vi, afterEach } from "vitest";
4
+ import { StratumContext } from "../provider.js";
5
+ import { TenantTree } from "../components/TenantTree.js";
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+ const mockTenant = {
10
+ id: "tenant-1",
11
+ name: "Root Tenant",
12
+ slug: "root-tenant",
13
+ status: "active",
14
+ parent_id: null,
15
+ ancestry_path: "tenant-1",
16
+ depth: 0,
17
+ isolation_strategy: "SHARED_RLS",
18
+ config: {},
19
+ metadata: {},
20
+ sort_order: 0,
21
+ deleted_at: null,
22
+ created_at: "2024-01-01T00:00:00Z",
23
+ updated_at: "2024-01-01T00:00:00Z",
24
+ };
25
+ const childTenant = {
26
+ id: "tenant-2",
27
+ name: "Child Tenant",
28
+ slug: "child-tenant",
29
+ status: "active",
30
+ parent_id: "tenant-1",
31
+ ancestry_path: "/tenant-1/tenant-2",
32
+ depth: 1,
33
+ isolation_strategy: "SHARED_RLS",
34
+ config: {},
35
+ metadata: {},
36
+ sort_order: 0,
37
+ deleted_at: null,
38
+ created_at: "2024-01-01T00:00:00Z",
39
+ updated_at: "2024-01-01T00:00:00Z",
40
+ };
41
+ const mockApiCall = vi.fn().mockResolvedValue({ data: [mockTenant, childTenant] });
42
+ const mockContextValue = {
43
+ currentTenant: mockTenant,
44
+ tenantContext: null,
45
+ loading: false,
46
+ error: null,
47
+ switchTenant: vi.fn().mockResolvedValue(undefined),
48
+ apiCall: mockApiCall,
49
+ messages: {
50
+ "tenantTree.loading": "Loading...",
51
+ "tenantTree.error": "Error: {message}",
52
+ "tenantTree.collapse": "Collapse",
53
+ "tenantTree.expand": "Expand",
54
+ "tenantTree.badgeRls": "RLS",
55
+ "tenantTree.archived": "Archived",
56
+ },
57
+ toast: {
58
+ success: vi.fn(),
59
+ error: vi.fn(),
60
+ warning: vi.fn(),
61
+ info: vi.fn(),
62
+ },
63
+ };
64
+ function renderWithContext(ui) {
65
+ return render(_jsx(StratumContext.Provider, { value: mockContextValue, children: ui }));
66
+ }
67
+ describe("TenantTree", () => {
68
+ it("renders without crashing", async () => {
69
+ const { container } = renderWithContext(_jsx(TenantTree, {}));
70
+ await waitFor(() => {
71
+ expect(within(container).queryByText("Loading...")).not.toBeInTheDocument();
72
+ });
73
+ expect(container.querySelector(".stratum-tree")).toBeInTheDocument();
74
+ });
75
+ it("renders a tree role element", async () => {
76
+ const { container } = renderWithContext(_jsx(TenantTree, {}));
77
+ await waitFor(() => {
78
+ expect(within(container).getByRole("tree")).toBeInTheDocument();
79
+ });
80
+ });
81
+ it("calls onSelect when a tenant label is clicked", async () => {
82
+ const onSelect = vi.fn();
83
+ const { container } = renderWithContext(_jsx(TenantTree, { onSelect: onSelect }));
84
+ await waitFor(() => {
85
+ expect(within(container).queryByText("Loading...")).not.toBeInTheDocument();
86
+ });
87
+ const labels = within(container).queryAllByText("Root Tenant");
88
+ if (labels.length > 0) {
89
+ fireEvent.click(labels[0]);
90
+ expect(onSelect).toHaveBeenCalledWith("tenant-1");
91
+ }
92
+ });
93
+ });
94
+ //# sourceMappingURL=TenantTree.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantTree.test.js","sourceRoot":"","sources":["../../src/__tests__/TenantTree.test.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACrF,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,cAAc,EAA4B,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAEzD,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,GAAG;IACjB,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,aAAa;IACnB,MAAM,EAAE,QAAiB;IACzB,SAAS,EAAE,IAAI;IACf,aAAa,EAAE,UAAU;IACzB,KAAK,EAAE,CAAC;IACR,kBAAkB,EAAE,YAAqB;IACzC,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,sBAAsB;CACnC,CAAC;AAEF,MAAM,WAAW,GAAG;IAClB,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,cAAc;IACpB,MAAM,EAAE,QAAiB;IACzB,SAAS,EAAE,UAAU;IACrB,aAAa,EAAE,oBAAoB;IACnC,KAAK,EAAE,CAAC;IACR,kBAAkB,EAAE,YAAqB;IACzC,MAAM,EAAE,EAAE;IACV,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,CAAC;IACb,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,sBAAsB;IAClC,UAAU,EAAE,sBAAsB;CACnC,CAAC;AAEF,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,CAAC,CAAC;AAEnF,MAAM,gBAAgB,GAAwB;IAC5C,aAAa,EAAE,UAAU;IACzB,aAAa,EAAE,IAAI;IACnB,OAAO,EAAE,KAAK;IACd,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;IAClD,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE;QACR,oBAAoB,EAAE,YAAY;QAClC,kBAAkB,EAAE,kBAAkB;QACtC,qBAAqB,EAAE,UAAU;QACjC,mBAAmB,EAAE,QAAQ;QAC7B,qBAAqB,EAAE,KAAK;QAC5B,qBAAqB,EAAE,UAAU;KAClC;IACD,KAAK,EAAE;QACL,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;QAChB,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE;QACd,OAAO,EAAE,EAAE,CAAC,EAAE,EAAE;QAChB,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE;KACd;CACF,CAAC;AAEF,SAAS,iBAAiB,CAAC,EAAsB;IAC/C,OAAO,MAAM,CACX,KAAC,cAAc,CAAC,QAAQ,IAAC,KAAK,EAAE,gBAAgB,YAC7C,EAAE,GACqB,CAC3B,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,KAAC,UAAU,KAAG,CAAC,CAAC;QACxD,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC9E,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,KAAC,UAAU,KAAG,CAAC,CAAC;QACxD,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;QAClE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,CAAC,KAAC,UAAU,IAAC,QAAQ,EAAE,QAAQ,GAAI,CAAC,CAAC;QAC5E,MAAM,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;QAC9E,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC3B,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;QACpD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Toast.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/Toast.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render, within, fireEvent, cleanup } from "@testing-library/react";
3
+ import { describe, it, expect, vi, afterEach } from "vitest";
4
+ import { Toast } from "../components/Toast.js";
5
+ afterEach(() => {
6
+ cleanup();
7
+ });
8
+ describe("Toast", () => {
9
+ it("renders without crashing", () => {
10
+ const onDismiss = vi.fn();
11
+ const { container } = render(_jsx(Toast, { message: "Operation successful", type: "success", onDismiss: onDismiss, autoDismiss: 0 }));
12
+ expect(container.querySelector(".stratum-toast")).toBeInTheDocument();
13
+ expect(container.textContent).toContain("Operation successful");
14
+ });
15
+ it("displays the message text", () => {
16
+ const onDismiss = vi.fn();
17
+ const { getByText } = render(_jsx(Toast, { message: "Something went wrong", type: "error", onDismiss: onDismiss }));
18
+ expect(getByText("Something went wrong")).toBeInTheDocument();
19
+ });
20
+ it("calls onDismiss when dismiss button is clicked", () => {
21
+ const onDismiss = vi.fn();
22
+ const { container } = render(_jsx(Toast, { message: "Info message", type: "info", onDismiss: onDismiss, autoDismiss: 0 }));
23
+ fireEvent.click(within(container).getByRole("button", { name: /dismiss/i }));
24
+ expect(onDismiss).toHaveBeenCalledTimes(1);
25
+ });
26
+ it("renders error type with correct role", () => {
27
+ const onDismiss = vi.fn();
28
+ const { container } = render(_jsx(Toast, { message: "Error occurred", type: "error", onDismiss: onDismiss }));
29
+ expect(within(container).getByRole("alert")).toBeInTheDocument();
30
+ });
31
+ });
32
+ //# sourceMappingURL=Toast.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toast.test.js","sourceRoot":"","sources":["../../src/__tests__/Toast.test.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAC5E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AAE/C,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;IACrB,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,KAAK,IAAC,OAAO,EAAC,sBAAsB,EAAC,IAAI,EAAC,SAAS,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,GAAI,CAC9F,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;QACtE,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,KAAK,IAAC,OAAO,EAAC,sBAAsB,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAE,SAAS,GAAI,CAC5E,CAAC;QACF,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gDAAgD,EAAE,GAAG,EAAE;QACxD,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,KAAK,IAAC,OAAO,EAAC,cAAc,EAAC,IAAI,EAAC,MAAM,EAAC,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,GAAI,CACnF,CAAC;QACF,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;QAC7E,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,CAC1B,KAAC,KAAK,IAAC,OAAO,EAAC,gBAAgB,EAAC,IAAI,EAAC,OAAO,EAAC,SAAS,EAAE,SAAS,GAAI,CACtE,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA,OAAO,kCAAkC,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,21 @@
1
1
  {
2
2
  "name": "@stratum-hq/react",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Stratum React UI components for multi-tenant administration",
6
+ "keywords": [
7
+ "multi-tenancy",
8
+ "multi-tenant",
9
+ "saas",
10
+ "tenant",
11
+ "react",
12
+ "ui",
13
+ "components",
14
+ "admin",
15
+ "dashboard",
16
+ "tenant-management",
17
+ "stratum"
18
+ ],
6
19
  "main": "./dist/index.js",
7
20
  "types": "./dist/index.d.ts",
8
21
  "exports": {
@@ -21,7 +34,7 @@
21
34
  "scripts": {
22
35
  "build": "tsc",
23
36
  "postbuild": "cp -r src/styles dist/styles",
24
- "test": "vitest run --passWithNoTests",
37
+ "test": "vitest run",
25
38
  "lint": "tsc --noEmit",
26
39
  "clean": "rm -rf dist",
27
40
  "storybook": "storybook dev -p 6006",
@@ -52,5 +65,10 @@
52
65
  "storybook": "^8.4.0",
53
66
  "typescript": "^5.4.0",
54
67
  "vitest": "^1.6.0"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "https://github.com/stratum-hq/Stratum.git",
72
+ "directory": "packages/react-ui"
55
73
  }
56
74
  }