@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,131 +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 { ExpressionEvaluator, evaluateExpression, evaluateCondition, evaluatePlainCondition } from '../ExpressionEvaluator';
11
- import { ExpressionContext } from '../ExpressionContext';
12
-
13
- describe('ExpressionContext', () => {
14
- it('should create context with initial data', () => {
15
- const ctx = new ExpressionContext({ name: 'John', age: 30 });
16
- expect(ctx.get('name')).toBe('John');
17
- expect(ctx.get('age')).toBe(30);
18
- });
19
-
20
- it('should support nested property access', () => {
21
- const ctx = new ExpressionContext({
22
- user: {
23
- name: 'John',
24
- profile: {
25
- email: 'john@example.com'
26
- }
27
- }
28
- });
29
-
30
- expect(ctx.get('user.name')).toBe('John');
31
- expect(ctx.get('user.profile.email')).toBe('john@example.com');
32
- });
33
-
34
- it('should support scope stacking', () => {
35
- const ctx = new ExpressionContext({ x: 10 });
36
- expect(ctx.get('x')).toBe(10);
37
-
38
- ctx.pushScope({ x: 20, y: 30 });
39
- expect(ctx.get('x')).toBe(20);
40
- expect(ctx.get('y')).toBe(30);
41
-
42
- ctx.popScope();
43
- expect(ctx.get('x')).toBe(10);
44
- expect(ctx.get('y')).toBeUndefined();
45
- });
46
- });
47
-
48
- describe('ExpressionEvaluator', () => {
49
- describe('evaluate', () => {
50
- it('should evaluate simple template expressions', () => {
51
- const evaluator = new ExpressionEvaluator({ name: 'John' });
52
- expect(evaluator.evaluate('Hello ${name}!')).toBe('Hello John!');
53
- });
54
-
55
- it('should evaluate nested property access', () => {
56
- const evaluator = new ExpressionEvaluator({
57
- data: { amount: 1500 }
58
- });
59
- expect(evaluator.evaluate('Amount: ${data.amount}')).toBe('Amount: 1500');
60
- });
61
-
62
- it('should handle non-string values', () => {
63
- const evaluator = new ExpressionEvaluator({});
64
- expect(evaluator.evaluate(true)).toBe(true);
65
- expect(evaluator.evaluate(42)).toBe(42);
66
- expect(evaluator.evaluate(null)).toBe(null);
67
- });
68
- });
69
-
70
- describe('evaluateExpression', () => {
71
- it('should evaluate comparison operators', () => {
72
- const evaluator = new ExpressionEvaluator({ data: { amount: 1500 } });
73
-
74
- expect(evaluator.evaluateExpression('data.amount > 1000')).toBe(true);
75
- expect(evaluator.evaluateExpression('data.amount < 1000')).toBe(false);
76
- });
77
-
78
- it('should block dangerous expressions', () => {
79
- const evaluator = new ExpressionEvaluator({});
80
-
81
- expect(() => evaluator.evaluateExpression('eval("malicious")')).toThrow();
82
- expect(() => evaluator.evaluateExpression('process.exit()')).toThrow();
83
- });
84
- });
85
-
86
- describe('evaluateCondition', () => {
87
- it('should return boolean for condition expressions', () => {
88
- const evaluator = new ExpressionEvaluator({ data: { age: 25 } });
89
-
90
- expect(evaluator.evaluateCondition('${data.age >= 18}')).toBe(true);
91
- expect(evaluator.evaluateCondition('${data.age < 18}')).toBe(false);
92
- });
93
-
94
- it('should handle boolean values directly', () => {
95
- const evaluator = new ExpressionEvaluator({});
96
-
97
- expect(evaluator.evaluateCondition(true)).toBe(true);
98
- expect(evaluator.evaluateCondition(false)).toBe(false);
99
- });
100
- });
101
- });
102
-
103
- describe('evaluatePlainCondition', () => {
104
- it('should evaluate a plain condition with direct field references', () => {
105
- expect(evaluatePlainCondition("status == 'overdue'", { status: 'overdue' })).toBe(true);
106
- expect(evaluatePlainCondition("status == 'overdue'", { status: 'active' })).toBe(false);
107
- });
108
-
109
- it('should evaluate numeric comparisons', () => {
110
- expect(evaluatePlainCondition('amount > 1000', { amount: 2500 })).toBe(true);
111
- expect(evaluatePlainCondition('amount > 1000', { amount: 500 })).toBe(false);
112
- });
113
-
114
- it('should evaluate compound conditions', () => {
115
- expect(evaluatePlainCondition("amount > 1000 && status === 'urgent'", { amount: 2000, status: 'urgent' })).toBe(true);
116
- expect(evaluatePlainCondition("amount > 1000 && status === 'urgent'", { amount: 2000, status: 'normal' })).toBe(false);
117
- });
118
-
119
- it('should support data.field references in template expressions', () => {
120
- expect(evaluatePlainCondition('${data.amount > 1000}', { amount: 2000 })).toBe(true);
121
- expect(evaluatePlainCondition('${data.amount > 1000}', { amount: 500 })).toBe(false);
122
- });
123
-
124
- it('should return false for invalid expressions', () => {
125
- expect(evaluatePlainCondition('!!!invalidSyntax', { status: 'ok' })).toBe(false);
126
- });
127
-
128
- it('should return false for non-boolean results', () => {
129
- expect(evaluatePlainCondition('status', { status: 'active' })).toBe(false);
130
- });
131
- });
@@ -1,447 +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 { FormulaFunctions } from '../FormulaFunctions';
11
- import { ExpressionEvaluator, evaluateExpression as evalExprFn } from '../ExpressionEvaluator';
12
-
13
- describe('FormulaFunctions', () => {
14
- describe('Registry', () => {
15
- it('should register and retrieve custom functions', () => {
16
- const formulas = new FormulaFunctions();
17
- formulas.register('DOUBLE', (x: number) => x * 2);
18
- expect(formulas.has('DOUBLE')).toBe(true);
19
- expect(formulas.get('DOUBLE')!(5)).toBe(10);
20
- });
21
-
22
- it('should be case-insensitive', () => {
23
- const formulas = new FormulaFunctions();
24
- expect(formulas.has('sum')).toBe(true);
25
- expect(formulas.has('SUM')).toBe(true);
26
- expect(formulas.has('Sum')).toBe(true);
27
- });
28
-
29
- it('should list all registered function names', () => {
30
- const formulas = new FormulaFunctions();
31
- const names = formulas.getNames();
32
- expect(names).toContain('SUM');
33
- expect(names).toContain('AVG');
34
- expect(names).toContain('IF');
35
- expect(names).toContain('TODAY');
36
- expect(names).toContain('CONCAT');
37
- });
38
-
39
- it('should export functions as an object', () => {
40
- const formulas = new FormulaFunctions();
41
- const obj = formulas.toObject();
42
- expect(typeof obj.SUM).toBe('function');
43
- expect(typeof obj.IF).toBe('function');
44
- });
45
- });
46
-
47
- describe('Aggregation Functions', () => {
48
- it('SUM should sum numeric values', () => {
49
- const formulas = new FormulaFunctions();
50
- const SUM = formulas.get('SUM')!;
51
- expect(SUM(1, 2, 3)).toBe(6);
52
- expect(SUM(10, 20)).toBe(30);
53
- });
54
-
55
- it('SUM should handle arrays', () => {
56
- const formulas = new FormulaFunctions();
57
- const SUM = formulas.get('SUM')!;
58
- expect(SUM([1, 2, 3])).toBe(6);
59
- expect(SUM([10], [20])).toBe(30);
60
- });
61
-
62
- it('SUM should handle empty args', () => {
63
- const formulas = new FormulaFunctions();
64
- const SUM = formulas.get('SUM')!;
65
- expect(SUM()).toBe(0);
66
- });
67
-
68
- it('AVG should calculate average', () => {
69
- const formulas = new FormulaFunctions();
70
- const AVG = formulas.get('AVG')!;
71
- expect(AVG(10, 20, 30)).toBe(20);
72
- expect(AVG([2, 4, 6])).toBe(4);
73
- });
74
-
75
- it('AVG should return 0 for empty args', () => {
76
- const formulas = new FormulaFunctions();
77
- const AVG = formulas.get('AVG')!;
78
- expect(AVG()).toBe(0);
79
- });
80
-
81
- it('COUNT should count non-null values', () => {
82
- const formulas = new FormulaFunctions();
83
- const COUNT = formulas.get('COUNT')!;
84
- expect(COUNT(1, 2, 3)).toBe(3);
85
- expect(COUNT(1, null, undefined, 4)).toBe(2);
86
- expect(COUNT([1, null, 3])).toBe(2);
87
- });
88
-
89
- it('MIN should return minimum value', () => {
90
- const formulas = new FormulaFunctions();
91
- const MIN = formulas.get('MIN')!;
92
- expect(MIN(5, 3, 8, 1)).toBe(1);
93
- expect(MIN([10, 2, 7])).toBe(2);
94
- });
95
-
96
- it('MIN should return 0 for empty args', () => {
97
- const formulas = new FormulaFunctions();
98
- const MIN = formulas.get('MIN')!;
99
- expect(MIN()).toBe(0);
100
- });
101
-
102
- it('MAX should return maximum value', () => {
103
- const formulas = new FormulaFunctions();
104
- const MAX = formulas.get('MAX')!;
105
- expect(MAX(5, 3, 8, 1)).toBe(8);
106
- expect(MAX([10, 2, 7])).toBe(10);
107
- });
108
-
109
- it('MAX should return 0 for empty args', () => {
110
- const formulas = new FormulaFunctions();
111
- const MAX = formulas.get('MAX')!;
112
- expect(MAX()).toBe(0);
113
- });
114
- });
115
-
116
- describe('Date Functions', () => {
117
- it('TODAY should return current date string', () => {
118
- const formulas = new FormulaFunctions();
119
- const TODAY = formulas.get('TODAY')!;
120
- const result = TODAY();
121
- // Should be YYYY-MM-DD format
122
- expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
123
- });
124
-
125
- it('NOW should return ISO timestamp', () => {
126
- const formulas = new FormulaFunctions();
127
- const NOW = formulas.get('NOW')!;
128
- const result = NOW();
129
- // Should be a valid ISO 8601 date
130
- expect(new Date(result).toISOString()).toBe(result);
131
- });
132
-
133
- it('DATEADD should add days', () => {
134
- const formulas = new FormulaFunctions();
135
- const DATEADD = formulas.get('DATEADD')!;
136
- const result = DATEADD('2025-01-01T00:00:00.000Z', 5, 'days');
137
- expect(new Date(result).getUTCDate()).toBe(6);
138
- });
139
-
140
- it('DATEADD should add months', () => {
141
- const formulas = new FormulaFunctions();
142
- const DATEADD = formulas.get('DATEADD')!;
143
- const result = DATEADD('2025-01-15T00:00:00.000Z', 2, 'months');
144
- expect(new Date(result).getUTCMonth()).toBe(2); // March (0-indexed)
145
- });
146
-
147
- it('DATEADD should add years', () => {
148
- const formulas = new FormulaFunctions();
149
- const DATEADD = formulas.get('DATEADD')!;
150
- const result = DATEADD('2025-06-15T00:00:00.000Z', 3, 'years');
151
- expect(new Date(result).getUTCFullYear()).toBe(2028);
152
- });
153
-
154
- it('DATEADD should throw for invalid date', () => {
155
- const formulas = new FormulaFunctions();
156
- const DATEADD = formulas.get('DATEADD')!;
157
- expect(() => DATEADD('invalid', 1, 'days')).toThrow('Invalid date');
158
- });
159
-
160
- it('DATEADD should throw for unsupported unit', () => {
161
- const formulas = new FormulaFunctions();
162
- const DATEADD = formulas.get('DATEADD')!;
163
- expect(() => DATEADD('2025-01-01', 1, 'weeks')).toThrow('Unsupported unit');
164
- });
165
-
166
- it('DATEDIFF should calculate day difference', () => {
167
- const formulas = new FormulaFunctions();
168
- const DATEDIFF = formulas.get('DATEDIFF')!;
169
- expect(DATEDIFF('2025-01-01', '2025-01-11', 'days')).toBe(10);
170
- });
171
-
172
- it('DATEDIFF should calculate month difference', () => {
173
- const formulas = new FormulaFunctions();
174
- const DATEDIFF = formulas.get('DATEDIFF')!;
175
- expect(DATEDIFF('2025-01-15', '2025-04-15', 'months')).toBe(3);
176
- });
177
-
178
- it('DATEDIFF should calculate year difference', () => {
179
- const formulas = new FormulaFunctions();
180
- const DATEDIFF = formulas.get('DATEDIFF')!;
181
- expect(DATEDIFF('2020-06-01', '2025-06-01', 'years')).toBe(5);
182
- });
183
-
184
- it('DATEDIFF should throw for invalid date', () => {
185
- const formulas = new FormulaFunctions();
186
- const DATEDIFF = formulas.get('DATEDIFF')!;
187
- expect(() => DATEDIFF('invalid', '2025-01-01', 'days')).toThrow('Invalid date');
188
- });
189
- });
190
-
191
- describe('Logic Functions', () => {
192
- it('IF should return trueValue when condition is truthy', () => {
193
- const formulas = new FormulaFunctions();
194
- const IF = formulas.get('IF')!;
195
- expect(IF(true, 'yes', 'no')).toBe('yes');
196
- expect(IF(1, 'yes', 'no')).toBe('yes');
197
- });
198
-
199
- it('IF should return falseValue when condition is falsy', () => {
200
- const formulas = new FormulaFunctions();
201
- const IF = formulas.get('IF')!;
202
- expect(IF(false, 'yes', 'no')).toBe('no');
203
- expect(IF(0, 'yes', 'no')).toBe('no');
204
- expect(IF(null, 'yes', 'no')).toBe('no');
205
- });
206
-
207
- it('AND should return true only when all args are truthy', () => {
208
- const formulas = new FormulaFunctions();
209
- const AND = formulas.get('AND')!;
210
- expect(AND(true, true, true)).toBe(true);
211
- expect(AND(true, false, true)).toBe(false);
212
- expect(AND(1, 'hello', true)).toBe(true);
213
- });
214
-
215
- it('OR should return true when any arg is truthy', () => {
216
- const formulas = new FormulaFunctions();
217
- const OR = formulas.get('OR')!;
218
- expect(OR(false, false, true)).toBe(true);
219
- expect(OR(false, false, false)).toBe(false);
220
- expect(OR(0, '', null)).toBe(false);
221
- });
222
-
223
- it('NOT should negate a value', () => {
224
- const formulas = new FormulaFunctions();
225
- const NOT = formulas.get('NOT')!;
226
- expect(NOT(true)).toBe(false);
227
- expect(NOT(false)).toBe(true);
228
- expect(NOT(0)).toBe(true);
229
- expect(NOT(1)).toBe(false);
230
- });
231
-
232
- it('SWITCH should match cases and return result', () => {
233
- const formulas = new FormulaFunctions();
234
- const SWITCH = formulas.get('SWITCH')!;
235
- expect(SWITCH('a', 'a', 1, 'b', 2, 'c', 3)).toBe(1);
236
- expect(SWITCH('b', 'a', 1, 'b', 2, 'c', 3)).toBe(2);
237
- });
238
-
239
- it('SWITCH should return default when no match', () => {
240
- const formulas = new FormulaFunctions();
241
- const SWITCH = formulas.get('SWITCH')!;
242
- expect(SWITCH('x', 'a', 1, 'b', 2, 'default')).toBe('default');
243
- });
244
-
245
- it('SWITCH should return undefined when no match and no default', () => {
246
- const formulas = new FormulaFunctions();
247
- const SWITCH = formulas.get('SWITCH')!;
248
- expect(SWITCH('x', 'a', 1, 'b', 2)).toBeUndefined();
249
- });
250
- });
251
-
252
- describe('String Functions', () => {
253
- it('CONCAT should join strings', () => {
254
- const formulas = new FormulaFunctions();
255
- const CONCAT = formulas.get('CONCAT')!;
256
- expect(CONCAT('Hello', ' ', 'World')).toBe('Hello World');
257
- expect(CONCAT('a', 'b', 'c')).toBe('abc');
258
- });
259
-
260
- it('CONCAT should handle null/undefined', () => {
261
- const formulas = new FormulaFunctions();
262
- const CONCAT = formulas.get('CONCAT')!;
263
- expect(CONCAT('Hello', null, 'World')).toBe('HelloWorld');
264
- });
265
-
266
- it('LEFT should return leftmost characters', () => {
267
- const formulas = new FormulaFunctions();
268
- const LEFT = formulas.get('LEFT')!;
269
- expect(LEFT('Hello World', 5)).toBe('Hello');
270
- expect(LEFT('Hi', 10)).toBe('Hi');
271
- });
272
-
273
- it('RIGHT should return rightmost characters', () => {
274
- const formulas = new FormulaFunctions();
275
- const RIGHT = formulas.get('RIGHT')!;
276
- expect(RIGHT('Hello World', 5)).toBe('World');
277
- expect(RIGHT('Hi', 10)).toBe('Hi');
278
- });
279
-
280
- it('TRIM should remove whitespace', () => {
281
- const formulas = new FormulaFunctions();
282
- const TRIM = formulas.get('TRIM')!;
283
- expect(TRIM(' hello ')).toBe('hello');
284
- expect(TRIM(' test ')).toBe('test');
285
- });
286
-
287
- it('UPPER should convert to uppercase', () => {
288
- const formulas = new FormulaFunctions();
289
- const UPPER = formulas.get('UPPER')!;
290
- expect(UPPER('hello')).toBe('HELLO');
291
- });
292
-
293
- it('LOWER should convert to lowercase', () => {
294
- const formulas = new FormulaFunctions();
295
- const LOWER = formulas.get('LOWER')!;
296
- expect(LOWER('HELLO')).toBe('hello');
297
- });
298
- });
299
-
300
- describe('String Search Functions', () => {
301
- const formulas = new FormulaFunctions();
302
-
303
- it('should find substring position with FIND', () => {
304
- const FIND = formulas.get('FIND')!;
305
- expect(FIND('world', 'hello world')).toBe(6);
306
- expect(FIND('xyz', 'hello world')).toBe(-1);
307
- });
308
-
309
- it('should find substring from start position', () => {
310
- const FIND = formulas.get('FIND')!;
311
- expect(FIND('l', 'hello world', 4)).toBe(9);
312
- });
313
-
314
- it('should replace all occurrences with REPLACE', () => {
315
- const REPLACE = formulas.get('REPLACE')!;
316
- expect(REPLACE('hello world', 'o', '0')).toBe('hell0 w0rld');
317
- });
318
-
319
- it('should extract substring with SUBSTRING', () => {
320
- const SUBSTRING = formulas.get('SUBSTRING')!;
321
- expect(SUBSTRING('hello world', 6)).toBe('world');
322
- expect(SUBSTRING('hello world', 0, 5)).toBe('hello');
323
- });
324
-
325
- it('should test regex pattern with REGEX', () => {
326
- const REGEX = formulas.get('REGEX')!;
327
- expect(REGEX('hello123', '\\d+')).toBe(true);
328
- expect(REGEX('hello', '\\d+')).toBe(false);
329
- expect(REGEX('Hello', '^hello$', 'i')).toBe(true);
330
- });
331
-
332
- it('should get string length with LEN', () => {
333
- const LEN = formulas.get('LEN')!;
334
- expect(LEN('hello')).toBe(5);
335
- expect(LEN('')).toBe(0);
336
- });
337
- });
338
-
339
- describe('Statistical Functions', () => {
340
- const formulas = new FormulaFunctions();
341
-
342
- it('should calculate MEDIAN for odd count', () => {
343
- const MEDIAN = formulas.get('MEDIAN')!;
344
- expect(MEDIAN(3, 1, 2)).toBe(2);
345
- });
346
-
347
- it('should calculate MEDIAN for even count', () => {
348
- const MEDIAN = formulas.get('MEDIAN')!;
349
- expect(MEDIAN(1, 2, 3, 4)).toBe(2.5);
350
- });
351
-
352
- it('should return 0 for empty MEDIAN', () => {
353
- const MEDIAN = formulas.get('MEDIAN')!;
354
- expect(MEDIAN()).toBe(0);
355
- });
356
-
357
- it('should calculate STDEV', () => {
358
- const STDEV = formulas.get('STDEV')!;
359
- const result = STDEV(2, 4, 4, 4, 5, 5, 7, 9);
360
- // Sample standard deviation (Bessel's correction, divides by n-1)
361
- expect(result).toBeCloseTo(2.138, 2);
362
- });
363
-
364
- it('should return 0 for STDEV with less than 2 values', () => {
365
- const STDEV = formulas.get('STDEV')!;
366
- expect(STDEV(5)).toBe(0);
367
- expect(STDEV()).toBe(0);
368
- });
369
-
370
- it('should calculate VARIANCE', () => {
371
- const VARIANCE = formulas.get('VARIANCE')!;
372
- const result = VARIANCE(2, 4, 4, 4, 5, 5, 7, 9);
373
- // Sample variance (Bessel's correction, divides by n-1)
374
- expect(result).toBeCloseTo(4.571, 2);
375
- });
376
-
377
- it('should calculate PERCENTILE', () => {
378
- const PERCENTILE = formulas.get('PERCENTILE')!;
379
- expect(PERCENTILE(0, 1, 2, 3, 4, 5)).toBe(1);
380
- expect(PERCENTILE(100, 1, 2, 3, 4, 5)).toBe(5);
381
- expect(PERCENTILE(50, 1, 2, 3, 4, 5)).toBe(3);
382
- });
383
-
384
- it('should handle PERCENTILE with empty values', () => {
385
- const PERCENTILE = formulas.get('PERCENTILE')!;
386
- expect(PERCENTILE(50)).toBe(0);
387
- });
388
- });
389
-
390
- describe('DATEFORMAT Function', () => {
391
- const formulas = new FormulaFunctions();
392
-
393
- it('should format date with DATEFORMAT', () => {
394
- const DATEFORMAT = formulas.get('DATEFORMAT')!;
395
- const result = DATEFORMAT('2026-02-10T00:00:00.000Z', 'YYYY-MM-DD');
396
- expect(result).toBe('2026-02-10');
397
- });
398
-
399
- it('should throw for invalid date in DATEFORMAT', () => {
400
- const DATEFORMAT = formulas.get('DATEFORMAT')!;
401
- expect(() => DATEFORMAT('invalid', 'YYYY')).toThrow('DATEFORMAT: Invalid date');
402
- });
403
- });
404
- });
405
-
406
- describe('ExpressionEvaluator with Formula Functions', () => {
407
- it('should use SUM in expressions', () => {
408
- const evaluator = new ExpressionEvaluator({ values: [10, 20, 30] });
409
- expect(evaluator.evaluateExpression('SUM(values)')).toBe(60);
410
- });
411
-
412
- it('should use AVG in expressions', () => {
413
- const evaluator = new ExpressionEvaluator({ values: [10, 20, 30] });
414
- expect(evaluator.evaluateExpression('AVG(values)')).toBe(20);
415
- });
416
-
417
- it('should use IF in expressions', () => {
418
- const evaluator = new ExpressionEvaluator({ data: { age: 25 } });
419
- expect(evaluator.evaluateExpression('IF(data.age >= 18, "adult", "minor")')).toBe('adult');
420
- });
421
-
422
- it('should use CONCAT in template expressions', () => {
423
- const evaluator = new ExpressionEvaluator({ first: 'John', last: 'Doe' });
424
- expect(evaluator.evaluate('${CONCAT(first, " ", last)}')).toBe('John Doe');
425
- });
426
-
427
- it('should use UPPER in expressions', () => {
428
- const evaluator = new ExpressionEvaluator({ name: 'hello' });
429
- expect(evaluator.evaluateExpression('UPPER(name)')).toBe('HELLO');
430
- });
431
-
432
- it('should support nested formula calls', () => {
433
- const evaluator = new ExpressionEvaluator({ items: [10, 20, 30] });
434
- expect(evaluator.evaluateExpression('IF(SUM(items) > 50, "high", "low")')).toBe('high');
435
- });
436
-
437
- it('should support registering custom functions', () => {
438
- const evaluator = new ExpressionEvaluator({ x: 5 });
439
- evaluator.registerFunction('DOUBLE', (n: number) => n * 2);
440
- expect(evaluator.evaluateExpression('DOUBLE(x)')).toBe(10);
441
- });
442
-
443
- it('should use convenience function with formulas', () => {
444
- expect(evalExprFn('${SUM(1, 2, 3)}')).toBe(6);
445
- expect(evalExprFn('${IF(true, "yes", "no")}')).toBe('yes');
446
- });
447
- });
@@ -1,12 +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 * from './ExpressionContext.js';
10
- export * from './ExpressionEvaluator.js';
11
- export * from './ExpressionCache.js';
12
- export * from './FormulaFunctions.js';
package/src/index.ts DELETED
@@ -1,38 +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 type { SchemaNode, ComponentRendererProps } from './types/index.js';
10
- export * from './registry/Registry.js';
11
- export * from './registry/PluginSystem.js';
12
- export * from './registry/PluginScopeImpl.js';
13
- export * from './registry/WidgetRegistry.js';
14
- export * from './validation/index.js';
15
- export * from './builder/schema-builder.js';
16
- export * from './utils/filter-converter.js';
17
- export * from './utils/normalize-quick-filter.js';
18
- export * from './utils/extract-records.js';
19
- export * from './utils/expand-fields.js';
20
- export * from './evaluator/index.js';
21
- export * from './actions/index.js';
22
- export * from './query/index.js';
23
- export * from './adapters/index.js';
24
- export * from './theme/index.js';
25
- export * from './data-scope/index.js';
26
- export * from './errors/index.js';
27
- export * from './utils/debug.js';
28
- export * from './utils/debug-collector.js';
29
- export * from './utils/merge-views-into-objects.js';
30
- export * from './protocols/index.js';
31
-
32
- /**
33
- * @deprecated Import `composeStacks` from `@objectstack/spec` instead.
34
- *
35
- * This re-export is kept only for backward compatibility and will be removed
36
- * in the next major version of `@object-ui/core`.
37
- */
38
- export { composeStacks } from '@objectstack/spec';