querysub 0.504.0 → 0.506.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.
- package/bin/function.js +3 -3
- package/bin/server-dev.js +11 -0
- package/bin/server.js +2 -0
- package/package.json +2 -1
- package/src/0-path-value-core/AuthorityLookup.ts +1 -0
- package/src/0-path-value-core/PathRouter.ts +27 -4
- package/src/0-path-value-core/PathRouterRouteOverride.ts +98 -18
- package/src/0-path-value-core/PathRouterServerAuthoritySpec.tsx +4 -1
- package/src/0-path-value-core/PathValueCommitter.ts +6 -1
- package/src/0-path-value-core/PathValueController.ts +7 -5
- package/src/0-path-value-core/PathWatcher.ts +11 -8
- package/src/0-path-value-core/hackedPackedPathParentFiltering.ts +24 -10
- package/src/0-path-value-core/pathValueCore.ts +15 -7
- package/src/1-path-client/RemoteWatcher.ts +35 -18
- package/src/3-path-functions/PathFunctionHelpers.ts +14 -15
- package/src/3-path-functions/PathFunctionRunner.ts +18 -12
- package/src/3-path-functions/PathFunctionRunnerMain.ts +5 -7
- package/src/3-path-functions/syncSchema.ts +3 -1
- package/src/4-querysub/Querysub.ts +14 -2
- package/src/4-querysub/QuerysubController.ts +13 -6
- package/src/config.ts +39 -3
- package/src/config2.ts +2 -8
- package/src/diagnostics/logs/errorTickets/TicketPage.tsx +4 -3
- package/src/diagnostics/managementPages.tsx +1 -1
- package/src/diagnostics/misc-pages/AuthoritySpecPage.tsx +30 -5
- package/src/misc/filterable.ts +0 -395
|
@@ -79,6 +79,7 @@ class AuthorityNodeRow extends qreact.Component<{ info: NodeAuthorityInfo }> {
|
|
|
79
79
|
<AuthorityRangeBar start={spec.routeStart} end={spec.routeEnd} />
|
|
80
80
|
<span className={css.colorhsl(0, 0, 50)}>width {(spec.routeEnd - spec.routeStart).toFixed(4)}</span>
|
|
81
81
|
{spec.excludeDefault && <span className={css.colorhsl(0, 70, 35)}>(excludes default)</span>}
|
|
82
|
+
{spec.networks && spec.networks.length > 0 && <span className={css.colorhsl(210, 60, 40)}>networks: {spec.networks === "all" && "all" || Array.isArray(spec.networks) && spec.networks.join(", ") || ""}</span>}
|
|
82
83
|
<span className={css.colorhsl(0, 0, 40).ellipsis.flexFillWidth}>{info.entryPoint || "(no entry point)"}</span>
|
|
83
84
|
</div>
|
|
84
85
|
{expanded &&
|
|
@@ -98,16 +99,40 @@ export class AuthoritySpecPage extends qreact.Component {
|
|
|
98
99
|
render() {
|
|
99
100
|
let infos = AuthoritySpecSynced(getBrowserUrlNode()).getAllNodeAuthoritySpecs();
|
|
100
101
|
if (!infos) {
|
|
101
|
-
return <div className={css.pad2(16)}>Loading
|
|
102
|
+
return <div className={css.pad2(16)}>Loading routing table...</div>;
|
|
102
103
|
}
|
|
103
104
|
infos = infos.filter(x => x.spec && x.spec.routeStart >= 0 && x.spec.routeEnd >= 0);
|
|
104
105
|
sort(infos, x => x.spec!.routeStart);
|
|
105
106
|
|
|
107
|
+
let infosPerNetwork = new Map<string, NodeAuthorityInfo[]>();
|
|
108
|
+
for (let info of infos) {
|
|
109
|
+
let networks = info.spec!.networks;
|
|
110
|
+
if (networks === "all") {
|
|
111
|
+
networks = ["all"];
|
|
112
|
+
}
|
|
113
|
+
if (!networks || networks.length === 0) {
|
|
114
|
+
networks = ["default"];
|
|
115
|
+
}
|
|
116
|
+
for (let network of networks) {
|
|
117
|
+
let list = infosPerNetwork.get(network);
|
|
118
|
+
if (!list) {
|
|
119
|
+
list = [];
|
|
120
|
+
infosPerNetwork.set(network, list);
|
|
121
|
+
}
|
|
122
|
+
list.push(info);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
let networkEntries = Array.from(infosPerNetwork.entries());
|
|
126
|
+
sort(networkEntries, entry => entry[0] === "default" && " " || entry[0]);
|
|
127
|
+
|
|
106
128
|
return <div className={css.vbox(12).pad2(16).fillWidth}>
|
|
107
|
-
<h2>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
<h2>Routing Table ({infos.length})</h2>
|
|
130
|
+
{networkEntries.map(([network, networkInfos]) =>
|
|
131
|
+
<div className={css.vbox(8).fillWidth}>
|
|
132
|
+
<h3>{network} ({networkInfos.length})</h3>
|
|
133
|
+
{networkInfos.map(info => <AuthorityNodeRow key={info.nodeId} info={info} />)}
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
111
136
|
</div>;
|
|
112
137
|
}
|
|
113
138
|
}
|
package/src/misc/filterable.ts
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { devDebugbreak } from "../config";
|
|
2
|
-
import debugbreak from "debugbreak";
|
|
3
|
-
|
|
4
|
-
export type Filterable = {
|
|
5
|
-
values: string[];
|
|
6
|
-
} | undefined;
|
|
7
|
-
// undefined FilterSelector only matches empty Filterables.
|
|
8
|
-
export type FilterSelector = {
|
|
9
|
-
expression: ASTNode;
|
|
10
|
-
} | undefined;
|
|
11
|
-
|
|
12
|
-
type ASTNode = {
|
|
13
|
-
type: "orList";
|
|
14
|
-
parts: ASTNode[];
|
|
15
|
-
} | {
|
|
16
|
-
type: "andList";
|
|
17
|
-
parts: ASTNode[];
|
|
18
|
-
} | {
|
|
19
|
-
type: "not";
|
|
20
|
-
part: ASTNode;
|
|
21
|
-
} | {
|
|
22
|
-
type: "leaf";
|
|
23
|
-
matcher: FilterMatcher;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type FilterMatcher = {
|
|
27
|
-
type: "includes";
|
|
28
|
-
value: string;
|
|
29
|
-
} | {
|
|
30
|
-
type: "regex";
|
|
31
|
-
value: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export function doesMatch(filterable: Filterable, filterSelector: FilterSelector): boolean {
|
|
35
|
-
if (!filterSelector) {
|
|
36
|
-
return !filterable;
|
|
37
|
-
}
|
|
38
|
-
return evaluateASTNode(filterSelector.expression, filterable);
|
|
39
|
-
}
|
|
40
|
-
function evaluateASTNode(node: ASTNode, filterable: Filterable): boolean {
|
|
41
|
-
if (node.type === "orList") {
|
|
42
|
-
return node.parts.some(part => evaluateASTNode(part, filterable));
|
|
43
|
-
} else if (node.type === "andList") {
|
|
44
|
-
return node.parts.every(part => evaluateASTNode(part, filterable));
|
|
45
|
-
} else if (node.type === "not") {
|
|
46
|
-
return !evaluateASTNode(node.part, filterable);
|
|
47
|
-
} else if (node.type === "leaf") {
|
|
48
|
-
return evaluateLeaf(node.matcher, filterable);
|
|
49
|
-
} else {
|
|
50
|
-
let unhandled: never = node;
|
|
51
|
-
}
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
function evaluateLeaf(matcher: FilterMatcher, filterable: Filterable): boolean {
|
|
55
|
-
if (!filterable) return false;
|
|
56
|
-
if (matcher.type === "includes") {
|
|
57
|
-
return filterable.values.some(value => value.includes(matcher.value));
|
|
58
|
-
} else if (matcher.type === "regex") {
|
|
59
|
-
return filterable.values.some(value => new RegExp(matcher.value).test(value));
|
|
60
|
-
} else {
|
|
61
|
-
let unhandled: never = matcher;
|
|
62
|
-
}
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function mergeSelectors(selectors: FilterSelector[]): FilterSelector {
|
|
67
|
-
if (selectors.length === 0) return undefined;
|
|
68
|
-
if (selectors.length === 1) return selectors[0];
|
|
69
|
-
let nodes: ASTNode[] = [];
|
|
70
|
-
for (let selector of selectors) {
|
|
71
|
-
if (!selector) continue;
|
|
72
|
-
// Flatten orLists
|
|
73
|
-
if (selector.expression.type === "orList") {
|
|
74
|
-
nodes.push(...selector.expression.parts);
|
|
75
|
-
} else {
|
|
76
|
-
nodes.push(selector.expression);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
expression: {
|
|
81
|
-
type: "orList",
|
|
82
|
-
parts: nodes,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
export function mergeFilterables(filterables: Filterable[]): Filterable {
|
|
87
|
-
if (filterables.length === 0) return undefined;
|
|
88
|
-
if (filterables.length === 1) return filterables[0];
|
|
89
|
-
let values = Array.from(new Set(filterables.flatMap(filterable => filterable?.values ?? [])));
|
|
90
|
-
if (values.length === 0) return undefined;
|
|
91
|
-
return {
|
|
92
|
-
values,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// apple | b & "cats are | funny" | !(d | elephant)
|
|
97
|
-
// " at the start and end allows using special characters in a value
|
|
98
|
-
// "" inside of " is parsed as a single "
|
|
99
|
-
// The values are trimmed
|
|
100
|
-
export function parseFilterSelector(filterSelector: string): FilterSelector {
|
|
101
|
-
if (!filterSelector.trim()) return undefined;
|
|
102
|
-
|
|
103
|
-
let pos = 0;
|
|
104
|
-
const input = filterSelector;
|
|
105
|
-
|
|
106
|
-
function parseValue(): string {
|
|
107
|
-
let value = "";
|
|
108
|
-
if (input[pos] === "\"") {
|
|
109
|
-
pos++; // Skip opening quote
|
|
110
|
-
while (pos < input.length) {
|
|
111
|
-
if (input[pos] === "\"") {
|
|
112
|
-
if (input[pos + 1] === "\"") {
|
|
113
|
-
value += "\"";
|
|
114
|
-
pos += 2;
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
pos++; // Skip closing quote
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
value += input[pos];
|
|
121
|
-
pos++;
|
|
122
|
-
}
|
|
123
|
-
} else {
|
|
124
|
-
while (pos < input.length && !/[\s|&!()]/.test(input[pos])) {
|
|
125
|
-
value += input[pos];
|
|
126
|
-
pos++;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return value.trim();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function skipWhitespace() {
|
|
133
|
-
while (pos < input.length && /\s/.test(input[pos])) pos++;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function parseExpression(): ASTNode {
|
|
137
|
-
skipWhitespace();
|
|
138
|
-
|
|
139
|
-
if (pos >= input.length) {
|
|
140
|
-
throw new Error(`Expected expression at position ${pos} but reached end of input`);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Handle NOT
|
|
144
|
-
if (input[pos] === "!") {
|
|
145
|
-
pos++;
|
|
146
|
-
skipWhitespace();
|
|
147
|
-
|
|
148
|
-
// Support both (!x) and !(x) forms
|
|
149
|
-
const needsClosingParen = input[pos] === "(";
|
|
150
|
-
if (needsClosingParen) {
|
|
151
|
-
pos++;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const expr = parseOrExpression();
|
|
155
|
-
skipWhitespace();
|
|
156
|
-
|
|
157
|
-
if (needsClosingParen) {
|
|
158
|
-
if (input[pos] !== ")") {
|
|
159
|
-
throw new Error(`Expected ")" at position ${pos}, found "${input[pos]}"`);
|
|
160
|
-
}
|
|
161
|
-
pos++;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return { type: "not", part: expr };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Handle parentheses
|
|
168
|
-
if (input[pos] === "(") {
|
|
169
|
-
pos++;
|
|
170
|
-
const expr = parseOrExpression();
|
|
171
|
-
skipWhitespace();
|
|
172
|
-
if (input[pos] !== ")") {
|
|
173
|
-
throw new Error(`Expected ")" at position ${pos}, found "${input[pos]}"`);
|
|
174
|
-
}
|
|
175
|
-
pos++;
|
|
176
|
-
return expr;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Handle leaf node (simple value)
|
|
180
|
-
const value = parseValue();
|
|
181
|
-
if (!value) {
|
|
182
|
-
throw new Error(`Expected value at position ${pos}`);
|
|
183
|
-
}
|
|
184
|
-
return {
|
|
185
|
-
type: "leaf",
|
|
186
|
-
matcher: { type: "includes", value }
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function parseAndExpression(): ASTNode {
|
|
191
|
-
let left = parseExpression();
|
|
192
|
-
skipWhitespace();
|
|
193
|
-
|
|
194
|
-
const parts: ASTNode[] = [left];
|
|
195
|
-
|
|
196
|
-
while (pos < input.length && input[pos] === "&") {
|
|
197
|
-
pos++;
|
|
198
|
-
skipWhitespace();
|
|
199
|
-
parts.push(parseExpression());
|
|
200
|
-
skipWhitespace();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (parts.length === 1) return parts[0];
|
|
204
|
-
return { type: "andList", parts };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function parseOrExpression(): ASTNode {
|
|
208
|
-
let left = parseAndExpression();
|
|
209
|
-
skipWhitespace();
|
|
210
|
-
|
|
211
|
-
const parts: ASTNode[] = [left];
|
|
212
|
-
|
|
213
|
-
while (pos < input.length && input[pos] === "|") {
|
|
214
|
-
pos++;
|
|
215
|
-
skipWhitespace();
|
|
216
|
-
parts.push(parseAndExpression());
|
|
217
|
-
skipWhitespace();
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (parts.length === 1) return parts[0];
|
|
221
|
-
return { type: "orList", parts };
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
const expression = parseOrExpression();
|
|
226
|
-
skipWhitespace();
|
|
227
|
-
|
|
228
|
-
if (pos < input.length) {
|
|
229
|
-
throw new Error(`Unexpected character "${input[pos]}" at position ${pos}`);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { expression };
|
|
233
|
-
} catch (error: any) {
|
|
234
|
-
throw new Error(`Failed to parse filter selector "${filterSelector}": ${error.message}`);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
export function serializeFilterSelector(filterSelector: FilterSelector): string {
|
|
238
|
-
if (!filterSelector) return "";
|
|
239
|
-
|
|
240
|
-
function serializeASTNode(node: ASTNode, parentPrecedence = 0): string {
|
|
241
|
-
// Precedence: OR = 1, AND = 2, NOT = 3, LEAF = 4
|
|
242
|
-
let result: string;
|
|
243
|
-
let currentPrecedence: number;
|
|
244
|
-
|
|
245
|
-
if (node.type === "orList") {
|
|
246
|
-
currentPrecedence = 1;
|
|
247
|
-
result = node.parts.map(part => serializeASTNode(part, currentPrecedence)).join(" | ");
|
|
248
|
-
} else if (node.type === "andList") {
|
|
249
|
-
currentPrecedence = 2;
|
|
250
|
-
result = node.parts.map(part => serializeASTNode(part, currentPrecedence)).join(" & ");
|
|
251
|
-
} else if (node.type === "not") {
|
|
252
|
-
currentPrecedence = 3;
|
|
253
|
-
if (
|
|
254
|
-
(node.part.type === "andList" || node.part.type === "orList") && node.part.parts.length === 1
|
|
255
|
-
|| node.part.type === "leaf"
|
|
256
|
-
) {
|
|
257
|
-
result = `!${serializeASTNode(node.part, 0)}`;
|
|
258
|
-
} else {
|
|
259
|
-
result = `!(${serializeASTNode(node.part, 0)})`;
|
|
260
|
-
}
|
|
261
|
-
} else if (node.type === "leaf") {
|
|
262
|
-
currentPrecedence = 4;
|
|
263
|
-
const value = node.matcher.value;
|
|
264
|
-
// If the value contains special characters, wrap it in quotes
|
|
265
|
-
if (/[\s|&!()]/.test(value)) {
|
|
266
|
-
// Escape quotes by doubling them
|
|
267
|
-
result = `"${value.replace(/"/g, "\"\"")}"`;
|
|
268
|
-
} else {
|
|
269
|
-
result = value;
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
let unhandled: never = node;
|
|
273
|
-
return "";
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Add parentheses if our precedence is lower than parent's
|
|
277
|
-
if (currentPrecedence < parentPrecedence) {
|
|
278
|
-
result = `(${result})`;
|
|
279
|
-
}
|
|
280
|
-
return result;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return serializeASTNode(filterSelector.expression);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// a | b => { values: ["a", "b"] }
|
|
287
|
-
export function parseFilterable(filterable: string): Filterable {
|
|
288
|
-
if (Array.isArray(filterable)) {
|
|
289
|
-
filterable = filterable.join("|");
|
|
290
|
-
}
|
|
291
|
-
if (!filterable.trim()) return undefined;
|
|
292
|
-
let values = filterable.split("|").map(value => value.trim());
|
|
293
|
-
return {
|
|
294
|
-
values,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
export function serializeFilterable(filterable: Filterable): string {
|
|
298
|
-
if (!filterable) return "";
|
|
299
|
-
return filterable.values.join("|");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
async function main() {
|
|
303
|
-
function assert(cond: () => boolean, message?: string) {
|
|
304
|
-
if (!cond()) throw new Error(`Failed for ${cond.toString()}${message ? `: ${message}` : ""}`);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function testFilter(input: string, matches: string[][], nonMatches: string[][]) {
|
|
308
|
-
console.log(`\nTesting filter: ${input}`);
|
|
309
|
-
const filter = parseFilterSelector(input);
|
|
310
|
-
const serialized = serializeFilterSelector(filter);
|
|
311
|
-
console.log("Serialized:", serialized);
|
|
312
|
-
|
|
313
|
-
// Test that parsing the serialized version gives same result
|
|
314
|
-
const reparsed = parseFilterSelector(serialized);
|
|
315
|
-
assert(() => JSON.stringify(filter) === JSON.stringify(reparsed),
|
|
316
|
-
"Serialized form should parse to equivalent AST");
|
|
317
|
-
|
|
318
|
-
// Test positive matches
|
|
319
|
-
for (const values of matches) {
|
|
320
|
-
assert(() => doesMatch({ values }, filter),
|
|
321
|
-
`Should match values: [${values.join(", ")}]`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Test negative matches
|
|
325
|
-
for (const values of nonMatches) {
|
|
326
|
-
assert(() => !doesMatch({ values }, filter),
|
|
327
|
-
`Should NOT match values: [${values.join(", ")}]`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Test basic operators
|
|
332
|
-
testFilter("a",
|
|
333
|
-
[["a"], ["ab"], ["a", "b"]],
|
|
334
|
-
[["b"], ["c"], []]);
|
|
335
|
-
|
|
336
|
-
testFilter("a & b",
|
|
337
|
-
[["a", "b"], ["ab", "b"], ["a", "ab"]],
|
|
338
|
-
[["a"], ["b"], ["c", "d"], []]);
|
|
339
|
-
|
|
340
|
-
testFilter("a | b",
|
|
341
|
-
[["a"], ["b"], ["a", "b"], ["ab"]],
|
|
342
|
-
[["c"], ["d"], []]);
|
|
343
|
-
|
|
344
|
-
// Test NOT operator
|
|
345
|
-
testFilter("!a",
|
|
346
|
-
[["b"], ["c"], ["b", "c"]],
|
|
347
|
-
[["a"], ["a", "b"]]);
|
|
348
|
-
|
|
349
|
-
testFilter("!(a | b)",
|
|
350
|
-
[["c"], ["d"], ["c", "d"]],
|
|
351
|
-
[["a"], ["b"], ["a", "c"], ["b", "d"]]);
|
|
352
|
-
|
|
353
|
-
// Test operator precedence
|
|
354
|
-
testFilter("a & b | c",
|
|
355
|
-
[["a", "b"], ["c"], ["a", "b", "c"]],
|
|
356
|
-
[["a"], ["b"]]);
|
|
357
|
-
|
|
358
|
-
testFilter("a | b & c",
|
|
359
|
-
[["a"], ["b", "c"], ["a", "b", "c"]],
|
|
360
|
-
[["b"], ["c"], ["b"]]);
|
|
361
|
-
|
|
362
|
-
// Test complex expressions
|
|
363
|
-
testFilter("(a | b & c) & !d",
|
|
364
|
-
[["a"], ["b", "c"], ["a", "b", "c"]],
|
|
365
|
-
[["a", "d"], ["b", "c", "d"], ["d"]]);
|
|
366
|
-
|
|
367
|
-
// Test quoted strings and special characters
|
|
368
|
-
testFilter("\"hello world\" & !\"good bye\"",
|
|
369
|
-
[["hello world"], ["hello world", "hi"]],
|
|
370
|
-
[["good bye"], ["hello world", "good bye"]]);
|
|
371
|
-
|
|
372
|
-
testFilter("\"a & b\" | \"c | d\"",
|
|
373
|
-
[["a & b"], ["c | d"], ["a & b", "e"]],
|
|
374
|
-
[["a"], ["b"], ["c"], ["d"]]);
|
|
375
|
-
|
|
376
|
-
// Test empty and undefined cases
|
|
377
|
-
assert(() => doesMatch(undefined, undefined), "undefined should match undefined");
|
|
378
|
-
assert(() => !doesMatch(undefined, parseFilterSelector("a")), "undefined should not match filter");
|
|
379
|
-
assert(() => !doesMatch({ values: ["a"] }, undefined), "non-empty values should not match undefined");
|
|
380
|
-
|
|
381
|
-
// Test mergeSelectors
|
|
382
|
-
const merged = mergeSelectors([
|
|
383
|
-
parseFilterSelector("a & b"),
|
|
384
|
-
parseFilterSelector("c | d"),
|
|
385
|
-
undefined,
|
|
386
|
-
parseFilterSelector("!e")
|
|
387
|
-
]);
|
|
388
|
-
assert(() => doesMatch({ values: ["a", "b"] }, merged), "should match first selector");
|
|
389
|
-
assert(() => doesMatch({ values: ["c"] }, merged), "should match second selector");
|
|
390
|
-
assert(() => doesMatch({ values: ["d"] }, merged), "should match second selector alternative");
|
|
391
|
-
assert(() => !doesMatch({ values: ["e"] }, merged), "should respect NOT in merged selectors");
|
|
392
|
-
|
|
393
|
-
console.log("All tests passed!");
|
|
394
|
-
}
|
|
395
|
-
//main().catch(console.error).finally(() => process.exit(0));
|