@legalplace/lplogic 2.3.0 → 2.3.2-staging.0

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.
@@ -0,0 +1,7 @@
1
+ GITLAB_PRIVATE_TOKEN=glpat-BOv5W1i6MoAAvHjzkmc5nW86MQp1OjkH.01.0w119catf
2
+ GITLAB_BASE_URL=https://git.legalplace.eu
3
+ GITLAB_API_PROJECT_ID=28
4
+ GITLAB_USER_ID=9
5
+ CURRENT_PROJECT_ID=39
6
+ SLACK_USER_ID=U0B3SNBV3
7
+ CLICKUP_PRIVATE_TOKEN=pk_32519579_K5W4L4KEVXY03A3D2WFA46D07HVGTL5L
package/.gitlab-ci.yml CHANGED
@@ -18,6 +18,7 @@ build:
18
18
  script:
19
19
  - export PATH=$PATH:./node_modules/.bin/
20
20
  - yarn install
21
+ - yarn test
21
22
  - yarn build
22
23
  except:
23
24
  - master # Excludes the master branch
@@ -14,10 +14,9 @@
14
14
  "files.autoSave": "onFocusChange",
15
15
  "editor.formatOnSave": false,
16
16
  "editor.formatOnType": true,
17
- "prettier.singleQuote": true,
18
- "eslint.packageManager": "yarn",
19
17
  "eslint.quiet": true,
