@ezez/utils 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/replace.d.ts.map +1 -1
- package/dist/replace.js.map +1 -1
- package/dist/safe.d.ts.map +1 -1
- package/dist/safe.js.map +1 -1
- package/dist/sample.d.ts +3 -0
- package/dist/sample.d.ts.map +1 -0
- package/dist/sample.js +8 -0
- package/dist/sample.js.map +1 -0
- package/dist/samples.d.ts +3 -0
- package/dist/samples.d.ts.map +1 -0
- package/dist/samples.js +28 -0
- package/dist/samples.js.map +1 -0
- package/dist/shuffle.d.ts +3 -0
- package/dist/shuffle.d.ts.map +1 -0
- package/dist/shuffle.js +9 -0
- package/dist/shuffle.js.map +1 -0
- package/dist/toggle.d.ts +3 -0
- package/dist/toggle.d.ts.map +1 -0
- package/dist/toggle.js +15 -0
- package/dist/toggle.js.map +1 -0
- package/dist/waitFor.d.ts +7 -1
- package/dist/waitFor.d.ts.map +1 -1
- package/dist/waitFor.js +35 -10
- package/dist/waitFor.js.map +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/functions/cap.html +9 -5
- package/docs/functions/capitalize.html +9 -5
- package/docs/functions/coalesce.html +9 -5
- package/docs/functions/compareArrays.html +9 -5
- package/docs/functions/compareProps.html +9 -5
- package/docs/functions/deserialize.html +9 -5
- package/docs/functions/ensureArray.html +9 -5
- package/docs/functions/ensureDate.html +9 -5
- package/docs/functions/ensureError.html +9 -5
- package/docs/functions/ensurePrefix.html +9 -5
- package/docs/functions/ensureSuffix.html +9 -5
- package/docs/functions/ensureTimestamp.html +9 -5
- package/docs/functions/escapeRegExp.html +9 -5
- package/docs/functions/formatDate.html +9 -5
- package/docs/functions/get.html +9 -5
- package/docs/functions/getMultiple.html +9 -5
- package/docs/functions/insertSeparator.html +9 -5
- package/docs/functions/isEmpty.html +9 -5
- package/docs/functions/isNumericString.html +9 -5
- package/docs/functions/isPlainObject.html +9 -5
- package/docs/functions/last.html +9 -5
- package/docs/functions/later-1.html +9 -5
- package/docs/functions/mapAsync.html +9 -5
- package/docs/functions/mapValues.html +9 -5
- package/docs/functions/match.html +9 -5
- package/docs/functions/merge.html +17 -13
- package/docs/functions/mostFrequent.html +9 -5
- package/docs/functions/noop.html +9 -5
- package/docs/functions/occurrences.html +9 -5
- package/docs/functions/omit.html +9 -5
- package/docs/functions/pick.html +9 -5
- package/docs/functions/pull.html +9 -5
- package/docs/functions/remove.html +9 -5
- package/docs/functions/removeCommonProperties.html +9 -5
- package/docs/functions/replace.html +9 -5
- package/docs/functions/replaceDeep.html +9 -5
- package/docs/functions/rethrow.html +9 -5
- package/docs/functions/round.html +9 -5
- package/docs/functions/safe.html +10 -6
- package/docs/functions/sample.html +146 -0
- package/docs/functions/samples.html +156 -0
- package/docs/functions/scale.html +9 -5
- package/docs/functions/seq.html +9 -5
- package/docs/functions/seqEarlyBreak.html +9 -5
- package/docs/functions/serialize.html +9 -5
- package/docs/functions/set.html +9 -5
- package/docs/functions/setImmutable.html +9 -5
- package/docs/functions/shuffle.html +146 -0
- package/docs/functions/sortBy.html +9 -5
- package/docs/functions/sortProps.html +9 -5
- package/docs/functions/stripPrefix.html +9 -5
- package/docs/functions/stripSuffix.html +9 -5
- package/docs/functions/throttle.html +9 -5
- package/docs/functions/toggle.html +151 -0
- package/docs/functions/truthy.html +9 -5
- package/docs/functions/unique.html +9 -5
- package/docs/functions/wait.html +9 -5
- package/docs/functions/waitFor.html +20 -18
- package/docs/functions/waitSync.html +9 -5
- package/docs/index.html +8 -4
- package/docs/interfaces/ComparePropsOptions.html +6 -6
- package/docs/interfaces/GetMultipleSource.html +9 -5
- package/docs/interfaces/GetSource.html +9 -5
- package/docs/interfaces/IsNumericStringOptions.html +9 -9
- package/docs/interfaces/OccurencesOptions.html +6 -6
- package/docs/interfaces/SetImmutableSource.html +9 -5
- package/docs/interfaces/SetSource.html +9 -5
- package/docs/interfaces/ThrottleOptions.html +7 -7
- package/docs/interfaces/ThrottledFunctionExtras.html +7 -7
- package/docs/modules.html +12 -4
- package/docs/pages/CHANGELOG.html +86 -46
- package/docs/pages/Introduction.html +8 -4
- package/docs/types/CustomDeserializers.html +9 -5
- package/docs/types/CustomSerializers.html +9 -5
- package/docs/types/Later.html +9 -5
- package/docs/types/MapValuesFn.html +9 -5
- package/docs/types/MatchCallback.html +9 -5
- package/docs/types/SeqEarlyBreaker.html +9 -5
- package/docs/types/SeqFn.html +9 -5
- package/docs/types/SeqFunctions.html +9 -5
- package/docs/types/SetImmutablePath.html +9 -5
- package/docs/types/ThrottledFunction.html +9 -5
- package/docs/variables/mapValuesUNSET.html +9 -5
- package/docs/variables/mergeUNSET.html +9 -5
- package/esm/index.d.ts +4 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +4 -0
- package/esm/index.js.map +1 -1
- package/esm/replace.d.ts.map +1 -1
- package/esm/replace.js.map +1 -1
- package/esm/safe.d.ts.map +1 -1
- package/esm/safe.js.map +1 -1
- package/esm/sample.d.ts +3 -0
- package/esm/sample.d.ts.map +1 -0
- package/esm/sample.js +5 -0
- package/esm/sample.js.map +1 -0
- package/esm/samples.d.ts +3 -0
- package/esm/samples.d.ts.map +1 -0
- package/esm/samples.js +25 -0
- package/esm/samples.js.map +1 -0
- package/esm/shuffle.d.ts +3 -0
- package/esm/shuffle.d.ts.map +1 -0
- package/esm/shuffle.js +6 -0
- package/esm/shuffle.js.map +1 -0
- package/esm/toggle.d.ts +3 -0
- package/esm/toggle.d.ts.map +1 -0
- package/esm/toggle.js +12 -0
- package/esm/toggle.js.map +1 -0
- package/esm/waitFor.d.ts +7 -1
- package/esm/waitFor.d.ts.map +1 -1
- package/esm/waitFor.js +35 -10
- package/esm/waitFor.js.map +1 -1
- package/package.json +4 -4
- package/pnpm-lock.yaml +1162 -986
- package/src/index.ts +4 -0
- package/src/replace.ts +0 -1
- package/src/safe.ts +0 -1
- package/src/sample.spec.ts +31 -0
- package/src/sample.ts +11 -0
- package/src/samples.spec.ts +50 -0
- package/src/samples.ts +41 -0
- package/src/shuffle.spec.ts +39 -0
- package/src/shuffle.ts +13 -0
- package/src/toggle.spec.ts +43 -0
- package/src/toggle.ts +22 -0
- package/src/waitFor.spec.ts +141 -0
- package/src/waitFor.ts +69 -18
package/src/index.ts
CHANGED
|
@@ -38,16 +38,20 @@ export * from "./replaceDeep.js";
|
|
|
38
38
|
export * from "./rethrow.js";
|
|
39
39
|
export * from "./round.js";
|
|
40
40
|
export * from "./safe.js";
|
|
41
|
+
export * from "./sample.js";
|
|
42
|
+
export * from "./samples.js";
|
|
41
43
|
export * from "./scale.js";
|
|
42
44
|
export * from "./seq.js";
|
|
43
45
|
export * from "./serialize.js";
|
|
44
46
|
export * from "./set.js";
|
|
45
47
|
export * from "./setImmutable.js";
|
|
48
|
+
export * from "./shuffle.js";
|
|
46
49
|
export * from "./sortBy.js";
|
|
47
50
|
export * from "./sortProps.js";
|
|
48
51
|
export * from "./stripPrefix.js";
|
|
49
52
|
export * from "./stripSuffix.js";
|
|
50
53
|
export * from "./throttle.js";
|
|
54
|
+
export * from "./toggle.js";
|
|
51
55
|
export * from "./truthy.js";
|
|
52
56
|
export * from "./unique.js";
|
|
53
57
|
export * from "./wait.js";
|
package/src/replace.ts
CHANGED
|
@@ -12,7 +12,6 @@ const replace = (source: string, replaceMap: Record<string, string>) => {
|
|
|
12
12
|
if (keys.length === 0) {
|
|
13
13
|
return source;
|
|
14
14
|
}
|
|
15
|
-
/* eslint-enable max-len */
|
|
16
15
|
const regex = new RegExp(keys.map(escapeRegExp).join("|"), "g");
|
|
17
16
|
return source.replace(regex, (matched) => replaceMap[matched]!);
|
|
18
17
|
};
|
package/src/safe.ts
CHANGED
|
@@ -11,7 +11,6 @@ function safe<T, Y>(fn: () => T, def: Y): T | Y;
|
|
|
11
11
|
* safe(() => trySomethingComplicated(), defaultValue); // if trySomethingComplicated throws, defaultValue will be returned
|
|
12
12
|
*/
|
|
13
13
|
function safe<T, Y>(fn: () => T, def?: Y) { // eslint-disable-line func-style
|
|
14
|
-
/* eslint-enable max-len */
|
|
15
14
|
try {
|
|
16
15
|
return fn();
|
|
17
16
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
2
|
+
import must from "must";
|
|
3
|
+
|
|
4
|
+
import { sample } from "./sample";
|
|
5
|
+
|
|
6
|
+
describe("sample", () => {
|
|
7
|
+
it("should return any random item of an array", async () => {
|
|
8
|
+
const array = [1, 2, 3, 4, 5];
|
|
9
|
+
const pickSet = new Set<number>();
|
|
10
|
+
for (let i = 0; i < 1000; i++) {
|
|
11
|
+
const item = sample(array);
|
|
12
|
+
must(array).include(item);
|
|
13
|
+
pickSet.add(item);
|
|
14
|
+
}
|
|
15
|
+
must(pickSet.size).equal(5);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should work with 1 item array", async () => {
|
|
19
|
+
const array = [1];
|
|
20
|
+
for (let i = 0; i < 1000; i++) {
|
|
21
|
+
const item = sample(array);
|
|
22
|
+
must(item).equal(1);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should work with 0 items array", async () => {
|
|
27
|
+
const array: never[] = [];
|
|
28
|
+
const item = sample(array);
|
|
29
|
+
must(item).equal(undefined);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/sample.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
2
|
+
import must from "must";
|
|
3
|
+
|
|
4
|
+
import { samples } from "./samples";
|
|
5
|
+
|
|
6
|
+
describe("samples", () => {
|
|
7
|
+
it("must pick random elements", async () => {
|
|
8
|
+
const elements = [1, 2, 3];
|
|
9
|
+
|
|
10
|
+
const result = samples(elements, 2);
|
|
11
|
+
must(result).have.length(2);
|
|
12
|
+
|
|
13
|
+
for (const item of result) {
|
|
14
|
+
must(elements).include(item);
|
|
15
|
+
}
|
|
16
|
+
must([...new Set(result)]).have.length(2);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should be able to pick one element", async () => {
|
|
20
|
+
const elements = [1, 2, 3];
|
|
21
|
+
const result = samples(elements, 1);
|
|
22
|
+
must(result).have.length(1);
|
|
23
|
+
must(elements).include(result[0]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should return empty array if no elements to pick", async () => {
|
|
27
|
+
const elements = [1, 2, 3];
|
|
28
|
+
const result = samples(elements, 0);
|
|
29
|
+
must(result).be.empty();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should return all elements if elements to pick is greater than array length", async () => {
|
|
33
|
+
const elements = [1, 2, 3];
|
|
34
|
+
const result = samples(elements, 5);
|
|
35
|
+
must(result).have.length(3);
|
|
36
|
+
must(result).eql(elements);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should return all elements if elements to pick is equal to array length", async () => {
|
|
40
|
+
const elements = [1, 2, 3];
|
|
41
|
+
const result = samples(elements, 3);
|
|
42
|
+
must(result).have.length(3);
|
|
43
|
+
must(result).eql(elements);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should throw if elements to pick is negative", async () => {
|
|
47
|
+
const elements = [1, 2, 3];
|
|
48
|
+
must(() => samples(elements, -1)).throw();
|
|
49
|
+
});
|
|
50
|
+
});
|
package/src/samples.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picks a given number of random elements from the array. It won't pick the same element twice (unless the array has
|
|
3
|
+
* duplicates).
|
|
4
|
+
*
|
|
5
|
+
* If the number of elements to pick is greater than the array length, it will return the original array.
|
|
6
|
+
*
|
|
7
|
+
* @param array - source array
|
|
8
|
+
* @param elementsToPick - number of elements to pick
|
|
9
|
+
* @param allowShuffle - if true, it will shuffle the values if elementsToPick is greater or equal to array length instead of returning the original array
|
|
10
|
+
*/
|
|
11
|
+
const samples = <T>(array: T[], elementsToPick: number, allowShuffle = false): T[] => { // eslint-disable-line max-statements,max-len
|
|
12
|
+
if (elementsToPick < 0) {
|
|
13
|
+
throw new Error("elementsToPick must be a positive number");
|
|
14
|
+
}
|
|
15
|
+
if (elementsToPick === 0) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!allowShuffle && elementsToPick >= array.length) {
|
|
20
|
+
return array;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keys = Object.keys(array);
|
|
24
|
+
let picked = 0;
|
|
25
|
+
const result: T[] = [];
|
|
26
|
+
|
|
27
|
+
while (picked < elementsToPick) {
|
|
28
|
+
const indexOfKey = Math.floor(Math.random() * keys.length);
|
|
29
|
+
const indexOfArray = Number(keys[indexOfKey]);
|
|
30
|
+
const element = array[indexOfArray]!;
|
|
31
|
+
result.push(element);
|
|
32
|
+
keys.splice(indexOfKey, 1);
|
|
33
|
+
picked++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
samples,
|
|
41
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
2
|
+
import must from "must";
|
|
3
|
+
|
|
4
|
+
import { shuffle } from "./shuffle";
|
|
5
|
+
|
|
6
|
+
describe("shuffle", () => {
|
|
7
|
+
it("should shuffle the array", async () => {
|
|
8
|
+
for (let i = 0; i < 100; i++) {
|
|
9
|
+
{
|
|
10
|
+
const elements = [1, 2, 3];
|
|
11
|
+
|
|
12
|
+
const result = shuffle(elements);
|
|
13
|
+
must(result).have.length(3);
|
|
14
|
+
|
|
15
|
+
for (const item of result) {
|
|
16
|
+
must(elements).include(item);
|
|
17
|
+
}
|
|
18
|
+
must([...new Set(result)]).have.length(3);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should shuffle the array with duplicates", async () => {
|
|
24
|
+
for (let i = 0; i < 100; i++) {
|
|
25
|
+
{
|
|
26
|
+
const elements = [1, 2, 2, 3];
|
|
27
|
+
|
|
28
|
+
const result = shuffle(elements);
|
|
29
|
+
must(result).have.length(4);
|
|
30
|
+
|
|
31
|
+
for (const item of result) {
|
|
32
|
+
must(elements).include(item);
|
|
33
|
+
}
|
|
34
|
+
must([...new Set(result)]).have.length(3);
|
|
35
|
+
must(result.filter(r => r === 2).length).equal(2);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
package/src/shuffle.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { samples } from "./samples.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shuffles an array, returning a new array.
|
|
5
|
+
* @param array source array
|
|
6
|
+
*/
|
|
7
|
+
const shuffle = <T>(array: T[]): T[] => {
|
|
8
|
+
return samples(array, array.length, true);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
shuffle,
|
|
13
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { toggle } from "./toggle.js";
|
|
2
|
+
|
|
3
|
+
describe("toggle", () => {
|
|
4
|
+
it("adds the value if it doesn't exist", async () => {
|
|
5
|
+
must(toggle([1], 2)).eql([1, 2]);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
it("removes the value if it exists", async () => {
|
|
9
|
+
must(toggle([1, 2], 2)).eql([1]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("removes only one occurrence", async () => {
|
|
13
|
+
must(toggle([1, 2, 2], 2)).eql([1, 2]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("modifies the original array when adding", async () => {
|
|
17
|
+
const array = [1];
|
|
18
|
+
toggle(array, 2);
|
|
19
|
+
must(array).eql([1, 2]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("modifies the original array when removing", async () => {
|
|
23
|
+
const array = [1, 2];
|
|
24
|
+
toggle(array, 2);
|
|
25
|
+
must(array).eql([1]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns the original array", async () => {
|
|
29
|
+
const array = [1];
|
|
30
|
+
// adding
|
|
31
|
+
must(toggle(array, 2)).equal(array);
|
|
32
|
+
// removing
|
|
33
|
+
must(toggle(array, 2)).equal(array);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("adds NaN values correctly", async () => {
|
|
37
|
+
must(toggle([1], NaN)).eql([1, NaN]);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("removes NaN values correctly", async () => {
|
|
41
|
+
must(toggle([1, NaN], NaN)).eql([1]);
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/toggle.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggles an item in an array. If it exists, remove it. If it doesn't exist, add it.
|
|
3
|
+
* If the array contains the given value more than once, this function will remove only one occurrence.
|
|
4
|
+
*
|
|
5
|
+
* @param array - source array
|
|
6
|
+
* @param item - item to toggle
|
|
7
|
+
*/
|
|
8
|
+
const toggle = <T>(array: T[], item: T): T[] => {
|
|
9
|
+
const index = array.findIndex((i) => Object.is(i, item));
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
|
11
|
+
if (index === -1) {
|
|
12
|
+
array.push(item);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
array.splice(index, 1);
|
|
16
|
+
}
|
|
17
|
+
return array;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
toggle,
|
|
22
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
|
2
|
+
import must from "must";
|
|
3
|
+
|
|
4
|
+
import createSpy from "../test/createSpy";
|
|
5
|
+
|
|
6
|
+
import { waitFor } from "./waitFor";
|
|
7
|
+
|
|
8
|
+
describe("waitFor", () => {
|
|
9
|
+
it("calls function multiple times until it returns truthy value", async () => {
|
|
10
|
+
let calls = 0;
|
|
11
|
+
const spy = createSpy(() => {
|
|
12
|
+
calls++;
|
|
13
|
+
return calls === 3;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
await waitFor(spy);
|
|
18
|
+
|
|
19
|
+
must(calls).equal(3);
|
|
20
|
+
// waited for 3 tries, should be 100ms (0 for first try) in total
|
|
21
|
+
must(Date.now() - start).be.gte(100);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns value if check passes", async () => {
|
|
25
|
+
const x = await waitFor(() => 5);
|
|
26
|
+
must(x).equal(5);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("returns value if check passes in non-first try", async () => {
|
|
30
|
+
let calls = 0;
|
|
31
|
+
const spy = createSpy(() => {
|
|
32
|
+
calls++;
|
|
33
|
+
return calls === 3;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
37
|
+
const res = await waitFor(spy);
|
|
38
|
+
must(res).equal(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("allows to adjust interval", async () => {
|
|
42
|
+
let calls = 0;
|
|
43
|
+
const spy = createSpy(() => {
|
|
44
|
+
calls++;
|
|
45
|
+
return calls === 3;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const start = Date.now();
|
|
49
|
+
await waitFor(spy, { interval: 150 });
|
|
50
|
+
|
|
51
|
+
must(calls).equal(3);
|
|
52
|
+
must(Date.now() - start).be.gte(300);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("time outs after given time", async () => {
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
57
|
+
await must(waitFor(() => null, {
|
|
58
|
+
interval: 40,
|
|
59
|
+
timeout: 300,
|
|
60
|
+
})).reject.to.instanceOf(Error);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("time outs after given amout of retries", async () => {
|
|
64
|
+
const spy = createSpy(() => false);
|
|
65
|
+
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
67
|
+
await must(waitFor(spy, {
|
|
68
|
+
interval: 40,
|
|
69
|
+
maxTries: 3,
|
|
70
|
+
})).reject.to.instanceOf(Error);
|
|
71
|
+
|
|
72
|
+
must(spy.__spy.calls).have.length(3);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("validates max tries", async () => {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
77
|
+
await must(waitFor(() => null, {
|
|
78
|
+
maxTries: 0,
|
|
79
|
+
})).reject.to.instanceOf(TypeError);
|
|
80
|
+
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/await-thenable
|
|
82
|
+
await must(waitFor(() => null, {
|
|
83
|
+
maxTries: -666,
|
|
84
|
+
})).reject.to.instanceOf(TypeError);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("crashes if check function crashes", async () => {
|
|
88
|
+
await waitFor(() => {
|
|
89
|
+
throw new Error(5);
|
|
90
|
+
}, 40).then(() => {
|
|
91
|
+
throw new Error("Should not resolve");
|
|
92
|
+
}, (e) => {
|
|
93
|
+
must(e).instanceOf(Error);
|
|
94
|
+
must(e.message).equal("[waitFor] check function threw an error");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("treats most falsy values as succeeded check", () => {
|
|
99
|
+
it("numeric zero", async () => {
|
|
100
|
+
const result = await waitFor(() => 0);
|
|
101
|
+
must(result).equal(0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("empty string", async () => {
|
|
105
|
+
const result = await waitFor(() => "");
|
|
106
|
+
must(result).equal("");
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("supports promises", () => {
|
|
111
|
+
it("truthy value", async () => {
|
|
112
|
+
const result = await waitFor(() => true);
|
|
113
|
+
must(result).be.true();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("by retrying if Promise returns false", async () => {
|
|
117
|
+
let calls = 0;
|
|
118
|
+
const spy = createSpy(() => {
|
|
119
|
+
calls++;
|
|
120
|
+
return calls === 3;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
124
|
+
const result = await waitFor(spy);
|
|
125
|
+
must(result).be.true();
|
|
126
|
+
must(calls).equal(3);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("by crashing if check function returns rejected promise", async () => {
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
return Promise.reject(new Error("oops"));
|
|
132
|
+
}, 40).then(() => {
|
|
133
|
+
throw new Error("Should not resolve");
|
|
134
|
+
}, (e) => {
|
|
135
|
+
must(e).instanceOf(Error);
|
|
136
|
+
must(e.message).equal("[waitFor] check function threw an error");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
package/src/waitFor.ts
CHANGED
|
@@ -1,46 +1,97 @@
|
|
|
1
|
+
import { noop } from "./noop.js";
|
|
2
|
+
|
|
1
3
|
const DEFAULT_INTERVAL = 50;
|
|
2
4
|
|
|
3
5
|
type TTimeout = ReturnType<typeof setTimeout>;
|
|
4
|
-
type
|
|
6
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
7
|
+
|
|
8
|
+
type Options = {
|
|
9
|
+
/**
|
|
10
|
+
* Interval between checks in milliseconds
|
|
11
|
+
*/
|
|
12
|
+
interval?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Timeout in milliseconds
|
|
15
|
+
*/
|
|
16
|
+
timeout?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum number of tries, 1 means no retry!
|
|
19
|
+
*/
|
|
20
|
+
maxTries?: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const defaultOptions: Required<Options> = {
|
|
24
|
+
interval: DEFAULT_INTERVAL,
|
|
25
|
+
timeout: Infinity,
|
|
26
|
+
maxTries: Infinity,
|
|
27
|
+
};
|
|
5
28
|
|
|
6
29
|
/**
|
|
7
30
|
* Runs the callback function every specified interval and returns a Promise that resolves when the callback returns
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
31
|
+
* any other value than `null`, `undefined` or `false`.
|
|
32
|
+
* If your callback throws (or Promise rejects) it will stop the interval and reject the returned Promise.
|
|
33
|
+
* To avoid that use:
|
|
34
|
+
* ```typescript
|
|
35
|
+
* waitFor(() => promiseReturningFunction().catch(() => null));
|
|
36
|
+
* // or
|
|
37
|
+
* waitFor(() => safe(() => functionThatThrows()));
|
|
38
|
+
* ```
|
|
39
|
+
* @param fn - callback function
|
|
40
|
+
* @param options - options object
|
|
14
41
|
*/
|
|
15
|
-
const waitFor = <T>(fn: () => T
|
|
42
|
+
const waitFor = <T>(fn: () => MaybePromise<T>, options: Options = defaultOptions) => {
|
|
16
43
|
return new Promise<T>((resolve, reject) => {
|
|
17
|
-
let intervalTimer:
|
|
44
|
+
let intervalTimer: TTimeout, failTimer: TTimeout;
|
|
18
45
|
|
|
19
|
-
|
|
46
|
+
const opts = { ...defaultOptions, ...options };
|
|
47
|
+
if (typeof opts.maxTries === "number" && opts.maxTries < 1) {
|
|
48
|
+
reject(new TypeError("[waitFor] maxTries must be greater than 0"));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (Number.isFinite(opts.timeout)) {
|
|
20
53
|
failTimer = setTimeout(() => {
|
|
21
54
|
reject(new Error("[waitFor] Timeout"));
|
|
22
|
-
|
|
23
|
-
}, timeout);
|
|
55
|
+
clearTimeout(intervalTimer);
|
|
56
|
+
}, opts.timeout);
|
|
24
57
|
}
|
|
25
58
|
|
|
26
|
-
|
|
59
|
+
let tries = 0;
|
|
60
|
+
|
|
61
|
+
// eslint-disable-next-line max-statements
|
|
62
|
+
const tryFn = (async () => {
|
|
27
63
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
64
|
+
tries++;
|
|
65
|
+
const result = await fn();
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
67
|
+
if (result != null && result !== false) {
|
|
30
68
|
clearTimeout(failTimer);
|
|
31
|
-
|
|
69
|
+
clearTimeout(intervalTimer);
|
|
32
70
|
resolve(result);
|
|
33
71
|
}
|
|
72
|
+
else {
|
|
73
|
+
if (Number.isFinite(opts.maxTries) && tries >= opts.maxTries) {
|
|
74
|
+
clearTimeout(failTimer);
|
|
75
|
+
clearTimeout(intervalTimer);
|
|
76
|
+
reject(new Error("[waitFor] Max tries reached"));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setTimeout(() => {
|
|
81
|
+
tryFn().catch(noop);
|
|
82
|
+
}, options.interval);
|
|
83
|
+
}
|
|
34
84
|
}
|
|
35
85
|
catch (error: unknown) {
|
|
36
86
|
clearTimeout(failTimer);
|
|
37
|
-
|
|
87
|
+
clearTimeout(intervalTimer);
|
|
38
88
|
|
|
39
89
|
const e: Error & { details?: unknown } = new Error("[waitFor] check function threw an error");
|
|
40
90
|
e.details = { error };
|
|
41
91
|
reject(e);
|
|
42
92
|
}
|
|
43
|
-
}
|
|
93
|
+
});
|
|
94
|
+
tryFn().catch(noop);
|
|
44
95
|
});
|
|
45
96
|
};
|
|
46
97
|
|