Files
Michael Telatynski 899cdb0e1d Switch from Jest to Vitest (#5131)
* Skip unwritten tests

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Tidy jest fake timers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unnecessary sessionStorage mock

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve types

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve async assertions

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve error assertions

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Improve object assertions

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove assertion testing unclear mock

This test failed when ran individually, same as after the clearAllMocks call

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Avoid awaiting non-thenables

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Pass nop function when stubbing out console, vitest won't accept it any other way

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unnecessary mock which causes tests to fail after updating fetch-mock & fix typo

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix mistaken assertions not testing all values in array

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix hidden non-running tests in room.spec.ts

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update fetch-mock-jest to @fetch-mock/jest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make knip happier

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Make knip happier 2.0

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Switch from Jest to Vitest

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Iterate

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Delint

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix CI

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Remove unnecessary fake timers

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Update vite

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Revert irrelevant changes

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix coverage spec paths

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix slow test reporter

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix bad merge conflict resolution

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

* Fix babel config

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>

---------

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
2026-01-15 11:15:37 +00:00

762 lines
29 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2023 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 * as utils from "../../src/utils";
import {
alphabetPad,
averageBetweenStrings,
baseToString,
deepSortedObjectEntries,
DEFAULT_ALPHABET,
lexicographicCompare,
nextString,
prevString,
recursiveMapToObject,
simpleRetryOperation,
stringToBase,
sortEventsByLatestContentTimestamp,
safeSet,
MapWithDefault,
globToRegexp,
escapeRegExp,
removeHiddenChars,
} from "../../src/utils";
import { logger } from "../../src/logger";
import { mkMessage } from "../test-utils/test-utils";
import { makeBeaconEvent } from "../test-utils/beacon";
import { ReceiptType } from "../../src/@types/read_receipts";
describe("utils", function () {
describe("encodeParams", function () {
it("should url encode and concat with &s", function () {
const params = {
foo: "bar",
baz: "beer@",
};
expect(utils.encodeParams(params).toString()).toEqual("foo=bar&baz=beer%40");
});
it("should handle boolean and numeric values", function () {
const params = {
string: "foobar",
number: 12345,
boolean: false,
};
expect(utils.encodeParams(params).toString()).toEqual("string=foobar&number=12345&boolean=false");
});
it("should handle string arrays", () => {
const params = {
via: ["one", "two", "three"],
};
expect(utils.encodeParams(params).toString()).toEqual("via=one&via=two&via=three");
});
});
describe("decodeParams", () => {
it("should be able to decode multiple values into an array", () => {
const params = "foo=bar&via=a&via=b&via=c";
expect(utils.decodeParams(params)).toEqual({
foo: "bar",
via: ["a", "b", "c"],
});
});
});
describe("encodeUri", function () {
it("should replace based on object keys and url encode", function () {
const path = "foo/bar/%something/%here";
const vals = {
"%something": "baz",
"%here": "beer@",
};
expect(utils.encodeUri(path, vals)).toEqual("foo/bar/baz/beer%40");
});
});
describe("removeElement", function () {
it("should remove only 1 element if there is a match", function () {
const matchFn = function () {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual([66, 77]);
});
it("should be able to remove in reverse order", function () {
const matchFn = function () {
return true;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn, true);
expect(arr).toEqual([55, 66]);
});
it("should remove nothing if the function never returns true", function () {
const matchFn = function () {
return false;
};
const arr = [55, 66, 77];
utils.removeElement(arr, matchFn);
expect(arr).toEqual(arr);
});
});
describe("isFunction", function () {
it("should return true for functions", function () {
expect(utils.isFunction([])).toBe(false);
expect(utils.isFunction([5, 3, 7])).toBe(false);
expect(utils.isFunction(undefined)).toBe(false);
expect(utils.isFunction(null)).toBe(false);
expect(utils.isFunction({})).toBe(false);
expect(utils.isFunction("foo")).toBe(false);
expect(utils.isFunction(555)).toBe(false);
expect(utils.isFunction(function () {})).toBe(true);
const s = { foo: function () {} };
expect(utils.isFunction(s.foo)).toBe(true);
});
});
describe("checkObjectHasKeys", function () {
it("should throw for missing keys", function () {
expect(function () {
utils.checkObjectHasKeys({}, ["foo"]);
}).toThrow();
expect(function () {
utils.checkObjectHasKeys(
{
foo: "bar",
},
["foo"],
);
}).not.toThrow();
});
});
describe("deepCompare", function () {
const assert = {
isTrue: function (x: any) {
// eslint-disable-next-line @vitest/no-standalone-expect
expect(x).toBe(true);
},
isFalse: function (x: any) {
// eslint-disable-next-line @vitest/no-standalone-expect
expect(x).toBe(false);
},
};
it("should handle primitives", function () {
assert.isTrue(utils.deepCompare(null, null));
assert.isFalse(utils.deepCompare(null, undefined));
assert.isTrue(utils.deepCompare("hi", "hi"));
assert.isTrue(utils.deepCompare(5, 5));
assert.isFalse(utils.deepCompare(5, 10));
});
it("should handle regexps", function () {
assert.isTrue(utils.deepCompare(/abc/, /abc/));
assert.isFalse(utils.deepCompare(/abc/, /123/));
const r = /abc/;
assert.isTrue(utils.deepCompare(r, r));
});
it("should handle dates", function () {
assert.isTrue(utils.deepCompare(new Date("2011-03-31"), new Date("2011-03-31")));
assert.isFalse(utils.deepCompare(new Date("2011-03-31"), new Date("1970-01-01")));
});
it("should handle arrays", function () {
assert.isTrue(utils.deepCompare([], []));
assert.isTrue(utils.deepCompare([1, 2], [1, 2]));
assert.isFalse(utils.deepCompare([1, 2], [2, 1]));
assert.isFalse(utils.deepCompare([1, 2], [1, 2, 3]));
});
it("should handle simple objects", function () {
assert.isTrue(utils.deepCompare({}, {}));
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 2 }));
assert.isTrue(utils.deepCompare({ a: 1, b: 2 }, { b: 2, a: 1 }));
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1, b: 3 }));
assert.isFalse(utils.deepCompare({ a: 1, b: 2 }, { a: 1 }));
assert.isFalse(utils.deepCompare({ a: 1 }, { a: 1, b: 2 }));
assert.isFalse(utils.deepCompare({ a: 1 }, { b: 1 }));
assert.isTrue(
utils.deepCompare(
{
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
},
{
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
},
),
);
assert.isFalse(
utils.deepCompare(
{
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 26 },
},
{
1: { name: "mhc", age: 28 },
2: { name: "arb", age: 27 },
},
),
);
assert.isFalse(utils.deepCompare({}, null));
assert.isFalse(utils.deepCompare({}, undefined));
});
it("should handle functions", function () {
// no two different function is equal really, they capture their
// context variables so even if they have same toString(), they
// won't have same functionality
const func = function () {
return true;
};
const func2 = function () {
return true;
};
assert.isTrue(utils.deepCompare(func, func));
assert.isFalse(utils.deepCompare(func, func2));
assert.isTrue(utils.deepCompare({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(utils.deepCompare({ a: { b: func } }, { a: { b: func2 } }));
});
});
describe("chunkPromises", function () {
it("should execute promises in chunks", async function () {
let promiseCount = 0;
async function fn1() {
await utils.sleep(1);
expect(promiseCount).toEqual(0);
++promiseCount;
}
async function fn2() {
expect(promiseCount).toEqual(1);
++promiseCount;
}
await utils.chunkPromises([fn1, fn2], 1);
expect(promiseCount).toEqual(2);
});
});
describe("simpleRetryOperation", () => {
it("should retry", async () => {
let count = 0;
const val = {};
const fn = (attempt: any) => {
count++;
// If this expectation fails then it can appear as a test timeout due to
// the retry running beyond the test limit.
expect(attempt).toEqual(count);
if (count > 1) {
return Promise.resolve(val);
} else {
return Promise.reject(new Error("Iterative failure"));
}
};
const ret = await simpleRetryOperation(fn);
expect(ret).toBe(val);
expect(count).toEqual(2);
});
// We don't test much else of the function because then we're just testing that the
// underlying library behaves, which should be tested on its own. Our API surface is
// all that concerns us.
});
describe("DEFAULT_ALPHABET", () => {
it("should be usefully printable ASCII in order", () => {
expect(DEFAULT_ALPHABET).toEqual(
" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~",
);
});
});
describe("alphabetPad", () => {
it("should pad to the alphabet length", () => {
const len = 12;
expect(alphabetPad("a", len)).toEqual("a" + "".padEnd(len - 1, DEFAULT_ALPHABET[0]));
expect(alphabetPad("a", len, "123")).toEqual("a" + "".padEnd(len - 1, "1"));
});
});
describe("baseToString", () => {
it("should calculate the appropriate string from numbers", () => {
// Verify the whole alphabet
for (let i = BigInt(1); i <= DEFAULT_ALPHABET.length; i++) {
logger.log({ i }); // for debugging
expect(baseToString(i)).toEqual(DEFAULT_ALPHABET[Number(i) - 1]);
}
// Just quickly double check that repeated characters aren't treated as padding, particularly
// at the beginning of the alphabet where they are most vulnerable to this behaviour.
expect(baseToString(BigInt(1))).toEqual(DEFAULT_ALPHABET[0].repeat(1));
expect(baseToString(BigInt(96))).toEqual(DEFAULT_ALPHABET[0].repeat(2));
expect(baseToString(BigInt(9121))).toEqual(DEFAULT_ALPHABET[0].repeat(3));
expect(baseToString(BigInt(866496))).toEqual(DEFAULT_ALPHABET[0].repeat(4));
expect(baseToString(BigInt(82317121))).toEqual(DEFAULT_ALPHABET[0].repeat(5));
expect(baseToString(BigInt(7820126496))).toEqual(DEFAULT_ALPHABET[0].repeat(6));
expect(baseToString(BigInt(10))).toEqual(DEFAULT_ALPHABET[9]);
expect(baseToString(BigInt(10), "abcdefghijklmnopqrstuvwxyz")).toEqual("j");
expect(baseToString(BigInt(6337))).toEqual("ab");
expect(baseToString(BigInt(80), "abcdefghijklmnopqrstuvwxyz")).toEqual("cb");
});
});
describe("stringToBase", () => {
it("should calculate the appropriate number for a string", () => {
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(1))).toEqual(BigInt(1));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(2))).toEqual(BigInt(96));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(3))).toEqual(BigInt(9121));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(4))).toEqual(BigInt(866496));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(5))).toEqual(BigInt(82317121));
expect(stringToBase(DEFAULT_ALPHABET[0].repeat(6))).toEqual(BigInt(7820126496));
expect(stringToBase("a", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(1));
expect(stringToBase("a")).toEqual(BigInt(66));
expect(stringToBase("c", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(3));
expect(stringToBase("ab")).toEqual(BigInt(6337));
expect(stringToBase("cb", "abcdefghijklmnopqrstuvwxyz")).toEqual(BigInt(80));
});
});
describe("averageBetweenStrings", () => {
it("should average appropriately", () => {
expect(averageBetweenStrings(" ", "!!")).toEqual(" P");
expect(averageBetweenStrings(" ", "!")).toEqual(" ");
expect(averageBetweenStrings("A", "B")).toEqual("A ");
expect(averageBetweenStrings("AA", "BB")).toEqual("Aq");
expect(averageBetweenStrings("A", "z")).toEqual("]");
expect(averageBetweenStrings("a", "z", "abcdefghijklmnopqrstuvwxyz")).toEqual("m");
expect(averageBetweenStrings("AA", "zz")).toEqual("^.");
expect(averageBetweenStrings("aa", "zz", "abcdefghijklmnopqrstuvwxyz")).toEqual("mz");
expect(averageBetweenStrings("cat", "doggo")).toEqual("d9>Cw");
expect(averageBetweenStrings("cat", "doggo", "abcdefghijklmnopqrstuvwxyz")).toEqual("cumqh");
});
});
describe("nextString", () => {
it("should find the next string appropriately", () => {
expect(nextString("A")).toEqual("B");
expect(nextString("b", "abcdefghijklmnopqrstuvwxyz")).toEqual("c");
expect(nextString("cat")).toEqual("cau");
expect(nextString("cat", "abcdefghijklmnopqrstuvwxyz")).toEqual("cau");
});
});
describe("prevString", () => {
it("should find the next string appropriately", () => {
expect(prevString("B")).toEqual("A");
expect(prevString("c", "abcdefghijklmnopqrstuvwxyz")).toEqual("b");
expect(prevString("cau")).toEqual("cat");
expect(prevString("cau", "abcdefghijklmnopqrstuvwxyz")).toEqual("cat");
});
});
// Let's just ensure the ordering is sensible for lexicographic ordering
describe("string averaging unified", () => {
it("should be truly previous and next", () => {
let midpoint = "cat";
// We run this test 100 times to ensure we end up with a sane sequence.
for (let i = 0; i < 100; i++) {
const next = nextString(midpoint);
const prev = prevString(midpoint);
logger.log({ i, midpoint, next, prev }); // for test debugging
expect(lexicographicCompare(midpoint, next) < 0).toBe(true);
expect(lexicographicCompare(midpoint, prev) > 0).toBe(true);
expect(averageBetweenStrings(prev, next)).toBe(midpoint);
midpoint = next;
}
});
it("should roll over", () => {
const lastAlpha = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1];
const firstAlpha = DEFAULT_ALPHABET[0];
const highRoll = firstAlpha + firstAlpha;
const lowRoll = lastAlpha;
expect(nextString(lowRoll)).toEqual(highRoll);
expect(prevString(highRoll)).toEqual(lowRoll);
});
it("should be reversible on small strings", () => {
// Large scale reversibility is tested for max space order value
const input = "cats";
expect(prevString(nextString(input))).toEqual(input);
});
// We want to explicitly make sure that Space order values are supported and roll appropriately
it("should properly handle rolling over at 50 characters", () => {
// Note: we also test reversibility of large strings here.
const maxSpaceValue = DEFAULT_ALPHABET[DEFAULT_ALPHABET.length - 1].repeat(50);
const fiftyFirstChar = DEFAULT_ALPHABET[0].repeat(51);
expect(nextString(maxSpaceValue)).toBe(fiftyFirstChar);
expect(prevString(fiftyFirstChar)).toBe(maxSpaceValue);
// We're testing that the rollover happened, which means that the next string come before
// the maximum space order value lexicographically.
expect(lexicographicCompare(maxSpaceValue, fiftyFirstChar) > 0).toBe(true);
});
});
describe("lexicographicCompare", () => {
it("should work", () => {
// Simple tests
expect(lexicographicCompare("a", "b") < 0).toBe(true);
expect(lexicographicCompare("ab", "b") < 0).toBe(true);
expect(lexicographicCompare("cat", "dog") < 0).toBe(true);
// Simple tests (reversed)
expect(lexicographicCompare("b", "a") > 0).toBe(true);
expect(lexicographicCompare("b", "ab") > 0).toBe(true);
expect(lexicographicCompare("dog", "cat") > 0).toBe(true);
// Simple equality tests
expect(lexicographicCompare("a", "a") === 0).toBe(true);
expect(lexicographicCompare("A", "A") === 0).toBe(true);
// ASCII rule testing
expect(lexicographicCompare("A", "a") < 0).toBe(true);
expect(lexicographicCompare("a", "A") > 0).toBe(true);
});
});
describe("deepSortedObjectEntries", () => {
it("should auto-return non-objects", () => {
expect(deepSortedObjectEntries(42)).toEqual(42);
expect(deepSortedObjectEntries("not object")).toEqual("not object");
expect(deepSortedObjectEntries(true)).toEqual(true);
expect(deepSortedObjectEntries([42])).toEqual([42]);
expect(deepSortedObjectEntries(null)).toEqual(null);
expect(deepSortedObjectEntries(undefined)).toEqual(undefined);
});
it("should sort objects appropriately", () => {
const input = {
a: 42,
b: {
d: {},
a: "test",
b: "alpha",
},
[72]: "test",
};
const output: any = [
["72", "test"],
["a", 42],
[
"b",
[
["a", "test"],
["b", "alpha"],
["d", []],
],
],
];
expect(deepSortedObjectEntries(input)).toMatchObject(output);
});
});
describe("recursivelyAssign", () => {
it("doesn't override with null/undefined", () => {
const result = utils.recursivelyAssign<
{
string: string;
object: object;
float: number;
},
{}
>(
{
string: "Hello world",
object: {},
float: 0.1,
},
{
string: null,
object: undefined,
},
true,
);
expect(result).toStrictEqual({
string: "Hello world",
object: {},
float: 0.1,
});
});
it("assigns recursively", () => {
const result = utils.recursivelyAssign<
{
number: number;
object: object;
thing: string | object;
},
{}
>(
{
number: 42,
object: {
message: "Hello world",
day: "Monday",
langs: {
compiled: ["c++"],
},
},
thing: "string",
},
{
number: 2,
object: {
message: "How are you",
day: "Friday",
langs: {
compiled: ["c++", "c"],
},
},
thing: {
aSubThing: "something",
},
},
);
expect(result).toStrictEqual({
number: 2,
object: {
message: "How are you",
day: "Friday",
langs: {
compiled: ["c++", "c"],
},
},
thing: {
aSubThing: "something",
},
});
});
});
describe("sortEventsByLatestContentTimestamp", () => {
const roomId = "!room:server";
const userId = "@user:server";
const eventWithoutContentTimestamp = mkMessage({ room: roomId, user: userId, event: true });
// m.beacon events have timestamp in content
const beaconEvent1 = makeBeaconEvent(userId, { timestamp: 1648804528557 });
const beaconEvent2 = makeBeaconEvent(userId, { timestamp: 1648804528558 });
const beaconEvent3 = makeBeaconEvent(userId, { timestamp: 1648804528000 });
const beaconEvent4 = makeBeaconEvent(userId, { timestamp: 0 });
it("sorts events with timestamps as later than events without", () => {
expect(
[beaconEvent4, eventWithoutContentTimestamp, beaconEvent1].sort(
utils.sortEventsByLatestContentTimestamp,
),
).toEqual([beaconEvent1, beaconEvent4, eventWithoutContentTimestamp]);
});
it("sorts by content timestamps correctly", () => {
expect([beaconEvent1, beaconEvent2, beaconEvent3].sort(sortEventsByLatestContentTimestamp)).toEqual([
beaconEvent2,
beaconEvent1,
beaconEvent3,
]);
});
});
describe("isSupportedReceiptType", () => {
it("should support m.read", () => {
expect(utils.isSupportedReceiptType(ReceiptType.Read)).toBeTruthy();
});
it("should support m.read.private", () => {
expect(utils.isSupportedReceiptType(ReceiptType.ReadPrivate)).toBeTruthy();
});
it("should not support other receipt types", () => {
expect(utils.isSupportedReceiptType("this is a receipt type")).toBeFalsy();
});
});
describe("recursiveMapToObject", () => {
it.each([
// empty map
{
map: new Map(),
expected: {},
},
// one level map
{
map: new Map<any, any>([
["key1", "value 1"],
["key2", 23],
["key3", undefined],
["key4", null],
["key5", [1, 2, 3]],
]),
expected: { key1: "value 1", key2: 23, key3: undefined, key4: null, key5: [1, 2, 3] },
},
// two level map
{
map: new Map<any, any>([
[
"key1",
new Map<any, any>([
["key1_1", "value 1"],
["key1_2", "value 1.2"],
]),
],
["key2", "value 2"],
]),
expected: { key1: { key1_1: "value 1", key1_2: "value 1.2" }, key2: "value 2" },
},
// multi level map
{
map: new Map<any, any>([
["key1", new Map<any, any>([["key1_1", new Map<any, any>([["key1_1_1", "value 1.1.1"]])]])],
]),
expected: { key1: { key1_1: { key1_1_1: "value 1.1.1" } } },
},
// list of maps
{
map: new Map<any, any>([
[
"key1",
[new Map<any, any>([["key1_1", "value 1.1"]]), new Map<any, any>([["key1_2", "value 1.2"]])],
],
]),
expected: { key1: [{ key1_1: "value 1.1" }, { key1_2: "value 1.2" }] },
},
// map → array → array → map
{
map: new Map<any, any>([["key1", [[new Map<any, any>([["key2", "value 2"]])]]]]),
expected: {
key1: [
[
{
key2: "value 2",
},
],
],
},
},
])("%# should convert the value", ({ map, expected }) => {
expect(recursiveMapToObject(map)).toStrictEqual(expected);
});
});
describe("safeSet", () => {
it("should set a value", () => {
const obj: Record<string, string> = {};
safeSet(obj, "testProp", "test value");
expect(obj).toEqual({ testProp: "test value" });
});
it.each(["__proto__", "prototype", "constructor"])("should raise an error when setting »%s«", (prop) => {
expect(() => {
safeSet(<Record<string, string>>{}, prop, "teset value");
}).toThrow("Trying to modify prototype or constructor");
});
});
describe("MapWithDefault", () => {
it("getOrCreate should create the value if it does not exist", () => {
const newValue = {};
const map = new MapWithDefault(() => newValue);
// undefined before getOrCreate
expect(map.get("test")).toBeUndefined();
expect(map.getOrCreate("test")).toBe(newValue);
// default value after getOrCreate
expect(map.get("test")).toBe(newValue);
// test that it always returns the same value
expect(map.getOrCreate("test")).toBe(newValue);
});
});
describe("sleep", () => {
// eslint-disable-next-line @vitest/expect-expect
it("resolves", async () => {
await utils.sleep(0);
});
it("resolves with the provided value", async () => {
const expected = Symbol("hi");
const result = await utils.sleep(0, expected);
expect(result).toBe(expected);
});
});
describe("immediate", () => {
// eslint-disable-next-line @vitest/expect-expect
it("resolves", async () => {
await utils.immediate();
});
});
describe("escapeRegExp", () => {
it("should escape XYZ", () => {
expect(escapeRegExp("[FIT-Connect Zustelldienst \\(Testumgebung\\)]")).toMatchInlineSnapshot(
`"\\[FIT-Connect Zustelldienst \\\\\\(Testumgebung\\\\\\)\\]"`,
);
});
});
describe("globToRegexp", () => {
it("should not explode when given regexes as globs", () => {
const result = globToRegexp("[FIT-Connect Zustelldienst \\(Testumgebung\\)]");
expect(result).toMatchInlineSnapshot(`"\\[FIT-Connect Zustelldienst \\\\\\(Testumgebung\\\\\\)\\]"`);
});
});
describe("removeHiddenChars", () => {
it.each([
["various width spaces U+2000 - U+200D", "\u2000\u200D", ""],
["LTR and RTL marks U+200E and U+200F", "\u200E\u200F", ""],
["LTR/RTL and other directional formatting marks U+202A - U+202F", "\u202A\u202F", ""],
["Arabic Letter RTL mark U+061C", "\u061C", ""],
["Combining characters U+0300 - U+036F", "\u3000\u036F", ""],
["Zero width no-break space (BOM) U+FEFF", "\uFEFF", ""],
["Blank/invisible characters (U2800, U2062-U2063)", "\u2800\u2062\u2063", ""],
["Zero Width Non Joiner", "", ""],
])("should strip invisible characters: %s", (_, input, expected) => {
expect(removeHiddenChars(input)).toBe(expected);
});
});
});