20
18
  "editor.codeActionsOnSave": {
21
- "source.fixAll.eslint": true
22
- }
23
- }
19
+ "source.fixAll.eslint": "explicit"
20
+ },
21
+ "typescript.tsdk": "node_modules/typescript/lib"
22
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.3.1](https://git.legalplace.eu/legalplace/lplogic/compare/v2.3.0...v2.3.1) (2024-01-12)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * delete trim when check with regex operator ([e437be5](https://git.legalplace.eu/legalplace/lplogic/commit/e437be5f936cd5cd92fcf25fa97dc5b6491b6012))
11
+
5
12
  ## [2.3.0](https://git.legalplace.eu/legalplace/lplogic/compare/v2.2.0...v2.3.0) (2024-01-09)
6
13
 
7
14
 
package/dist/LpLogic.js CHANGED
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  var json_logic_js_1 = __importDefault(require("@legalplace/json-logic-js"));
7
7
  var reduceParents = function (parents, length) {
8
8
  var reducedParents = [];
9
- parents.sort(function (a, b) { return b.length - a.length; });
9
+ var safeParents = parents.filter(function (entry) { return Array.isArray(entry); });
10
+ safeParents.sort(function (a, b) { return b.length - a.length; });
10
11
  var _loop_1 = function (index) {
11
- var currentIndexResult = parents.reduce(function (previousValue, currentValue) {
12
+ var currentIndexResult = safeParents.reduce(function (previousValue, currentValue) {
12
13
  if (currentValue.length === 1)
13
14
  return previousValue && currentValue[0];
14
15
  if (currentValue[index] === undefined)
@@ -23,7 +24,7 @@ var reduceParents = function (parents, length) {
23
24
  return reducedParents;
24
25
  };
25
26
  var reduceValues = function (values, parents, compareFunction) {
26
- if (parents === undefined || parents.length === 0) {
27
+ if (!Array.isArray(parents) || parents.length === 0) {
27
28
  return values.map(function (currentValue) {
28
29
  return compareFunction(currentValue, true);
29
30
  });
@@ -141,7 +142,6 @@ var is = function (values, comparator, parents) {
141
142
  return reducedValues.length === filter.length;
142
143
  };
143
144
  json_logic_js_1.default.add_operation("is", is);
144
- json_logic_js_1.default.add_operation("contains", is);
145
145
  var not = function (values, comparator, parents) {
146
146
  var reducedValues = reduceValues(values, parents, function (value, parent) {
147
147
  var cleanValue = (typeof value === "number"
@@ -156,7 +156,52 @@ var not = function (values, comparator, parents) {
156
156
  return reducedValues.length === filter.length;
157
157
  };
158
158
  json_logic_js_1.default.add_operation("not", not);
159
- json_logic_js_1.default.add_operation("does-not-contain", not);
159
+ var contains = function (values, comparator, parents) {
160
+ var reducedValues = reduceValues(values, parents, function (value, parent) {
161
+ if (Array.isArray(value)) {
162
+ return value.some(function (val) {
163
+ return (typeof val === "string" ? val.trim() : val.toString())
164
+ .toLowerCase()
165
+ .includes((typeof comparator === "string"
166
+ ? comparator.trim()
167
+ : comparator.toString()).toLowerCase()) && parent;
168
+ });
169
+ }
170
+ else {
171
+ var cleanValue = (typeof value === "number" ? value.toString() : value.trim()).toLowerCase();
172
+ var cleanComparator = (typeof comparator === "number"
173
+ ? comparator.toString()
174
+ : comparator.trim()).toLowerCase();
175
+ return cleanValue.includes(cleanComparator) && parent;
176
+ }
177
+ });
178
+ var filter = reducedValues.filter(function (value) { return value === true; });
179
+ return reducedValues.length === filter.length;
180
+ };
181
+ json_logic_js_1.default.add_operation("contains", contains);
182
+ var doesNotContain = function (values, comparator, parents) {
183
+ var reducedValues = reduceValues(values, parents, function (value, parent) {
184
+ if (Array.isArray(value)) {
185
+ return value.every(function (val) {
186
+ return !(typeof val === "string" ? val.trim() : val.toString())
187
+ .toLowerCase()
188
+ .includes((typeof comparator === "string"
189
+ ? comparator.trim()
190
+ : comparator.toString()).toLowerCase()) && parent;
191
+ });
192
+ }
193
+ else {
194
+ var cleanValue = (typeof value === "number" ? value.toString() : value.trim()).toLowerCase();
195
+ var cleanComparator = (typeof comparator === "number"
196
+ ? comparator.toString()
197
+ : comparator.trim()).toLowerCase();
198
+ return !cleanValue.includes(cleanComparator) && parent;
199
+ }
200
+ });
201
+ var filter = reducedValues.filter(function (value) { return value === true; });
202
+ return reducedValues.length === filter.length;
203
+ };
204
+ json_logic_js_1.default.add_operation("does-not-contain", doesNotContain);
160
205
  json_logic_js_1.default.add_operation("empty", function (values, comparator, parents) {
161
206
  var reducedValues = reduceValues(values, parents, function (value, parent) {
162
207
  var stringValue = typeof value === "number" ? value.toString() : value;
@@ -181,7 +226,7 @@ var like = function (values, comparator, parents) {
181
226
  var cleanComparator = (typeof comparator === "number"
182
227
  ? comparator.toString()
183
228
  : comparator.trim()).toLowerCase();
184
- return (new RegExp("( |^)" + cleanComparator + "( |$)", "i").test(cleanValue) && parent);
229
+ return (new RegExp("( |^)".concat(cleanComparator, "( |$)"), "i").test(cleanValue) && parent);
185
230
  });
186
231
  var filter = reducedValues.filter(function (_value) { return _value === true; });
187
232
  return reducedValues.length === filter.length;
@@ -195,7 +240,7 @@ var notLike = function (values, comparator, parents) {
195
240
  var cleanComparator = (typeof comparator === "number"
196
241
  ? comparator.toString()
197
242
  : comparator.trim()).toLowerCase();
198
- return (!new RegExp("( |^)" + cleanComparator + "( |$)", "i").test(cleanValue) &&
243
+ return (!new RegExp("( |^)".concat(cleanComparator, "( |$)"), "i").test(cleanValue) &&
199
244
  parent);
200
245
  });
201
246
  var filter = reducedValues.filter(function (_value) { return _value === true; });
@@ -204,7 +249,7 @@ var notLike = function (values, comparator, parents) {
204
249
  json_logic_js_1.default.add_operation("not-like", notLike);
205
250
  var regexMatch = function (values, comparator, parents) {
206
251
  var reducedValues = reduceValues(values, parents, function (value, parent) {
207
- var cleanValue = typeof value === "number" ? value.toString() : value.trim();
252
+ var cleanValue = typeof value === "number" ? value.toString() : value;
208
253
  var regex;
209
254
  if (typeof comparator === 'string') {
210
255
  try {
@@ -226,5 +271,108 @@ var regexMatch = function (values, comparator, parents) {
226
271
  return reducedValues.length === filter.length;
227
272
  };
228
273
  json_logic_js_1.default.add_operation("regex-match", regexMatch);
274
+ var aggregateAny = function (reducedValues) {
275
+ return reducedValues.includes(true);
276
+ };
277
+ var aggregateFirst = function (reducedValues) {
278
+ return reducedValues[0] === true;
279
+ };
280
+ var cleanString = function (value) {
281
+ return (typeof value === "number" ? value.toString() : value.trim()).toLowerCase();
282
+ };
283
+ var registerScopedBooleanOp = function (baseName, compareFn) {
284
+ json_logic_js_1.default.add_operation("".concat(baseName, "-first"), function (values, _comparator, parents) {
285
+ var reducedValue = reduceValues(values, parents, compareFn);
286
+ return aggregateFirst(reducedValue);
287
+ });
288
+ json_logic_js_1.default.add_operation("".concat(baseName, "-any"), function (values, _comparator, parents) {
289
+ var reducedValue = reduceValues(values, parents, compareFn);
290
+ return aggregateAny(reducedValue);
291
+ });
292
+ };
293
+ registerScopedBooleanOp("selected", function (value, parent) { return value && parent; });
294
+ registerScopedBooleanOp("not-selected", function (value, parent) { return !value && parent; });
295
+ var conditionMet = function (value, parent) {
296
+ return value === true && parent;
297
+ };
298
+ var conditionNotMet = function (value, parent) {
299
+ return value !== true && parent;
300
+ };
301
+ json_logic_js_1.default.add_operation("conditions-met", function (values, comparator, parents) {
302
+ return reduceValues(values, parents, conditionMet).every(function (value) { return value === true; });
303
+ });
304
+ json_logic_js_1.default.add_operation("conditions-not-met", function (values, comparator, parents) {
305
+ return reduceValues(values, parents, conditionNotMet).every(function (value) { return value === true; });
306
+ });
307
+ registerScopedBooleanOp("conditions-met", conditionMet);
308
+ registerScopedBooleanOp("conditions-not-met", conditionNotMet);
309
+ var registerScopedComparisonOp = function (baseName, compareFn) {
310
+ json_logic_js_1.default.add_operation("".concat(baseName, "-any"), function (values, comparator, parents) {
311
+ var reducedValues = reduceValues(values, parents, function (value, parent) {
312
+ return compareFn(value, comparator, parent);
313
+ });
314
+ return aggregateAny(reducedValues);
315
+ });
316
+ json_logic_js_1.default.add_operation("".concat(baseName, "-first"), function (values, comparator, parents) {
317
+ var reducedValues = reduceValues(values, parents, function (value, parent) {
318
+ return compareFn(value, comparator, parent);
319
+ });
320
+ return aggregateFirst(reducedValues);
321
+ });
322
+ };
323
+ registerScopedComparisonOp("=", function (value, comparator, parent) {
324
+ return safeEqual(value, comparator) && parent;
325
+ });
326
+ registerScopedComparisonOp("<>", function (value, comparator, parent) {
327
+ return !safeEqual(value, comparator) && parent;
328
+ });
329
+ registerScopedComparisonOp("==", function (value, comparator, parent) {
330
+ return parseNumber(value) === parseNumber(comparator) && parent;
331
+ });
332
+ registerScopedComparisonOp(">=", function (value, comparator, parent) {
333
+ return parseNumber(value) >= parseNumber(comparator) && parent;
334
+ });
335
+ registerScopedComparisonOp("<=", function (value, comparator, parent) {
336
+ return parseNumber(value) <= parseNumber(comparator) && parent;
337
+ });
338
+ registerScopedComparisonOp(">", function (value, comparator, parent) {
339
+ return parseNumber(value) > parseNumber(comparator) && parent;
340
+ });
341
+ registerScopedComparisonOp("<", function (value, comparator, parent) {
342
+ return parseNumber(value) < parseNumber(comparator) && parent;
343
+ });
344
+ registerScopedComparisonOp("is", function (value, comparator, parent) {
345
+ return cleanString(value) === cleanString(comparator) && parent;
346
+ });
347
+ registerScopedComparisonOp("not", function (value, comparator, parent) {
348
+ return cleanString(value) !== cleanString(comparator) && parent;
349
+ });
350
+ registerScopedComparisonOp("contains", function (value, comparator, parent) {
351
+ return cleanString(value).includes(cleanString(comparator)) && parent;
352
+ });
353
+ registerScopedComparisonOp("does-not-contain", function (value, comparator, parent) {
354
+ return !cleanString(value).includes(cleanString(comparator)) && parent;
355
+ });
356
+ registerScopedComparisonOp("like", function (value, comparator, parent) {
357
+ return new RegExp("( |^)".concat(cleanString(comparator), "( |$)"), "i").test(cleanString(value)) && parent;
358
+ });
359
+ registerScopedComparisonOp("not-like", function (value, comparator, parent) {
360
+ return !new RegExp("( |^)".concat(cleanString(comparator), "( |$)"), "i").test(cleanString(value)) && parent;
361
+ });
362
+ registerScopedComparisonOp("regex-match", function (value, comparator, parent) {
363
+ if (typeof comparator !== "string")
364
+ return false;
365
+ var regex;
366
+ try {
367
+ regex = new RegExp(comparator);
368
+ }
369
+ catch (error) {
370
+ return false;
371
+ }
372
+ var stringValue = typeof value === "number" ? value.toString() : value;
373
+ return regex.test(stringValue) && parent;
374
+ });
375
+ registerScopedComparisonOp("empty", function (value, _comparator, parent) { return cleanString(value).length === 0 && parent; });
376
+ registerScopedComparisonOp("not-empty", function (value, _comparator, parent) { return cleanString(value).length > 0 && parent; });
229
377
  var LpLogic = json_logic_js_1.default.apply;
230
378
  exports.default = LpLogic;
package/dist/Parser.js CHANGED
@@ -16,7 +16,7 @@ var Parser = (function () {
16
16
  Parser.prototype.validateCondition = function (condition) {
17
17
  var result = (0, jsonschema_1.validate)(condition, condition_v1_schema_1.default);
18
18
  if (result.errors.length > 0) {
19
- throw new Error(JSON.stringify(condition) + " is not a valid condition");
19
+ throw new Error("".concat(JSON.stringify(condition), " is not a valid condition"));
20
20
  }
21
21
  };
22
22
  Parser.prototype.convertConditions = function (conditions) {
@@ -31,7 +31,7 @@ var Parser = (function () {
31
31
  operator = value.t;
32
32
  }
33
33
  else if (value.t !== operator) {
34
- throw new Error("Conditions object is not valid, " + operator + " is not equal to " + value.t + " at index " + index + " in " + JSON.stringify(conditions));
34
+ throw new Error("Conditions object is not valid, ".concat(operator, " is not equal to ").concat(value.t, " at index ").concat(index, " in ").concat(JSON.stringify(conditions)));
35
35
  }
36
36
  }
37
37
  }
@@ -57,7 +57,7 @@ var Parser = (function () {
57
57
  logic = logicConditions[0];
58
58
  }
59
59
  else {
60
- throw new Error("Invalid condition " + conditions);
60
+ throw new Error("Invalid condition ".concat(conditions));
61
61
  }
62
62
  return logic;
63
63
  };
@@ -70,18 +70,18 @@ var Parser = (function () {
70
70
  var dataReference;
71
71
  if (condition.o !== false && condition.o !== undefined) {
72
72
  dataReference = {
73
- var: "o." + condition.o
73
+ var: "o.".concat(condition.o)
74
74
  };
75
75
  rule[operation] = [dataReference];
76
76
  }
77
77
  else if (condition.v !== false && condition.v !== undefined) {
78
78
  dataReference = {
79
- var: "v." + condition.v
79
+ var: "v.".concat(condition.v)
80
80
  };
81
81
  rule[operation] = [dataReference, condition.value];
82
82
  }
83
83
  else {
84
- throw new Error("Invalid condition " + condition);
84
+ throw new Error("Invalid condition ".concat(condition));
85
85
  }
86
86
  return rule;
87
87
  };
@@ -1,4 +1,4 @@
1
- export declare type ConditionV1Single = {
1
+ export type ConditionV1Single = {
2
2
  c?: "selected" | "not-selected" | "has-one" | "at-least-one" | "has-many" | "contains" | "does-not-contain" | "=" | ">" | "<" | "!=" | ">=" | "<=" | "<>";
3
3
  o?: false | number | string;
4
4
  p?: string | number;
@@ -8,11 +8,11 @@ export declare type ConditionV1Single = {
8
8
  m?: boolean;
9
9
  t?: "and" | "or";
10
10
  };
11
- export declare type ConditionV1Array = {
11
+ export type ConditionV1Array = {
12
12
  condition_array?: ConditionV1Compose[];
13
13
  };
14
- declare type ConditionV1Compose = ConditionV1Array & ConditionV1Single;
15
- export declare type CompiledConditionV1 = ConditionV1Compose[];
14
+ type ConditionV1Compose = ConditionV1Array & ConditionV1Single;
15
+ export type CompiledConditionV1 = ConditionV1Compose[];
16
16
  export interface ModelV1 {
17
17
  contract_name: string;
18
18
  sections: SectionV1[];
package/jest.config.js CHANGED
@@ -1,13 +1,10 @@
1
1
  module.exports = {
2
- "roots": [
3
- "./tests"
4
- ],
5
- "transform": {
6
- "^.+\\.tsx?$": "ts-jest"
7
- },
8
- "globals": {
9
- "ts-jest": {
10
- tsConfig: "tsconfig.json"
11
- }
12
- }
13
- }
2
+ roots: [
3
+ "./tests"
4
+ ],
5
+ transform: {
6
+ "^.+\\.tsx?$": ['ts-jest', {
7
+ tsconfig: "tsconfig.json"
8
+ }],
9
+ },
10
+ };
package/package.json CHANGED
@@ -1,24 +1,27 @@
1
1
  {
2
2
  "name": "@legalplace/lplogic",
3
- "version": "2.3.0",
3
+ "version": "2.3.2-staging.0",
4
4
  "description": "LegalPlace LpLogic",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
7
- "test": "npx jest",
8
- "build": "npx tsc",
7
+ "test": "jest",
8
+ "build": "tsc",
9
9
  "release": "standard-version -a --no-verify"
10
10
  },
11
11
  "outDir": "./dist",
12
12
  "author": "Moncef Hammou <moncef@legalplace.fr>",
13
13
  "license": "ISC",
14
14
  "dependencies": {
15
- "@legalplace/models-v3-types": "5.1.24",
16
15
  "@legalplace/json-logic-js": "^1.2.3",
16
+ "@legalplace/models-v3-types": "5.1.24",
17
17
  "jsonschema": "^1.2.4"
18
18
  },
19
19
  "devDependencies": {
20
20
  "@babel/core": "^7.7.2",
21
21
  "@babel/preset-typescript": "^7.7.2",
22
+ "@commitlint/cli": "^9.0.1",
23
+ "@commitlint/config-conventional": "^9.0.1",
24
+ "@types/jest": "^29.5.14",
22
25
  "@types/node": "^12.12.6",
23
26
  "@typescript-eslint/eslint-plugin": "^2.6.1",
24
27
  "@typescript-eslint/parser": "^2.6.1",
@@ -26,12 +29,12 @@
26
29
  "eslint": "^6.6.0",
27
30
  "eslint-config-airbnb-base": "^14.0.0",
28
31
  "eslint-plugin-import": "^2.18.2",
29
- "typescript": "4.4.3",
30
- "semantic-release": "^17.1.1",
31
32
  "husky": "^4.2.5",
32
- "@commitlint/cli": "^9.0.1",
33
- "@commitlint/config-conventional": "^9.0.1",
34
- "standard-version": "^8.0.0"
33
+ "jest": "^29.7.0",
34
+ "semantic-release": "^17.1.1",
35
+ "standard-version": "^8.0.0",
36
+ "ts-jest": "^29.2.5",
37
+ "typescript": "5.6.3"
35
38
  },
36
39
  "husky": {
37
40
  "hooks": {
@@ -1,131 +1,257 @@
1
1
  import LpLogic from "../lib/index"
2
2
 
3
3
  test('Testing "selected" operator on a boolean `true`', async () => {
4
- expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":true}})).toBe(true)
4
+ expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[true]}})).toBe(true)
5
5
  })
6
6
 
7
7
  test('Testing "selected" operator on an array of boolean `true`', async () => {
8
- expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[true, true]}})).toBe(true)
8
+ expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[true, true]}})).toBe(true)
9
9
  })
10
10
 
11
11
 
12
12
  test('Testing "selected" operator on an array of boolean `false`', async () => {
13
- expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":false}})).toBe(false)
13
+ expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[false]}})).toBe(false)
14
14
  })
15
15
 
16
16
  test('Testing "selected" operator on an array containing a boolean `false`', async () => {
17
- expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[true, false]}})).toBe(false)
17
+ expect(LpLogic({"selected":[{"var":"o.1"}]}, {"o":{"1":[true, false]}})).toBe(false)
18
18
  })
19
19
 
20
20
  test('Testing "not-selected" operator on a boolean `false`', async () => {
21
- expect(LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":false}})).toBe(true)
21
+ expect(LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[false]}})).toBe(true)
22
22
  })
23
23
 
24
24
  test('Testing "not-selected" operator on an array of boolean `false`', async () => {
25
- expect(LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[false, false]}})).toBe(true)
25
+ expect(LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[false, false]}})).toBe(true)
26
26
  })
27
27
 
28
28
  // Returns false
29
29
 
30
30
  test('Testing "not-selected" operator on a boolean `true`', async () => {
31
- expect( LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":true}}) ).toBe(false)
31
+ expect( LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[true]}}) ).toBe(false)
32
32
  })
33
33
 
34
34
  test('Testing "not-selected" operator on an array containing a boolean `true`', async () => {
35
- expect( LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[true, false]}}) ).toBe(false)
35
+ expect( LpLogic({"not-selected":[{"var":"o.1"}]}, {"o":{"1":[true, false]}}) ).toBe(false)
36
36
  })
