@stratum-hq/react 0.2.1 → 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.
Files changed (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +82 -0
  3. package/dist/__tests__/ConfigEditor.test.d.ts +2 -0
  4. package/dist/__tests__/ConfigEditor.test.d.ts.map +1 -0
  5. package/dist/__tests__/ConfigEditor.test.js +111 -0
  6. package/dist/__tests__/ConfigEditor.test.js.map +1 -0
  7. package/dist/__tests__/PermissionEditor.test.d.ts +2 -0
  8. package/dist/__tests__/PermissionEditor.test.d.ts.map +1 -0
  9. package/dist/__tests__/PermissionEditor.test.js +123 -0
  10. package/dist/__tests__/PermissionEditor.test.js.map +1 -0
  11. package/dist/__tests__/TenantSwitcher.test.d.ts +2 -0
  12. package/dist/__tests__/TenantSwitcher.test.d.ts.map +1 -0
  13. package/dist/__tests__/TenantSwitcher.test.js +76 -0
  14. package/dist/__tests__/TenantSwitcher.test.js.map +1 -0
  15. package/dist/__tests__/TenantTree.test.d.ts +2 -0
  16. package/dist/__tests__/TenantTree.test.d.ts.map +1 -0
  17. package/dist/__tests__/TenantTree.test.js +94 -0
  18. package/dist/__tests__/TenantTree.test.js.map +1 -0
  19. package/dist/__tests__/Toast.test.d.ts +2 -0
  20. package/dist/__tests__/Toast.test.d.ts.map +1 -0
  21. package/dist/__tests__/Toast.test.js +32 -0
  22. package/dist/__tests__/Toast.test.js.map +1 -0
  23. package/dist/__tests__/setup.d.ts +2 -0
  24. package/dist/__tests__/setup.d.ts.map +1 -0
  25. package/dist/__tests__/setup.js +2 -0
  26. package/dist/__tests__/setup.js.map +1 -0
  27. package/dist/components/ConfigDriftIndicator.d.ts +9 -0
  28. package/dist/components/ConfigDriftIndicator.d.ts.map +1 -0
  29. package/dist/components/ConfigDriftIndicator.js +24 -0
  30. package/dist/components/ConfigDriftIndicator.js.map +1 -0
  31. package/dist/components/EmptyState.d.ts +12 -0
  32. package/dist/components/EmptyState.d.ts.map +1 -0
  33. package/dist/components/EmptyState.js +5 -0
  34. package/dist/components/EmptyState.js.map +1 -0
  35. package/dist/components/TenantHealthCard.d.ts +15 -0
  36. package/dist/components/TenantHealthCard.d.ts.map +1 -0
  37. package/dist/components/TenantHealthCard.js +12 -0
  38. package/dist/components/TenantHealthCard.js.map +1 -0
  39. package/dist/components/TenantThemeProvider.d.ts +14 -0
  40. package/dist/components/TenantThemeProvider.d.ts.map +1 -0
  41. package/dist/components/TenantThemeProvider.js +19 -0
  42. package/dist/components/TenantThemeProvider.js.map +1 -0
  43. package/dist/index.d.ts +8 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +4 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/styles/default.css +1143 -0
  48. package/package.json +21 -2
  49. package/dist/components/ConfigEditor.stories.d.ts +0 -35
  50. package/dist/components/ConfigEditor.stories.d.ts.map +0 -1
  51. package/dist/components/ConfigEditor.stories.js +0 -73
  52. package/dist/components/ConfigEditor.stories.js.map +0 -1
  53. package/dist/components/PermissionEditor.stories.d.ts +0 -34
  54. package/dist/components/PermissionEditor.stories.d.ts.map +0 -1
  55. package/dist/components/PermissionEditor.stories.js +0 -90
  56. package/dist/components/PermissionEditor.stories.js.map +0 -1
  57. package/dist/components/Skeleton.stories.d.ts +0 -46
  58. package/dist/components/Skeleton.stories.d.ts.map +0 -1
  59. package/dist/components/Skeleton.stories.js +0 -125
  60. package/dist/components/Skeleton.stories.js.map +0 -1
  61. package/dist/components/TenantSwitcher.stories.d.ts +0 -30
  62. package/dist/components/TenantSwitcher.stories.d.ts.map +0 -1
  63. package/dist/components/TenantSwitcher.stories.js +0 -76
  64. package/dist/components/TenantSwitcher.stories.js.map +0 -1
  65. package/dist/components/TenantTree.stories.d.ts +0 -28
  66. package/dist/components/TenantTree.stories.d.ts.map +0 -1
  67. package/dist/components/TenantTree.stories.js +0 -71
  68. package/dist/components/TenantTree.stories.js.map +0 -1
  69. package/dist/components/Toast.stories.d.ts +0 -36
  70. package/dist/components/Toast.stories.d.ts.map +0 -1
  71. package/dist/components/Toast.stories.js +0 -105
  72. package/dist/components/Toast.stories.js.map +0 -1
  73. package/dist/components/storybook-helpers.d.ts +0 -96
  74. package/dist/components/storybook-helpers.d.ts.map +0 -1
  75. package/dist/components/storybook-helpers.js +0 -473
  76. package/dist/components/storybook-helpers.js.map +0 -1
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":"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"}
@@ -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"}
@@ -0,0 +1,9 @@
1
+ export type ConfigDriftStatus = "ok" | "override" | "missing" | "conflict";
2
+ export interface ConfigDriftIndicatorProps {
3
+ status: ConfigDriftStatus;
4
+ label?: string;
5
+ size?: "sm" | "md";
6
+ className?: string;
7
+ }
8
+ export declare function ConfigDriftIndicator({ status, label, size, className, }: ConfigDriftIndicatorProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=ConfigDriftIndicator.d.ts.map