Path: blob/main/src/vs/platform/contextkey/test/common/contextkey.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { isLinux, isMacintosh, isWindows } from '../../../../base/common/platform.js';6import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';7import { ContextKeyExpr, ContextKeyExpression, implies } from '../../common/contextkey.js';89function createContext(ctx: any) {10return {11getValue: (key: string) => {12return ctx[key];13}14};15}1617suite('ContextKeyExpr', () => {1819ensureNoDisposablesAreLeakedInTestSuite();2021test('ContextKeyExpr.equals', () => {22const a = ContextKeyExpr.and(23ContextKeyExpr.has('a1'),24ContextKeyExpr.and(ContextKeyExpr.has('and.a')),25ContextKeyExpr.has('a2'),26ContextKeyExpr.regex('d3', /d.*/),27ContextKeyExpr.regex('d4', /\*\*3*/),28ContextKeyExpr.equals('b1', 'bb1'),29ContextKeyExpr.equals('b2', 'bb2'),30ContextKeyExpr.notEquals('c1', 'cc1'),31ContextKeyExpr.notEquals('c2', 'cc2'),32ContextKeyExpr.not('d1'),33ContextKeyExpr.not('d2')34)!;35const b = ContextKeyExpr.and(36ContextKeyExpr.equals('b2', 'bb2'),37ContextKeyExpr.notEquals('c1', 'cc1'),38ContextKeyExpr.not('d1'),39ContextKeyExpr.regex('d4', /\*\*3*/),40ContextKeyExpr.notEquals('c2', 'cc2'),41ContextKeyExpr.has('a2'),42ContextKeyExpr.equals('b1', 'bb1'),43ContextKeyExpr.regex('d3', /d.*/),44ContextKeyExpr.has('a1'),45ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),46ContextKeyExpr.not('d2')47)!;48assert(a.equals(b), 'expressions should be equal');49});5051test('issue #134942: Equals in comparator expressions', () => {52function testEquals(expr: ContextKeyExpression | undefined, str: string): void {53const deserialized = ContextKeyExpr.deserialize(str);54assert.ok(expr);55assert.ok(deserialized);56assert.strictEqual(expr.equals(deserialized), true, str);57}58testEquals(ContextKeyExpr.greater('value', 0), 'value > 0');59testEquals(ContextKeyExpr.greaterEquals('value', 0), 'value >= 0');60testEquals(ContextKeyExpr.smaller('value', 0), 'value < 0');61testEquals(ContextKeyExpr.smallerEquals('value', 0), 'value <= 0');62});6364test('normalize', () => {65const key1IsTrue = ContextKeyExpr.equals('key1', true);66const key1IsNotFalse = ContextKeyExpr.notEquals('key1', false);67const key1IsFalse = ContextKeyExpr.equals('key1', false);68const key1IsNotTrue = ContextKeyExpr.notEquals('key1', true);6970assert.ok(key1IsTrue.equals(ContextKeyExpr.has('key1')));71assert.ok(key1IsNotFalse.equals(ContextKeyExpr.has('key1')));72assert.ok(key1IsFalse.equals(ContextKeyExpr.not('key1')));73assert.ok(key1IsNotTrue.equals(ContextKeyExpr.not('key1')));74});7576test('evaluate', () => {77const context = createContext({78'a': true,79'b': false,80'c': '5',81'd': 'd'82});83function testExpression(expr: string, expected: boolean): void {84// console.log(expr + ' ' + expected);85const rules = ContextKeyExpr.deserialize(expr);86assert.strictEqual(rules!.evaluate(context), expected, expr);87}88function testBatch(expr: string, value: any): void {89/* eslint-disable eqeqeq */90testExpression(expr, !!value);91testExpression(expr + ' == true', !!value);92testExpression(expr + ' != true', !value);93testExpression(expr + ' == false', !value);94testExpression(expr + ' != false', !!value);95testExpression(expr + ' == 5', value == <any>'5');96testExpression(expr + ' != 5', value != <any>'5');97testExpression('!' + expr, !value);98testExpression(expr + ' =~ /d.*/', /d.*/.test(value));99testExpression(expr + ' =~ /D/i', /D/i.test(value));100/* eslint-enable eqeqeq */101}102103testBatch('a', true);104testBatch('b', false);105testBatch('c', '5');106testBatch('d', 'd');107testBatch('z', undefined);108109testExpression('true', true);110testExpression('false', false);111testExpression('a && !b', true && !false);112testExpression('a && b', true && false);113testExpression('a && !b && c == 5', true && !false && '5' === '5');114testExpression('d =~ /e.*/', false);115116// precedence test: false && true || true === true because && is evaluated first117testExpression('b && a || a', true);118119testExpression('a || b', true);120testExpression('b || b', false);121testExpression('b && a || a && b', false);122});123124test('negate', () => {125function testNegate(expr: string, expected: string): void {126const actual = ContextKeyExpr.deserialize(expr)!.negate().serialize();127assert.strictEqual(actual, expected);128}129testNegate('true', 'false');130testNegate('false', 'true');131testNegate('a', '!a');132testNegate('a && b || c', '!a && !c || !b && !c');133testNegate('a && b || c || d', '!a && !c && !d || !b && !c && !d');134testNegate('!a && !b || !c && !d', 'a && c || a && d || b && c || b && d');135testNegate('!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');136});137138test('false, true', () => {139function testNormalize(expr: string, expected: string): void {140const actual = ContextKeyExpr.deserialize(expr)!.serialize();141assert.strictEqual(actual, expected);142}143testNormalize('true', 'true');144testNormalize('!true', 'false');145testNormalize('false', 'false');146testNormalize('!false', 'true');147testNormalize('a && true', 'a');148testNormalize('a && false', 'false');149testNormalize('a || true', 'true');150testNormalize('a || false', 'a');151testNormalize('isMac', isMacintosh ? 'true' : 'false');152testNormalize('isLinux', isLinux ? 'true' : 'false');153testNormalize('isWindows', isWindows ? 'true' : 'false');154});155156test('issue #101015: distribute OR', () => {157function t(expr1: string, expr2: string, expected: string | undefined): void {158const e1 = ContextKeyExpr.deserialize(expr1);159const e2 = ContextKeyExpr.deserialize(expr2);160const actual = ContextKeyExpr.and(e1, e2)?.serialize();161assert.strictEqual(actual, expected);162}163t('a', 'b', 'a && b');164t('a || b', 'c', 'a && c || b && c');165t('a || b', 'c || d', 'a && c || a && d || b && c || b && d');166t('a || b', 'c && d', 'a && c && d || b && c && d');167t('a || b', 'c && d || e', 'a && e || b && e || a && c && d || b && c && d');168});169170test('ContextKeyInExpr', () => {171const ainb = ContextKeyExpr.deserialize('a in b')!;172assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), true);173assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), true);174assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), false);175assert.strictEqual(ainb.evaluate(createContext({ 'a': 3 })), false);176assert.strictEqual(ainb.evaluate(createContext({ 'a': 3, 'b': null })), false);177assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), true);178assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), false);179assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': {} })), false);180assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), true);181assert.strictEqual(ainb.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), true);182assert.strictEqual(ainb.evaluate(createContext({ 'a': 'prototype', 'b': {} })), false);183});184185test('ContextKeyNotInExpr', () => {186const aNotInB = ContextKeyExpr.deserialize('a not in b')!;187assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [3, 2, 1] })), false);188assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [1, 2, 3] })), false);189assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': [1, 2] })), true);190assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3 })), true);191assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 3, 'b': null })), true);192assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': ['x'] })), false);193assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': ['y'] })), true);194assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': {} })), true);195assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': { 'x': false } })), false);196assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'x', 'b': { 'x': true } })), false);197assert.strictEqual(aNotInB.evaluate(createContext({ 'a': 'prototype', 'b': {} })), true);198});199200test('issue #106524: distributing AND should normalize', () => {201const actual = ContextKeyExpr.and(202ContextKeyExpr.or(203ContextKeyExpr.has('a'),204ContextKeyExpr.has('b')205),206ContextKeyExpr.has('c')207);208const expected = ContextKeyExpr.or(209ContextKeyExpr.and(210ContextKeyExpr.has('a'),211ContextKeyExpr.has('c')212),213ContextKeyExpr.and(214ContextKeyExpr.has('b'),215ContextKeyExpr.has('c')216)217);218assert.strictEqual(actual!.equals(expected!), true);219});220221test('issue #129625: Removes duplicated terms in OR expressions', () => {222const expr = ContextKeyExpr.or(223ContextKeyExpr.has('A'),224ContextKeyExpr.has('B'),225ContextKeyExpr.has('A')226)!;227assert.strictEqual(expr.serialize(), 'A || B');228});229230test('Resolves true constant OR expressions', () => {231const expr = ContextKeyExpr.or(232ContextKeyExpr.has('A'),233ContextKeyExpr.not('A')234)!;235assert.strictEqual(expr.serialize(), 'true');236});237238test('Resolves false constant AND expressions', () => {239const expr = ContextKeyExpr.and(240ContextKeyExpr.has('A'),241ContextKeyExpr.not('A')242)!;243assert.strictEqual(expr.serialize(), 'false');244});245246test('issue #129625: Removes duplicated terms in AND expressions', () => {247const expr = ContextKeyExpr.and(248ContextKeyExpr.has('A'),249ContextKeyExpr.has('B'),250ContextKeyExpr.has('A')251)!;252assert.strictEqual(expr.serialize(), 'A && B');253});254255test('issue #129625: Remove duplicated terms when negating', () => {256const expr = ContextKeyExpr.and(257ContextKeyExpr.has('A'),258ContextKeyExpr.or(259ContextKeyExpr.has('B1'),260ContextKeyExpr.has('B2'),261)262)!;263assert.strictEqual(expr.serialize(), 'A && B1 || A && B2');264assert.strictEqual(expr.negate()!.serialize(), '!A || !A && !B1 || !A && !B2 || !B1 && !B2');265assert.strictEqual(expr.negate()!.negate()!.serialize(), 'A && B1 || A && B2');266assert.strictEqual(expr.negate()!.negate()!.negate()!.serialize(), '!A || !A && !B1 || !A && !B2 || !B1 && !B2');267});268269test('issue #129625: remove redundant terms in OR expressions', () => {270function strImplies(p0: string, q0: string): boolean {271const p = ContextKeyExpr.deserialize(p0)!;272const q = ContextKeyExpr.deserialize(q0)!;273return implies(p, q);274}275assert.strictEqual(strImplies('a && b', 'a'), true);276assert.strictEqual(strImplies('a', 'a && b'), false);277});278279test('implies', () => {280function strImplies(p0: string, q0: string): boolean {281const p = ContextKeyExpr.deserialize(p0)!;282const q = ContextKeyExpr.deserialize(q0)!;283return implies(p, q);284}285assert.strictEqual(strImplies('a', 'a'), true);286assert.strictEqual(strImplies('a', 'a || b'), true);287assert.strictEqual(strImplies('a', 'a && b'), false);288assert.strictEqual(strImplies('a', 'a && b || a && c'), false);289assert.strictEqual(strImplies('a && b', 'a'), true);290assert.strictEqual(strImplies('a && b', 'b'), true);291assert.strictEqual(strImplies('a && b', 'a && b || c'), true);292assert.strictEqual(strImplies('a || b', 'a || c'), false);293assert.strictEqual(strImplies('a || b', 'a || b'), true);294assert.strictEqual(strImplies('a && b', 'a && b'), true);295assert.strictEqual(strImplies('a || b', 'a || b || c'), true);296assert.strictEqual(strImplies('c && a && b', 'c && a'), true);297});298299test('Greater, GreaterEquals, Smaller, SmallerEquals evaluate', () => {300function checkEvaluate(expr: string, ctx: any, expected: any): void {301const _expr = ContextKeyExpr.deserialize(expr)!;302assert.strictEqual(_expr.evaluate(createContext(ctx)), expected);303}304305checkEvaluate('a > 1', {}, false);306checkEvaluate('a > 1', { a: 0 }, false);307checkEvaluate('a > 1', { a: 1 }, false);308checkEvaluate('a > 1', { a: 2 }, true);309checkEvaluate('a > 1', { a: '0' }, false);310checkEvaluate('a > 1', { a: '1' }, false);311checkEvaluate('a > 1', { a: '2' }, true);312checkEvaluate('a > 1', { a: 'a' }, false);313314checkEvaluate('a > 10', { a: 2 }, false);315checkEvaluate('a > 10', { a: 11 }, true);316checkEvaluate('a > 10', { a: '11' }, true);317checkEvaluate('a > 10', { a: '2' }, false);318checkEvaluate('a > 10', { a: '11' }, true);319320checkEvaluate('a > 1.1', { a: 1 }, false);321checkEvaluate('a > 1.1', { a: 2 }, true);322checkEvaluate('a > 1.1', { a: 11 }, true);323checkEvaluate('a > 1.1', { a: '1.1' }, false);324checkEvaluate('a > 1.1', { a: '2' }, true);325checkEvaluate('a > 1.1', { a: '11' }, true);326327checkEvaluate('a > b', { a: 'b' }, false);328checkEvaluate('a > b', { a: 'c' }, false);329checkEvaluate('a > b', { a: 1000 }, false);330331checkEvaluate('a >= 2', { a: '1' }, false);332checkEvaluate('a >= 2', { a: '2' }, true);333checkEvaluate('a >= 2', { a: '3' }, true);334335checkEvaluate('a < 2', { a: '1' }, true);336checkEvaluate('a < 2', { a: '2' }, false);337checkEvaluate('a < 2', { a: '3' }, false);338339checkEvaluate('a <= 2', { a: '1' }, true);340checkEvaluate('a <= 2', { a: '2' }, true);341checkEvaluate('a <= 2', { a: '3' }, false);342});343344test('Greater, GreaterEquals, Smaller, SmallerEquals negate', () => {345function checkNegate(expr: string, expected: string): void {346const a = ContextKeyExpr.deserialize(expr)!;347const b = a.negate();348assert.strictEqual(b.serialize(), expected);349}350351checkNegate('a > 1', 'a <= 1');352checkNegate('a > 1.1', 'a <= 1.1');353checkNegate('a > b', 'a <= b');354355checkNegate('a >= 1', 'a < 1');356checkNegate('a >= 1.1', 'a < 1.1');357checkNegate('a >= b', 'a < b');358359checkNegate('a < 1', 'a >= 1');360checkNegate('a < 1.1', 'a >= 1.1');361checkNegate('a < b', 'a >= b');362363checkNegate('a <= 1', 'a > 1');364checkNegate('a <= 1.1', 'a > 1.1');365checkNegate('a <= b', 'a > b');366});367368test('issue #111899: context keys can use `<` or `>` ', () => {369const actual = ContextKeyExpr.deserialize('editorTextFocus && vim.active && vim.use<C-r>')!;370assert.ok(actual.equals(371ContextKeyExpr.and(372ContextKeyExpr.has('editorTextFocus'),373ContextKeyExpr.has('vim.active'),374ContextKeyExpr.has('vim.use<C-r>'),375)!376));377});378});379380381