Merge branch 'develop' into travis/error-states

This commit is contained in:
Travis Ralston 2021-04-23 15:53:37 -06:00
commit 82119ad595
36 changed files with 1724 additions and 191 deletions

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Singleflight} from "../src/utils/Singleflight";
import {Singleflight} from "../../src/utils/Singleflight";
describe('Singleflight', () => {
afterEach(() => {

294
test/utils/arrays-test.ts Normal file
View file

@ -0,0 +1,294 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {
arrayDiff,
arrayFastClone,
arrayFastResample,
arrayHasDiff,
arrayHasOrderChange,
arrayMerge,
arraySeed,
arrayUnion,
ArrayUtil,
GroupedArray,
} from "../../src/utils/arrays";
import {objectFromEntries} from "../../src/utils/objects";
function expectSample(i: number, input: number[], expected: number[]) {
console.log(`Resample case index: ${i}`); // for debugging test failures
const result = arrayFastResample(input, expected.length);
expect(result).toBeDefined();
expect(result).toHaveLength(expected.length);
expect(result).toEqual(expected);
}
describe('arrays', () => {
describe('arrayFastResample', () => {
it('should downsample', () => {
[
{input: [1, 2, 3, 4, 5], output: [1, 4]}, // Odd -> Even
{input: [1, 2, 3, 4, 5], output: [1, 3, 5]}, // Odd -> Odd
{input: [1, 2, 3, 4], output: [1, 2, 3]}, // Even -> Odd
{input: [1, 2, 3, 4], output: [1, 3]}, // Even -> Even
].forEach((c, i) => expectSample(i, c.input, c.output));
});
it('should upsample', () => {
[
{input: [1, 2, 3], output: [1, 1, 2, 2, 3, 3]}, // Odd -> Even
{input: [1, 2, 3], output: [1, 1, 2, 2, 3]}, // Odd -> Odd
{input: [1, 2], output: [1, 1, 1, 2, 2]}, // Even -> Odd
{input: [1, 2], output: [1, 1, 1, 2, 2, 2]}, // Even -> Even
].forEach((c, i) => expectSample(i, c.input, c.output));
});
it('should maintain sample', () => {
[
{input: [1, 2, 3], output: [1, 2, 3]}, // Odd
{input: [1, 2], output: [1, 2]}, // Even
].forEach((c, i) => expectSample(i, c.input, c.output));
});
});
describe('arraySeed', () => {
it('should create an array of given length', () => {
const val = 1;
const output = [val, val, val];
const result = arraySeed(val, output.length);
expect(result).toBeDefined();
expect(result).toHaveLength(output.length);
expect(result).toEqual(output);
});
it('should maintain pointers', () => {
const val = {}; // this works because `{} !== {}`, which is what toEqual checks
const output = [val, val, val];
const result = arraySeed(val, output.length);
expect(result).toBeDefined();
expect(result).toHaveLength(output.length);
expect(result).toEqual(output);
});
});
describe('arrayFastClone', () => {
it('should break pointer reference on source array', () => {
const val = {}; // we'll test to make sure the values maintain pointers too
const input = [val, val, val];
const result = arrayFastClone(input);
expect(result).toBeDefined();
expect(result).toHaveLength(input.length);
expect(result).toEqual(input); // we want the array contents to match...
expect(result).not.toBe(input); // ... but be a different reference
});
});
describe('arrayHasOrderChange', () => {
it('should flag true on B ordering difference', () => {
const a = [1, 2, 3];
const b = [3, 2, 1];
const result = arrayHasOrderChange(a, b);
expect(result).toBe(true);
});
it('should flag false on no ordering difference', () => {
const a = [1, 2, 3];
const b = [1, 2, 3];
const result = arrayHasOrderChange(a, b);
expect(result).toBe(false);
});
it('should flag true on A length > B length', () => {
const a = [1, 2, 3, 4];
const b = [1, 2, 3];
const result = arrayHasOrderChange(a, b);
expect(result).toBe(true);
});
it('should flag true on A length < B length', () => {
const a = [1, 2, 3];
const b = [1, 2, 3, 4];
const result = arrayHasOrderChange(a, b);
expect(result).toBe(true);
});
});
describe('arrayHasDiff', () => {
it('should flag true on A length > B length', () => {
const a = [1, 2, 3, 4];
const b = [1, 2, 3];
const result = arrayHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag true on A length < B length', () => {
const a = [1, 2, 3];
const b = [1, 2, 3, 4];
const result = arrayHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag true on element differences', () => {
const a = [1, 2, 3];
const b = [4, 5, 6];
const result = arrayHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag false if same but order different', () => {
const a = [1, 2, 3];
const b = [3, 1, 2];
const result = arrayHasDiff(a, b);
expect(result).toBe(false);
});
it('should flag false if same', () => {
const a = [1, 2, 3];
const b = [1, 2, 3];
const result = arrayHasDiff(a, b);
expect(result).toBe(false);
});
});
describe('arrayDiff', () => {
it('should see added from A->B', () => {
const a = [1, 2, 3];
const b = [1, 2, 3, 4];
const result = arrayDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(0);
expect(result.added).toEqual([4]);
});
it('should see removed from A->B', () => {
const a = [1, 2, 3];
const b = [1, 2];
const result = arrayDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(1);
expect(result.removed).toEqual([3]);
});
it('should see added and removed in the same set', () => {
const a = [1, 2, 3];
const b = [1, 2, 4]; // note diff
const result = arrayDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(1);
expect(result.added).toEqual([4]);
expect(result.removed).toEqual([3]);
});
});
describe('arrayUnion', () => {
it('should return a union', () => {
const a = [1, 2, 3];
const b = [1, 2, 4]; // note diff
const result = arrayUnion(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
expect(result).toEqual([1, 2]);
});
it('should return an empty array on no matches', () => {
const a = [1, 2, 3];
const b = [4, 5, 6];
const result = arrayUnion(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
});
describe('arrayMerge', () => {
it('should merge 3 arrays with deduplication', () => {
const a = [1, 2, 3];
const b = [1, 2, 4, 5]; // note missing 3
const c = [6, 7, 8, 9];
const result = arrayMerge(a, b, c);
expect(result).toBeDefined();
expect(result).toHaveLength(9);
expect(result).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
it('should deduplicate a single array', () => {
// dev note: this is technically an edge case, but it is described behaviour if the
// function is only provided one function (it'll merge the array against itself)
const a = [1, 1, 2, 2, 3, 3];
const result = arrayMerge(a);
expect(result).toBeDefined();
expect(result).toHaveLength(3);
expect(result).toEqual([1, 2, 3]);
});
});
describe('ArrayUtil', () => {
it('should maintain the pointer to the given array', () => {
const input = [1, 2, 3];
const result = new ArrayUtil(input);
expect(result.value).toBe(input);
});
it('should group appropriately', () => {
const input = [['a', 1], ['b', 2], ['c', 3], ['a', 4], ['a', 5], ['b', 6]];
const output = {
'a': [['a', 1], ['a', 4], ['a', 5]],
'b': [['b', 2], ['b', 6]],
'c': [['c', 3]],
};
const result = new ArrayUtil(input).groupBy(p => p[0]);
expect(result).toBeDefined();
expect(result.value).toBeDefined();
const asObject = objectFromEntries(result.value.entries());
expect(asObject).toMatchObject(output);
});
});
describe('GroupedArray', () => {
it('should maintain the pointer to the given map', () => {
const input = new Map([
['a', [1, 2, 3]],
['b', [7, 8, 9]],
['c', [4, 5, 6]],
]);
const result = new GroupedArray(input);
expect(result.value).toBe(input);
});
it('should ordering by the provided key order', () => {
const input = new Map([
['a', [1, 2, 3]],
['b', [7, 8, 9]], // note counting diff
['c', [4, 5, 6]],
]);
const output = [4, 5, 6, 1, 2, 3, 7, 8, 9];
const keyOrder = ['c', 'a', 'b']; // note weird order to cause the `output` to be strange
const result = new GroupedArray(input).orderBy(keyOrder);
expect(result).toBeDefined();
expect(result.value).toBeDefined();
expect(result.value).toEqual(output);
});
});
});

67
test/utils/enums-test.ts Normal file
View file

@ -0,0 +1,67 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {getEnumValues, isEnumValue} from "../../src/utils/enums";
enum TestStringEnum {
First = "__first__",
Second = "__second__",
}
enum TestNumberEnum {
FirstKey = 10,
SecondKey = 20,
}
describe('enums', () => {
describe('getEnumValues', () => {
it('should work on string enums', () => {
const result = getEnumValues(TestStringEnum);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
expect(result).toEqual(['__first__', '__second__']);
});
it('should work on number enums', () => {
const result = getEnumValues(TestNumberEnum);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
expect(result).toEqual([10, 20]);
});
});
describe('isEnumValue', () => {
it('should return true on values in a string enum', () => {
const result = isEnumValue(TestStringEnum, '__first__');
expect(result).toBe(true);
});
it('should return false on values not in a string enum', () => {
const result = isEnumValue(TestStringEnum, 'not a value');
expect(result).toBe(false);
});
it('should return true on values in a number enum', () => {
const result = isEnumValue(TestNumberEnum, 10);
expect(result).toBe(true);
});
it('should return false on values not in a number enum', () => {
const result = isEnumValue(TestStringEnum, 99);
expect(result).toBe(false);
});
});
});

View file

@ -0,0 +1,77 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {iterableDiff, iterableUnion} from "../../src/utils/iterables";
describe('iterables', () => {
describe('iterableUnion', () => {
it('should return a union', () => {
const a = [1, 2, 3];
const b = [1, 2, 4]; // note diff
const result = iterableUnion(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(2);
expect(result).toEqual([1, 2]);
});
it('should return an empty array on no matches', () => {
const a = [1, 2, 3];
const b = [4, 5, 6];
const result = iterableUnion(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
});
describe('iterableDiff', () => {
it('should see added from A->B', () => {
const a = [1, 2, 3];
const b = [1, 2, 3, 4];
const result = iterableDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(0);
expect(result.added).toEqual([4]);
});
it('should see removed from A->B', () => {
const a = [1, 2, 3];
const b = [1, 2];
const result = iterableDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(1);
expect(result.removed).toEqual([3]);
});
it('should see added and removed in the same set', () => {
const a = [1, 2, 3];
const b = [1, 2, 4]; // note diff
const result = iterableDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(1);
expect(result.added).toEqual([4]);
expect(result.removed).toEqual([3]);
});
});
});

245
test/utils/maps-test.ts Normal file
View file

@ -0,0 +1,245 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {EnhancedMap, mapDiff, mapKeyChanges} from "../../src/utils/maps";
describe('maps', () => {
describe('mapDiff', () => {
it('should indicate no differences when the pointers are the same', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const result = mapDiff(a, a);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
});
it('should indicate no differences when there are none', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2], [3, 3]]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
});
it('should indicate added properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(0);
expect(result.added).toEqual([4]);
});
it('should indicate removed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2]]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(1);
expect(result.changed).toHaveLength(0);
expect(result.removed).toEqual([3]);
});
it('should indicate changed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2], [3, 4]]); // note change
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(1);
expect(result.changed).toEqual([3]);
});
it('should indicate changed, added, and removed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 8], [4, 4]]); // note change
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(1);
expect(result.changed).toHaveLength(1);
expect(result.added).toEqual([4]);
expect(result.removed).toEqual([3]);
expect(result.changed).toEqual([2]);
});
it('should indicate changes for difference in pointers', () => {
const a = new Map([[1, {}]]); // {} always creates a new object
const b = new Map([[1, {}]]);
const result = mapDiff(a, b);
expect(result).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toHaveLength(1);
expect(result.changed).toEqual([1]);
});
});
describe('mapKeyChanges', () => {
it('should indicate no changes for unchanged pointers', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const result = mapKeyChanges(a, a);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
it('should indicate no changes for unchanged maps with different pointers', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2], [3, 3]]);
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
it('should indicate changes for added properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
expect(result).toEqual([4]);
});
it('should indicate changes for removed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
const b = new Map([[1, 1], [2, 2], [3, 3]]);
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
expect(result).toEqual([4]);
});
it('should indicate changes for changed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3], [4, 4]]);
const b = new Map([[1, 1], [2, 2], [3, 3], [4, 55]]);
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
expect(result).toEqual([4]);
});
it('should indicate changes for properties with different pointers', () => {
const a = new Map([[1, {}]]); // {} always creates a new object
const b = new Map([[1, {}]]);
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(1);
expect(result).toEqual([1]);
});
it('should indicate changes for changed, added, and removed properties', () => {
const a = new Map([[1, 1], [2, 2], [3, 3]]);
const b = new Map([[1, 1], [2, 8], [4, 4]]); // note change
const result = mapKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(3);
expect(result).toEqual([3, 4, 2]); // order irrelevant, but the test cares
});
});
describe('EnhancedMap', () => {
// Most of these tests will make sure it implements the Map<K, V> class
it('should be empty by default', () => {
const result = new EnhancedMap();
expect(result.size).toBe(0);
});
it('should use the provided entries', () => {
const obj = {a: 1, b: 2};
const result = new EnhancedMap(Object.entries(obj));
expect(result.size).toBe(2);
expect(result.get('a')).toBe(1);
expect(result.get('b')).toBe(2);
});
it('should create keys if they do not exist', () => {
const key = 'a';
const val = {}; // we'll check pointers
const result = new EnhancedMap<string, any>();
expect(result.size).toBe(0);
let get = result.getOrCreate(key, val);
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
get = result.getOrCreate(key, 44); // specifically change `val`
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
get = result.get(key); // use the base class function
expect(get).toBeDefined();
expect(get).toBe(val);
expect(result.size).toBe(1);
});
it('should proxy remove to delete and return it', () => {
const val = {};
const result = new EnhancedMap<string, any>();
result.set('a', val);
expect(result.size).toBe(1);
const removed = result.remove('a');
expect(result.size).toBe(0);
expect(removed).toBeDefined();
expect(removed).toBe(val);
});
it('should support removing unknown keys', () => {
const val = {};
const result = new EnhancedMap<string, any>();
result.set('a', val);
expect(result.size).toBe(1);
const removed = result.remove('not-a');
expect(result.size).toBe(1);
expect(removed).not.toBeDefined();
});
});
});

163
test/utils/numbers-test.ts Normal file
View file

@ -0,0 +1,163 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {clamp, defaultNumber, percentageOf, percentageWithin, sum} from "../../src/utils/numbers";
describe('numbers', () => {
describe('defaultNumber', () => {
it('should use the default when the input is not a number', () => {
const def = 42;
let result = defaultNumber(null, def);
expect(result).toBe(def);
result = defaultNumber(undefined, def);
expect(result).toBe(def);
result = defaultNumber(Number.NaN, def);
expect(result).toBe(def);
});
it('should use the number when it is a number', () => {
const input = 24;
const def = 42;
const result = defaultNumber(input, def);
expect(result).toBe(input);
});
});
describe('clamp', () => {
it('should clamp high numbers', () => {
const input = 101;
const min = 0;
const max = 100;
const result = clamp(input, min, max);
expect(result).toBe(max);
});
it('should clamp low numbers', () => {
const input = -1;
const min = 0;
const max = 100;
const result = clamp(input, min, max);
expect(result).toBe(min);
});
it('should not clamp numbers in range', () => {
const input = 50;
const min = 0;
const max = 100;
const result = clamp(input, min, max);
expect(result).toBe(input);
});
it('should clamp floats', () => {
const min = -0.10;
const max = +0.10;
let result = clamp(-1.2, min, max);
expect(result).toBe(min);
result = clamp(1.2, min, max);
expect(result).toBe(max);
result = clamp(0.02, min, max);
expect(result).toBe(0.02);
});
});
describe('sum', () => {
it('should sum', () => { // duh
const result = sum(1, 2, 1, 4);
expect(result).toBe(8);
});
});
describe('percentageWithin', () => {
it('should work within 0-100', () => {
const result = percentageWithin(0.4, 0, 100);
expect(result).toBe(40);
});
it('should work within 0-100 when pct > 1', () => {
const result = percentageWithin(1.4, 0, 100);
expect(result).toBe(140);
});
it('should work within 0-100 when pct < 0', () => {
const result = percentageWithin(-1.4, 0, 100);
expect(result).toBe(-140);
});
it('should work with ranges other than 0-100', () => {
const result = percentageWithin(0.4, 10, 20);
expect(result).toBe(14);
});
it('should work with ranges other than 0-100 when pct > 1', () => {
const result = percentageWithin(1.4, 10, 20);
expect(result).toBe(24);
});
it('should work with ranges other than 0-100 when pct < 0', () => {
const result = percentageWithin(-1.4, 10, 20);
expect(result).toBe(-4);
});
it('should work with floats', () => {
const result = percentageWithin(0.4, 10.2, 20.4);
expect(result).toBe(14.28);
});
});
// These are the inverse of percentageWithin
describe('percentageOf', () => {
it('should work within 0-100', () => {
const result = percentageOf(40, 0, 100);
expect(result).toBe(0.4);
});
it('should work within 0-100 when val > 100', () => {
const result = percentageOf(140, 0, 100);
expect(result).toBe(1.40);
});
it('should work within 0-100 when val < 0', () => {
const result = percentageOf(-140, 0, 100);
expect(result).toBe(-1.40);
});
it('should work with ranges other than 0-100', () => {
const result = percentageOf(14, 10, 20);
expect(result).toBe(0.4);
});
it('should work with ranges other than 0-100 when val > 100', () => {
const result = percentageOf(24, 10, 20);
expect(result).toBe(1.4);
});
it('should work with ranges other than 0-100 when val < 0', () => {
const result = percentageOf(-4, 10, 20);
expect(result).toBe(-1.4);
});
it('should work with floats', () => {
const result = percentageOf(14.28, 10.2, 20.4);
expect(result).toBe(0.4);
});
});
});

262
test/utils/objects-test.ts Normal file
View file

@ -0,0 +1,262 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {
objectClone,
objectDiff,
objectExcluding,
objectFromEntries,
objectHasDiff,
objectKeyChanges,
objectShallowClone,
objectWithOnly,
} from "../../src/utils/objects";
describe('objects', () => {
describe('objectExcluding', () => {
it('should exclude the given properties', () => {
const input = {hello: "world", test: true};
const output = {hello: "world"};
const props = ["test", "doesnotexist"]; // we also make sure it doesn't explode on missing props
const result = objectExcluding(input, <any>props); // any is to test the missing prop
expect(result).toBeDefined();
expect(result).toMatchObject(output);
});
});
describe('objectWithOnly', () => {
it('should exclusively use the given properties', () => {
const input = {hello: "world", test: true};
const output = {hello: "world"};
const props = ["hello", "doesnotexist"]; // we also make sure it doesn't explode on missing props
const result = objectWithOnly(input, <any>props); // any is to test the missing prop
expect(result).toBeDefined();
expect(result).toMatchObject(output);
});
});
describe('objectShallowClone', () => {
it('should create a new object', () => {
const input = {test: 1};
const result = objectShallowClone(input);
expect(result).toBeDefined();
expect(result).not.toBe(input);
expect(result).toMatchObject(input);
});
it('should only clone the top level properties', () => {
const input = {a: 1, b: {c: 2}};
const result = objectShallowClone(input);
expect(result).toBeDefined();
expect(result).toMatchObject(input);
expect(result.b).toBe(input.b);
});
it('should support custom clone functions', () => {
const input = {a: 1, b: 2};
const output = {a: 4, b: 8};
const result = objectShallowClone(input, (k, v) => {
// XXX: inverted expectation for ease of assertion
expect(Object.keys(input)).toContain(k);
return v * 4;
});
expect(result).toBeDefined();
expect(result).toMatchObject(output);
});
});
describe('objectHasDiff', () => {
it('should return false for the same pointer', () => {
const a = {};
const result = objectHasDiff(a, a);
expect(result).toBe(false);
});
it('should return true if keys for A > keys for B', () => {
const a = {a: 1, b: 2};
const b = {a: 1};
const result = objectHasDiff(a, b);
expect(result).toBe(true);
});
it('should return true if keys for A < keys for B', () => {
const a = {a: 1};
const b = {a: 1, b: 2};
const result = objectHasDiff(a, b);
expect(result).toBe(true);
});
it('should return false if the objects are the same but different pointers', () => {
const a = {a: 1, b: 2};
const b = {a: 1, b: 2};
const result = objectHasDiff(a, b);
expect(result).toBe(false);
});
it('should consider pointers when testing values', () => {
const a = {a: {}, b: 2}; // `{}` is shorthand for `new Object()`
const b = {a: {}, b: 2};
const result = objectHasDiff(a, b);
expect(result).toBe(true); // even though the keys are the same, the value pointers vary
});
});
describe('objectDiff', () => {
it('should return empty sets for the same object', () => {
const a = {a: 1, b: 2};
const b = {a: 1, b: 2};
const result = objectDiff(a, b);
expect(result).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(0);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
});
it('should return empty sets for the same object pointer', () => {
const a = {a: 1, b: 2};
const result = objectDiff(a, a);
expect(result).toBeDefined();
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(0);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
});
it('should indicate when property changes are made', () => {
const a = {a: 1, b: 2};
const b = {a: 11, b: 2};
const result = objectDiff(a, b);
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(1);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(0);
expect(result.changed).toEqual(['a']);
});
it('should indicate when properties are added', () => {
const a = {a: 1, b: 2};
const b = {a: 1, b: 2, c: 3};
const result = objectDiff(a, b);
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(0);
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(0);
expect(result.added).toEqual(['c']);
});
it('should indicate when properties are removed', () => {
const a = {a: 1, b: 2};
const b = {a: 1};
const result = objectDiff(a, b);
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(0);
expect(result.added).toHaveLength(0);
expect(result.removed).toHaveLength(1);
expect(result.removed).toEqual(['b']);
});
it('should indicate when multiple aspects change', () => {
const a = {a: 1, b: 2, c: 3};
const b: (typeof a | {d: number}) = {a: 1, b: 22, d: 4};
const result = objectDiff(a, b);
expect(result.changed).toBeDefined();
expect(result.added).toBeDefined();
expect(result.removed).toBeDefined();
expect(result.changed).toHaveLength(1);
expect(result.added).toHaveLength(1);
expect(result.removed).toHaveLength(1);
expect(result.changed).toEqual(['b']);
expect(result.removed).toEqual(['c']);
expect(result.added).toEqual(['d']);
});
});
describe('objectKeyChanges', () => {
it('should return an empty set if no properties changed', () => {
const a = {a: 1, b: 2};
const b = {a: 1, b: 2};
const result = objectKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
it('should return an empty set if no properties changed for the same pointer', () => {
const a = {a: 1, b: 2};
const result = objectKeyChanges(a, a);
expect(result).toBeDefined();
expect(result).toHaveLength(0);
});
it('should return properties which were changed, added, or removed', () => {
const a = {a: 1, b: 2, c: 3};
const b: (typeof a | {d: number}) = {a: 1, b: 22, d: 4};
const result = objectKeyChanges(a, b);
expect(result).toBeDefined();
expect(result).toHaveLength(3);
expect(result).toEqual(['c', 'd', 'b']); // order isn't important, but the test cares
});
});
describe('objectClone', () => {
it('should deep clone an object', () => {
const a = {
hello: "world",
test: {
another: "property",
test: 42,
third: {
prop: true,
},
},
};
const result = objectClone(a);
expect(result).toBeDefined();
expect(result).not.toBe(a);
expect(result).toMatchObject(a);
expect(result.test).not.toBe(a.test);
expect(result.test.third).not.toBe(a.test.third);
});
});
describe('objectFromEntries', () => {
it('should create an object from an array of entries', () => {
const output = {a: 1, b: 2, c: 3};
const result = objectFromEntries(Object.entries(output));
expect(result).toBeDefined();
expect(result).toMatchObject(output);
});
it('should maintain pointers in values', () => {
const output = {a: {}, b: 2, c: 3};
const result = objectFromEntries(Object.entries(output));
expect(result).toBeDefined();
expect(result).toMatchObject(output);
expect(result['a']).toBe(output.a);
});
});
});

56
test/utils/sets-test.ts Normal file
View file

@ -0,0 +1,56 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
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 {setHasDiff} from "../../src/utils/sets";
describe('sets', () => {
describe('setHasDiff', () => {
it('should flag true on A length > B length', () => {
const a = new Set([1, 2, 3, 4]);
const b = new Set([1, 2, 3]);
const result = setHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag true on A length < B length', () => {
const a = new Set([1, 2, 3]);
const b = new Set([1, 2, 3, 4]);
const result = setHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag true on element differences', () => {
const a = new Set([1, 2, 3]);
const b = new Set([4, 5, 6]);
const result = setHasDiff(a, b);
expect(result).toBe(true);
});
it('should flag false if same but order different', () => {
const a = new Set([1, 2, 3]);
const b = new Set([3, 1, 2]);
const result = setHasDiff(a, b);
expect(result).toBe(false);
});
it('should flag false if same', () => {
const a = new Set([1, 2, 3]);
const b = new Set([1, 2, 3]);
const result = setHasDiff(a, b);
expect(result).toBe(false);
});
});
});