@elevasis/core 0.36.0 → 0.37.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/dist/auth/index.d.ts +79 -0
- package/dist/index.d.ts +334 -3
- package/dist/index.js +21 -5
- package/dist/knowledge/index.d.ts +81 -0
- package/dist/organization-model/index.d.ts +334 -3
- package/dist/organization-model/index.js +21 -5
- package/dist/test-utils/index.d.ts +79 -0
- package/dist/test-utils/index.js +18 -3
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +67 -0
- package/src/organization-model/__tests__/domains/navigation-topbar.test.ts +282 -0
- package/src/organization-model/defaults.ts +2 -1
- package/src/organization-model/domains/navigation.ts +176 -139
- package/src/organization-model/icons.ts +1 -0
- package/src/organization-model/published.ts +6 -6
- package/src/organization-model/types.ts +5 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +67 -0
|
@@ -4046,6 +4046,84 @@ declare const OrganizationModelSchema: z.ZodObject<{
|
|
|
4046
4046
|
primary: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodType<SidebarNode, unknown, z.core.$ZodTypeInternals<SidebarNode, unknown>>>>;
|
|
4047
4047
|
bottom: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodType<SidebarNode, unknown, z.core.$ZodTypeInternals<SidebarNode, unknown>>>>;
|
|
4048
4048
|
}, z.core.$strip>>;
|
|
4049
|
+
topbar: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
4050
|
+
id: z.ZodString;
|
|
4051
|
+
label: z.ZodString;
|
|
4052
|
+
tooltip: z.ZodOptional<z.ZodString>;
|
|
4053
|
+
icon: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
|
|
4054
|
+
message: "message";
|
|
4055
|
+
error: "error";
|
|
4056
|
+
agent: "agent";
|
|
4057
|
+
workflow: "workflow";
|
|
4058
|
+
"google-sheets": "google-sheets";
|
|
4059
|
+
dashboard: "dashboard";
|
|
4060
|
+
calendar: "calendar";
|
|
4061
|
+
sales: "sales";
|
|
4062
|
+
crm: "crm";
|
|
4063
|
+
"lead-gen": "lead-gen";
|
|
4064
|
+
projects: "projects";
|
|
4065
|
+
clients: "clients";
|
|
4066
|
+
operations: "operations";
|
|
4067
|
+
monitoring: "monitoring";
|
|
4068
|
+
knowledge: "knowledge";
|
|
4069
|
+
settings: "settings";
|
|
4070
|
+
admin: "admin";
|
|
4071
|
+
archive: "archive";
|
|
4072
|
+
business: "business";
|
|
4073
|
+
finance: "finance";
|
|
4074
|
+
platform: "platform";
|
|
4075
|
+
seo: "seo";
|
|
4076
|
+
playbook: "playbook";
|
|
4077
|
+
strategy: "strategy";
|
|
4078
|
+
reference: "reference";
|
|
4079
|
+
integration: "integration";
|
|
4080
|
+
database: "database";
|
|
4081
|
+
user: "user";
|
|
4082
|
+
team: "team";
|
|
4083
|
+
gmail: "gmail";
|
|
4084
|
+
attio: "attio";
|
|
4085
|
+
overview: "overview";
|
|
4086
|
+
"command-view": "command-view";
|
|
4087
|
+
"command-queue": "command-queue";
|
|
4088
|
+
pipeline: "pipeline";
|
|
4089
|
+
lists: "lists";
|
|
4090
|
+
resources: "resources";
|
|
4091
|
+
approve: "approve";
|
|
4092
|
+
reject: "reject";
|
|
4093
|
+
retry: "retry";
|
|
4094
|
+
edit: "edit";
|
|
4095
|
+
view: "view";
|
|
4096
|
+
launch: "launch";
|
|
4097
|
+
"message-plus": "message-plus";
|
|
4098
|
+
escalate: "escalate";
|
|
4099
|
+
promote: "promote";
|
|
4100
|
+
submit: "submit";
|
|
4101
|
+
email: "email";
|
|
4102
|
+
success: "success";
|
|
4103
|
+
warning: "warning";
|
|
4104
|
+
info: "info";
|
|
4105
|
+
pending: "pending";
|
|
4106
|
+
bolt: "bolt";
|
|
4107
|
+
building: "building";
|
|
4108
|
+
briefcase: "briefcase";
|
|
4109
|
+
apps: "apps";
|
|
4110
|
+
graph: "graph";
|
|
4111
|
+
shield: "shield";
|
|
4112
|
+
users: "users";
|
|
4113
|
+
"chart-bar": "chart-bar";
|
|
4114
|
+
search: "search";
|
|
4115
|
+
}>, z.ZodString]>>;
|
|
4116
|
+
order: z.ZodOptional<z.ZodNumber>;
|
|
4117
|
+
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
4118
|
+
devOnly: z.ZodOptional<z.ZodBoolean>;
|
|
4119
|
+
requiresAdmin: z.ZodOptional<z.ZodBoolean>;
|
|
4120
|
+
targets: z.ZodOptional<z.ZodDefault<z.ZodObject<{
|
|
4121
|
+
systems: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
4122
|
+
entities: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
4123
|
+
resources: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
4124
|
+
actions: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString>>>;
|
|
4125
|
+
}, z.core.$strip>>>;
|
|
4126
|
+
}, z.core.$strip>>>;
|
|
4049
4127
|
}, z.core.$strip>>;
|
|
4050
4128
|
identity: z.ZodDefault<z.ZodObject<{
|
|
4051
4129
|
mission: z.ZodDefault<z.ZodString>;
|
|
@@ -4714,6 +4792,7 @@ declare const OrganizationModelSchema: z.ZodObject<{
|
|
|
4714
4792
|
edit: "edit";
|
|
4715
4793
|
view: "view";
|
|
4716
4794
|
launch: "launch";
|
|
4795
|
+
"message-plus": "message-plus";
|
|
4717
4796
|
escalate: "escalate";
|
|
4718
4797
|
promote: "promote";
|
|
4719
4798
|
submit: "submit";
|
package/dist/test-utils/index.js
CHANGED
|
@@ -19817,6 +19817,7 @@ var ORGANIZATION_MODEL_ICON_TOKENS = [
|
|
|
19817
19817
|
"view",
|
|
19818
19818
|
"launch",
|
|
19819
19819
|
"message",
|
|
19820
|
+
"message-plus",
|
|
19820
19821
|
"escalate",
|
|
19821
19822
|
"promote",
|
|
19822
19823
|
"submit",
|
|
@@ -20001,9 +20002,22 @@ var SidebarNavigationSchema = z.object({
|
|
|
20001
20002
|
primary: SidebarSectionSchema,
|
|
20002
20003
|
bottom: SidebarSectionSchema
|
|
20003
20004
|
}).default({ primary: {}, bottom: {} });
|
|
20005
|
+
var TopbarActionNodeSchema = z.object({
|
|
20006
|
+
id: ModelIdSchema,
|
|
20007
|
+
label: LabelSchema,
|
|
20008
|
+
tooltip: DescriptionSchema.optional(),
|
|
20009
|
+
icon: IconNameSchema.optional(),
|
|
20010
|
+
order: z.number().int().optional(),
|
|
20011
|
+
enabled: z.boolean().default(true),
|
|
20012
|
+
devOnly: z.boolean().optional(),
|
|
20013
|
+
requiresAdmin: z.boolean().optional(),
|
|
20014
|
+
targets: SidebarSurfaceTargetsSchema.optional()
|
|
20015
|
+
});
|
|
20016
|
+
var TopbarSectionSchema = z.record(z.string(), TopbarActionNodeSchema).default({});
|
|
20004
20017
|
var OrganizationModelNavigationSchema = z.object({
|
|
20005
|
-
sidebar: SidebarNavigationSchema
|
|
20006
|
-
|
|
20018
|
+
sidebar: SidebarNavigationSchema,
|
|
20019
|
+
topbar: TopbarSectionSchema
|
|
20020
|
+
}).default({ sidebar: { primary: {}, bottom: {} }, topbar: {} });
|
|
20007
20021
|
z.object({
|
|
20008
20022
|
id: ModelIdSchema,
|
|
20009
20023
|
label: LabelSchema,
|
|
@@ -21608,7 +21622,8 @@ var DEFAULT_ORGANIZATION_MODEL_NAVIGATION = {
|
|
|
21608
21622
|
sidebar: {
|
|
21609
21623
|
primary: {},
|
|
21610
21624
|
bottom: {}
|
|
21611
|
-
}
|
|
21625
|
+
},
|
|
21626
|
+
topbar: {}
|
|
21612
21627
|
};
|
|
21613
21628
|
var DEFAULT_ORGANIZATION_MODEL = {
|
|
21614
21629
|
version: 1,
|
package/package.json
CHANGED
|
@@ -135,6 +135,18 @@ export type OrganizationModelSidebarSurfaceNode = Extract<OrganizationModelSideb
|
|
|
135
135
|
export type OrganizationModelSidebarGroupNode = Extract<OrganizationModelSidebarNode, { type: 'group' }>
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
+
### `OrganizationModelTopbarActionNode`
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
export type OrganizationModelTopbarActionNode = z.infer<typeof TopbarActionNodeSchema>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `OrganizationModelTopbarSection`
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
export type OrganizationModelTopbarSection = z.infer<typeof TopbarSectionSchema>
|
|
148
|
+
```
|
|
149
|
+
|
|
138
150
|
### `OrganizationModelTechStackEntry`
|
|
139
151
|
|
|
140
152
|
```typescript
|
|
@@ -845,6 +857,57 @@ export interface ShellRuntime {
|
|
|
845
857
|
}
|
|
846
858
|
```
|
|
847
859
|
|
|
860
|
+
### `ResolvedTopbarAction`
|
|
861
|
+
|
|
862
|
+
```typescript
|
|
863
|
+
/**
|
|
864
|
+
* A resolved topbar action — the OM node after gating/filtering, with icon resolved
|
|
865
|
+
* from the semantic icon registry. Passed to `TopbarActionModule.render`.
|
|
866
|
+
*/
|
|
867
|
+
export interface ResolvedTopbarAction {
|
|
868
|
+
id: string
|
|
869
|
+
label: string
|
|
870
|
+
tooltip?: string
|
|
871
|
+
icon: TablerIconComponent
|
|
872
|
+
order: number
|
|
873
|
+
}
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
### `TopbarActionModule`
|
|
877
|
+
|
|
878
|
+
```typescript
|
|
879
|
+
/**
|
|
880
|
+
* A topbar action module — binds a registry key to UI behavior (a render callback).
|
|
881
|
+
* The key must match `navigation.topbar[id]`. The OM supplies data + visibility;
|
|
882
|
+
* the module supplies behavior.
|
|
883
|
+
*/
|
|
884
|
+
export interface TopbarActionModule {
|
|
885
|
+
/** Stable key that matches the `id` of a `navigation.topbar` node. */
|
|
886
|
+
key: string
|
|
887
|
+
/** Render the topbar action. Receives the resolved OM node (icon pre-resolved). */
|
|
888
|
+
render: (ctx: { node: ResolvedTopbarAction }) => ReactNode
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### `ResolvedTopbarActionEntry`
|
|
893
|
+
|
|
894
|
+
```typescript
|
|
895
|
+
/** A joined entry emitted by `getTopbarActions`: resolved OM node + module behavior. */
|
|
896
|
+
export interface ResolvedTopbarActionEntry {
|
|
897
|
+
node: ResolvedTopbarAction
|
|
898
|
+
render: TopbarActionModule['render']
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
### `TopbarActionsProjectionOptions`
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
export interface TopbarActionsProjectionOptions {
|
|
906
|
+
isPlatformAdmin?: boolean
|
|
907
|
+
isDev?: boolean
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
848
911
|
### `OrganizationGraphSystemBridge`
|
|
849
912
|
|
|
850
913
|
```typescript
|
|
@@ -868,6 +931,8 @@ export interface OrganizationGraphContextValue {
|
|
|
868
931
|
```typescript
|
|
869
932
|
export interface ElevasisSystemsProviderProps {
|
|
870
933
|
systems?: SystemModule[]
|
|
934
|
+
/** Registered topbar action modules. OM node presence controls visibility; module supplies behavior. */
|
|
935
|
+
topbarActions?: TopbarActionModule[]
|
|
871
936
|
organizationModel?: ElevasisOrganizationModel
|
|
872
937
|
timeRange?: TimeRange
|
|
873
938
|
operationsApiUrl?: string
|
|
@@ -886,6 +951,8 @@ export interface ElevasisSystemsContextValue {
|
|
|
886
951
|
shellModel: ResolvedShellModel
|
|
887
952
|
shellRuntime: ShellRuntime
|
|
888
953
|
getSidebarLinks: (options?: ShellSidebarProjectionOptions) => ShellSidebarLinkGroup[]
|
|
954
|
+
/** Returns the list of topbar actions visible to the current user, in order. */
|
|
955
|
+
getTopbarActions: (options?: TopbarActionsProjectionOptions) => ResolvedTopbarActionEntry[]
|
|
889
956
|
enabledResolvedSystems: ResolvedSystemModule[]
|
|
890
957
|
resolvedSystems: ResolvedSystemModule[]
|
|
891
958
|
organizationGraph: OrganizationGraphContextValue
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
OrganizationModelNavigationSchema,
|
|
4
|
+
TopbarActionNodeSchema,
|
|
5
|
+
TopbarSectionSchema
|
|
6
|
+
} from '../../domains/navigation'
|
|
7
|
+
import { OrganizationModelSchema } from '../../schema'
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Helpers
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
function makeMinimalSystems() {
|
|
14
|
+
return {
|
|
15
|
+
dashboard: {
|
|
16
|
+
id: 'dashboard',
|
|
17
|
+
order: 10,
|
|
18
|
+
label: 'Dashboard',
|
|
19
|
+
enabled: true,
|
|
20
|
+
lifecycle: 'active' as const,
|
|
21
|
+
path: '/'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeMinimalModel(overrides: Record<string, unknown> = {}) {
|
|
27
|
+
return {
|
|
28
|
+
version: 1,
|
|
29
|
+
branding: { organizationName: 'Test', productName: 'Test OS', shortName: 'Test' },
|
|
30
|
+
systems: makeMinimalSystems(),
|
|
31
|
+
entities: {},
|
|
32
|
+
actions: {},
|
|
33
|
+
...overrides
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// TopbarActionNodeSchema
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
describe('TopbarActionNodeSchema — leaf node shape', () => {
|
|
42
|
+
it('accepts a minimal node (id + label + enabled default)', () => {
|
|
43
|
+
const result = TopbarActionNodeSchema.safeParse({
|
|
44
|
+
id: 'request',
|
|
45
|
+
label: 'Request a feature or report an issue'
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
expect(result.success).toBe(true)
|
|
49
|
+
if (result.success) {
|
|
50
|
+
expect(result.data.enabled).toBe(true)
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('accepts a fully populated node', () => {
|
|
55
|
+
const result = TopbarActionNodeSchema.safeParse({
|
|
56
|
+
id: 'request',
|
|
57
|
+
label: 'Request a feature or report an issue',
|
|
58
|
+
tooltip: 'Request a feature or report an issue',
|
|
59
|
+
icon: 'message-plus',
|
|
60
|
+
order: 10,
|
|
61
|
+
enabled: true,
|
|
62
|
+
devOnly: false,
|
|
63
|
+
requiresAdmin: false,
|
|
64
|
+
targets: { systems: ['monitoring'], actions: [] }
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
expect(result.success).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('defaults enabled to true when omitted', () => {
|
|
71
|
+
const result = TopbarActionNodeSchema.parse({ id: 'docs', label: 'Docs' })
|
|
72
|
+
|
|
73
|
+
expect(result.enabled).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('accepts enabled: false to mark an item as hidden', () => {
|
|
77
|
+
const result = TopbarActionNodeSchema.parse({ id: 'docs', label: 'Docs', enabled: false })
|
|
78
|
+
|
|
79
|
+
expect(result.enabled).toBe(false)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('accepts optional devOnly flag', () => {
|
|
83
|
+
const result = TopbarActionNodeSchema.parse({ id: 'devtools', label: 'Dev Tools', devOnly: true })
|
|
84
|
+
|
|
85
|
+
expect(result.devOnly).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('accepts optional requiresAdmin flag', () => {
|
|
89
|
+
const result = TopbarActionNodeSchema.parse({ id: 'admin-panel', label: 'Admin Panel', requiresAdmin: true })
|
|
90
|
+
|
|
91
|
+
expect(result.requiresAdmin).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('rejects a node missing id', () => {
|
|
95
|
+
const result = TopbarActionNodeSchema.safeParse({ label: 'No Id' })
|
|
96
|
+
|
|
97
|
+
expect(result.success).toBe(false)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('rejects a node missing label', () => {
|
|
101
|
+
const result = TopbarActionNodeSchema.safeParse({ id: 'no-label' })
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(false)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('does NOT have a path field (leaf, not a route)', () => {
|
|
107
|
+
const result = TopbarActionNodeSchema.parse({ id: 'request', label: 'Request' })
|
|
108
|
+
|
|
109
|
+
expect('path' in result).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('does NOT have a surfaceType field (closed enum untouched)', () => {
|
|
113
|
+
const result = TopbarActionNodeSchema.parse({ id: 'request', label: 'Request' })
|
|
114
|
+
|
|
115
|
+
expect('surfaceType' in result).toBe(false)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// TopbarSectionSchema
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
describe('TopbarSectionSchema — flat map of topbar action nodes', () => {
|
|
124
|
+
it('defaults to an empty record', () => {
|
|
125
|
+
const result = TopbarSectionSchema.parse(undefined)
|
|
126
|
+
|
|
127
|
+
expect(result).toEqual({})
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('accepts a populated map', () => {
|
|
131
|
+
const result = TopbarSectionSchema.safeParse({
|
|
132
|
+
request: { id: 'request', label: 'Request' },
|
|
133
|
+
docs: { id: 'docs', label: 'Docs', icon: 'knowledge' }
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
expect(result.success).toBe(true)
|
|
137
|
+
if (result.success) {
|
|
138
|
+
expect(Object.keys(result.data)).toEqual(['request', 'docs'])
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// OrganizationModelNavigationSchema — topbar field added
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
describe('OrganizationModelNavigationSchema — topbar region', () => {
|
|
148
|
+
it('defaults topbar to an empty record', () => {
|
|
149
|
+
const result = OrganizationModelNavigationSchema.parse({})
|
|
150
|
+
|
|
151
|
+
expect(result.topbar).toEqual({})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('retains existing sidebar defaults (regression: append only, sidebar untouched)', () => {
|
|
155
|
+
const result = OrganizationModelNavigationSchema.parse({})
|
|
156
|
+
|
|
157
|
+
expect(result.sidebar.primary).toEqual({})
|
|
158
|
+
expect(result.sidebar.bottom).toEqual({})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('accepts topbar nodes alongside sidebar', () => {
|
|
162
|
+
const result = OrganizationModelNavigationSchema.safeParse({
|
|
163
|
+
sidebar: { primary: {}, bottom: {} },
|
|
164
|
+
topbar: {
|
|
165
|
+
request: {
|
|
166
|
+
id: 'request',
|
|
167
|
+
label: 'Request a feature or report an issue',
|
|
168
|
+
tooltip: 'Request a feature or report an issue',
|
|
169
|
+
icon: 'message-plus',
|
|
170
|
+
order: 10,
|
|
171
|
+
enabled: true
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
expect(result.success).toBe(true)
|
|
177
|
+
if (result.success) {
|
|
178
|
+
expect(result.data.topbar['request']?.id).toBe('request')
|
|
179
|
+
expect(result.data.topbar['request']?.icon).toBe('message-plus')
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// OrganizationModelSchema — navigation.topbar propagates through full schema
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
|
|
188
|
+
describe('OrganizationModelSchema — navigation.topbar contract', () => {
|
|
189
|
+
it('accepts a model with topbar declared', () => {
|
|
190
|
+
const result = OrganizationModelSchema.safeParse(
|
|
191
|
+
makeMinimalModel({
|
|
192
|
+
navigation: {
|
|
193
|
+
topbar: {
|
|
194
|
+
request: {
|
|
195
|
+
id: 'request',
|
|
196
|
+
label: 'Request a feature or report an issue',
|
|
197
|
+
tooltip: 'Request a feature or report an issue',
|
|
198
|
+
icon: 'message-plus',
|
|
199
|
+
order: 10,
|
|
200
|
+
enabled: true
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
expect(result.success).toBe(true)
|
|
208
|
+
if (result.success) {
|
|
209
|
+
expect(result.data.navigation.topbar['request']?.id).toBe('request')
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('defaults navigation.topbar to empty when navigation is omitted', () => {
|
|
214
|
+
const result = OrganizationModelSchema.safeParse(makeMinimalModel())
|
|
215
|
+
|
|
216
|
+
expect(result.success).toBe(true)
|
|
217
|
+
if (result.success) {
|
|
218
|
+
expect(result.data.navigation.topbar).toEqual({})
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('defaults navigation.topbar to empty when only sidebar is provided', () => {
|
|
223
|
+
const result = OrganizationModelSchema.safeParse(
|
|
224
|
+
makeMinimalModel({
|
|
225
|
+
navigation: {
|
|
226
|
+
sidebar: {
|
|
227
|
+
primary: {
|
|
228
|
+
dashboard: {
|
|
229
|
+
type: 'surface',
|
|
230
|
+
label: 'Dashboard',
|
|
231
|
+
path: '/',
|
|
232
|
+
surfaceType: 'dashboard',
|
|
233
|
+
order: 10
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
bottom: {}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
expect(result.success).toBe(true)
|
|
243
|
+
if (result.success) {
|
|
244
|
+
expect(result.data.navigation.topbar).toEqual({})
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('coexists with sidebar — both sections independently populated', () => {
|
|
249
|
+
const result = OrganizationModelSchema.safeParse(
|
|
250
|
+
makeMinimalModel({
|
|
251
|
+
navigation: {
|
|
252
|
+
sidebar: {
|
|
253
|
+
primary: {
|
|
254
|
+
dashboard: {
|
|
255
|
+
type: 'surface',
|
|
256
|
+
label: 'Dashboard',
|
|
257
|
+
path: '/',
|
|
258
|
+
surfaceType: 'dashboard',
|
|
259
|
+
order: 10
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
bottom: {}
|
|
263
|
+
},
|
|
264
|
+
topbar: {
|
|
265
|
+
request: {
|
|
266
|
+
id: 'request',
|
|
267
|
+
label: 'Request',
|
|
268
|
+
order: 10,
|
|
269
|
+
enabled: true
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
expect(result.success).toBe(true)
|
|
277
|
+
if (result.success) {
|
|
278
|
+
expect(result.data.navigation.sidebar.primary['dashboard']?.label).toBe('Dashboard')
|
|
279
|
+
expect(result.data.navigation.topbar['request']?.label).toBe('Request')
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
})
|