@object-ui/types 3.0.3 → 3.1.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/app.d.ts +217 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +85 -1
- package/dist/complex.d.ts +118 -0
- package/dist/complex.d.ts.map +1 -1
- package/dist/data-display.d.ts +105 -1
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data.d.ts +45 -0
- package/dist/data.d.ts.map +1 -1
- package/dist/designer.d.ts +197 -35
- package/dist/designer.d.ts.map +1 -1
- package/dist/designer.js +11 -1
- package/dist/index.d.ts +21 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/layout.d.ts +39 -2
- package/dist/layout.d.ts.map +1 -1
- package/dist/navigation.d.ts +27 -0
- package/dist/navigation.d.ts.map +1 -1
- package/dist/objectql.d.ts +641 -7
- package/dist/objectql.d.ts.map +1 -1
- package/dist/record-components.d.ts +160 -0
- package/dist/record-components.d.ts.map +1 -0
- package/dist/record-components.js +8 -0
- package/dist/reports.d.ts +37 -0
- package/dist/reports.d.ts.map +1 -1
- package/dist/theme.d.ts +5 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/views.d.ts +257 -3
- package/dist/views.d.ts.map +1 -1
- package/dist/workflow.d.ts +198 -0
- package/dist/workflow.d.ts.map +1 -1
- package/dist/zod/app.zod.d.ts +42 -2
- package/dist/zod/app.zod.d.ts.map +1 -1
- package/dist/zod/app.zod.js +61 -1
- package/dist/zod/complex.zod.d.ts +122 -0
- package/dist/zod/complex.zod.d.ts.map +1 -1
- package/dist/zod/complex.zod.js +57 -0
- package/dist/zod/data-display.zod.d.ts +4 -0
- package/dist/zod/data-display.zod.d.ts.map +1 -1
- package/dist/zod/data-display.zod.js +2 -0
- package/dist/zod/form.zod.d.ts +6 -6
- package/dist/zod/index.zod.d.ts +364 -41
- package/dist/zod/index.zod.d.ts.map +1 -1
- package/dist/zod/index.zod.js +2 -2
- package/dist/zod/layout.zod.d.ts +6 -6
- package/dist/zod/navigation.zod.d.ts +58 -12
- package/dist/zod/navigation.zod.d.ts.map +1 -1
- package/dist/zod/navigation.zod.js +21 -9
- package/dist/zod/objectql.zod.d.ts +515 -27
- package/dist/zod/objectql.zod.d.ts.map +1 -1
- package/dist/zod/objectql.zod.js +162 -0
- package/dist/zod/reports.zod.d.ts +38 -38
- package/dist/zod/views.zod.d.ts +161 -7
- package/dist/zod/views.zod.d.ts.map +1 -1
- package/dist/zod/views.zod.js +21 -2
- package/package.json +2 -2
- package/src/__tests__/app-creation-types.test.ts +177 -0
- package/src/__tests__/dashboard-config.test.ts +208 -0
- package/src/__tests__/examples-metadata-compliance.test.ts +264 -0
- package/src/__tests__/navigation-model.test.ts +406 -0
- package/src/__tests__/p1-spec-alignment.test.ts +660 -0
- package/src/__tests__/p2-spec-exports.test.ts +312 -0
- package/src/__tests__/phase2-schemas.test.ts +108 -0
- package/src/app.ts +377 -0
- package/src/complex.ts +120 -0
- package/src/data-display.ts +107 -0
- package/src/data.ts +49 -0
- package/src/designer.ts +219 -30
- package/src/index.ts +192 -3
- package/src/layout.ts +55 -2
- package/src/navigation.ts +20 -0
- package/src/objectql.ts +757 -8
- package/src/record-components.ts +188 -0
- package/src/reports.ts +43 -0
- package/src/theme.ts +6 -0
- package/src/views.ts +275 -3
- package/src/workflow.ts +226 -0
- package/src/zod/app.zod.ts +74 -1
- package/src/zod/complex.zod.ts +59 -0
- package/src/zod/data-display.zod.ts +2 -0
- package/src/zod/index.zod.ts +5 -0
- package/src/zod/navigation.zod.ts +22 -10
- package/src/zod/objectql.zod.ts +167 -0
- package/src/zod/views.zod.ts +21 -2
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Unified Navigation Model
|
|
3
|
+
*
|
|
4
|
+
* Validates NavigationItem, NavigationArea types, Zod schemas,
|
|
5
|
+
* and the MenuItem → NavigationItem transform.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect } from 'vitest';
|
|
8
|
+
import {
|
|
9
|
+
AppSchema,
|
|
10
|
+
NavigationItemSchema,
|
|
11
|
+
NavigationAreaSchema,
|
|
12
|
+
} from '../zod/index.zod';
|
|
13
|
+
import { menuItemToNavigationItem } from '../app';
|
|
14
|
+
import type { MenuItem, NavigationItem, NavigationArea } from '../app';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// NavigationItem Zod Schema
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
describe('NavigationItem Zod Schema', () => {
|
|
21
|
+
it('should validate an object navigation item', () => {
|
|
22
|
+
const item = {
|
|
23
|
+
id: 'nav_contacts',
|
|
24
|
+
type: 'object',
|
|
25
|
+
label: 'Contacts',
|
|
26
|
+
icon: 'Users',
|
|
27
|
+
objectName: 'contact',
|
|
28
|
+
};
|
|
29
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
30
|
+
expect(result.success).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should validate a dashboard navigation item', () => {
|
|
34
|
+
const item = {
|
|
35
|
+
id: 'nav_dash',
|
|
36
|
+
type: 'dashboard',
|
|
37
|
+
label: 'Overview',
|
|
38
|
+
icon: 'BarChart3',
|
|
39
|
+
dashboardName: 'sales_overview',
|
|
40
|
+
};
|
|
41
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
42
|
+
expect(result.success).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should validate a page navigation item', () => {
|
|
46
|
+
const item = {
|
|
47
|
+
id: 'nav_settings',
|
|
48
|
+
type: 'page',
|
|
49
|
+
label: 'Settings',
|
|
50
|
+
pageName: 'app_settings',
|
|
51
|
+
};
|
|
52
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
53
|
+
expect(result.success).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should validate a report navigation item', () => {
|
|
57
|
+
const item = {
|
|
58
|
+
id: 'nav_report',
|
|
59
|
+
type: 'report',
|
|
60
|
+
label: 'Sales Report',
|
|
61
|
+
reportName: 'monthly_sales',
|
|
62
|
+
};
|
|
63
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
64
|
+
expect(result.success).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should validate a url navigation item', () => {
|
|
68
|
+
const item = {
|
|
69
|
+
id: 'nav_docs',
|
|
70
|
+
type: 'url',
|
|
71
|
+
label: 'Documentation',
|
|
72
|
+
url: 'https://docs.example.com',
|
|
73
|
+
target: '_blank',
|
|
74
|
+
};
|
|
75
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
76
|
+
expect(result.success).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should validate a group navigation item with children', () => {
|
|
80
|
+
const item = {
|
|
81
|
+
id: 'nav_sales_group',
|
|
82
|
+
type: 'group',
|
|
83
|
+
label: 'Sales',
|
|
84
|
+
icon: 'DollarSign',
|
|
85
|
+
defaultOpen: true,
|
|
86
|
+
children: [
|
|
87
|
+
{ id: 'nav_leads', type: 'object', label: 'Leads', objectName: 'lead' },
|
|
88
|
+
{ id: 'nav_opps', type: 'object', label: 'Opportunities', objectName: 'opportunity' },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
92
|
+
expect(result.success).toBe(true);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should validate a separator navigation item', () => {
|
|
96
|
+
const item = {
|
|
97
|
+
id: 'sep_1',
|
|
98
|
+
type: 'separator',
|
|
99
|
+
label: '',
|
|
100
|
+
};
|
|
101
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
102
|
+
expect(result.success).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should validate UX enhancement fields', () => {
|
|
106
|
+
const item = {
|
|
107
|
+
id: 'nav_tasks',
|
|
108
|
+
type: 'object',
|
|
109
|
+
label: 'Tasks',
|
|
110
|
+
objectName: 'task',
|
|
111
|
+
badge: 5,
|
|
112
|
+
badgeVariant: 'destructive',
|
|
113
|
+
pinned: true,
|
|
114
|
+
order: 10,
|
|
115
|
+
};
|
|
116
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
117
|
+
expect(result.success).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should validate visibility and permission fields', () => {
|
|
121
|
+
const item = {
|
|
122
|
+
id: 'nav_admin',
|
|
123
|
+
type: 'page',
|
|
124
|
+
label: 'Admin Panel',
|
|
125
|
+
pageName: 'admin',
|
|
126
|
+
visible: "${user.role === 'admin'}",
|
|
127
|
+
requiredPermissions: ['admin:read'],
|
|
128
|
+
};
|
|
129
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
130
|
+
expect(result.success).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should reject invalid type', () => {
|
|
134
|
+
const item = {
|
|
135
|
+
id: 'nav_bad',
|
|
136
|
+
type: 'invalid_type',
|
|
137
|
+
label: 'Bad',
|
|
138
|
+
};
|
|
139
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
140
|
+
expect(result.success).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should reject missing required id', () => {
|
|
144
|
+
const item = {
|
|
145
|
+
type: 'object',
|
|
146
|
+
label: 'No ID',
|
|
147
|
+
objectName: 'contact',
|
|
148
|
+
};
|
|
149
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
150
|
+
expect(result.success).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should reject missing required label', () => {
|
|
154
|
+
const item = {
|
|
155
|
+
id: 'nav_no_label',
|
|
156
|
+
type: 'object',
|
|
157
|
+
objectName: 'contact',
|
|
158
|
+
};
|
|
159
|
+
const result = NavigationItemSchema.safeParse(item);
|
|
160
|
+
expect(result.success).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ============================================================================
|
|
165
|
+
// NavigationArea Zod Schema
|
|
166
|
+
// ============================================================================
|
|
167
|
+
|
|
168
|
+
describe('NavigationArea Zod Schema', () => {
|
|
169
|
+
it('should validate a complete area', () => {
|
|
170
|
+
const area = {
|
|
171
|
+
id: 'sales',
|
|
172
|
+
label: 'Sales',
|
|
173
|
+
icon: 'DollarSign',
|
|
174
|
+
navigation: [
|
|
175
|
+
{ id: 'nav_leads', type: 'object', label: 'Leads', objectName: 'lead' },
|
|
176
|
+
{ id: 'nav_opps', type: 'object', label: 'Opportunities', objectName: 'opportunity' },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
const result = NavigationAreaSchema.safeParse(area);
|
|
180
|
+
expect(result.success).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should validate area with visibility and permissions', () => {
|
|
184
|
+
const area = {
|
|
185
|
+
id: 'admin_area',
|
|
186
|
+
label: 'Administration',
|
|
187
|
+
navigation: [
|
|
188
|
+
{ id: 'nav_users', type: 'object', label: 'Users', objectName: 'user' },
|
|
189
|
+
],
|
|
190
|
+
visible: "${user.isAdmin}",
|
|
191
|
+
requiredPermissions: ['admin:access'],
|
|
192
|
+
};
|
|
193
|
+
const result = NavigationAreaSchema.safeParse(area);
|
|
194
|
+
expect(result.success).toBe(true);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should reject area without navigation', () => {
|
|
198
|
+
const area = {
|
|
199
|
+
id: 'empty_area',
|
|
200
|
+
label: 'Empty',
|
|
201
|
+
};
|
|
202
|
+
const result = NavigationAreaSchema.safeParse(area);
|
|
203
|
+
expect(result.success).toBe(false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// AppSchema with navigation and areas
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
describe('AppSchema with unified navigation', () => {
|
|
212
|
+
it('should validate AppSchema with navigation field', () => {
|
|
213
|
+
const app = {
|
|
214
|
+
type: 'app',
|
|
215
|
+
name: 'crm',
|
|
216
|
+
title: 'CRM App',
|
|
217
|
+
layout: 'sidebar',
|
|
218
|
+
navigation: [
|
|
219
|
+
{ id: 'nav_dash', type: 'dashboard', label: 'Dashboard', dashboardName: 'overview' },
|
|
220
|
+
{ id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'contact' },
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
const result = AppSchema.safeParse(app);
|
|
224
|
+
expect(result.success).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should validate AppSchema with areas', () => {
|
|
228
|
+
const app = {
|
|
229
|
+
type: 'app',
|
|
230
|
+
name: 'enterprise_crm',
|
|
231
|
+
title: 'Enterprise CRM',
|
|
232
|
+
layout: 'sidebar',
|
|
233
|
+
areas: [
|
|
234
|
+
{
|
|
235
|
+
id: 'sales',
|
|
236
|
+
label: 'Sales',
|
|
237
|
+
icon: 'DollarSign',
|
|
238
|
+
navigation: [
|
|
239
|
+
{ id: 'nav_leads', type: 'object', label: 'Leads', objectName: 'lead' },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'service',
|
|
244
|
+
label: 'Service',
|
|
245
|
+
icon: 'Headphones',
|
|
246
|
+
navigation: [
|
|
247
|
+
{ id: 'nav_cases', type: 'object', label: 'Cases', objectName: 'case' },
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
],
|
|
251
|
+
};
|
|
252
|
+
const result = AppSchema.safeParse(app);
|
|
253
|
+
expect(result.success).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should validate AppSchema with both legacy menu and new navigation', () => {
|
|
257
|
+
const app = {
|
|
258
|
+
type: 'app',
|
|
259
|
+
name: 'migration_app',
|
|
260
|
+
menu: [
|
|
261
|
+
{ type: 'item', label: 'Home', path: '/home' },
|
|
262
|
+
],
|
|
263
|
+
navigation: [
|
|
264
|
+
{ id: 'nav_home', type: 'page', label: 'Home', pageName: 'home' },
|
|
265
|
+
],
|
|
266
|
+
};
|
|
267
|
+
const result = AppSchema.safeParse(app);
|
|
268
|
+
expect(result.success).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// ============================================================================
|
|
273
|
+
// MenuItem → NavigationItem Transform
|
|
274
|
+
// ============================================================================
|
|
275
|
+
|
|
276
|
+
describe('menuItemToNavigationItem', () => {
|
|
277
|
+
it('should convert a simple path-based item', () => {
|
|
278
|
+
const menuItem: MenuItem = {
|
|
279
|
+
type: 'item',
|
|
280
|
+
label: 'Dashboard',
|
|
281
|
+
icon: 'LayoutDashboard',
|
|
282
|
+
path: '/dashboard',
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const result = menuItemToNavigationItem(menuItem, 0);
|
|
286
|
+
expect(result.type).toBe('page');
|
|
287
|
+
expect(result.label).toBe('Dashboard');
|
|
288
|
+
expect(result.icon).toBe('LayoutDashboard');
|
|
289
|
+
expect(result.pageName).toBe('/dashboard');
|
|
290
|
+
expect(result.id).toBe('migrated_0');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should convert an external link item', () => {
|
|
294
|
+
const menuItem: MenuItem = {
|
|
295
|
+
type: 'item',
|
|
296
|
+
label: 'Docs',
|
|
297
|
+
href: 'https://docs.example.com',
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const result = menuItemToNavigationItem(menuItem, 1);
|
|
301
|
+
expect(result.type).toBe('url');
|
|
302
|
+
expect(result.url).toBe('https://docs.example.com');
|
|
303
|
+
expect(result.target).toBe('_blank');
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should convert a group with children', () => {
|
|
307
|
+
const menuItem: MenuItem = {
|
|
308
|
+
type: 'group',
|
|
309
|
+
label: 'Sales',
|
|
310
|
+
children: [
|
|
311
|
+
{ type: 'item', label: 'Leads', path: '/leads' },
|
|
312
|
+
{ type: 'item', label: 'Opportunities', path: '/opportunities' },
|
|
313
|
+
],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const result = menuItemToNavigationItem(menuItem, 2);
|
|
317
|
+
expect(result.type).toBe('group');
|
|
318
|
+
expect(result.label).toBe('Sales');
|
|
319
|
+
expect(result.children).toHaveLength(2);
|
|
320
|
+
expect(result.children![0].type).toBe('page');
|
|
321
|
+
expect(result.children![0].pageName).toBe('/leads');
|
|
322
|
+
expect(result.defaultOpen).toBe(true);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should convert a separator', () => {
|
|
326
|
+
const menuItem: MenuItem = { type: 'separator' };
|
|
327
|
+
|
|
328
|
+
const result = menuItemToNavigationItem(menuItem, 3);
|
|
329
|
+
expect(result.type).toBe('separator');
|
|
330
|
+
expect(result.label).toBe('');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should invert hidden to visible', () => {
|
|
334
|
+
const menuItem: MenuItem = {
|
|
335
|
+
type: 'item',
|
|
336
|
+
label: 'Admin',
|
|
337
|
+
path: '/admin',
|
|
338
|
+
hidden: true,
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
const result = menuItemToNavigationItem(menuItem, 4);
|
|
342
|
+
expect(result.visible).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('should preserve badge', () => {
|
|
346
|
+
const menuItem: MenuItem = {
|
|
347
|
+
type: 'item',
|
|
348
|
+
label: 'Notifications',
|
|
349
|
+
path: '/notifications',
|
|
350
|
+
badge: 42,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const result = menuItemToNavigationItem(menuItem, 5);
|
|
354
|
+
expect(result.badge).toBe(42);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should handle item without explicit type', () => {
|
|
358
|
+
const menuItem: MenuItem = {
|
|
359
|
+
label: 'About',
|
|
360
|
+
path: '/about',
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const result = menuItemToNavigationItem(menuItem, 6);
|
|
364
|
+
expect(result.type).toBe('page');
|
|
365
|
+
expect(result.label).toBe('About');
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// ============================================================================
|
|
370
|
+
// Type-level sanity checks (compile-time assertions at runtime)
|
|
371
|
+
// ============================================================================
|
|
372
|
+
|
|
373
|
+
describe('Type exports', () => {
|
|
374
|
+
it('should export NavigationItem type fields', () => {
|
|
375
|
+
const item: NavigationItem = {
|
|
376
|
+
id: 'test',
|
|
377
|
+
type: 'object',
|
|
378
|
+
label: 'Test',
|
|
379
|
+
objectName: 'test_object',
|
|
380
|
+
icon: 'Database',
|
|
381
|
+
visible: true,
|
|
382
|
+
requiredPermissions: ['test:read'],
|
|
383
|
+
badge: 3,
|
|
384
|
+
badgeVariant: 'default',
|
|
385
|
+
pinned: false,
|
|
386
|
+
order: 1,
|
|
387
|
+
};
|
|
388
|
+
expect(item.id).toBe('test');
|
|
389
|
+
expect(item.type).toBe('object');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should export NavigationArea type fields', () => {
|
|
393
|
+
const area: NavigationArea = {
|
|
394
|
+
id: 'sales',
|
|
395
|
+
label: 'Sales',
|
|
396
|
+
icon: 'DollarSign',
|
|
397
|
+
navigation: [
|
|
398
|
+
{ id: 'nav_leads', type: 'object', label: 'Leads', objectName: 'lead' },
|
|
399
|
+
],
|
|
400
|
+
visible: true,
|
|
401
|
+
requiredPermissions: ['sales:access'],
|
|
402
|
+
};
|
|
403
|
+
expect(area.id).toBe('sales');
|
|
404
|
+
expect(area.navigation).toHaveLength(1);
|
|
405
|
+
});
|
|
406
|
+
});
|