From 4870f553a16a2937624fa2f7b775f6e7f6b6f098 Mon Sep 17 00:00:00 2001 From: esrakartalOpt Date: Tue, 28 Jan 2025 17:24:54 -0600 Subject: [PATCH 1/3] [FSSDK-11095] rewrite condition_tree_evaluator tests in Typescript --- .../condition_tree_evaluator/index.spec.ts | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 lib/core/condition_tree_evaluator/index.spec.ts diff --git a/lib/core/condition_tree_evaluator/index.spec.ts b/lib/core/condition_tree_evaluator/index.spec.ts new file mode 100644 index 000000000..10da5de40 --- /dev/null +++ b/lib/core/condition_tree_evaluator/index.spec.ts @@ -0,0 +1,230 @@ +/**************************************************************************** + * Copyright 2018, 2020-2021, Optimizely, Inc. and contributors * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); * + * you may not use this file except in compliance with the License. * + * You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + ***************************************************************************/ +import { describe, it, vi, expect } from 'vitest'; + +import * as conditionTreeEvaluator from '.'; + +const conditionA = { + name: 'browser_type', + value: 'safari', + type: 'custom_attribute', +}; +const conditionB = { + name: 'device_model', + value: 'iphone6', + type: 'custom_attribute', +}; +const conditionC = { + name: 'location', + match: 'exact', + type: 'custom_attribute', + value: 'CA', +}; + +describe.only('lib/core/condition_tree_evaluator', function() { + describe('APIs', function() { + describe('evaluate', function() { + it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return true; + }) + ).toBe(true); + }); + + it('should return false for a leaf condition when the leaf condition evaluator returns false', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return false; + }) + ).toBe(false) + }); + + describe('and evaluation', function() { + it('should return true when ALL conditions evaluate to true', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return true; + }) + ).toBe(true); + }); + + it('should return false if one condition evaluates to false', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return false when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + it('should return false when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB, conditionC], leafEvaluator)).toBe(false); + }); + }); + }); + + describe('or evaluation', function() { + it('should return true if any condition evaluates to true', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false) + .mockImplementationOnce(() => true); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return false if all conditions evaluate to false', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return true when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return null when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return true when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB, conditionC], leafEvaluator)).toBe(true); + }); + }); + }); + + describe('not evaluation', function() { + it('should return true if the condition evaluates to false', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return false; + }) + ).toBe(true); + }); + + it('should return false if the condition evaluates to true', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionB], function() { + return true; + }) + ).toBe(false); + }); + + it('should return the result of negating the first condition, and ignore any additional conditions', function() { + let result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id) { + return String(id) === '1'; + }); + expect(result).toBe(false); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id) { + return String(id) === '2'; + }); + expect(result).toBe(true); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '3'], function(id) { + return String(id) === '1' ? null : String(id) === '3'; + }); + expect(result).toBeNull(); + }); + + describe('null handling', function() { + it('should return null when operand evaluates to null', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when there are no operands', function() { + expect( + conditionTreeEvaluator.evaluate(['not'], function() { + return null; + }) + ).toBeNull(); + }); + }); + }); + + describe('implicit operator', function() { + it('should behave like an "or" operator when the first item in the array is not a recognized operator', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate([conditionA, conditionB], leafEvaluator)).toBe(true); + expect( + conditionTreeEvaluator.evaluate([conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + }); + }); + }); +}); From d29cc0fd49af2f63f4e5f9252bed9dc19bb82e74 Mon Sep 17 00:00:00 2001 From: esrakartalOpt Date: Tue, 28 Jan 2025 17:35:24 -0600 Subject: [PATCH 2/3] Remove only tag --- lib/core/condition_tree_evaluator/index.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/condition_tree_evaluator/index.spec.ts b/lib/core/condition_tree_evaluator/index.spec.ts index 10da5de40..77cca1623 100644 --- a/lib/core/condition_tree_evaluator/index.spec.ts +++ b/lib/core/condition_tree_evaluator/index.spec.ts @@ -34,7 +34,7 @@ const conditionC = { value: 'CA', }; -describe.only('lib/core/condition_tree_evaluator', function() { +describe('lib/core/condition_tree_evaluator', function() { describe('APIs', function() { describe('evaluate', function() { it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { From a607621183a76f0be2e84bc1babb60df4e8752d1 Mon Sep 17 00:00:00 2001 From: esrakartalOpt Date: Wed, 29 Jan 2025 10:30:17 -0600 Subject: [PATCH 3/3] Implement comments --- .../condition_tree_evaluator/index.spec.ts | 370 +++++++++--------- 1 file changed, 179 insertions(+), 191 deletions(-) diff --git a/lib/core/condition_tree_evaluator/index.spec.ts b/lib/core/condition_tree_evaluator/index.spec.ts index 77cca1623..5afdd0d7d 100644 --- a/lib/core/condition_tree_evaluator/index.spec.ts +++ b/lib/core/condition_tree_evaluator/index.spec.ts @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2018, 2020-2021, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { describe, it, vi, expect } from 'vitest'; import * as conditionTreeEvaluator from '.'; @@ -33,198 +33,186 @@ const conditionC = { type: 'custom_attribute', value: 'CA', }; +describe('evaluate', function() { + it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return true; + }) + ).toBe(true); + }); + + it('should return false for a leaf condition when the leaf condition evaluator returns false', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return false; + }) + ).toBe(false); + }); -describe('lib/core/condition_tree_evaluator', function() { - describe('APIs', function() { - describe('evaluate', function() { - it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { + describe('and evaluation', function() { + it('should return true when ALL conditions evaluate to true', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return true; + }) + ).toBe(true); + }); + + it('should return false if one condition evaluates to false', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { expect( - conditionTreeEvaluator.evaluate(conditionA, function() { - return true; + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return null; }) - ).toBe(true); + ).toBeNull(); + }); + + it('should return null when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBeNull(); }); - it('should return false for a leaf condition when the leaf condition evaluator returns false', function() { + it('should return false when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + it('should return false when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB, conditionC], leafEvaluator)).toBe(false); + }); + }); + }); + + describe('or evaluation', function() { + it('should return true if any condition evaluates to true', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => true); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return false if all conditions evaluate to false', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { expect( - conditionTreeEvaluator.evaluate(conditionA, function() { - return false; + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return null; }) - ).toBe(false) + ).toBeNull(); }); - describe('and evaluation', function() { - it('should return true when ALL conditions evaluate to true', function() { - expect( - conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { - return true; - }) - ).toBe(true); - }); - - it('should return false if one condition evaluates to false', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => false); - expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); - }); - - describe('null handling', function() { - it('should return null when all operands evaluate to null', function() { - expect( - conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { - return null; - }) - ).toBeNull(); - }); - - it('should return null when operands evaluate to trues and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => null); - expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBeNull(); - }); - - it('should return false when operands evaluate to falses and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => false) - .mockImplementationOnce(() => null); - expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); - - leafEvaluator.mockReset(); - leafEvaluator.mockImplementationOnce(() => null) - .mockImplementationOnce(() => false); - expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); - }); - - it('should return false when operands evaluate to trues, falses, and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => false) - .mockImplementationOnce(() => null); - expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB, conditionC], leafEvaluator)).toBe(false); - }); - }); + it('should return true when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); }); - describe('or evaluation', function() { - it('should return true if any condition evaluates to true', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => false) - .mockImplementationOnce(() => true); - expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); - }); - - it('should return false if all conditions evaluate to false', function() { - expect( - conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { - return false; - }) - ).toBe(false); - }); - - describe('null handling', function() { - it('should return null when all operands evaluate to null', function() { - expect( - conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { - return null; - }) - ).toBeNull(); - }); - - it('should return true when operands evaluate to trues and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => null); - expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); - }); - - it('should return null when operands evaluate to falses and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => null) - .mockImplementationOnce(() => false); - expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); - - leafEvaluator.mockReset(); - leafEvaluator.mockImplementationOnce(() => false) - .mockImplementationOnce(() => null); - expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); - }); - - it('should return true when operands evaluate to trues, falses, and nulls', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => null) - .mockImplementationOnce(() => false); - expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB, conditionC], leafEvaluator)).toBe(true); - }); - }); + it('should return null when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); }); - describe('not evaluation', function() { - it('should return true if the condition evaluates to false', function() { - expect( - conditionTreeEvaluator.evaluate(['not', conditionA], function() { - return false; - }) - ).toBe(true); - }); - - it('should return false if the condition evaluates to true', function() { - expect( - conditionTreeEvaluator.evaluate(['not', conditionB], function() { - return true; - }) - ).toBe(false); - }); - - it('should return the result of negating the first condition, and ignore any additional conditions', function() { - let result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id) { - return String(id) === '1'; - }); - expect(result).toBe(false); - result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id) { - return String(id) === '2'; - }); - expect(result).toBe(true); - result = conditionTreeEvaluator.evaluate(['not', '1', '2', '3'], function(id) { - return String(id) === '1' ? null : String(id) === '3'; - }); - expect(result).toBeNull(); - }); - - describe('null handling', function() { - it('should return null when operand evaluates to null', function() { - expect( - conditionTreeEvaluator.evaluate(['not', conditionA], function() { - return null; - }) - ).toBeNull(); - }); - - it('should return null when there are no operands', function() { - expect( - conditionTreeEvaluator.evaluate(['not'], function() { - return null; - }) - ).toBeNull(); - }); - }); + it('should return true when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB, conditionC], leafEvaluator)).toBe(true); + }); + }); + }); + + describe('not evaluation', function() { + it('should return true if the condition evaluates to false', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return false; + }) + ).toBe(true); + }); + + it('should return false if the condition evaluates to true', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionB], function() { + return true; + }) + ).toBe(false); + }); + + it('should return the result of negating the first condition, and ignore any additional conditions', function() { + let result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '1'; + }); + expect(result).toBe(false); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '2'; + }); + expect(result).toBe(true); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '3'], function(id: string) { + return id === '1' ? null : id === '3'; + }); + expect(result).toBeNull(); + }); + + describe('null handling', function() { + it('should return null when operand evaluates to null', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return null; + }) + ).toBeNull(); }); - describe('implicit operator', function() { - it('should behave like an "or" operator when the first item in the array is not a recognized operator', function() { - const leafEvaluator = vi.fn(); - leafEvaluator.mockImplementationOnce(() => true) - .mockImplementationOnce(() => false); - expect(conditionTreeEvaluator.evaluate([conditionA, conditionB], leafEvaluator)).toBe(true); - expect( - conditionTreeEvaluator.evaluate([conditionA, conditionB], function() { - return false; - }) - ).toBe(false); - }); + it('should return null when there are no operands', function() { + expect( + conditionTreeEvaluator.evaluate(['not'], function() { + return null; + }) + ).toBeNull(); }); }); }); + + describe('implicit operator', function() { + it('should behave like an "or" operator when the first item in the array is not a recognized operator', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate([conditionA, conditionB], leafEvaluator)).toBe(true); + expect( + conditionTreeEvaluator.evaluate([conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + }); });