@object-ui/core 3.3.0 → 3.3.1

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 (99) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +20 -1
  3. package/dist/actions/ActionRunner.d.ts +9 -0
  4. package/dist/actions/ActionRunner.js +41 -4
  5. package/dist/adapters/ValueDataSource.js +3 -1
  6. package/dist/utils/filter-converter.js +25 -5
  7. package/package.json +32 -8
  8. package/.turbo/turbo-build.log +0 -4
  9. package/src/__benchmarks__/core.bench.ts +0 -64
  10. package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
  11. package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
  12. package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
  13. package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
  14. package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
  15. package/src/actions/ActionEngine.ts +0 -268
  16. package/src/actions/ActionRunner.ts +0 -717
  17. package/src/actions/TransactionManager.ts +0 -521
  18. package/src/actions/UndoManager.ts +0 -215
  19. package/src/actions/__tests__/ActionEngine.test.ts +0 -206
  20. package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
  21. package/src/actions/__tests__/ActionRunner.test.ts +0 -711
  22. package/src/actions/__tests__/TransactionManager.test.ts +0 -447
  23. package/src/actions/__tests__/UndoManager.test.ts +0 -320
  24. package/src/actions/index.ts +0 -12
  25. package/src/adapters/ApiDataSource.ts +0 -376
  26. package/src/adapters/README.md +0 -180
  27. package/src/adapters/ValueDataSource.ts +0 -459
  28. package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
  29. package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
  30. package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
  31. package/src/adapters/index.ts +0 -15
  32. package/src/adapters/resolveDataSource.ts +0 -79
  33. package/src/builder/__tests__/schema-builder.test.ts +0 -235
  34. package/src/builder/schema-builder.ts +0 -584
  35. package/src/data-scope/DataScopeManager.ts +0 -269
  36. package/src/data-scope/ViewDataProvider.ts +0 -282
  37. package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
  38. package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
  39. package/src/data-scope/index.ts +0 -24
  40. package/src/errors/__tests__/errors.test.ts +0 -292
  41. package/src/errors/index.ts +0 -269
  42. package/src/evaluator/ExpressionCache.ts +0 -206
  43. package/src/evaluator/ExpressionContext.ts +0 -118
  44. package/src/evaluator/ExpressionEvaluator.ts +0 -315
  45. package/src/evaluator/FormulaFunctions.ts +0 -398
  46. package/src/evaluator/SafeExpressionParser.ts +0 -893
  47. package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
  48. package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
  49. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
  50. package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
  51. package/src/evaluator/index.ts +0 -13
  52. package/src/index.ts +0 -38
  53. package/src/protocols/DndProtocol.ts +0 -168
  54. package/src/protocols/KeyboardProtocol.ts +0 -181
  55. package/src/protocols/NotificationProtocol.ts +0 -150
  56. package/src/protocols/ResponsiveProtocol.ts +0 -210
  57. package/src/protocols/SharingProtocol.ts +0 -185
  58. package/src/protocols/index.ts +0 -13
  59. package/src/query/__tests__/query-ast.test.ts +0 -211
  60. package/src/query/__tests__/window-functions.test.ts +0 -275
  61. package/src/query/index.ts +0 -7
  62. package/src/query/query-ast.ts +0 -341
  63. package/src/registry/PluginScopeImpl.ts +0 -259
  64. package/src/registry/PluginSystem.ts +0 -206
  65. package/src/registry/Registry.ts +0 -219
  66. package/src/registry/WidgetRegistry.ts +0 -316
  67. package/src/registry/__tests__/PluginSystem.test.ts +0 -309
  68. package/src/registry/__tests__/Registry.test.ts +0 -293
  69. package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
  70. package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
  71. package/src/theme/ThemeEngine.ts +0 -530
  72. package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
  73. package/src/theme/index.ts +0 -24
  74. package/src/types/index.ts +0 -21
  75. package/src/utils/__tests__/debug-collector.test.ts +0 -102
  76. package/src/utils/__tests__/debug.test.ts +0 -134
  77. package/src/utils/__tests__/expand-fields.test.ts +0 -120
  78. package/src/utils/__tests__/extract-records.test.ts +0 -50
  79. package/src/utils/__tests__/filter-converter.test.ts +0 -118
  80. package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
  81. package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
  82. package/src/utils/debug-collector.ts +0 -100
  83. package/src/utils/debug.ts +0 -148
  84. package/src/utils/expand-fields.ts +0 -76
  85. package/src/utils/extract-records.ts +0 -33
  86. package/src/utils/filter-converter.ts +0 -133
  87. package/src/utils/merge-views-into-objects.ts +0 -36
  88. package/src/utils/normalize-quick-filter.ts +0 -78
  89. package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
  90. package/src/validation/__tests__/schema-validator.test.ts +0 -118
  91. package/src/validation/__tests__/validation-engine.test.ts +0 -102
  92. package/src/validation/index.ts +0 -10
  93. package/src/validation/schema-validator.ts +0 -344
  94. package/src/validation/validation-engine.ts +0 -528
  95. package/src/validation/validators/index.ts +0 -25
  96. package/src/validation/validators/object-validation-engine.ts +0 -722
  97. package/tsconfig.json +0 -15
  98. package/tsconfig.tsbuildinfo +0 -1
  99. package/vitest.config.ts +0 -2