37
37
 
38
38
  // Returns true
39
39
  test('Testing "has-one" on an array of one element', async () => {
40
- expect(LpLogic({"has-one":[{"var":"o.1"}]}, {"o":{"1":[true]}})).toBe(true)
40
+ expect(LpLogic({"has-one":[{"var":"o.1"}]}, {"o":{"1":[true]}})).toBe(true)
41
41
  })
42
42
 
43
43
  // Returns false
44
44
  test('Testing "has-one" on an array of two elements', async () => {
45
- expect( LpLogic({"has-one":[{"var":"o.1"}]}, {"o":{"1":[true, true]}}) ).toBe(false)
45
+ expect( LpLogic({"has-one":[{"var":"o.1"}]}, {"o":{"1":[true, true]}}) ).toBe(false)
46
46
  })
47
47
 
48
48
 
49
49
  // Returns true
50
50
  test('Testing "has-many" on an array of two elements', async () => {
51
- expect( LpLogic({"has-many":[{"var":"o.1"}]}, {"o":{"1":[true, true]}}) ).toBe(true)
51
+ expect( LpLogic({"has-many":[{"var":"o.1"}]}, {"o":{"1":[true, true]}}) ).toBe(true)
52
52
  });
53
53
 
54
54
 
55
55
  // Returns false
