@object-ui/types 3.0.2 → 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.
Files changed (85) hide show
  1. package/dist/app.d.ts +217 -0
  2. package/dist/app.d.ts.map +1 -1
  3. package/dist/app.js +85 -1
  4. package/dist/complex.d.ts +129 -35
  5. package/dist/complex.d.ts.map +1 -1
  6. package/dist/data-display.d.ts +105 -1
  7. package/dist/data-display.d.ts.map +1 -1
  8. package/dist/data.d.ts +45 -0
  9. package/dist/data.d.ts.map +1 -1
  10. package/dist/designer.d.ts +197 -35
  11. package/dist/designer.d.ts.map +1 -1
  12. package/dist/designer.js +11 -1
  13. package/dist/index.d.ts +21 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -0
  16. package/dist/layout.d.ts +39 -2
  17. package/dist/layout.d.ts.map +1 -1
  18. package/dist/navigation.d.ts +27 -0
  19. package/dist/navigation.d.ts.map +1 -1
  20. package/dist/objectql.d.ts +641 -7
  21. package/dist/objectql.d.ts.map +1 -1
  22. package/dist/record-components.d.ts +160 -0
  23. package/dist/record-components.d.ts.map +1 -0
  24. package/dist/record-components.js +8 -0
  25. package/dist/reports.d.ts +37 -0
  26. package/dist/reports.d.ts.map +1 -1
  27. package/dist/theme.d.ts +5 -0
  28. package/dist/theme.d.ts.map +1 -1
  29. package/dist/views.d.ts +257 -3
  30. package/dist/views.d.ts.map +1 -1
  31. package/dist/workflow.d.ts +198 -0
  32. package/dist/workflow.d.ts.map +1 -1
  33. package/dist/zod/app.zod.d.ts +42 -2
  34. package/dist/zod/app.zod.d.ts.map +1 -1
  35. package/dist/zod/app.zod.js +61 -1
  36. package/dist/zod/complex.zod.d.ts +138 -6
  37. package/dist/zod/complex.zod.d.ts.map +1 -1
  38. package/dist/zod/complex.zod.js +65 -2
  39. package/dist/zod/data-display.zod.d.ts +4 -0
  40. package/dist/zod/data-display.zod.d.ts.map +1 -1
  41. package/dist/zod/data-display.zod.js +2 -0
  42. package/dist/zod/form.zod.d.ts +6 -6
  43. package/dist/zod/index.zod.d.ts +368 -43
  44. package/dist/zod/index.zod.d.ts.map +1 -1
  45. package/dist/zod/index.zod.js +2 -2
  46. package/dist/zod/layout.zod.d.ts +6 -6
  47. package/dist/zod/navigation.zod.d.ts +58 -12
  48. package/dist/zod/navigation.zod.d.ts.map +1 -1
  49. package/dist/zod/navigation.zod.js +21 -9
  50. package/dist/zod/objectql.zod.d.ts +515 -27
  51. package/dist/zod/objectql.zod.d.ts.map +1 -1
  52. package/dist/zod/objectql.zod.js +162 -0
  53. package/dist/zod/reports.zod.d.ts +38 -38
  54. package/dist/zod/views.zod.d.ts +161 -7
  55. package/dist/zod/views.zod.d.ts.map +1 -1
  56. package/dist/zod/views.zod.js +21 -2
  57. package/package.json +2 -2
  58. package/src/__tests__/app-creation-types.test.ts +177 -0
  59. package/src/__tests__/dashboard-config.test.ts +208 -0
  60. package/src/__tests__/examples-metadata-compliance.test.ts +264 -0
  61. package/src/__tests__/navigation-model.test.ts +406 -0
  62. package/src/__tests__/p1-spec-alignment.test.ts +660 -0
  63. package/src/__tests__/p2-spec-exports.test.ts +312 -0
  64. package/src/__tests__/phase2-schemas.test.ts +108 -0
  65. package/src/app.ts +377 -0
  66. package/src/complex.ts +131 -31
  67. package/src/data-display.ts +107 -0
  68. package/src/data.ts +49 -0
  69. package/src/designer.ts +219 -30
  70. package/src/index.ts +192 -3
  71. package/src/layout.ts +55 -2
  72. package/src/navigation.ts +20 -0
  73. package/src/objectql.ts +757 -8
  74. package/src/record-components.ts +188 -0
  75. package/src/reports.ts +43 -0
  76. package/src/theme.ts +6 -0
  77. package/src/views.ts +275 -3
  78. package/src/workflow.ts +226 -0
  79. package/src/zod/app.zod.ts +74 -1
  80. package/src/zod/complex.zod.ts +67 -2
  81. package/src/zod/data-display.zod.ts +2 -0
  82. package/src/zod/index.zod.ts +5 -0
  83. package/src/zod/navigation.zod.ts +22 -10
  84. package/src/zod/objectql.zod.ts +167 -0
  85. package/src/zod/views.zod.ts +21 -2
