@legalplace/conditions-runner 2.2.20 → 2.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [2.3.1](https://git.legalplace.eu/legalplace/monorepo/compare/@legalplace/conditions-runner@2.3.0...@legalplace/conditions-runner@2.3.1) (2026-09-01)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * prevent LpLogic cleanString crash on null related-row values ([c07292d](https://git.legalplace.eu/legalplace/monorepo/commits/c07292d1ccdb615ee4ab885cafea10d6b08f479e))
12
+
13
+
14
+
15
+
16
+
17
+ # [2.3.0](https://git.legalplace.eu/legalplace/monorepo/compare/@legalplace/conditions-runner@2.2.20...@legalplace/conditions-runner@2.3.0) (2026-09-01)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * fix conditions on multiples and repeated outputs ([d77efbe](https://git.legalplace.eu/legalplace/monorepo/commits/d77efbe706da435c6e853176432f64954217d046))
23
+ * update InputsType import to align with new module structure ([61b0b47](https://git.legalplace.eu/legalplace/monorepo/commits/61b0b473168e298ded44eeeb7d041a52608dc038))
24
+
25
+
26
+ ### Features
27
+
28
+ * output image assets, documentStyle font sizes and table column widths ([8dfb115](https://git.legalplace.eu/legalplace/monorepo/commits/8dfb115d74cd3e5acbb11dd798efa749a8098001))
29
+
30
+
31
+
32
+
33
+
6
34
  ## [2.2.20](https://git.legalplace.eu/legalplace/monorepo/compare/@legalplace/conditions-runner@2.2.19...@legalplace/conditions-runner@2.2.20) (2026-08-27)
7
35
 
8
36
  **Note:** Version bump only for package @legalplace/conditions-runner
@@ -0,0 +1,22 @@
1
+ import type { Types } from "@legalplace/referencesparser";
2
+ import type { InputsType } from "@legalplace/types";
3
+ export declare const CONDITION_COMPARATOR_EMPTY_SENTINEL = "\uE000__LP_EMPTY__\uE000";
4
+ export declare const SCOPED_OPERATOR_REGEX: RegExp;
5
+ export type ComparatorResolverContext = {
6
+ optionId: number;
7
+ index: number;
8
+ conditionVariableId?: number;
9
+ conditionOptionId?: number;
10
+ evalVariableId?: number;
11
+ };
12
+ export type ComparatorResolverDeps = {
13
+ references: Types.ReferencesType;
14
+ ovc: InputsType;
15
+ };
16
+ export declare const getConditionVariableOptionId: (references: Types.ReferencesType, variableId: number) => number;
17
+ export declare const extractVarTagIds: (text: string) => number[];
18
+ export declare const extractLhsVariableId: (lhs: unknown) => number | undefined;
19
+ export declare const areVariablesSiblings: (references: Types.ReferencesType, variableA: number, variableB: number) => boolean;
20
+ export declare const parseConditionComparatorWithVariables: (text: string, optionId: number, index: number, deps: ComparatorResolverDeps, context?: Pick<ComparatorResolverContext, "conditionVariableId" | "conditionOptionId" | "evalVariableId">) => string;
21
+ export declare const resolveComparatorString: (value: string, context: ComparatorResolverContext, deps: ComparatorResolverDeps) => string;
22
+ export declare const resolveConditionComparators: (conditions: unknown, context: ComparatorResolverContext, deps: ComparatorResolverDeps) => unknown;
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveConditionComparators = exports.resolveComparatorString = exports.parseConditionComparatorWithVariables = exports.areVariablesSiblings = exports.extractLhsVariableId = exports.extractVarTagIds = exports.getConditionVariableOptionId = exports.SCOPED_OPERATOR_REGEX = exports.CONDITION_COMPARATOR_EMPTY_SENTINEL = void 0;
4
+ const referencesparser_1 = require("@legalplace/referencesparser");
5
+ exports.CONDITION_COMPARATOR_EMPTY_SENTINEL = "\uE000__LP_EMPTY__\uE000";
6
+ const VAR_TAG_IN_TEXT_REGEX = /\[var:[0-9]+\]/i;
7
+ const UI_METADATA_KEYS = new Set(["scope", "groupMeta"]);
8
+ const RECOGNIZED_OPERATOR_KEYS = new Set((0, referencesparser_1.getRecognizedOperatorKeys)());
9
+ exports.SCOPED_OPERATOR_REGEX = /-(first|any)$/;
10
+ const getConditionVariableOptionId = (references, variableId) => {
11
+ var _a;
12
+ const parents = (_a = references.relations.variables[variableId]) === null || _a === void 0 ? void 0 : _a.parents;
13
+ return parents !== undefined && parents.length > 0 ? parents[0] : variableId;
14
+ };
15
+ exports.getConditionVariableOptionId = getConditionVariableOptionId;
16
+ const extractVarTagIds = (text) => {
17
+ const tagsMatch = text.match(/\[var:([0-9]+)\]/gi);
18
+ if (tagsMatch === null)
19
+ return [];
20
+ return [
21
+ ...new Set(tagsMatch.map((tag) => {
22
+ const match = tag.match(/([0-9]+)/);
23
+ return match === null ? 0 : parseInt(match[0], 10);
24
+ })),
25
+ ];
26
+ };
27
+ exports.extractVarTagIds = extractVarTagIds;
28
+ const extractLhsVariableId = (lhs) => {
29
+ if (typeof lhs !== "object" ||
30
+ lhs === null ||
31
+ !("var" in lhs) ||
32
+ typeof lhs.var !== "string") {
33
+ return undefined;
34
+ }
35
+ const match = lhs.var.match(/^v\.([0-9]+)$/);
36
+ return match === null ? undefined : parseInt(match[1], 10);
37
+ };
38
+ exports.extractLhsVariableId = extractLhsVariableId;
39
+ const areVariablesSiblings = (references, variableA, variableB) => {
40
+ var _a, _b, _c, _d;
41
+ const rootA = (_b = (_a = references.relations.variables[variableA]) === null || _a === void 0 ? void 0 : _a.parents) === null || _b === void 0 ? void 0 : _b.slice(-1)[0];
42
+ const rootB = (_d = (_c = references.relations.variables[variableB]) === null || _c === void 0 ? void 0 : _c.parents) === null || _d === void 0 ? void 0 : _d.slice(-1)[0];
43
+ return rootA !== undefined && rootA === rootB;
44
+ };
45
+ exports.areVariablesSiblings = areVariablesSiblings;
46
+ const shouldUseConditionRowIndex = (references, variableId, optionId, context) => {
47
+ var _a, _b;
48
+ const contextVariableId = context === null || context === void 0 ? void 0 : context.contextVariableId;
49
+ if (contextVariableId !== undefined &&
50
+ (0, exports.areVariablesSiblings)(references, variableId, contextVariableId)) {
51
+ return true;
52
+ }
53
+ if ((context === null || context === void 0 ? void 0 : context.conditionOptionId) !== undefined) {
54
+ const parents = (_a = references.relations.variables[variableId]) === null || _a === void 0 ? void 0 : _a.parents;
55
+ if (parents !== undefined &&
56
+ parents.length > 0 &&
57
+ parents[0] === context.conditionOptionId) {
58
+ return true;
59
+ }
60
+ }
61
+ const variableParents = (_b = references.relations.variables[variableId]) === null || _b === void 0 ? void 0 : _b.parents;
62
+ if (variableParents === undefined || variableParents.length === 0) {
63
+ return false;
64
+ }
65
+ const rootVariableParentId = variableParents[variableParents.length - 1];
66
+ const rootVariableParentRelations = references.relations.options[rootVariableParentId];
67
+ if (rootVariableParentRelations === undefined)
68
+ return false;
69
+ return (rootVariableParentRelations.children.options.includes(optionId) ||
70
+ rootVariableParentRelations.dependants.includes(optionId) ||
71
+ optionId === rootVariableParentId);
72
+ };
73
+ const normalizeConditionValue = (currentValue) => {
74
+ if (currentValue === null || currentValue === undefined)
75
+ return "";
76
+ if (typeof currentValue === "number")
77
+ return currentValue.toString();
78
+ if (typeof currentValue === "string")
79
+ return currentValue;
80
+ return "";
81
+ };
82
+ const parseConditionComparatorWithVariables = (text, optionId, index, deps, context) => {
83
+ var _a;
84
+ const variableIds = (0, exports.extractVarTagIds)(text);
85
+ if (variableIds.length === 0)
86
+ return text;
87
+ const rowContext = {
88
+ contextVariableId: (_a = context === null || context === void 0 ? void 0 : context.evalVariableId) !== null && _a !== void 0 ? _a : context === null || context === void 0 ? void 0 : context.conditionVariableId,
89
+ conditionOptionId: context === null || context === void 0 ? void 0 : context.conditionOptionId,
90
+ };
91
+ let parsedText = text;
92
+ variableIds.forEach((variableId) => {
93
+ var _a;
94
+ const values = (_a = deps.ovc.variables[variableId]) !== null && _a !== void 0 ? _a : [];
95
+ const useRow = shouldUseConditionRowIndex(deps.references, variableId, optionId, rowContext);
96
+ const cleanValues = useRow
97
+ ? [normalizeConditionValue(values[index])]
98
+ : values.map((value) => normalizeConditionValue(value));
99
+ let resolved = cleanValues.length === 1 ? cleanValues[0] : cleanValues.join(", ");
100
+ if (resolved.trim().length === 0) {
101
+ resolved = exports.CONDITION_COMPARATOR_EMPTY_SENTINEL;
102
+ }
103
+ parsedText = parsedText.replace(new RegExp(`\\[var:${variableId}\\]`, "gi"), resolved);
104
+ });
105
+ return parsedText;
106
+ };
107
+ exports.parseConditionComparatorWithVariables = parseConditionComparatorWithVariables;
108
+ const resolveLeafContext = (lhs, context, deps) => {
109
+ const variableId = (0, exports.extractLhsVariableId)(lhs);
110
+ if (variableId === undefined)
111
+ return context;
112
+ return {
113
+ ...context,
114
+ optionId: (0, exports.getConditionVariableOptionId)(deps.references, variableId),
115
+ evalVariableId: variableId,
116
+ };
117
+ };
118
+ const resolveComparatorString = (value, context, deps) => {
119
+ if (!VAR_TAG_IN_TEXT_REGEX.test(value))
120
+ return value;
121
+ return (0, exports.parseConditionComparatorWithVariables)(value, context.optionId, context.index, deps, {
122
+ evalVariableId: context.evalVariableId,
123
+ conditionVariableId: context.conditionVariableId,
124
+ conditionOptionId: context.conditionOptionId,
125
+ });
126
+ };
127
+ exports.resolveComparatorString = resolveComparatorString;
128
+ const walkConditionNode = (node, context, deps) => {
129
+ if (node === null || node === undefined)
130
+ return node;
131
+ if (Array.isArray(node)) {
132
+ return node.map((child) => walkConditionNode(child, context, deps));
133
+ }
134
+ if (typeof node !== "object")
135
+ return node;
136
+ const conditionNode = node;
137
+ const structuralKeys = Object.keys(conditionNode).filter((key) => !UI_METADATA_KEYS.has(key));
138
+ if (structuralKeys.length === 0)
139
+ return conditionNode;
140
+ const operatorKey = structuralKeys.find((key) => RECOGNIZED_OPERATOR_KEYS.has(key));
141
+ if (structuralKeys.length > 1 && operatorKey === undefined) {
142
+ return conditionNode;
143
+ }
144
+ const currentKey = operatorKey !== null && operatorKey !== void 0 ? operatorKey : structuralKeys[0];
145
+ if (currentKey === "and" || currentKey === "or") {
146
+ const children = conditionNode[currentKey];
147
+ if (!Array.isArray(children))
148
+ return conditionNode;
149
+ return {
150
+ ...conditionNode,
151
+ [currentKey]: children.map((child) => walkConditionNode(child, context, deps)),
152
+ };
153
+ }
154
+ if (RECOGNIZED_OPERATOR_KEYS.has(currentKey)) {
155
+ const args = conditionNode[currentKey];
156
+ if (!Array.isArray(args) || args.length < 2)
157
+ return conditionNode;
158
+ const resolvedArgs = [...args];
159
+ const comparator = resolvedArgs[1];
160
+ if (typeof comparator === "string") {
161
+ const baseContext = exports.SCOPED_OPERATOR_REGEX.test(currentKey)
162
+ ? { ...context, index: 0 }
163
+ : context;
164
+ const leafContext = resolveLeafContext(resolvedArgs[0], baseContext, deps);
165
+ resolvedArgs[1] = (0, exports.resolveComparatorString)(comparator, leafContext, deps);
166
+ }
167
+ return {
168
+ ...conditionNode,
169
+ [currentKey]: resolvedArgs,
170
+ };
171
+ }
172
+ return conditionNode;
173
+ };
174
+ const resolveConditionComparators = (conditions, context, deps) => {
175
+ if (Array.isArray(conditions)) {
176
+ return conditions.map((condition) => walkConditionNode(condition, context, deps));
177
+ }
178
+ return walkConditionNode(conditions, context, deps);
179
+ };
180
+ exports.resolveConditionComparators = resolveConditionComparators;
@@ -15,5 +15,6 @@ declare class ConditionsRunner {
15
15
  dataMap: ConditionDataMap;
16
16
  conditions: ConditionV3;
17
17
  }, id: number, index: number, conditionType: "options" | "optionValidator" | "variables" | "prefillers" | "variableValidator" | "sections" | "documents"): any;
18
+ private buildComparatorResolverContext;
18
19
  }
19
20
  export default ConditionsRunner;
@@ -4,7 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const lplogic_1 = __importDefault(require("@legalplace/lplogic"));
7
+ const referencesparser_1 = require("@legalplace/referencesparser");
7
8
  const DataPopulator_1 = __importDefault(require("./DataPopulator"));
9
+ const ScopedLeafEvaluator_1 = require("./ScopedLeafEvaluator");
10
+ const ComparatorResolver_1 = require("./ComparatorResolver");
8
11
  class ConditionsRunner {
9
12
  constructor(references, ovc) {
10
13
  this.executeConditionMemo = {};
@@ -77,9 +80,28 @@ class ConditionsRunner {
77
80
  else if (conditionType === "sections" || conditionType === "documents") {
78
81
  currentData = new DataPopulator_1.default(this, this.references, this.ovc, condition.dataMap, id, index).getData();
79
82
  }
80
- const result = (0, lplogic_1.default)(condition.conditions, currentData);
83
+ const resolverContext = this.buildComparatorResolverContext(id, index, conditionType);
84
+ const deps = { references: this.references, ovc: this.ovc };
85
+ const aggregatedConditions = (0, ScopedLeafEvaluator_1.aggregateScopedLeaves)((0, referencesparser_1.lowerScope)(condition.conditions), new DataPopulator_1.default(this, this.references, this.ovc, condition.dataMap).getData(), resolverContext, deps);
86
+ const resolvedConditions = (0, ComparatorResolver_1.resolveConditionComparators)(aggregatedConditions, resolverContext, deps);
87
+ const result = (0, lplogic_1.default)(resolvedConditions, currentData);
81
88
  this.executeConditionMemo[memoizedKey] = result;
82
89
  return result;
83
90
  }
91
+ buildComparatorResolverContext(id, index, conditionType) {
92
+ if (conditionType === "options" || conditionType === "optionValidator") {
93
+ return { optionId: id, index, conditionOptionId: id };
94
+ }
95
+ if (conditionType === "variables" ||
96
+ conditionType === "prefillers" ||
97
+ conditionType === "variableValidator") {
98
+ return {
99
+ optionId: (0, ComparatorResolver_1.getConditionVariableOptionId)(this.references, id),
100
+ index,
101
+ conditionVariableId: id,
102
+ };
103
+ }
104
+ return { optionId: id, index };
105
+ }
84
106
  }
85
107
  exports.default = ConditionsRunner;
@@ -3,11 +3,292 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const referencesparser_1 = require("@legalplace/referencesparser");
6
7
  const ConditionsRunner_1 = __importDefault(require("./ConditionsRunner"));
8
+ const ComparatorResolver_1 = require("./ComparatorResolver");
7
9
  const inputs_type_json_1 = __importDefault(require("./fixtures/inputs-type.json"));
8
10
  const references_json_1 = __importDefault(require("./fixtures/references.json"));
11
+ const emptyOptionRelations = {
12
+ options: [],
13
+ variables: [],
14
+ sections: {},
15
+ documents: [],
16
+ prefillers: [],
17
+ validators: { options: [], variables: [] },
18
+ parents: [],
19
+ children: { options: [], variables: [] },
20
+ dependants: [],
21
+ };
22
+ const buildRelatedDocumentReferences = () => ({
23
+ options: {
24
+ 2: {
25
+ meta: {
26
+ id: 2,
27
+ type: "static",
28
+ label: "Partner",
29
+ step: "*",
30
+ multiple: { enabled: true },
31
+ },
32
+ options: [3, 4],
33
+ variables: [],
34
+ },
35
+ 3: {
36
+ meta: { id: 3, type: "radio", label: "Yes", step: "*" },
37
+ options: [],
38
+ variables: [],
39
+ },
40
+ 4: {
41
+ meta: { id: 4, type: "radio", label: "No", step: "*" },
42
+ options: [],
43
+ variables: [],
44
+ },
45
+ },
46
+ variables: {},
47
+ outputs: {},
48
+ boxes: {},
49
+ documents: {
50
+ annex: {
51
+ name: "Annex",
52
+ params: { multiple: { enabled: true, repeatOption: 2 } },
53
+ },
54
+ },
55
+ sections: {},
56
+ relations: {
57
+ options: {
58
+ 2: {
59
+ ...emptyOptionRelations,
60
+ children: { options: [3, 4], variables: [] },
61
+ },
62
+ 3: { ...emptyOptionRelations, parents: [2] },
63
+ 4: { ...emptyOptionRelations, parents: [2] },
64
+ },
65
+ variables: {},
66
+ sections: {},
67
+ },
68
+ conditions: {
69
+ options: {},
70
+ variables: {},
71
+ documents: {},
72
+ sections: { main: {} },
73
+ prefillers: {},
74
+ validators: { options: {}, variables: {} },
75
+ },
76
+ defaults: {},
77
+ evaluations: {},
78
+ optionTags: {},
79
+ variableTags: {},
80
+ });
81
+ const buildSiblingVariableReferences = () => ({
82
+ options: {
83
+ 10: {
84
+ meta: {
85
+ id: 10,
86
+ type: "static",
87
+ label: "Repeat",
88
+ step: "*",
89
+ multiple: { enabled: true },
90
+ },
91
+ options: [],
92
+ variables: [3, 4],
93
+ },
94
+ 20: {
95
+ meta: { id: 20, type: "static", label: "Other", step: "*" },
96
+ options: [],
97
+ variables: [5],
98
+ },
99
+ },
100
+ variables: {
101
+ 3: { meta: { id: 3, type: "string", label: "Left" }, options: [] },
102
+ 4: { meta: { id: 4, type: "string", label: "Right" }, options: [] },
103
+ 5: { meta: { id: 5, type: "string", label: "Elsewhere" }, options: [] },
104
+ },
105
+ outputs: {},
106
+ boxes: {},
107
+ documents: {},
108
+ sections: {},
109
+ relations: {
110
+ options: {
111
+ 10: {
112
+ ...emptyOptionRelations,
113
+ children: { options: [], variables: [3, 4] },
114
+ },
115
+ 20: {
116
+ ...emptyOptionRelations,
117
+ children: { options: [], variables: [5] },
118
+ },
119
+ },
120
+ variables: {
121
+ 3: { ...emptyOptionRelations, parents: [10] },
122
+ 4: { ...emptyOptionRelations, parents: [10] },
123
+ 5: { ...emptyOptionRelations, parents: [20] },
124
+ },
125
+ sections: {},
126
+ },
127
+ conditions: {
128
+ options: {},
129
+ variables: {},
130
+ documents: {},
131
+ sections: { main: {} },
132
+ prefillers: {},
133
+ validators: { options: {}, variables: {} },
134
+ },
135
+ defaults: {},
136
+ evaluations: {},
137
+ optionTags: {},
138
+ variableTags: {},
139
+ });
140
+ const relatedOvc = {
141
+ options: {
142
+ 2: [true, true],
143
+ 3: [true, false],
144
+ 4: [false, true],
145
+ },
146
+ variables: {},
147
+ };
148
+ const siblingOvc = {
149
+ options: {
150
+ 10: [true, true, true],
151
+ 20: [true],
152
+ },
153
+ variables: {
154
+ 3: ["same", "b", "c"],
155
+ 4: ["same", "y", "c"],
156
+ 5: ["solo-a", "solo-b"],
157
+ },
158
+ };
159
+ const mappedCondition = (conditions) => ({
160
+ dataMap: (0, referencesparser_1.extractConditionDataMap)(conditions),
161
+ conditions,
162
+ });
9
163
  describe("ConditionsRunner class", () => {
10
164
  it("should not raise error when instanciating class", () => {
11
165
  expect(new ConditionsRunner_1.default(references_json_1.default, inputs_type_json_1.default)).toBeDefined();
12
166
  });
167
+ it("slices related document conditions to the current occurrence", () => {
168
+ const runner = new ConditionsRunner_1.default(buildRelatedDocumentReferences(), relatedOvc);
169
+ const condition = mappedCondition({
170
+ selected: [{ var: "o.3" }],
171
+ scope: "related",
172
+ });
173
+ expect(runner.executeCondition(condition, 2, 0, "documents")).toBe(true);
174
+ expect(runner.executeCondition(condition, 2, 1, "documents")).toBe(false);
175
+ });
176
+ it("evaluates selected + first against the first row after id/index slicing", () => {
177
+ const runner = new ConditionsRunner_1.default(buildRelatedDocumentReferences(), relatedOvc);
178
+ const condition = mappedCondition({
179
+ selected: [{ var: "o.3" }],
180
+ scope: "first",
181
+ });
182
+ expect(runner.executeCondition(condition, 2, 1, "documents")).toBe(true);
183
+ });
184
+ it("related sibling [var:N] uses the current occurrence index", () => {
185
+ const runner = new ConditionsRunner_1.default(buildSiblingVariableReferences(), siblingOvc);
186
+ const condition = mappedCondition({
187
+ "=": [{ var: "v.3" }, "[var:4]"],
188
+ scope: "related",
189
+ });
190
+ expect(runner.executeCondition(condition, 3, 0, "variables")).toBe(true);
191
+ expect(runner.executeCondition(condition, 3, 1, "variables")).toBe(false);
192
+ expect(runner.executeCondition(condition, 3, 2, "variables")).toBe(true);
193
+ });
194
+ it("first-scoped sibling [var:N] uses occurrence 0", () => {
195
+ const runner = new ConditionsRunner_1.default(buildSiblingVariableReferences(), siblingOvc);
196
+ const condition = mappedCondition({
197
+ "=": [{ var: "v.3" }, "[var:4]"],
198
+ scope: "first",
199
+ });
200
+ expect(runner.executeCondition(condition, 3, 2, "variables")).toBe(true);
201
+ });
202
+ it("all-scope sibling [var:N] is pairwise true when every row matches", () => {
203
+ const runner = new ConditionsRunner_1.default(buildSiblingVariableReferences(), {
204
+ options: { 10: [true, true], 20: [true] },
205
+ variables: {
206
+ 3: [1, 2],
207
+ 4: [2, 3],
208
+ 5: ["solo-a"],
209
+ },
210
+ });
211
+ const condition = mappedCondition({
212
+ "<": [{ var: "v.3" }, "[var:4]"],
213
+ scope: "all",
214
+ });
215
+ expect(runner.executeCondition(condition, 3, 0, "options")).toBe(true);
216
+ });
217
+ it("all-scope sibling [var:N] is pairwise false when a row fails", () => {
218
+ const runner = new ConditionsRunner_1.default(buildSiblingVariableReferences(), {
219
+ options: { 10: [true, true], 20: [true] },
220
+ variables: {
221
+ 3: [1, 5],
222
+ 4: [2, 3],
223
+ 5: ["solo-a"],
224
+ },
225
+ });
226
+ const condition = mappedCondition({
227
+ "<": [{ var: "v.3" }, "[var:4]"],
228
+ scope: "all",
229
+ });
230
+ expect(runner.executeCondition(condition, 3, 0, "options")).toBe(false);
231
+ });
232
+ it("any-scope sibling [var:N] succeeds on a later pairwise row", () => {
233
+ const runner = new ConditionsRunner_1.default(buildSiblingVariableReferences(), {
234
+ options: { 10: [true, true], 20: [true] },
235
+ variables: {
236
+ 3: [5, 1],
237
+ 4: [2, 10],
238
+ 5: ["solo-a"],
239
+ },
240
+ });
241
+ const condition = mappedCondition({
242
+ "<": [{ var: "v.3" }, "[var:4]"],
243
+ scope: "any",
244
+ });
245
+ expect(runner.executeCondition(condition, 5, 0, "options")).toBe(true);
246
+ });
247
+ it("joins non-sibling [var:N] values instead of slicing by row", () => {
248
+ const references = buildSiblingVariableReferences();
249
+ const resolved = (0, ComparatorResolver_1.resolveConditionComparators)((0, referencesparser_1.lowerScope)({
250
+ "=": [{ var: "v.3" }, "[var:5]"],
251
+ scope: "related",
252
+ }), { optionId: 10, index: 2, conditionVariableId: 3 }, { references, ovc: siblingOvc });
253
+ expect(resolved).toEqual({
254
+ "=": [{ var: "v.3" }, "solo-a, solo-b"],
255
+ });
256
+ });
257
+ });
258
+ describe("resolveConditionComparators", () => {
259
+ const references = buildSiblingVariableReferences();
260
+ const deps = { references, ovc: siblingOvc };
261
+ it("related leaf: sibling [var:N] uses the current occurrence index", () => {
262
+ const resolved = (0, ComparatorResolver_1.resolveConditionComparators)((0, referencesparser_1.lowerScope)({
263
+ "=": [{ var: "v.3" }, "[var:4]"],
264
+ scope: "related",
265
+ }), { optionId: 10, index: 2, conditionVariableId: 3 }, deps);
266
+ expect(resolved).toEqual({
267
+ "=": [{ var: "v.3" }, "c"],
268
+ });
269
+ });
270
+ it("first-scoped leaf: sibling [var:N] uses occurrence 0", () => {
271
+ const resolved = (0, ComparatorResolver_1.resolveConditionComparators)((0, referencesparser_1.lowerScope)({
272
+ "=": [{ var: "v.3" }, "[var:4]"],
273
+ scope: "first",
274
+ }), { optionId: 10, index: 2, conditionVariableId: 3 }, deps);
275
+ expect(resolved).toEqual({
276
+ "=-first": [{ var: "v.3" }, "same"],
277
+ });
278
+ });
279
+ it("uses empty sentinel for missing sibling values", () => {
280
+ const resolved = (0, ComparatorResolver_1.resolveConditionComparators)((0, referencesparser_1.lowerScope)({
281
+ "=": [{ var: "v.3" }, "[var:4]"],
282
+ scope: "related",
283
+ }), { optionId: 10, index: 5, conditionVariableId: 3 }, {
284
+ references,
285
+ ovc: {
286
+ options: siblingOvc.options,
287
+ variables: { 3: ["a"], 4: ["b"], 5: [] },
288
+ },
289
+ });
290
+ expect(resolved).toEqual({
291
+ "=": [{ var: "v.3" }, ComparatorResolver_1.CONDITION_COMPARATOR_EMPTY_SENTINEL],
292
+ });
293
+ });
13
294
  });
@@ -95,14 +95,14 @@ class DataPopulator {
95
95
  getVariableValues(variableId) {
96
96
  const value = this.ovc.variables[variableId] || [];
97
97
  if (this.id === undefined || this.index === undefined) {
98
- return value;
98
+ return this.cleanArrayFromNullValues([...value]);
99
99
  }
100
100
  const variableRootParent = this.getVariableRootParent(variableId);
101
101
  const variableRootRelations = this.references.relations.options[variableRootParent];
102
102
  if (variableRootRelations.children.options.includes(this.id) ||
103
103
  variableRootRelations.dependants.includes(this.id) ||
104
104
  this.id === variableRootParent) {
105
- return [value[this.index]];
105
+ return this.cleanArrayFromNullValues([value[this.index]]);
106
106
  }
107
107
  return this.cleanArrayFromNullValues([...value]);
108
108
  }
@@ -111,7 +111,7 @@ class DataPopulator {
111
111
  return variableParents[variableParents.length - 1];
112
112
  }
113
113
  cleanArrayFromNullValues(arr) {
114
- return arr.map((v) => (v === null ? "" : v));
114
+ return arr.map((v) => (v === null || v === undefined ? "" : v));
115
115
  }
116
116
  populateOptions() {
117
117
  for (let i = 0; i < this.dataMap.o.length; i += 1) {
@@ -0,0 +1,3 @@
1
+ import type { ConditionData } from "./types/conditions.d";
2
+ import { type ComparatorResolverContext, type ComparatorResolverDeps } from "./ComparatorResolver";
3
+ export declare const aggregateScopedLeaves: (tree: unknown, unslicedData: ConditionData, context: ComparatorResolverContext, deps: ComparatorResolverDeps) => unknown;