56
56
  test('Testing "has-many" on an array of one element', async () => {
57
- expect( LpLogic({"has-many":[{"var":"o.1"}]}, {"o":{"1":[true]}}) ).toBe(false)
57
+ expect( LpLogic({"has-many":[{"var":"o.1"}]}, {"o":{"1":[true]}}) ).toBe(false)
58
58
  })
59
59
 
60
60
  // Returns true
61
61
  test('Testing "at-least-one" on a array of three elements with one as true', async () => {
62
- expect( LpLogic({"at-least-one":[{"var":"o.1"}]}, {"o":{"1":[false, false, true]}}) ).toBe(true)
62
+ expect( LpLogic({"at-least-one":[{"var":"o.1"}]}, {"o":{"1":[false, false, true]}}) ).toBe(true)
63
63
  })
64
64
 
65
65
  // Returns false
66
66
  test('Testing "at-least-one" on an array of three elements with all elements as false', async () => {
67
- expect( LpLogic({"at-least-one":[{"var":"o.1"}]}, {"o":{"1":[false, false, false]}}) ).toBe(false)
67
+ expect( LpLogic({"at-least-one":[{"var":"o.1"}]}, {"o":{"1":[false, false, false]}}) ).toBe(false)
68
68
  });
69
69
 
70
70
 