@@ -0,0 +1,660 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ /**
10
+ * P1 Spec Protocol Alignment Tests
11
+ * Tests for all P1 sub-items: ListView, FormView, Dashboard, Page, Record Components, i18n/ARIA
12
+ */
13
+ import { describe, it, expect } from 'vitest';
14
+ import type {
15
+ // P1.1 ListView types
16
+ ListViewSchema,
17
+ ObjectGridSchema,
18
+ // P1.2 FormView types
19
+ ObjectFormSchema,
20
+ ObjectFormSection,
21
+ // P1.3 Dashboard types
22
+ DashboardWidgetSchema,
23
+ DashboardSchema,
24
+ // P1.4 Page types
25
+ PageType,
26
+ PageVariable,
27
+ PageSchema,
28
+ // P1.5 Record component types
29
+ RecordDetailsComponentProps,
30
+ RecordHighlightsComponentProps,
31
+ RecordRelatedListComponentProps,
32
+ RecordActivityComponentProps,
33
+ RecordChatterComponentProps,
34
+ RecordPathComponentProps,
35
+ } from '../index';
36
+
37
+ // ============================================================================
38
+ // P1.1 ListView Spec Alignment
39
+ // ============================================================================
40
+ describe('P1.1 ListView Spec Alignment', () => {
41
+ it('should accept rowActions and bulkActions as string arrays', () => {
42
+ const schema: ListViewSchema = {
43
+ type: 'list-view',
44
+ objectName: 'Account',
45
+ rowActions: ['edit', 'delete', 'clone'],
46
+ bulkActions: ['delete', 'assign', 'export'],
47
+ };
48
+ expect(schema.rowActions).toHaveLength(3);
49
+ expect(schema.bulkActions).toHaveLength(3);
50
+ });
51
+
52
+ it('should accept virtualScroll boolean', () => {
53
+ const schema: ListViewSchema = {
54
+ type: 'list-view',
55
+ objectName: 'Account',
56
+ virtualScroll: true,
57
+ };
58
+ expect(schema.virtualScroll).toBe(true);
59
+ });
60
+
61
+ it('should accept showRecordCount and allowPrinting', () => {
62
+ const schema: ListViewSchema = {
63
+ type: 'list-view',
64
+ objectName: 'Account',
65
+ showRecordCount: true,
66
+ allowPrinting: true,
67
+ };
68
+ expect(schema.showRecordCount).toBe(true);
69
+ expect(schema.allowPrinting).toBe(true);
70
+ });
71
+
72
+ it('should accept userActions configuration', () => {
73
+ const schema: ListViewSchema = {
74
+ type: 'list-view',
75
+ objectName: 'Account',
76
+ userActions: {
77
+ sort: true,
78
+ search: true,
79
+ filter: true,
80
+ rowHeight: false,
81
+ addRecordForm: true,
82
+ buttons: ['custom_action_1'],
83
+ },
84
+ };
85
+ expect(schema.userActions?.sort).toBe(true);
86
+ expect(schema.userActions?.buttons).toEqual(['custom_action_1']);
87
+ });
88
+
89
+ it('should accept appearance configuration', () => {
90
+ const schema: ListViewSchema = {
91
+ type: 'list-view',
92
+ objectName: 'Account',
93
+ appearance: {
94
+ showDescription: true,
95
+ allowedVisualizations: ['grid', 'kanban'],
96
+ },
97
+ };
98
+ expect(schema.appearance?.showDescription).toBe(true);
99
+ expect(schema.appearance?.allowedVisualizations).toHaveLength(2);
100
+ });
101
+
102
+ it('should accept tabs configuration', () => {
103
+ const schema: ListViewSchema = {
104
+ type: 'list-view',
105
+ objectName: 'Account',
106
+ tabs: [
107
+ { name: 'all', label: 'All Records', isDefault: true },
108
+ { name: 'mine', label: 'My Records', filter: ['owner', '=', 'current_user'] },
109
+ ],
110
+ };
111
+ expect(schema.tabs).toHaveLength(2);
112
+ expect(schema.tabs![0].isDefault).toBe(true);
113
+ });
114
+
115
+ it('should accept addRecord configuration', () => {
116
+ const schema: ListViewSchema = {
117
+ type: 'list-view',
118
+ objectName: 'Account',
119
+ addRecord: {
120
+ enabled: true,
121
+ position: 'top',
122
+ mode: 'inline',
123
+ formView: 'quick_create',
124
+ },
125
+ };
126
+ expect(schema.addRecord?.enabled).toBe(true);
127
+ expect(schema.addRecord?.mode).toBe('inline');
128
+ });
129
+
130
+ it('should accept ObjectGridSchema with spec-aligned conditionalFormatting and emptyState', () => {
131
+ const schema: ObjectGridSchema = {
132
+ type: 'object-grid',
133
+ objectName: 'Account',
134
+ conditionalFormatting: [
135
+ { condition: '${data.amount > 10000}', style: { backgroundColor: '#fee2e2' } },
136
+ ],
137
+ emptyState: { title: 'No Records', message: 'Create your first account', icon: 'Database' },
138
+ virtualScroll: true,
139
+ rowSpecActions: ['edit', 'delete'],
140
+ bulkSpecActions: ['delete', 'export'],
141
+ };
142
+ expect(schema.conditionalFormatting).toHaveLength(1);
143
+ expect(schema.emptyState?.title).toBe('No Records');
144
+ expect(schema.virtualScroll).toBe(true);
145
+ expect(schema.rowSpecActions).toEqual(['edit', 'delete']);
146
+ });
147
+
148
+ // P2: Sharing / ExportOptions / Pagination protocol alignment tests
149
+ it('should accept sharing in spec format { type, lockedBy }', () => {
150
+ const schema: ListViewSchema = {
151
+ type: 'list-view',
152
+ objectName: 'Account',
153
+ sharing: {
154
+ type: 'collaborative',
155
+ lockedBy: 'admin@example.com',
156
+ },
157
+ };
158
+ expect(schema.sharing?.type).toBe('collaborative');
159
+ expect(schema.sharing?.lockedBy).toBe('admin@example.com');
160
+ });
161
+
162
+ it('should accept sharing in ObjectUI format { visibility, enabled }', () => {
163
+ const schema: ListViewSchema = {
164
+ type: 'list-view',
165
+ objectName: 'Account',
166
+ sharing: {
167
+ visibility: 'team',
168
+ enabled: true,
169
+ },
170
+ };
171
+ expect(schema.sharing?.visibility).toBe('team');
172
+ expect(schema.sharing?.enabled).toBe(true);
173
+ });
174
+
175
+ it('should accept sharing with both spec and ObjectUI fields merged', () => {
176
+ const schema: ListViewSchema = {
177
+ type: 'list-view',
178
+ objectName: 'Account',
179
+ sharing: {
180
+ type: 'personal',
181
+ visibility: 'private',
182
+ enabled: true,
183
+ lockedBy: 'user@example.com',
184
+ },
185
+ };
186
+ expect(schema.sharing?.type).toBe('personal');
187
+ expect(schema.sharing?.visibility).toBe('private');
188
+ expect(schema.sharing?.enabled).toBe(true);
189
+ expect(schema.sharing?.lockedBy).toBe('user@example.com');
190
+ });
191
+
192
+ it('should accept exportOptions as spec string[] format', () => {
193
+ const schema: ListViewSchema = {
194
+ type: 'list-view',
195
+ objectName: 'Account',
196
+ exportOptions: ['csv', 'xlsx'],
197
+ };
198
+ expect(Array.isArray(schema.exportOptions)).toBe(true);
199
+ expect(schema.exportOptions).toEqual(['csv', 'xlsx']);
200
+ });
201
+
202
+ it('should accept exportOptions as ObjectUI object format', () => {
203
+ const schema: ListViewSchema = {
204
+ type: 'list-view',
205
+ objectName: 'Account',
206
+ exportOptions: {
207
+ formats: ['csv', 'json', 'pdf'],
208
+ maxRecords: 5000,
209
+ includeHeaders: true,
210
+ fileNamePrefix: 'accounts_export',
211
+ },
212
+ };
213
+ expect(Array.isArray(schema.exportOptions)).toBe(false);
214
+ const opts = schema.exportOptions as { formats?: string[]; maxRecords?: number };
215
+ expect(opts.formats).toEqual(['csv', 'json', 'pdf']);
216
+ expect(opts.maxRecords).toBe(5000);
217
+ });
218
+
219
+ it('should accept pagination with pageSizeOptions', () => {
220
+ const schema: ListViewSchema = {
221
+ type: 'list-view',
222
+ objectName: 'Account',
223
+ pagination: {
224
+ pageSize: 25,
225
+ pageSizeOptions: [10, 25, 50, 100],
226
+ },
227
+ };
228
+ expect(schema.pagination?.pageSize).toBe(25);
229
+ expect(schema.pagination?.pageSizeOptions).toEqual([10, 25, 50, 100]);
230
+ });
231
+ });
232
+
233
+ // ============================================================================
234
+ // P1.2 FormView Spec Alignment
235
+ // ============================================================================
236
+ describe('P1.2 FormView Spec Alignment', () => {
237
+ it('should accept all formType variants', () => {
238
+ const formTypes: Array<ObjectFormSchema['formType']> = [
239
+ 'simple', 'tabbed', 'wizard', 'split', 'drawer', 'modal',
240
+ ];
241
+ formTypes.forEach((formType) => {
242
+ const schema: ObjectFormSchema = {
243
+ type: 'object-form',
244
+ objectName: 'Account',
245
+ mode: 'create',
246
+ formType,
247
+ };
248
+ expect(schema.formType).toBe(formType);
249
+ });
250
+ });
251
+
252
+ it('should accept FormSection with 1-4 column layout', () => {
253
+ const columns: Array<ObjectFormSection['columns']> = [1, 2, 3, 4];
254
+ columns.forEach((col) => {
255
+ const section: ObjectFormSection = {
256
+ label: 'Basic Info',
257
+ columns: col,
258
+ fields: ['name', 'email'],
259
+ collapsible: true,
260
+ collapsed: false,
261
+ };
262
+ expect(section.columns).toBe(col);
263
+ });
264
+ });
265
+
266
+ it('should accept FormField properties: widget, dependsOn, visibleOn, colSpan', () => {
267
+ const schema: ObjectFormSchema = {
268
+ type: 'object-form',
269
+ objectName: 'Account',
270
+ mode: 'edit',
271
+ customFields: [
272
+ {
273
+ name: 'industry',
274
+ label: 'Industry',
275
+ type: 'select',
276
+ widget: 'industry-picker',
277
+ },
278
+ {
279
+ name: 'sub_industry',
280
+ label: 'Sub-Industry',
281
+ type: 'select',
282
+ dependsOn: ['industry'],
283
+ visibleOn: '${data.industry != null}',
284
+ colSpan: 2,
285
+ },
286
+ ],
287
+ };
288
+ expect(schema.customFields).toHaveLength(2);
289
+ expect(schema.customFields![1].dependsOn).toEqual(['industry']);
290
+ expect(schema.customFields![1].visibleOn).toBe('${data.industry != null}');
291
+ expect(schema.customFields![1].colSpan).toBe(2);
292
+ });
293
+ });
294
+
295
+ // ============================================================================
296
+ // P1.3 Dashboard Spec Alignment
297
+ // ============================================================================
298
+ describe('P1.3 Dashboard Spec Alignment', () => {
299
+ it('should accept widget data binding properties', () => {
300
+ const widget: DashboardWidgetSchema = {
301
+ type: 'bar-chart',
302
+ title: 'Revenue by Region',
303
+ object: 'Opportunity',
304
+ filter: [['stage', '=', 'Closed Won']],
305
+ categoryField: 'region',
306
+ valueField: 'amount',
307
+ aggregate: 'sum',
308
+ };
309
+ expect(widget.object).toBe('Opportunity');
310
+ expect(widget.categoryField).toBe('region');
311
+ expect(widget.valueField).toBe('amount');
312
+ expect(widget.aggregate).toBe('sum');
313
+ });
314
+
315
+ it('should accept widget color variants', () => {
316
+ const variants: Array<DashboardWidgetSchema['colorVariant']> = [
317
+ 'default', 'blue', 'teal', 'orange', 'purple', 'success', 'warning', 'danger',
318
+ ];
319
+ variants.forEach((variant) => {
320
+ const widget: DashboardWidgetSchema = {
321
+ type: 'metric',
322
+ title: 'Test',
323
+ colorVariant: variant,
324
+ };
325
+ expect(widget.colorVariant).toBe(variant);
326
+ });
327
+ });
328
+
329
+ it('should accept widget measures (pivot/matrix)', () => {
330
+ const widget: DashboardWidgetSchema = {
331
+ type: 'pivot',
332
+ title: 'Sales Matrix',
333
+ measures: [
334
+ { valueField: 'amount', aggregate: 'sum', label: 'Total Sales', format: '$0,0' },
335
+ { valueField: 'count', aggregate: 'count', label: 'Deal Count' },
336
+ ],
337
+ };
338
+ expect(widget.measures).toHaveLength(2);
339
+ expect(widget.measures![0].label).toBe('Total Sales');
340
+ });
341
+
342
+ it('should accept globalFilters with optionsFrom', () => {
343
+ const dashboard: DashboardSchema = {
344
+ type: 'dashboard',
345
+ widgets: [],
346
+ globalFilters: [
347
+ {
348
+ field: 'region',
349
+ label: 'Region',
350
+ type: 'select',
351
+ optionsFrom: {
352
+ object: 'Region',
353
+ valueField: 'id',
354
+ labelField: 'name',
355
+ },
356
+ targetWidgets: ['widget-0', 'widget-1'],
357
+ },
358
+ ],
359
+ };
360
+ expect(dashboard.globalFilters).toHaveLength(1);
361
+ expect(dashboard.globalFilters![0].optionsFrom?.object).toBe('Region');
362
+ });
363
+
364
+ it('should accept date range filter', () => {
365
+ const dashboard: DashboardSchema = {
366
+ type: 'dashboard',
367
+ widgets: [],
368
+ dateRange: {
369
+ field: 'created_at',
370
+ defaultRange: 'last_30_days',
371
+ allowCustomRange: true,
372
+ },
373
+ };
374
+ expect(dashboard.dateRange?.defaultRange).toBe('last_30_days');
375
+ expect(dashboard.dateRange?.allowCustomRange).toBe(true);
376
+ });
377
+
378
+ it('should accept DashboardHeader with actions', () => {
379
+ const dashboard: DashboardSchema = {
380
+ type: 'dashboard',
381
+ widgets: [],
382
+ header: {
383
+ showTitle: true,
384
+ showDescription: false,
385
+ actions: [
386
+ { label: 'Refresh', actionType: 'refresh', icon: 'RefreshCw' },
387
+ { label: 'Export', actionUrl: '/api/export', icon: 'Download' },
388
+ ],
389
+ },
390
+ };
391
+ expect(dashboard.header?.showTitle).toBe(true);
392
+ expect(dashboard.header?.actions).toHaveLength(2);
393
+ });
394
+
395
+ it('should accept widget ARIA properties', () => {
396
+ const widget: DashboardWidgetSchema = {
397
+ type: 'metric',
398
+ title: 'Revenue',
399
+ aria: {
400
+ ariaLabel: 'Total Revenue Widget',
401
+ role: 'figure',
402
+ },
403
+ };
404
+ expect(widget.aria?.ariaLabel).toBe('Total Revenue Widget');
405
+ });
406
+ });
407
+
408
+ // ============================================================================
409
+ // P1.4 Page Composition Spec Alignment
410
+ // ============================================================================
411
+ describe('P1.4 Page Composition Spec Alignment', () => {
412
+ it('should accept all 16 page types', () => {
413
+ const allTypes: PageType[] = [
414
+ 'record', 'home', 'app', 'utility',
415
+ 'dashboard', 'grid', 'list', 'gallery',
416
+ 'kanban', 'calendar', 'timeline', 'form',
417
+ 'record_detail', 'record_review', 'overview', 'blank',
418
+ ];
419
+ allTypes.forEach((type) => {
420
+ const page: PageSchema = {
421
+ type: 'page',
422
+ pageType: type,
423
+ };
424
+ expect(page.pageType).toBe(type);
425
+ });
426
+ });
427
+
428
+ it('should accept record_id in PageVariable type', () => {
429
+ const variable: PageVariable = {
430
+ name: 'recordId',
431
+ type: 'record_id',
432
+ source: 'url_param',
433
+ };
434
+ expect(variable.type).toBe('record_id');
435
+ expect(variable.source).toBe('url_param');
436
+ });
437
+
438
+ it('should accept blank page layout', () => {
439
+ const page: PageSchema = {
440
+ type: 'page',
441
+ pageType: 'blank',
442
+ blankLayout: {
443
+ columns: 12,
444
+ rowHeight: 60,
445
+ gap: 8,
446
+ items: [
447
+ { componentId: 'header-1', x: 0, y: 0, width: 12, height: 2 },
448
+ { componentId: 'chart-1', x: 0, y: 2, width: 6, height: 4 },
449
+ { componentId: 'metric-1', x: 6, y: 2, width: 6, height: 4 },
450
+ ],
451
+ },
452
+ };
453
+ expect(page.blankLayout?.columns).toBe(12);
454
+ expect(page.blankLayout?.items).toHaveLength(3);
455
+ expect(page.blankLayout?.items![0].componentId).toBe('header-1');
456
+ });
457
+
458
+ it('should accept page ARIA properties', () => {
459
+ const page: PageSchema = {
460
+ type: 'page',
461
+ aria: {
462
+ ariaLabel: 'Account Details Page',
463
+ role: 'main',
464
+ },
465
+ };
466
+ expect(page.aria?.ariaLabel).toBe('Account Details Page');
467
+ });
468
+ });
469
+
470
+ // ============================================================================
471
+ // P1.5 Record Components
472
+ // ============================================================================
473
+ describe('P1.5 Record Components', () => {
474
+ it('should define RecordDetailsComponentProps', () => {
475
+ const props: RecordDetailsComponentProps = {
476
+ columns: 2,
477
+ layout: 'stacked',
478
+ sections: [
479
+ { label: 'Basic Info', fields: ['name', 'email', 'phone'], collapsible: true },
480
+ { label: 'Address', fields: ['street', 'city', 'state'], collapsed: true },
481
+ ],
482
+ fields: ['name', 'email'],
483
+ aria: { ariaLabel: 'Account Details' },
484
+ };
485
+ expect(props.columns).toBe(2);
486
+ expect(props.sections).toHaveLength(2);
487
+ expect(props.layout).toBe('stacked');
488
+ });
489
+
490
+ it('should define RecordHighlightsComponentProps', () => {
491
+ const props: RecordHighlightsComponentProps = {
492
+ fields: ['name', 'status', 'owner', 'amount'],
493
+ layout: 'horizontal',
494
+ aria: { ariaLabel: 'Key Highlights' },
495
+ };
496
+ expect(props.fields).toHaveLength(4);
497
+ expect(props.layout).toBe('horizontal');
498
+ });
499
+
500
+ it('should define RecordRelatedListComponentProps', () => {
501
+ const props: RecordRelatedListComponentProps = {
502
+ objectName: 'Contact',
503
+ relationshipField: 'account_id',
504
+ columns: ['name', 'email', 'phone'],
505
+ sort: [{ field: 'name', order: 'asc' }],
506
+ limit: 5,
507
+ filter: [['active', '=', true]],
508
+ title: 'Related Contacts',
509
+ showViewAll: true,
510
+ actions: ['new', 'edit'],
511
+ aria: { ariaLabel: 'Related Contacts List' },
512
+ };
513
+ expect(props.objectName).toBe('Contact');
514
+ expect(props.relationshipField).toBe('account_id');
515
+ expect(props.columns).toHaveLength(3);
516
+ });
517
+
518
+ it('should define RecordActivityComponentProps', () => {
519
+ const props: RecordActivityComponentProps = {
520
+ types: ['comment', 'email', 'task', 'event'],
521
+ filterMode: 'all',
522
+ showFilterToggle: true,
523
+ limit: 20,
524
+ showCompleted: false,
525
+ unifiedTimeline: true,
526
+ showCommentInput: true,
527
+ enableMentions: true,
528
+ enableReactions: true,
529
+ enableThreading: true,
530
+ showSubscriptionToggle: true,
531
+ aria: { ariaLabel: 'Activity Timeline' },
532
+ };
533
+ expect(props.types).toHaveLength(4);
534
+ expect(props.enableMentions).toBe(true);
535
+ expect(props.unifiedTimeline).toBe(true);
536
+ });
537
+
538
+ it('should define RecordChatterComponentProps with feed', () => {
539
+ const props: RecordChatterComponentProps = {
540
+ position: 'right',
541
+ width: '350px',
542
+ collapsible: true,
543
+ defaultCollapsed: false,
544
+ feed: {
545
+ types: ['comment'],
546
+ showCommentInput: true,
547
+ enableMentions: true,
548
+ enableThreading: true,
549
+ },
550
+ aria: { ariaLabel: 'Record Discussion' },
551
+ };
552
+ expect(props.position).toBe('right');
553
+ expect(props.feed?.enableMentions).toBe(true);
554
+ });
555
+
556
+ it('should define RecordPathComponentProps', () => {
557
+ const props: RecordPathComponentProps = {
558
+ statusField: 'stage',
559
+ stages: [
560
+ { value: 'prospecting', label: 'Prospecting' },
561
+ { value: 'qualification', label: 'Qualification' },
562
+ { value: 'proposal', label: 'Proposal' },
563
+ { value: 'closed_won', label: 'Closed Won' },
564
+ ],
565
+ aria: { ariaLabel: 'Opportunity Stage Path' },
566
+ };
567
+ expect(props.statusField).toBe('stage');
568
+ expect(props.stages).toHaveLength(4);
569
+ expect(props.stages[0].value).toBe('prospecting');
570
+ });
571
+ });
572
+
573
+ // ============================================================================
574
+ // P1.6 i18n & ARIA Protocol Alignment
575
+ // ============================================================================
576
+ describe('P1.6 i18n & ARIA Protocol Alignment', () => {
577
+ it('should accept ARIA props on ListViewSchema', () => {
578
+ const schema: ListViewSchema = {
579
+ type: 'list-view',
580
+ objectName: 'Account',
581
+ aria: {
582
+ label: 'Accounts List',
583
+ describedBy: 'accounts-description',
584
+ live: 'polite',
585
+ },
586
+ };
587
+ expect(schema.aria?.label).toBe('Accounts List');
588
+ expect(schema.aria?.live).toBe('polite');
589
+ });
590
+
591
+ it('should accept ARIA props on DashboardSchema', () => {
592
+ const schema: DashboardSchema = {
593
+ type: 'dashboard',
594
+ widgets: [],
595
+ aria: {
596
+ ariaLabel: 'Sales Dashboard',
597
+ role: 'region',
598
+ },
599
+ };
600
+ expect(schema.aria?.ariaLabel).toBe('Sales Dashboard');
601
+ });
602
+
603
+ it('should accept ARIA props on PageSchema', () => {
604
+ const schema: PageSchema = {
605
+ type: 'page',
606
+ aria: {
607
+ ariaLabel: 'Home Page',
608
+ ariaDescribedBy: 'home-description',
609
+ role: 'main',
610
+ },
611
+ };
612
+ expect(schema.aria?.ariaLabel).toBe('Home Page');
613
+ });
614
+ });
615
+
616
+ // ============================================================================
617
+ // NamedListView & ListViewSchema — Toolbar/Display Properties
618
+ // ============================================================================
619
+ describe('NamedListView toolbar and display properties', () => {
620
+ it('should accept showSearch, showSort, showFilters on NamedListView', () => {
621
+ const view: import('../index').NamedListView = {
622
+ label: 'My View',
623
+ type: 'grid',
624
+ showSearch: false,
625
+ showSort: true,
626
+ showFilters: false,
627
+ };
628
+ expect(view.showSearch).toBe(false);
629
+ expect(view.showSort).toBe(true);
630
+ expect(view.showFilters).toBe(false);
631
+ });
632
+
633
+ it('should accept striped, bordered, color on NamedListView', () => {
634
+ const view: import('../index').NamedListView = {
635
+ label: 'Styled View',
636
+ type: 'kanban',
637
+ striped: true,
638
+ bordered: true,
639
+ color: 'status',
640
+ };
641
+ expect(view.striped).toBe(true);
642
+ expect(view.bordered).toBe(true);
643
+ expect(view.color).toBe('status');
644
+ });
645
+
646
+ it('should accept showSearch, showSort, showFilters, color on ListViewSchema', () => {
647
+ const schema: ListViewSchema = {
648
+ type: 'list-view',
649
+ objectName: 'Account',
650
+ showSearch: true,
651
+ showSort: false,
652
+ showFilters: true,
653
+ color: 'priority',
654
+ };
655
+ expect(schema.showSearch).toBe(true);
656
+ expect(schema.showSort).toBe(false);
657
+ expect(schema.showFilters).toBe(true);
658
+ expect(schema.color).toBe('priority');
659
+ });
660
+ });