Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/contextkey/test/common/contextkey.test.ts
5256 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import assert from 'assert';
6
import { isLinux, isMacintosh, isWindows } from '../../../../base/common/platform.js';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
8
import { ContextKeyExpr, ContextKeyExpression, implies } from '../../common/contextkey.js';
9
10
function createContext(ctx: any) {
11
return {
12
getValue: (key: string) => {
13
return ctx[key];
14
}
15
};
16
}
17
18
suite('ContextKeyExpr', () => {
19
20
ensureNoDisposablesAreLeakedInTestSuite();
21
22
test('ContextKeyExpr.equals', () => {
23
const a = ContextKeyExpr.and(
24
ContextKeyExpr.has('a1'),
25
ContextKeyExpr.and(ContextKeyExpr.has('and.a')),
26
ContextKeyExpr.has('a2'),
27
ContextKeyExpr.regex('d3', /d.*/),
28
ContextKeyExpr.regex('d4', /\*\*3*/),
29
ContextKeyExpr.equals('b1', 'bb1'),
30
ContextKeyExpr.equals('b2', 'bb2'),
31
ContextKeyExpr.notEquals('c1', 'cc1'),
32
ContextKeyExpr.notEquals('c2', 'cc2'),
33
ContextKeyExpr.not('d1'),
34
ContextKeyExpr.not('d2')
35
)!;
36
const b = ContextKeyExpr.and(
37
ContextKeyExpr.equals('b2', 'bb2'),
38
ContextKeyExpr.notEquals('c1', 'cc1'),
39
ContextKeyExpr.not('d1'),
40
ContextKeyExpr.regex('d4', /\*\*3*/),
41
ContextKeyExpr.notEquals('c2', 'cc2'),
42
ContextKeyExpr.has('a2'),
43
ContextKeyExpr.equals('b1', 'bb1'),
44
ContextKeyExpr.regex('d3', /d.*/),
45
ContextKeyExpr.has('a1'),
46
ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),
47
ContextKeyExpr.not('d2')
48
)!;
49
assert(a.equals(b), 'expressions should be equal');
50
});
51
52
test('issue #134942: Equals in comparator expressions', () => {
53
function testEquals(expr: ContextKeyExpression | undefined, str: string): void {
54
const deserialized = ContextKeyExpr.deserialize(str);
55
assert.ok(expr);
56
assert.ok(deserialized);
57
assert.strictEqual(expr.equals(deserialized), true, str);
58
}
59
testEquals(ContextKeyExpr.greater('value', 0), 'value > 0');
60
testEquals(ContextKeyExpr.greaterEquals('value', 0), 'value >= 0');
61
testEquals(ContextKeyExpr.smaller('value', 0), 'value < 0');
62
testEquals(ContextKeyExpr.smallerEquals('value', 0), 'value <= 0');
63
});
64
65
test('normalize', () => {
66
const key1IsTrue = ContextKeyExpr.equals('key1', true);
67
const key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);
68
const key1IsFalse = ContextKeyExpr.equals('key1', false);
69
const key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);
70
71
assert.ok(key1IsTrue.equals(ContextKeyExpr.has('key1')));
72
assert.ok(key1IsNotFalse.equals(ContextKeyExpr.has('key1')));
73
assert.ok(key1IsFalse.equals(ContextKeyExpr.not('key1')));
74
assert.ok(key1IsNotTrue.equals(ContextKeyExpr.not('key1')));
75
});
76
77
test('evaluate', () => {
78
const context = createContext({
79
'a': true,
80
'b': false,
81
'c': '5',
82
'd': 'd'
83
});
84
function testExpression(expr: string, expected: boolean): void {
85
// console.log(expr + ' ' + expected);
86
const rules = ContextKeyExpr.deserialize(expr);
87
assert.strictEqual(rules!.evaluate(context), expected, expr);
88
}
89
function testBatch(expr: string, value: any): void {
90
/* eslint-disable eqeqeq */
91
testExpression(expr, !!value);
92
testExpression(expr + ' == true', !!value);
93
testExpression(expr + ' != true', !value);
94
testExpression(expr + ' == false', !value);
95
testExpression(expr + ' != false', !!value);
96
// eslint-disable-next-line local/code-no-any-casts
97
testExpression(expr + ' == 5', value == <any>'5');
98
// eslint-disable-next-line local/code-no-any-casts
99
testExpression(expr + ' != 5', value != <any>'5');
100
testExpression('!' + expr, !value);
101
testExpression(expr + ' =~ /d.*/', /d.*/.test(value));
102
testExpression(expr + ' =~ /D/i', /D/i.test(value));
103
/* eslint-enable eqeqeq */
104
}
105
106
testBatch('a', true);
107
testBatch('b', false);
108
testBatch('c', '5');
109
testBatch('d', 'd');
110
testBatch('z', undefined);
111
112
testExpression('true', true);
113
testExpression('false', false);
114
testExpression('a && !b', true && !false);
115
testExpression('a && b', true && false);
116
testExpression('a && !b && c == 5', true && !false && '5' === '5');
117
testExpression('d =~ /e.*/', false);
118
119
// precedence test: false && true || true === true because && is evaluated first
120
testExpression('b && a || a', true);
121
122
testExpression('a || b', true);
123
testExpression('b || b', false);
124
testExpression('b && a || a && b', false);
125
});
126
127
test('negate', () => {
128
function testNegate(expr: string, expected: string): void {
129
const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize();
130
assert.strictEqual(actual, expected);
131
}
132
testNegate('true', 'false');
133
testNegate('false', 'true');
134
testNegate('a', '!a');
135
testNegate('a && b || c', '!a && !c || !b && !c');
136
testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d');
137
testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d');
138
testNegate('!a && !b || !c && !d || !e && !f', 'a && c && e || a && c && f || a && d && e || a && d && f || b && c && e || b && c && f || b && d && e || b && d && f');
139
});
140
141
test('false, true', () => {
142
function testNormalize(expr: string, expected: string): void {
143
const actual = ContextKeyExpr.deserialize(expr)!.serialize();
144
assert.strictEqual(actual, expected);
145
}
146
testNormalize('true', 'true');
147
testNormalize('!true', 'false');
148
testNormalize('false', 'false');
149
testNormalize('!false', 'true');
150
testNormalize('a && true', 'a');
151
testNormalize('a && false', 'false');
152
testNormalize('a || true', 'true');
153
testNormalize('a || false', 'a');
154
testNormalize('isMac', isMacintosh ? 'true' : 'false');
155
testNormalize('isLinux', isLinux ? 'true' : 'false');
156
testNormalize('isWindows', isWindows ? 'true' : 'false');
157
});
158
159
test('issue #101015: distribute OR', () => {
160
function t(expr1: string, expr2: string, expected: string | undefined): void {
161
const e1 = ContextKeyExpr.deserialize(expr1);
162
const e2 = ContextKeyExpr.deserialize(expr2);
163
const actual = ContextKeyExpr.and(e1, e2)?.serialize();
164
assert.strictEqual(actual, expected);
165
}
166
t('a', 'b', 'a && b');
167
t('a || b', 'c', 'a && c || b && c');
168
t('a || b', 'c || d', 'a && c || a && d || b && c || b && d');
169
t('a || b', 'c && d', 'a && c && d || b && c && d');
170
t('a || b', 'c && d || e', 'a && e || b && e || a && c && d || b && c && d');
171
});
172
173
test('ContextKeyInExpr', () => {
174
const ainb = ContextKeyExpr.deserialize('a in b')!;
175
assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), true);
176
assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), true);
177
assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), false);
178
assert.strictEqual(ainb.evaluate(createContext({ 'a': 3 })), false);
179
assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': null })), false);
180
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), true);
181
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), false);
182
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': {} })), false);
183
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), true);
184
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), true);
185
assert.strictEqual(ainb.evaluate(createContext({ 'a': 'prototype', 'b': {} })), false);
186
});
187
188
test('ContextKeyNotInExpr', () => {
189
const aNotInB = ContextKeyExpr.deserialize('a not in b')!;
190
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), false);
191
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), false);
192
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), true);
193
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3 })), true);
194
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': null })), true);
195
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), false);
196
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), true);
197
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': {} })), true);
198
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), false);
199
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), false);
200
assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'prototype', 'b': {} })), true);
201
});
202
203
test('issue #106524: distributing AND should normalize', () => {
204
const actual = ContextKeyExpr.and(
205
ContextKeyExpr.or(
206
ContextKeyExpr.has('a'),
207
ContextKeyExpr.has('b')
208
),
209
ContextKeyExpr.has('c')
210
);
211
const expected = ContextKeyExpr.or(
212
ContextKeyExpr.and(
213
ContextKeyExpr.has('a'),
214
ContextKeyExpr.has('c')
215
),
216
ContextKeyExpr.and(
217
ContextKeyExpr.has('b'),
218
ContextKeyExpr.has('c')
219
)
220
);
221
assert.strictEqual(actual!.equals(expected!), true);
222
});
223
224
test('issue #129625: Removes duplicated terms in OR expressions', () => {
225
const expr = ContextKeyExpr.or(
226
ContextKeyExpr.has('A'),
227
ContextKeyExpr.has('B'),
228
ContextKeyExpr.has('A')
229
)!;
230
assert.strictEqual(expr.serialize(), 'A || B');
231
});
232
233
test('Resolves true constant OR expressions', () => {
234
const expr = ContextKeyExpr.or(
235
ContextKeyExpr.has('A'),
236
ContextKeyExpr.not('A')
237
)!;
238
assert.strictEqual(expr.serialize(), 'true');
239
});
240
241
test('Resolves false constant AND expressions', () => {
242
const expr = ContextKeyExpr.and(
243
ContextKeyExpr.has('A'),
244
ContextKeyExpr.not('A')
245
)!;
246
assert.strictEqual(expr.serialize(), 'false');
247
});
248
249
test('issue #129625: Removes duplicated terms in AND expressions', () => {
250
const expr = ContextKeyExpr.and(
251
ContextKeyExpr.has('A'),
252
ContextKeyExpr.has('B'),
253
ContextKeyExpr.has('A')
254
)!;
255
assert.strictEqual(expr.serialize(), 'A && B');
256
});
257
258
test('issue #129625: Remove duplicated terms when negating', () => {
259
const expr = ContextKeyExpr.and(
260
ContextKeyExpr.has('A'),
261
ContextKeyExpr.or(
262
ContextKeyExpr.has('B1'),
263
ContextKeyExpr.has('B2'),
264
)
265
)!;
266
assert.strictEqual(expr.serialize(), 'A && B1 || A && B2');
267
assert.strictEqual(expr.negate()!.serialize(), '!A || !A && !B1 || !A && !B2 || !B1 && !B2');
268
assert.strictEqual(expr.negate()!.negate()!.serialize(), 'A && B1 || A && B2');
269
assert.strictEqual(expr.negate()!.negate()!.negate()!.serialize(), '!A || !A && !B1 || !A && !B2 || !B1 && !B2');
270
});
271
272
test('issue #129625: remove redundant terms in OR expressions', () => {
273
function strImplies(p0: string, q0: string): boolean {
274
const p = ContextKeyExpr.deserialize(p0)!;
275
const q = ContextKeyExpr.deserialize(q0)!;
276
return implies(p, q);
277
}
278
assert.strictEqual(strImplies('a && b', 'a'), true);
279
assert.strictEqual(strImplies('a', 'a && b'), false);
280
});
281
282
test('implies', () => {
283
function strImplies(p0: string, q0: string): boolean {
284
const p = ContextKeyExpr.deserialize(p0)!;
285
const q = ContextKeyExpr.deserialize(q0)!;
286
return implies(p, q);
287
}
288
assert.strictEqual(strImplies('a', 'a'), true);
289
assert.strictEqual(strImplies('a', 'a || b'), true);
290
assert.strictEqual(strImplies('a', 'a && b'), false);
291
assert.strictEqual(strImplies('a', 'a && b || a && c'), false);
292
assert.strictEqual(strImplies('a && b', 'a'), true);
293
assert.strictEqual(strImplies('a && b', 'b'), true);
294
assert.strictEqual(strImplies('a && b', 'a && b || c'), true);
295
assert.strictEqual(strImplies('a || b', 'a || c'), false);
296
assert.strictEqual(strImplies('a || b', 'a || b'), true);
297
assert.strictEqual(strImplies('a && b', 'a && b'), true);
298
assert.strictEqual(strImplies('a || b', 'a || b || c'), true);
299
assert.strictEqual(strImplies('c && a && b', 'c && a'), true);
300
});
301
302
test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => {
303
function checkEvaluate(expr: string, ctx: any, expected: any): void {
304
const _expr = ContextKeyExpr.deserialize(expr)!;
305
assert.strictEqual(_expr.evaluate(createContext(ctx)), expected);
306
}
307
308
checkEvaluate('a > 1', {}, false);
309
checkEvaluate('a > 1', { a: 0 }, false);
310
checkEvaluate('a > 1', { a: 1 }, false);
311
checkEvaluate('a > 1', { a: 2 }, true);
312
checkEvaluate('a > 1', { a: '0' }, false);
313
checkEvaluate('a > 1', { a: '1' }, false);
314
checkEvaluate('a > 1', { a: '2' }, true);
315
checkEvaluate('a > 1', { a: 'a' }, false);
316
317
checkEvaluate('a > 10', { a: 2 }, false);
318
checkEvaluate('a > 10', { a: 11 }, true);
319
checkEvaluate('a > 10', { a: '11' }, true);
320
checkEvaluate('a > 10', { a: '2' }, false);
321
checkEvaluate('a > 10', { a: '11' }, true);
322
323
checkEvaluate('a > 1.1', { a: 1 }, false);
324
checkEvaluate('a > 1.1', { a: 2 }, true);
325
checkEvaluate('a > 1.1', { a: 11 }, true);
326
checkEvaluate('a > 1.1', { a: '1.1' }, false);
327
checkEvaluate('a > 1.1', { a: '2' }, true);
328
checkEvaluate('a > 1.1', { a: '11' }, true);
329
330
checkEvaluate('a > b', { a: 'b' }, false);
331
checkEvaluate('a > b', { a: 'c' }, false);
332
checkEvaluate('a > b', { a: 1000 }, false);
333
334
checkEvaluate('a >= 2', { a: '1' }, false);
335
checkEvaluate('a >= 2', { a: '2' }, true);
336
checkEvaluate('a >= 2', { a: '3' }, true);
337
338
checkEvaluate('a < 2', { a: '1' }, true);
339
checkEvaluate('a < 2', { a: '2' }, false);
340
checkEvaluate('a < 2', { a: '3' }, false);
341
342
checkEvaluate('a <= 2', { a: '1' }, true);
343
checkEvaluate('a <= 2', { a: '2' }, true);
344
checkEvaluate('a <= 2', { a: '3' }, false);
345
});
346
347
test('Greater, GreaterEquals, Smaller, SmallerEquals negate', () => {
348
function checkNegate(expr: string, expected: string): void {
349
const a = ContextKeyExpr.deserialize(expr)!;
350
const b = a.negate();
351
assert.strictEqual(b.serialize(), expected);
352
}
353
354
checkNegate('a > 1', 'a <= 1');
355
checkNegate('a > 1.1', 'a <= 1.1');
356
checkNegate('a > b', 'a <= b');
357
358
checkNegate('a >= 1', 'a < 1');
359
checkNegate('a >= 1.1', 'a < 1.1');
360
checkNegate('a >= b', 'a < b');
361
362
checkNegate('a < 1', 'a >= 1');
363
checkNegate('a < 1.1', 'a >= 1.1');
364
checkNegate('a < b', 'a >= b');
365
366
checkNegate('a <= 1', 'a > 1');
367
checkNegate('a <= 1.1', 'a > 1.1');
368
checkNegate('a <= b', 'a > b');
369
});
370
371
test('issue #111899: context keys can use `<` or `>` ', () => {
372
const actual = ContextKeyExpr.deserialize('editorTextFocus && vim.active && vim.use<C-r>')!;
373
assert.ok(actual.equals(
374
ContextKeyExpr.and(
375
ContextKeyExpr.has('editorTextFocus'),
376
ContextKeyExpr.has('vim.active'),
377
ContextKeyExpr.has('vim.use<C-r>'),
378
)!
379
));
380
});
381
});
382
383