71
71
  // Returns true
72
72
  test('Testing "contains" Hello on a value `Hello`', async () => {
73
- expect( LpLogic({"contains":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Hello"}}) ).toBe(true)
73
+ expect( LpLogic({"contains":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Hello"]}}) ).toBe(true)
74
74
  })
75
75
 
76
76
 
77
77
  // Returns false
78
78
  test('Testing "contains" Hello on a value `Bye`', async () => {
79
- expect( LpLogic({"contains":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Bye"}}) ).toBe(false)
79
+ expect( LpLogic({"contains":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Bye"]}}) ).toBe(false)
80
80
  })
81
81
 
82
82
  // Returns true
83
83
  test('Testing "is" Hello on a value `Hello`', async () => {
84
- expect( LpLogic({"is":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Hello"}}) ).toBe(true)
84
+ expect( LpLogic({"is":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Hello"]}}) ).toBe(true)
85
85
  })
86
86
 
87
87
 
88
88
  // Returns false
89
89
  test('Testing "is" Hello on a value `Bye`', async () => {
90
- expect( LpLogic({"is":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Bye"}}) ).toBe(false)
90
+ expect( LpLogic({"is":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Bye"]}}) ).toBe(false)
91
91
  })
92
92
 
93
93
  // Returns true
94
94
  test('Testing "does-not-contain" Hello on a value `Bye`', async () => {
95
- expect( LpLogic({"does-not-contain":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Bye"}}) ).toBe(true)
95
+ expect( LpLogic({"does-not-contain":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Bye"]}}) ).toBe(true)
96
96
  })
97
97
 
98
98
  // Returns false
99
99
  test('Testing "does-not-contain" Hello on a value `Hello`', async () => {
100
- expect( LpLogic({"does-not-contain":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Hello"}}) ).toBe(false)
100
+ expect( LpLogic({"does-not-contain":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Hello"]}}) ).toBe(false)
101
101
  })
102
102
 
103
103
  // Returns true
104
104
  test('Testing "not" Hello on a value `Bye`', async () => {
105
- expect( LpLogic({"not":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Bye"}}) ).toBe(true)
105
+ expect( LpLogic({"not":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Bye"]}}) ).toBe(true)
106
106
  })
107
107
 
108
108
  // Returns false
109
109
  test('Testing "not" Hello on a value `Hello`', async () => {
110
- expect( LpLogic({"not":[{"var":"v.1"}, "Hello"]}, {"v":{"1":"Hello"}}) ).toBe(false)
110
+ expect( LpLogic({"not":[{"var":"v.1"}, "Hello"]}, {"v":{"1":["Hello"]}}) ).toBe(false)
111
111
  })
112
112
 
113
113
  // Returns true
114
114
  test('Testing "like" World on a value `Hello world`', async () => {
115
- expect( LpLogic({"like":[{"var":"v.1"}, "World"]}, {"v":{"1":"Hello world"}}) ).toBe(true)
115
+ expect( LpLogic({"like":[{"var":"v.1"}, "World"]}, {"v":{"1":["Hello world"]}}) ).toBe(true)
116
116
  })
117
117
 
118
118
  // Returns false
119
119
  test('Testing "like" Bye on a value `Hello world`', async () => {
120
- expect( LpLogic({"like":[{"var":"v.1"}, "Bye"]}, {"v":{"1":"Hello world"}}) ).toBe(false)
120
+ expect( LpLogic({"like":[{"var":"v.1"}, "Bye"]}, {"v":{"1":["Hello world"]}}) ).toBe(false)
121
121
  })
122
122
 
123
123
  // Returns true
124
124
  test('Testing "not-like" Bye on a value `Hello world`', async () => {
125
- expect( LpLogic({"not-like":[{"var":"v.1"}, "Bye"]}, {"v":{"1":"Hello world"}}) ).toBe(true)
125
+ expect( LpLogic({"not-like":[{"var":"v.1"}, "Bye"]}, {"v":{"1":["Hello world"]}}) ).toBe(true)
126
126
  })
127
127
 
128
128
  // Returns false
129
129
  test('Testing "not-like" World on a value `Hello world`', async () => {
130
- expect( LpLogic({"not-like":[{"var":"v.1"}, "World"]}, {"v":{"1":"Hello world"}}) ).toBe(false)
131
- })
130
+ expect( LpLogic({"not-like":[{"var":"v.1"}, "World"]}, {"v":{"1":["Hello world"]}}) ).toBe(false)
131
+ })
132
+
133
+ test('Testing "selected-first" on first occurrence only', async () => {
134
+ expect(LpLogic({"selected-first":[{"var":"o.1"}]}, {"o":{"1":[true, false]}})).toBe(true)
135
+ expect(LpLogic({"selected-first":[{"var":"o.1"}]}, {"o":{"1":[false, true]}})).toBe(false)
136
+ })
137
+
138
+ test('Testing "=-any" on multiple values', async () => {
139
+ expect(LpLogic({"=-any":[{"var":"v.1"}, "a"]}, {"v":{"1":["a", "b"]}})).toBe(true)
140
+ expect(LpLogic({"=-any":[{"var":"v.1"}, "a"]}, {"v":{"1":["b", "c"]}})).toBe(false)
141
+ })
142
+
143
+ /**
144
+ * empty-any / not-empty-any cover occurrence scope "at least one" for emptiness checks.
145
+ */
146
+ test('Testing "empty-any" on multiple values', async () => {
147
+ expect(LpLogic({"empty-any":[{"var":"v.1"}]}, {"v":{"1":["", "x"]}})).toBe(true)
148
+ expect(LpLogic({"empty-any":[{"var":"v.1"}]}, {"v":{"1":["x", "y"]}})).toBe(false)
149
+ })
150
+
151
+ test('Testing "empty-first" on multiple values', async () => {
152
+ expect(LpLogic({"empty-first":[{"var":"v.1"}]}, {"v":{"1":["", "x"]}})).toBe(true)
153
+ expect(LpLogic({"empty-first":[{"var":"v.1"}]}, {"v":{"1":["x", ""]}})).toBe(false)
154
+ })
155
+
156
+ test('Testing "not-empty-any" on multiple values', async () => {
157
+ expect(LpLogic({"not-empty-any":[{"var":"v.1"}]}, {"v":{"1":["", "x"]}})).toBe(true)
158
+ expect(LpLogic({"not-empty-any":[{"var":"v.1"}]}, {"v":{"1":["", ""]}})).toBe(false)
159
+ })
160
+
161
+ test('Testing "=-first" on multiple values', async () => {
162
+ expect(LpLogic({"=-first":[{"var":"v.1"}, "a"]}, {"v":{"1":["a", "b"]}})).toBe(true)
163
+ expect(LpLogic({"=-first":[{"var":"v.1"}, "a"]}, {"v":{"1":["b", "a"]}})).toBe(false)
164
+ })
165
+
166
+ test('Testing "conditions-met" without parents', async () => {
167
+ expect(
168
+ LpLogic({"conditions-met":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[true, true]}}})
169
+ ).toBe(true)
170
+ expect(
171
+ LpLogic({"conditions-met":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[true, false]}}})
172
+ ).toBe(false)
173
+ })
174
+
175
+ test('Testing "conditions-met" with parents', async () => {
176
+ expect(
177
+ LpLogic(
178
+ {"conditions-met":[{"var":"c.o.2"}, undefined, [{"var":"o.1"}]]},
179
+ {"c":{"o":{"2":[true, true]}}, "o":{"1":[true, true]}}
180
+ )
181
+ ).toBe(true)
182
+ expect(
183
+ LpLogic(
184
+ {"conditions-met":[{"var":"c.o.2"}, undefined, [{"var":"o.1"}]]},
185
+ {"c":{"o":{"2":[true, true]}}, "o":{"1":[true, false]}}
186
+ )
187
+ ).toBe(false)
188
+ })
189
+
190
+ test('Testing "conditions-met-first" on first occurrence only', async () => {
191
+ expect(
192
+ LpLogic({"conditions-met-first":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[true, false]}}})
193
+ ).toBe(true)
194
+ expect(
195
+ LpLogic({"conditions-met-first":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[false, true]}}})
196
+ ).toBe(false)
197
+ })
198
+
199
+ test('Testing "conditions-met-any" on multiple values', async () => {
200
+ expect(
201
+ LpLogic({"conditions-met-any":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[false, true]}}})
202
+ ).toBe(true)
203
+ expect(
204
+ LpLogic({"conditions-met-any":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[false, false]}}})
205
+ ).toBe(false)
206
+ })
207
+
208
+ test('Testing "conditions-not-met"', async () => {
209
+ expect(
210
+ LpLogic({"conditions-not-met":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[false, false]}}})
211
+ ).toBe(true)
212
+ expect(
213
+ LpLogic({"conditions-not-met":[{"var":"c.o.1"}]}, {"c":{"o":{"1":[true, false]}}})
214
+ ).toBe(false)
215
+ })
216
+
217
+ /**
218
+ * Regression: when a condition tree containing `undefined` parents is passed
219
+ * through any JSON serialization (e.g. Redux DevTools), `undefined` becomes
220
+ * `null` (`JSON.stringify([a, undefined])` → `'[a,null]'`). LpLogic must
221
+ * treat a `null` parents arg as "no parents" instead of throwing on
222
+ * `null.length`.
223
+ */
224
+ test('Testing "conditions-met" with null parents (JSON round-trip safety)', async () => {
225
+ expect(
226
+ LpLogic(
227
+ { "conditions-met": [{ var: "c.o.1" }, null, null] },
228
+ { "c": { "o": { "1": [true, true] } } }
229
+ )
230
+ ).toBe(true)
231
+ expect(
232
+ LpLogic(
233
+ { "conditions-met-first": [{ var: "c.o.1" }, null, null] },
234
+ { "c": { "o": { "1": [true, false] } } }
235
+ )
236
+ ).toBe(true)
237
+ })
238
+
239
+ test('Testing "selected" with null parents (JSON round-trip safety)', async () => {
240
+ expect(
241
+ LpLogic(
242
+ { "selected": [{ var: "o.1" }, null, null] },
243
+ { "o": { "1": [true, true] } }
244
+ )
245
+ ).toBe(true)
246
+ })
247
+
248
+ test('Testing "conditions-met" with parents containing missing refs (null entries)', async () => {
249
+ // `{ var: "o.999" }` resolves to `null` because `o.999` is absent from data.
250
+ // Parents array is `[null]`; LpLogic must not throw.
251
+ expect(
252
+ LpLogic(
253
+ { "conditions-met": [{ var: "c.o.1" }, undefined, [{ var: "o.999" }]] },
254
+ { "c": { "o": { "1": [true] } } }
255
+ )
256
+ ).toBe(true)
257
+ })