Skip to content

Commit 5db8f1a

Browse files
committed
feat: 😱😱😱 7 years after my first implementation with jQuery, here we come, a fully-featured UI Predicate component from scratch finally written in 10 non-consecutive days with an agnostic core (#SoC) and a first UI Framework adapter (#futurproof) for VueJS. FINALLY. This is soOOOOOOOOOOOOOOOOOOOOOOOOOOoooooooo exciting!
1 parent 22bc055 commit 5db8f1a

17 files changed

+999
-312
lines changed

Diff for: ‎packages/ui-predicate-core/src/PredicateCore.js

+198-95
Large diffs are not rendered by default.

Diff for: ‎packages/ui-predicate-core/src/PredicateCore.test.js

+303-40
Large diffs are not rendered by default.

Diff for: ‎packages/ui-predicate-core/src/__snapshots__/PredicateCore.test.js.snap

+35-14
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Object {
99
"logicalType_id": "any",
1010
"predicates": Array [
1111
Object {
12-
"arguments": Array [],
12+
"argument": null,
1313
"operator_id": "is",
1414
"target_id": "title",
1515
},
@@ -28,8 +28,10 @@ Array [
2828
"setPredicateTarget_id",
2929
"setPredicateOperator_id",
3030
"setPredicateLogicalType_id",
31-
"root",
31+
"setArgumentValue",
32+
"getArgumentTypeComponentById",
3233
"toJSON",
34+
"root",
3335
"columns",
3436
"options",
3537
]
@@ -39,17 +41,23 @@ exports[`UIPredicateCore constructor rejects an error if json data does not star
3941

4042
exports[`UIPredicateCore constructor rejects an error if json data is neither a CompoundPredicate or a ComparisonPredicate 1`] = `[UnknownJSONData]`;
4143

44+
exports[`UIPredicateCore constructor throws columns is not an object 1`] = `[Error: Object is required, got: null.]`;
45+
46+
exports[`UIPredicateCore constructor throws if columns does not have minimum required parameters 1`] = `[Error: Object {} MUST have a 'targets' property.]`;
47+
48+
exports[`UIPredicateCore constructor throws if options.getDefaultArgumentComponent is not overriden by the UI Framework adapter 1`] = `"UIFrameworkMustImplementgetDefaultArgumentComponent"`;
49+
4250
exports[`UIPredicateCore ctrl.add adds a second Predicate (a ComparisonPredicate) to the root CompoundPredicate 1`] = `
4351
Object {
4452
"logicalType_id": "any",
4553
"predicates": Array [
4654
Object {
47-
"arguments": Array [],
55+
"argument": null,
4856
"operator_id": "is",
4957
"target_id": "title",
5058
},
5159
Object {
52-
"arguments": Array [],
60+
"argument": null,
5361
"operator_id": "is",
5462
"target_id": "title",
5563
},
@@ -76,9 +84,10 @@ Object {
7684
Object {
7785
"$_type": "ComparisonPredicate",
7886
"$canBeRemoved": false,
79-
"arguments": Array [],
87+
"argument": null,
8088
"operator": Object {
8189
"$_type": "Operator",
90+
"argumentType_id": "smallString",
8291
"label": "is",
8392
"operator_id": "is",
8493
},
@@ -89,12 +98,14 @@ Object {
8998
"$operators": Array [
9099
Object {
91100
"$_type": "Operator",
101+
"argumentType_id": "smallString",
92102
"label": "is",
93103
"operator_id": "is",
94104
},
95105
Object {
96106
"$_type": "Operator",
97-
"label": "contains",
107+
"argumentType_id": "smallString",
108+
"label": "Contains",
98109
"operator_id": "contains",
99110
},
100111
],
@@ -126,9 +137,10 @@ Object {
126137
Object {
127138
"$_type": "ComparisonPredicate",
128139
"$canBeRemoved": false,
129-
"arguments": Array [],
140+
"argument": null,
130141
"operator": Object {
131142
"$_type": "Operator",
143+
"argumentType_id": "smallString",
132144
"label": "is",
133145
"operator_id": "is",
134146
},
@@ -139,12 +151,14 @@ Object {
139151
"$operators": Array [
140152
Object {
141153
"$_type": "Operator",
154+
"argumentType_id": "smallString",
142155
"label": "is",
143156
"operator_id": "is",
144157
},
145158
Object {
146159
"$_type": "Operator",
147-
"label": "contains",
160+
"argumentType_id": "smallString",
161+
"label": "Contains",
148162
"operator_id": "contains",
149163
},
150164
],
@@ -176,9 +190,10 @@ Object {
176190
Object {
177191
"$_type": "ComparisonPredicate",
178192
"$canBeRemoved": true,
179-
"arguments": Array [],
193+
"argument": null,
180194
"operator": Object {
181195
"$_type": "Operator",
196+
"argumentType_id": "smallString",
182197
"label": "is",
183198
"operator_id": "is",
184199
},
@@ -189,12 +204,14 @@ Object {
189204
"$operators": Array [
190205
Object {
191206
"$_type": "Operator",
207+
"argumentType_id": "smallString",
192208
"label": "is",
193209
"operator_id": "is",
194210
},
195211
Object {
196212
"$_type": "Operator",
197-
"label": "contains",
213+
"argumentType_id": "smallString",
214+
"label": "Contains",
198215
"operator_id": "contains",
199216
},
200217
],
@@ -233,9 +250,10 @@ exports[`UIPredicateCore ctrl.setPredicateTarget_id work if we set a predicate t
233250
Object {
234251
"$_type": "ComparisonPredicate",
235252
"$canBeRemoved": false,
236-
"arguments": Array [],
253+
"argument": null,
237254
"operator": Object {
238255
"$_type": "Operator",
256+
"argumentType_id": "number",
239257
"label": "<",
240258
"operator_id": "isLowerThan",
241259
},
@@ -246,16 +264,19 @@ Object {
246264
"$operators": Array [
247265
Object {
248266
"$_type": "Operator",
267+
"argumentType_id": "number",
249268
"label": "<",
250269
"operator_id": "isLowerThan",
251270
},
252271
Object {
253272
"$_type": "Operator",
273+
"argumentType_id": "number",
254274
"label": "=",
255275
"operator_id": "isEqualTo",
256276
},
257277
Object {
258278
"$_type": "Operator",
279+
"argumentType_id": "number",
259280
"label": ">",
260281
"operator_id": "isHigherThan",
261282
},
@@ -279,23 +300,23 @@ Object {
279300
"logicalType_id": "any",
280301
"predicates": Array [
281302
Object {
282-
"arguments": Array [],
303+
"argument": null,
283304
"operator_id": "is",
284305
"target_id": "title",
285306
},
286307
Object {
287308
"logicalType_id": "any",
288309
"predicates": Array [
289310
Object {
290-
"arguments": Array [],
311+
"argument": null,
291312
"operator_id": "is",
292313
"target_id": "title",
293314
},
294315
Object {
295316
"logicalType_id": "any",
296317
"predicates": Array [
297318
Object {
298-
"arguments": Array [],
319+
"argument": null,
299320
"operator_id": "is",
300321
"target_id": "title",
301322
},

Diff for: ‎packages/ui-predicate-core/src/dataclasses/columns.js

+79-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { mergeAll } = require('ramda');
1+
const { mergeAll, trim } = require('ramda');
22
const $_type = require('./$_type');
33

44
/**
@@ -17,8 +17,11 @@ const $_type = require('./$_type');
1717
* @memberof dataclasses
1818
*/
1919
function Target(target) {
20+
const { target_id, label, type_id } = _requireProps(
21+
target,
22+
'target_id,label,type_id'
23+
);
2024
// target MUST at least have the attributes bellow
21-
const { target_id, label, type_id } = target;
2225
return mergeAll([$_type('Target'), { target_id, label, type_id }, target]);
2326
}
2427

@@ -42,17 +45,22 @@ Target.toJSON = function(target) {
4245
* Create a new operator
4346
* @param {object} operator
4447
* @param {string} operator.operator_id - unique id for this operator
48+
* @param {string} operator.argumentType_id - the argumentType associated with the operator. For example to define a "is" operator and associate it with two targets "article publishing date" and "article title" then we would have to define 2 operators because ["article publishing date" "is"] and ["article title" "is"] do NOT have the same kind of arguments (one is a date the other is a string) and associated UI components (one is a date picker the other is a text input)
4549
* @param {string} operator.label - label that will be displayed for this operator
4650
* @return {Operator} {@link dataclasses.Operator}
4751
* @memberof dataclasses
4852
*/
4953
function Operator(operator) {
50-
// operator MUST at least have the attributes bellow
51-
const { operator_id, label } = operator;
54+
// operator MUST at least have the properties bellow
55+
const { operator_id, label, argumentType_id } = _requireProps(
56+
operator,
57+
'operator_id,label,argumentType_id'
58+
);
5259
return mergeAll([
5360
$_type('Operator'),
5461
{
5562
operator_id,
63+
argumentType_id,
5664
label,
5765
},
5866
operator,
@@ -84,8 +92,8 @@ Operator.toJSON = function(operator) {
8492
* @memberof dataclasses
8593
*/
8694
function Type(type) {
87-
// operator MUST at least have the attributes bellow
88-
const { type_id, operator_ids } = type;
95+
// type MUST at least have the properties bellow
96+
const { type_id, operator_ids } = _requireProps(type, 'type_id,operator_ids');
8997
return mergeAll([
9098
$_type('Type'),
9199
{
@@ -105,8 +113,11 @@ function Type(type) {
105113
* @memberof dataclasses
106114
*/
107115
function LogicalType(logicalType) {
108-
// logicalType MUST at least have the attributes bellow
109-
const { logicalType_id, label } = logicalType;
116+
// logicalType MUST at least have the properties bellow
117+
const { logicalType_id, label } = _requireProps(
118+
logicalType,
119+
'logicalType_id,label'
120+
);
110121
return mergeAll([
111122
$_type('LogicalType'),
112123
{
@@ -127,4 +138,63 @@ LogicalType.toJSON = function(logicalType) {
127138
};
128139
};
129140

130-
module.exports = { Type, Target, Operator, LogicalType };
141+
/**
142+
* Create a new argument type
143+
* @param {Object} argumentType
144+
* @param {string} type.argumentType_id
145+
* @param {*} type.component this attribute will be used by the UI Framework adapter
146+
* @memberof dataclasses
147+
*/
148+
function ArgumentType(argumentType) {
149+
// argumentType MUST at least have the properties bellow
150+
const { argumentType_id, component } = _requireProps(
151+
argumentType,
152+
'argumentType_id,component'
153+
);
154+
return mergeAll([
155+
$_type('ArgumentType'),
156+
{
157+
argumentType_id,
158+
component,
159+
},
160+
argumentType,
161+
]);
162+
}
163+
164+
const _toString = Object.prototype.toString;
165+
function _isObject(mixed) {
166+
return _toString.call(mixed) === '[object Object]';
167+
}
168+
/**
169+
* @param {Object} object
170+
* @param {string} properties comma-separated list of properties
171+
* @return {Object}
172+
* @throws throw if a property is missing from the object
173+
*/
174+
function _requireProps(object, properties) {
175+
if (!_isObject(object)) {
176+
throw new Error(`Object is required, got: ${JSON.stringify(object)}.`);
177+
}
178+
179+
const props = properties.split(',').map(trim);
180+
let prop;
181+
182+
while ((prop = props.pop())) {
183+
if (!object.hasOwnProperty(prop)) {
184+
throw new Error(
185+
`Object ${JSON.stringify(object)} MUST have a '${prop}' property.`
186+
);
187+
}
188+
}
189+
190+
return object;
191+
}
192+
193+
module.exports = {
194+
Type,
195+
Target,
196+
Operator,
197+
LogicalType,
198+
ArgumentType,
199+
_requireProps,
200+
};

Diff for: ‎packages/ui-predicate-core/src/dataclasses/columns.test.js

+10
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ describe('Type', () => {
1414
).toBe(true);
1515
});
1616
});
17+
18+
describe('Operator', () => {
19+
it('throws if a property is missing', () => {
20+
expect(() =>
21+
Operator({
22+
// do not specify anything
23+
})
24+
).toThrow(/MUST have a 'argumentType_id' property/);
25+
});
26+
});

Diff for: ‎packages/ui-predicate-core/src/dataclasses/predicates.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,24 @@ module.exports = ({ invariants, errors }) => {
6464
* @typedef {object} ComparisonPredicate
6565
* @param {string} target - unique id for this target
6666
* @param {string} operator - label that will be displayed for this target
67-
* @param {string} arguments - the type_id name this target has
67+
* @param {string} argument - the type_id name this target has
6868
* @memberof dataclasses
6969
*/
7070

7171
/**
7272
* A specialized predicate that you use to compare expressions.
7373
* @param {dataclasses.Target} target
7474
* @param {dataclasses.Operator} operator
75-
* @param {Array<*>} args
75+
* @param {*} argument
7676
* @return {Promise<dataclasses.ComparisonPredicate>} yield a ComparisonPredicate or a rejected promise
7777
* @memberof dataclasses
7878
*/
79-
function ComparisonPredicate(target, operator, args) {
79+
function ComparisonPredicate(target, operator, argument = null) {
8080
return Predicate(ComparisonPredicate).then(predicate =>
8181
merge(predicate, {
8282
target: target,
8383
operator: operator,
84-
arguments: args,
84+
argument: argument,
8585
})
8686
);
8787
}
@@ -98,7 +98,7 @@ module.exports = ({ invariants, errors }) => {
9898
Target.toJSON(predicate.target),
9999
Operator.toJSON(predicate.operator),
100100
{
101-
arguments: predicate.arguments,
101+
argument: predicate.argument,
102102
},
103103
]);
104104
};
@@ -112,7 +112,7 @@ module.exports = ({ invariants, errors }) => {
112112
internalAPI.getTargetById(json.target_id),
113113
internalAPI.getOperatorById(json.operator_id),
114114
]).then(([target, operator]) =>
115-
ComparisonPredicate(target, operator, json.arguments)
115+
ComparisonPredicate(target, operator, json.argument)
116116
);
117117
};
118118

Diff for: ‎packages/ui-predicate-core/src/errors.js

+8
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,12 @@ module.exports = mergeAll([
148148
* @since 1.0.0
149149
*/
150150
err('CannotAddSomethingElseThanACompoundPredicateOrAComparisonPredicate'),
151+
152+
/**
153+
* Thrown when the UI Framework adapter forgot to pass `getDefaultArgumentComponent` in the option object to UIPredicateCore constructor
154+
* @typedef {Error} UIFrameworkMustImplementgetDefaultArgumentComponent
155+
* @memberof errors
156+
* @since 1.0.0
157+
*/
158+
err('UIFrameworkMustImplementgetDefaultArgumentComponent'),
151159
]);

0 commit comments

Comments
 (0)