@object-ui/core 3.3.0 → 3.3.2
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/CHANGELOG.md +12 -0
- package/README.md +20 -1
- package/dist/actions/ActionRunner.d.ts +9 -0
- package/dist/actions/ActionRunner.js +41 -4
- package/dist/adapters/ValueDataSource.js +3 -1
- package/dist/registry/Registry.d.ts +47 -0
- package/dist/registry/Registry.js +92 -0
- package/dist/utils/filter-converter.js +25 -5
- package/package.json +32 -8
- package/.turbo/turbo-build.log +0 -4
- package/src/__benchmarks__/core.bench.ts +0 -64
- package/src/__tests__/protocols/DndProtocol.test.ts +0 -186
- package/src/__tests__/protocols/KeyboardProtocol.test.ts +0 -177
- package/src/__tests__/protocols/NotificationProtocol.test.ts +0 -142
- package/src/__tests__/protocols/ResponsiveProtocol.test.ts +0 -176
- package/src/__tests__/protocols/SharingProtocol.test.ts +0 -188
- package/src/actions/ActionEngine.ts +0 -268
- package/src/actions/ActionRunner.ts +0 -717
- package/src/actions/TransactionManager.ts +0 -521
- package/src/actions/UndoManager.ts +0 -215
- package/src/actions/__tests__/ActionEngine.test.ts +0 -206
- package/src/actions/__tests__/ActionRunner.params.test.ts +0 -134
- package/src/actions/__tests__/ActionRunner.test.ts +0 -711
- package/src/actions/__tests__/TransactionManager.test.ts +0 -447
- package/src/actions/__tests__/UndoManager.test.ts +0 -320
- package/src/actions/index.ts +0 -12
- package/src/adapters/ApiDataSource.ts +0 -376
- package/src/adapters/README.md +0 -180
- package/src/adapters/ValueDataSource.ts +0 -459
- package/src/adapters/__tests__/ApiDataSource.test.ts +0 -418
- package/src/adapters/__tests__/ValueDataSource.test.ts +0 -571
- package/src/adapters/__tests__/resolveDataSource.test.ts +0 -144
- package/src/adapters/index.ts +0 -15
- package/src/adapters/resolveDataSource.ts +0 -79
- package/src/builder/__tests__/schema-builder.test.ts +0 -235
- package/src/builder/schema-builder.ts +0 -584
- package/src/data-scope/DataScopeManager.ts +0 -269
- package/src/data-scope/ViewDataProvider.ts +0 -282
- package/src/data-scope/__tests__/DataScopeManager.test.ts +0 -211
- package/src/data-scope/__tests__/ViewDataProvider.test.ts +0 -270
- package/src/data-scope/index.ts +0 -24
- package/src/errors/__tests__/errors.test.ts +0 -292
- package/src/errors/index.ts +0 -269
- package/src/evaluator/ExpressionCache.ts +0 -206
- package/src/evaluator/ExpressionContext.ts +0 -118
- package/src/evaluator/ExpressionEvaluator.ts +0 -315
- package/src/evaluator/FormulaFunctions.ts +0 -398
- package/src/evaluator/SafeExpressionParser.ts +0 -893
- package/src/evaluator/__tests__/ExpressionCache.test.ts +0 -135
- package/src/evaluator/__tests__/ExpressionContext.test.ts +0 -110
- package/src/evaluator/__tests__/ExpressionEvaluator.test.ts +0 -558
- package/src/evaluator/__tests__/FormulaFunctions.test.ts +0 -447
- package/src/evaluator/index.ts +0 -13
- package/src/index.ts +0 -38
- package/src/protocols/DndProtocol.ts +0 -168
- package/src/protocols/KeyboardProtocol.ts +0 -181
- package/src/protocols/NotificationProtocol.ts +0 -150
- package/src/protocols/ResponsiveProtocol.ts +0 -210
- package/src/protocols/SharingProtocol.ts +0 -185
- package/src/protocols/index.ts +0 -13
- package/src/query/__tests__/query-ast.test.ts +0 -211
- package/src/query/__tests__/window-functions.test.ts +0 -275
- package/src/query/index.ts +0 -7
- package/src/query/query-ast.ts +0 -341
- package/src/registry/PluginScopeImpl.ts +0 -259
- package/src/registry/PluginSystem.ts +0 -206
- package/src/registry/Registry.ts +0 -219
- package/src/registry/WidgetRegistry.ts +0 -316
- package/src/registry/__tests__/PluginSystem.test.ts +0 -309
- package/src/registry/__tests__/Registry.test.ts +0 -293
- package/src/registry/__tests__/WidgetRegistry.test.ts +0 -321
- package/src/registry/__tests__/plugin-scope-integration.test.ts +0 -283
- package/src/theme/ThemeEngine.ts +0 -530
- package/src/theme/__tests__/ThemeEngine.test.ts +0 -668
- package/src/theme/index.ts +0 -24
- package/src/types/index.ts +0 -21
- package/src/utils/__tests__/debug-collector.test.ts +0 -102
- package/src/utils/__tests__/debug.test.ts +0 -134
- package/src/utils/__tests__/expand-fields.test.ts +0 -120
- package/src/utils/__tests__/extract-records.test.ts +0 -50
- package/src/utils/__tests__/filter-converter.test.ts +0 -118
- package/src/utils/__tests__/merge-views-into-objects.test.ts +0 -110
- package/src/utils/__tests__/normalize-quick-filter.test.ts +0 -123
- package/src/utils/debug-collector.ts +0 -100
- package/src/utils/debug.ts +0 -148
- package/src/utils/expand-fields.ts +0 -76
- package/src/utils/extract-records.ts +0 -33
- package/src/utils/filter-converter.ts +0 -133
- package/src/utils/merge-views-into-objects.ts +0 -36
- package/src/utils/normalize-quick-filter.ts +0 -78
- package/src/validation/__tests__/object-validation-engine.test.ts +0 -567
- package/src/validation/__tests__/schema-validator.test.ts +0 -118
- package/src/validation/__tests__/validation-engine.test.ts +0 -102
- package/src/validation/index.ts +0 -10
- package/src/validation/schema-validator.ts +0 -344
- package/src/validation/validation-engine.ts +0 -528
- package/src/validation/validators/index.ts +0 -25
- package/src/validation/validators/object-validation-engine.ts +0 -722
- package/tsconfig.json +0 -15
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -2
package/src/types/index.ts
DELETED
|
@@ -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
|
-
});
|