@@ -1,21 +0,0 @@
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
- export interface SchemaNode {
10
- type: string;
11
- id?: string;
12
- className?: string;
13
- data?: any;
14
- body?: SchemaNode | SchemaNode[];
15
- [key: string]: any;
16
- }
17
-
18
- export interface ComponentRendererProps {
19
- schema: SchemaNode;
20
- [key: string]: any;
21
- }
@@ -1,102 +0,0 @@
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
- import { describe, it, expect, vi, beforeEach } from 'vitest';
10
- import { DebugCollector } from '../debug-collector';
11
-
12
- describe('DebugCollector', () => {
13
- beforeEach(() => {
14
- DebugCollector.resetInstance();
15
- });
16
-
17
- it('should return a singleton instance', () => {
18
- const a = DebugCollector.getInstance();
19
- const b = DebugCollector.getInstance();
20
- expect(a).toBe(b);
21
- });
22
-
23
- it('should collect perf entries', () => {
24
- const collector = DebugCollector.getInstance();
25
- collector.addPerf({ type: 'button', id: 'btn1', durationMs: 5.2, timestamp: Date.now() });
26
- const entries = collector.getEntries('perf');
27
- expect(entries).toHaveLength(1);
28
- expect(entries[0].kind).toBe('perf');
29
- expect((entries[0].data as any).type).toBe('button');
30
- });
31
-
32
- it('should collect expr entries', () => {
33
- const collector = DebugCollector.getInstance();
34
- collector.addExpr({ expression: '${data.x > 1}', result: true, timestamp: Date.now() });
35
- const entries = collector.getEntries('expr');
36
- expect(entries).toHaveLength(1);
37
- expect(entries[0].kind).toBe('expr');
38
- expect((entries[0].data as any).result).toBe(true);
39
- });
40
-
41
- it('should collect event entries', () => {
42
- const collector = DebugCollector.getInstance();
43
- collector.addEvent({ action: 'navigate', payload: { to: '/home' }, timestamp: Date.now() });
44
- const entries = collector.getEntries('event');
45
- expect(entries).toHaveLength(1);
46
- expect(entries[0].kind).toBe('event');
47
- expect((entries[0].data as any).action).toBe('navigate');
48
- });
49
-
50
- it('should return all entries when no kind filter', () => {
51
- const collector = DebugCollector.getInstance();
52
- collector.addPerf({ type: 'text', durationMs: 1, timestamp: Date.now() });
53
- collector.addExpr({ expression: 'a', result: 1, timestamp: Date.now() });
54
- collector.addEvent({ action: 'click', timestamp: Date.now() });
55
- expect(collector.getEntries()).toHaveLength(3);
56
- });
57
-
58
- it('should notify subscribers on new entry', () => {
59
- const collector = DebugCollector.getInstance();
60
- const fn = vi.fn();
61
- collector.subscribe(fn);
62
- collector.addPerf({ type: 'card', durationMs: 2, timestamp: Date.now() });
63
- expect(fn).toHaveBeenCalledTimes(1);
64
- expect(fn).toHaveBeenCalledWith(expect.objectContaining({ kind: 'perf' }));
65
- });
66
-
67
- it('should allow unsubscribe', () => {
68
- const collector = DebugCollector.getInstance();
69
- const fn = vi.fn();
70
- const unsub = collector.subscribe(fn);
71
- unsub();
72
- collector.addPerf({ type: 'x', durationMs: 0, timestamp: Date.now() });
73
- expect(fn).not.toHaveBeenCalled();
74
- });
75
-
76
- it('should clear entries', () => {
77
- const collector = DebugCollector.getInstance();
78
- collector.addPerf({ type: 'x', durationMs: 0, timestamp: Date.now() });
79
- collector.addExpr({ expression: 'a', result: 1, timestamp: Date.now() });
80
- collector.clear();
81
- expect(collector.getEntries()).toHaveLength(0);
82
- });
83
-
84
- it('should cap entries at MAX_ENTRIES', () => {
85
- const collector = DebugCollector.getInstance();
86
- for (let i = 0; i < 250; i++) {
87
- collector.addPerf({ type: `c${i}`, durationMs: i, timestamp: Date.now() });
88
- }
89
- // MAX_ENTRIES is 200
90
- expect(collector.getEntries().length).toBeLessThanOrEqual(200);
91
- });
92
-
93
- it('should swallow subscriber errors gracefully', () => {
94
- const collector = DebugCollector.getInstance();
95
- const badFn = vi.fn(() => { throw new Error('boom'); });
96
- const goodFn = vi.fn();
97
- collector.subscribe(badFn);
98
- collector.subscribe(goodFn);
99
- collector.addPerf({ type: 'x', durationMs: 0, timestamp: Date.now() });
100
- expect(goodFn).toHaveBeenCalledTimes(1);
101
- });
102
- });
@@ -1,134 +0,0 @@
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
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10
- import { debugLog, debugTime, debugTimeEnd, parseDebugFlags, isDebugEnabled } from '../debug';
11
-
12
- describe('Debug Utilities', () => {
13
- let consoleSpy: ReturnType<typeof vi.spyOn>;
14
-
15
- beforeEach(() => {
16
- consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
17
- });
18
-
19
- afterEach(() => {
20
- consoleSpy.mockRestore();
21
- (globalThis as any).OBJECTUI_DEBUG = undefined;
22
- });
23
-
24
- describe('debugLog', () => {
25
- it('should not log when debug is disabled', () => {
26
- (globalThis as any).OBJECTUI_DEBUG = false;
27
- debugLog('schema', 'test message');
28
- expect(consoleSpy).not.toHaveBeenCalled();
29
- });
30
-
31
- it('should log when OBJECTUI_DEBUG is true', () => {
32
- (globalThis as any).OBJECTUI_DEBUG = true;
33
- debugLog('schema', 'Resolving component');
34
- expect(consoleSpy).toHaveBeenCalledWith('[ObjectUI Debug][schema] Resolving component');
35
- });
36
-
37
- it('should log with data when provided', () => {
38
- (globalThis as any).OBJECTUI_DEBUG = true;
39
- debugLog('registry', 'Registered', { type: 'Button' });
40
- expect(consoleSpy).toHaveBeenCalledWith(
41
- '[ObjectUI Debug][registry] Registered',
42
- { type: 'Button' }
43
- );
44
- });
45
-
46
- it('should not log when debug is undefined', () => {
47
- debugLog('action', 'test');
48
- expect(consoleSpy).not.toHaveBeenCalled();
49
- });
50
- });
51
-
52
- describe('debugTime / debugTimeEnd', () => {
53
- it('should not log timing when debug is disabled', () => {
54
- (globalThis as any).OBJECTUI_DEBUG = false;
55
- debugTime('test-timer');
56
- debugTimeEnd('test-timer');
57
- expect(consoleSpy).not.toHaveBeenCalled();
58
- });
59
-
60
- it('should measure and log elapsed time when debug is enabled', () => {
61
- (globalThis as any).OBJECTUI_DEBUG = true;
62
- debugTime('render-test');
63
-
64
- // Simulate some delay via a busy loop
65
- const start = performance.now();
66
- while (performance.now() - start < 5) {
67
- // wait ~5ms
68
- }
69
-
70
- debugTimeEnd('render-test');
71
- expect(consoleSpy).toHaveBeenCalledTimes(1);
72
- expect(consoleSpy).toHaveBeenCalledWith(
73
- expect.stringMatching(/^\[ObjectUI Debug\]\[perf\] render-test: \d+\.\d{2}ms$/)
74
- );
75
- });
76
-
77
- it('should not log if debugTimeEnd is called without debugTime', () => {
78
- (globalThis as any).OBJECTUI_DEBUG = true;
79
- debugTimeEnd('nonexistent');
80
- expect(consoleSpy).not.toHaveBeenCalled();
81
- });
82
- });
83
-
84
- describe('parseDebugFlags', () => {
85
- it('should return enabled:false for empty search string', () => {
86
- expect(parseDebugFlags('')).toEqual({ enabled: false });
87
- });
88
-
89
- it('should detect __debug master switch', () => {
90
- expect(parseDebugFlags('?__debug')).toEqual({ enabled: true });
91
- });
92
-
93
- it('should detect individual sub-flags', () => {
94
- expect(parseDebugFlags('?__debug_schema')).toEqual({ enabled: true, schema: true });
95
- expect(parseDebugFlags('?__debug_perf')).toEqual({ enabled: true, perf: true });
96
- expect(parseDebugFlags('?__debug_data')).toEqual({ enabled: true, data: true });
97
- expect(parseDebugFlags('?__debug_expr')).toEqual({ enabled: true, expr: true });
98
- expect(parseDebugFlags('?__debug_events')).toEqual({ enabled: true, events: true });
99
- expect(parseDebugFlags('?__debug_registry')).toEqual({ enabled: true, registry: true });
100
- });
101
-
102
- it('should combine multiple sub-flags', () => {
103
- const flags = parseDebugFlags('?__debug_schema&__debug_perf&__debug_data');
104
- expect(flags).toEqual({ enabled: true, schema: true, perf: true, data: true });
105
- });
106
-
107
- it('should handle unrelated params gracefully', () => {
108
- const flags = parseDebugFlags('?foo=bar&baz=1');
109
- expect(flags).toEqual({ enabled: false });
110
- });
111
-
112
- it('should handle __debug mixed with sub-flags', () => {
113
- const flags = parseDebugFlags('?__debug&__debug_schema');
114
- expect(flags).toEqual({ enabled: true, schema: true });
115
- });
116
- });
117
-
118
- describe('isDebugEnabled', () => {
119
- it('should return true when globalThis.OBJECTUI_DEBUG is true', () => {
120
- (globalThis as any).OBJECTUI_DEBUG = true;
121
- expect(isDebugEnabled()).toBe(true);
122
- });
123
-
124
- it('should return true when globalThis.OBJECTUI_DEBUG is "true"', () => {
125
- (globalThis as any).OBJECTUI_DEBUG = 'true';
126
- expect(isDebugEnabled()).toBe(true);
127
- });
128
-
129
- it('should return false when no debug flag is set', () => {
130
- (globalThis as any).OBJECTUI_DEBUG = undefined;
131
- expect(isDebugEnabled()).toBe(false);
132
- });
133
- });
134
- });
@@ -1,120 +0,0 @@
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
- import { describe, it, expect } from 'vitest';
10
- import { buildExpandFields } from '../expand-fields';
11
-
12
- describe('buildExpandFields', () => {
13
- const sampleFields = {
14
- name: { type: 'text', label: 'Name' },
15
- email: { type: 'email', label: 'Email' },
16
- account: { type: 'lookup', label: 'Account', reference_to: 'accounts' },
17
- parent: { type: 'master_detail', label: 'Parent', reference_to: 'contacts' },
18
- status: { type: 'select', label: 'Status' },
19
- };
20
-
21
- it('should return lookup and master_detail field names', () => {
22
- const result = buildExpandFields(sampleFields);
23
- expect(result).toEqual(['account', 'parent']);
24
- });
25
-
26
- it('should return empty array when no lookup/master_detail fields exist', () => {
27
- const fields = {
28
- name: { type: 'text' },
29
- age: { type: 'number' },
30
- };
31
- expect(buildExpandFields(fields)).toEqual([]);
32
- });
33
-
34
- it('should return empty array for null/undefined schema', () => {
35
- expect(buildExpandFields(null)).toEqual([]);
36
- expect(buildExpandFields(undefined)).toEqual([]);
37
- });
38
-
39
- it('should return empty array for empty fields object', () => {
40
- expect(buildExpandFields({})).toEqual([]);
41
- });
42
-
43
- it('should filter by string columns when provided', () => {
44
- const result = buildExpandFields(sampleFields, ['name', 'account']);
45
- expect(result).toEqual(['account']);
46
- });
47
-
48
- it('should filter by ListColumn objects with field property', () => {
49
- const columns = [
50
- { field: 'name', label: 'Name' },
51
- { field: 'parent', label: 'Parent Contact' },
52
- ];
53
- const result = buildExpandFields(sampleFields, columns);
54
- expect(result).toEqual(['parent']);
55
- });
56
-
57
- it('should support columns with name property', () => {
58
- const columns = [
59
- { name: 'account', label: 'Account' },
60
- ];
61
- const result = buildExpandFields(sampleFields, columns);
62
- expect(result).toEqual(['account']);
63
- });
64
-
65
- it('should support columns with fieldName property', () => {
66
- const columns = [
67
- { fieldName: 'parent', label: 'Parent' },
68
- ];
69
- const result = buildExpandFields(sampleFields, columns);
70
- expect(result).toEqual(['parent']);
71
- });
72
-
73
- it('should return empty array when columns have no lookup fields', () => {
74
- const result = buildExpandFields(sampleFields, ['name', 'email']);
75
- expect(result).toEqual([]);
76
- });
77
-
78
- it('should handle mixed string and object columns', () => {
79
- const columns = [
80
- 'name',
81
- { field: 'account' },
82
- 'parent',
83
- ];
84
- const result = buildExpandFields(sampleFields, columns);
85
- expect(result).toEqual(['account', 'parent']);
86
- });
87
-
88
- it('should return all lookup fields when columns is empty array', () => {
89
- // Empty columns array does not satisfy the length > 0 check,
90
- // so no column restriction is applied → all lookup fields returned
91
- const result = buildExpandFields(sampleFields, []);
92
- expect(result).toEqual(['account', 'parent']);
93
- });
94
-
95
- it('should handle malformed field definitions gracefully', () => {
96
- const fields = {
97
- name: null,
98
- account: { type: 'lookup' },
99
- broken: 'not-an-object',
100
- empty: {},
101
- };
102
- const result = buildExpandFields(fields as any);
103
- expect(result).toEqual(['account']);
104
- });
105
-
106
- it('should handle only lookup fields', () => {
107
- const fields = {
108
- ref1: { type: 'lookup', reference_to: 'obj1' },
109
- ref2: { type: 'lookup', reference_to: 'obj2' },
110
- };
111
- expect(buildExpandFields(fields)).toEqual(['ref1', 'ref2']);
112
- });
113
-
114
- it('should handle only master_detail fields', () => {
115
- const fields = {
116
- detail1: { type: 'master_detail', reference_to: 'obj1' },
117
- };
118
- expect(buildExpandFields(fields)).toEqual(['detail1']);
119
- });
120
- });
@@ -1,50 +0,0 @@
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
- import { describe, it, expect } from 'vitest';
10
- import { extractRecords } from '../extract-records';
11
-
12
- describe('extractRecords', () => {
13
- const sampleData = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
14
-
15
- it('should return the array directly when results is an array', () => {
16
- expect(extractRecords(sampleData)).toEqual(sampleData);
17
- });
18
-
19
- it('should extract from results.records', () => {
20
- expect(extractRecords({ records: sampleData })).toEqual(sampleData);
21
- });
22
-
23
- it('should extract from results.data', () => {
24
- expect(extractRecords({ data: sampleData })).toEqual(sampleData);
25
- });
26
-
27
- it('should extract from results.value', () => {
28
- expect(extractRecords({ value: sampleData })).toEqual(sampleData);
29
- });
30
-
31
- it('should return empty array for null/undefined', () => {
32
- expect(extractRecords(null)).toEqual([]);
33
- expect(extractRecords(undefined)).toEqual([]);
34
- });
35
-
36
- it('should return empty array for non-array/non-object', () => {
37
- expect(extractRecords('string')).toEqual([]);
38
- expect(extractRecords(42)).toEqual([]);
39
- });
40
-
41
- it('should return empty array for object without recognized keys', () => {
42
- expect(extractRecords({ total: 100 })).toEqual([]);
43
- });
44
-
45
- it('should prefer records over data and value', () => {
46
- const records = [{ id: 1 }];
47
- const data = [{ id: 2 }];
48
- expect(extractRecords({ records, data })).toEqual(records);
49
- });
50
- });
@@ -1,118 +0,0 @@
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
- import { describe, it, expect, vi } from 'vitest';
10
- import { convertFiltersToAST, convertOperatorToAST, type FilterNode } from '../filter-converter';
11
-
12
- describe('Filter Converter Utilities', () => {
13
- describe('convertOperatorToAST', () => {
14
- it('should convert known operators', () => {
15
- expect(convertOperatorToAST('$eq')).toBe('=');
16
- expect(convertOperatorToAST('$ne')).toBe('!=');
17
- expect(convertOperatorToAST('$gt')).toBe('>');
18
- expect(convertOperatorToAST('$gte')).toBe('>=');
19
- expect(convertOperatorToAST('$lt')).toBe('<');
20
- expect(convertOperatorToAST('$lte')).toBe('<=');
21
- expect(convertOperatorToAST('$in')).toBe('in');
22
- expect(convertOperatorToAST('$nin')).toBe('notin');
23
- expect(convertOperatorToAST('$notin')).toBe('notin');
24
- expect(convertOperatorToAST('$contains')).toBe('contains');
25
- expect(convertOperatorToAST('$startswith')).toBe('startswith');
26
- expect(convertOperatorToAST('$between')).toBe('between');
27
- });
28
-
29
- it('should return null for unknown operators', () => {
30
- expect(convertOperatorToAST('$unknown')).toBe(null);
31
- expect(convertOperatorToAST('$exists')).toBe(null);
32
- });
33
- });
34
-
35
- describe('convertFiltersToAST', () => {
36
- it('should convert simple equality filter', () => {
37
- const result = convertFiltersToAST({ status: 'active' });
38
- expect(result).toEqual(['status', '=', 'active']);
39
- });
40
-
41
- it('should convert single operator filter', () => {
42
- const result = convertFiltersToAST({ age: { $gte: 18 } });
43
- expect(result).toEqual(['age', '>=', 18]);
44
- });
45
-
46
- it('should convert multiple operators on same field', () => {
47
- const result = convertFiltersToAST({ age: { $gte: 18, $lte: 65 } }) as FilterNode;
48
- expect(result[0]).toBe('and');
49
- expect(result.slice(1)).toContainEqual(['age', '>=', 18]);
50
- expect(result.slice(1)).toContainEqual(['age', '<=', 65]);
51
- });
52
-
53
- it('should convert multiple fields with and logic', () => {
54
- const result = convertFiltersToAST({
55
- age: { $gte: 18 },
56
- status: 'active'
57
- }) as FilterNode;
58
- expect(result[0]).toBe('and');
59
- expect(result.slice(1)).toContainEqual(['age', '>=', 18]);
60
- expect(result.slice(1)).toContainEqual(['status', '=', 'active']);
61
- });
62
-
63
- it('should handle $in operator', () => {
64
- const result = convertFiltersToAST({
65
- status: { $in: ['active', 'pending'] }
66
- });
67
- expect(result).toEqual(['status', 'in', ['active', 'pending']]);
68
- });
69
-
70
- it('should handle $nin operator', () => {
71
- const result = convertFiltersToAST({
72
- status: { $nin: ['archived'] }
73
- });
74
- expect(result).toEqual(['status', 'notin', ['archived']]);
75
- });
76
-
77
- it('should warn on $regex operator and convert to contains', () => {
78
- const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
79
-
80
- const result = convertFiltersToAST({
81
- name: { $regex: '^John' }
82
- });
83
-
84
- expect(result).toEqual(['name', 'contains', '^John']);
85
- expect(consoleSpy).toHaveBeenCalledWith(
86
- expect.stringContaining('[ObjectUI] Warning: $regex operator is not fully supported')
87
- );
88
-
89
- consoleSpy.mockRestore();
90
- });
91
-
92
- it('should throw error on unknown operator', () => {
93
- expect(() => {
94
- convertFiltersToAST({ age: { $unknown: 18 } });
95
- }).toThrow('[ObjectUI] Unknown filter operator');
96
- });
97
-
98
- it('should skip null and undefined values', () => {
99
- const result = convertFiltersToAST({
100
- name: 'John',
101
- age: null,
102
- email: undefined
103
- });
104
- expect(result).toEqual(['name', '=', 'John']);
105
- });
106
-
107
- it('should return original filter if empty after filtering', () => {
108
- const result = convertFiltersToAST({
109
- age: null,
110
- email: undefined
111
- });
112
- expect(result).toEqual({
113
- age: null,
114
- email: undefined
115
- });
116
- });
117
- });
118
- });
@@ -1,110 +0,0 @@
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
- import { describe, it, expect } from 'vitest';
10
- import { mergeViewsIntoObjects } from '../merge-views-into-objects';
11
-
12
- describe('mergeViewsIntoObjects', () => {
13
- it('should merge listViews from views into corresponding objects', () => {
14
- const objects = [{ name: 'account', label: 'Account', fields: {} }];
15
- const views = [
16
- {
17
- listViews: {
18
- all_accounts: {
19
- name: 'all_accounts',
20
- label: 'All Accounts',
21
- type: 'grid',
22
- data: { provider: 'object', object: 'account' },
23
- },
24
- },
25
- },
26
- ];
27
-
28
- const result = mergeViewsIntoObjects(objects, views);
29
- expect(result[0].listViews.all_accounts).toBeDefined();
30
- expect(result[0].listViews.all_accounts.label).toBe('All Accounts');
31
- });
32
-
33
- it('should preserve existing listViews on objects', () => {
34
- const objects = [
35
- {
36
- name: 'todo_task',
37
- label: 'Task',
38
- fields: {},
39
- listViews: {
40
- existing: { name: 'existing', label: 'Existing View', type: 'grid', data: { provider: 'object', object: 'todo_task' } },
41
- },
42
- },
43
- ];
44
- const views = [
45
- {
46
- listViews: {
47
- new_view: {
48
- name: 'new_view',
49
- label: 'New View',
50
- type: 'grid',
51
- data: { provider: 'object', object: 'todo_task' },
52
- },
53
- },
54
- },
55
- ];
56
-
57
- const result = mergeViewsIntoObjects(objects, views);
58
- const task = result[0];
59
- expect(task.listViews.existing).toBeDefined();
60
- expect(task.listViews.new_view).toBeDefined();
61
- });
62
-
63
- it('should ignore listViews without data.object', () => {
64
- const objects = [{ name: 'account', label: 'Account', fields: {} }];
65
- const views = [
66
- {
67
- listViews: {
68
- orphan: { name: 'orphan', label: 'Orphan View', type: 'grid' },
69
- },
70
- },
71
- ];
72
-
73
- const result = mergeViewsIntoObjects(objects, views);
74
- expect(result[0].listViews).toBeUndefined();
75
- });
76
-
77
- it('should return objects unchanged when views is empty', () => {
78
- const objects = [{ name: 'account', label: 'Account', fields: {} }];
79
-
80
- const result = mergeViewsIntoObjects(objects, []);
81
- expect(result).toEqual(objects);
82
- });
83
-
84
- it('should ignore views without listViews property', () => {
85
- const objects = [{ name: 'account', label: 'Account', fields: {} }];
86
- const views = [{ someOtherProp: true }];
87
-
88
- const result = mergeViewsIntoObjects(objects, views);
89
- expect(result).toEqual(objects);
90
- });
91
-
92
- it('should merge views from multiple view entries into different objects', () => {
93
- const objects = [
94
- { name: 'account', label: 'Account', fields: {} },
95
- { name: 'contact', label: 'Contact', fields: {} },
96
- ];
97
- const views = [
98
- {
99
- listViews: {
100
- all_accounts: { name: 'all_accounts', label: 'All Accounts', type: 'grid', data: { provider: 'object', object: 'account' } },
101
- all_contacts: { name: 'all_contacts', label: 'All Contacts', type: 'grid', data: { provider: 'object', object: 'contact' } },
102
- },
103
- },
104
- ];
105
-
106
- const result = mergeViewsIntoObjects(objects, views);
107
- expect(result[0].listViews.all_accounts).toBeDefined();
108
- expect(result[1].listViews.all_contacts).toBeDefined();
109
- });
110
- });