@object-ui/core 3.1.5 → 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 (110) hide show
  1. package/CHANGELOG.md +18 -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.d.ts +5 -1
  6. package/dist/adapters/ValueDataSource.js +30 -1
  7. package/dist/errors/index.js +2 -3
  8. package/dist/evaluator/ExpressionCache.d.ts +9 -10
  9. package/dist/evaluator/ExpressionCache.js +29 -8
  10. package/dist/evaluator/SafeExpressionParser.d.ts +131 -0
  11. package/dist/evaluator/SafeExpressionParser.js +851 -0
  12. package/dist/evaluator/index.d.ts +1 -0
  13. package/dist/evaluator/index.js +1 -0
  14. package/dist/protocols/DndProtocol.js +2 -14
  15. package/dist/protocols/KeyboardProtocol.js +1 -4
  16. package/dist/protocols/NotificationProtocol.js +3 -13
  17. package/dist/utils/debug.js +2 -1
  18. package/dist/utils/filter-converter.js +25 -5
  19. package/package.json +33 -9
  20. package/.turbo/turbo-build.log +0 -4
  21. package/src/__benchmarks__/core.bench.ts +0 -64
  22. package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
  23. package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
  24. package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
  25. package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
  26. package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
  27. package/src/actions/ActionEngine.ts +0 -268
  28. package/src/actions/ActionRunner.ts +0 -717
  29. package/src/actions/TransactionManager.ts +0 -521
  30. package/src/actions/UndoManager.ts +0 -215
  31. package/src/actions/__tests__/ActionEngine.test.ts +0 -206
  32. package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
  33. package/src/actions/__tests__/ActionRunner.test.ts +0 -711
  34. package/src/actions/__tests__/TransactionManager.test.ts +0 -447
  35. package/src/actions/__tests__/UndoManager.test.ts +0 -320
  36. package/src/actions/index.ts +0 -12
  37. package/src/adapters/ApiDataSource.ts +0 -376
  38. package/src/adapters/README.md +0 -180
  39. package/src/adapters/ValueDataSource.ts +0 -438
  40. package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
  41. package/src/adapters/__tests__/ValueDataSource.test.ts +0 -472
  42. package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
  43. package/src/adapters/index.ts +0 -15
  44. package/src/adapters/resolveDataSource.ts +0 -79
  45. package/src/builder/__tests__/schema-builder.test.ts +0 -235
  46. package/src/builder/schema-builder.ts +0 -584
  47. package/src/data-scope/DataScopeManager.ts +0 -269
  48. package/src/data-scope/ViewDataProvider.ts +0 -282
  49. package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
  50. package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
  51. package/src/data-scope/index.ts +0 -24
  52. package/src/errors/__tests__/errors.test.ts +0 -292
  53. package/src/errors/index.ts +0 -270
  54. package/src/evaluator/ExpressionCache.ts +0 -192
  55. package/src/evaluator/ExpressionContext.ts +0 -118
  56. package/src/evaluator/ExpressionEvaluator.ts +0 -315
  57. package/src/evaluator/FormulaFunctions.ts +0 -398
  58. package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
  59. package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
  60. package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -131
  61. package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
  62. package/src/evaluator/index.ts +0 -12
  63. package/src/index.ts +0 -38
  64. package/src/protocols/DndProtocol.ts +0 -184
  65. package/src/protocols/KeyboardProtocol.ts +0 -185
  66. package/src/protocols/NotificationProtocol.ts +0 -159
  67. package/src/protocols/ResponsiveProtocol.ts +0 -210
  68. package/src/protocols/SharingProtocol.ts +0 -185
  69. package/src/protocols/index.ts +0 -13
  70. package/src/query/__tests__/query-ast.test.ts +0 -211
  71. package/src/query/__tests__/window-functions.test.ts +0 -275
  72. package/src/query/index.ts +0 -7
  73. package/src/query/query-ast.ts +0 -341
  74. package/src/registry/PluginScopeImpl.ts +0 -259
  75. package/src/registry/PluginSystem.ts +0 -206
  76. package/src/registry/Registry.ts +0 -219
  77. package/src/registry/WidgetRegistry.ts +0 -316
  78. package/src/registry/__tests__/PluginSystem.test.ts +0 -309
  79. package/src/registry/__tests__/Registry.test.ts +0 -293
  80. package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
  81. package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
  82. package/src/theme/ThemeEngine.ts +0 -530
  83. package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
  84. package/src/theme/index.ts +0 -24
  85. package/src/types/index.ts +0 -21
  86. package/src/utils/__tests__/debug-collector.test.ts +0 -102
  87. package/src/utils/__tests__/debug.test.ts +0 -134
  88. package/src/utils/__tests__/expand-fields.test.ts +0 -120
  89. package/src/utils/__tests__/extract-records.test.ts +0 -50
  90. package/src/utils/__tests__/filter-converter.test.ts +0 -118
  91. package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
  92. package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
  93. package/src/utils/debug-collector.ts +0 -100
  94. package/src/utils/debug.ts +0 -147
  95. package/src/utils/expand-fields.ts +0 -76
  96. package/src/utils/extract-records.ts +0 -33
  97. package/src/utils/filter-converter.ts +0 -133
  98. package/src/utils/merge-views-into-objects.ts +0 -36
  99. package/src/utils/normalize-quick-filter.ts +0 -78
  100. package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
  101. package/src/validation/__tests__/schema-validator.test.ts +0 -118
  102. package/src/validation/__tests__/validation-engine.test.ts +0 -102
  103. package/src/validation/index.ts +0 -10
  104. package/src/validation/schema-validator.ts +0 -344
  105. package/src/validation/validation-engine.ts +0 -528
  106. package/src/validation/validators/index.ts +0 -25
  107. package/src/validation/validators/object-validation-engine.ts +0 -722
  108. package/tsconfig.json +0 -15
  109. package/tsconfig.tsbuildinfo +0 -1
  110. 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
- });