Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions test/test-clear.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const test = require("node:test");
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { setImmediate } = require("node:timers/promises");
const when = require("../");

test("clear stops future checks and is safe to repeat", async (t) => {
beforeEach((t) => t.mock.timers.enable({ apis: ["setTimeout"] }));

test("clear stops future checks and is safe to repeat", (t) => {
let checks = 0;
let calls = 0;
const handle = when(
Expand All @@ -17,13 +18,13 @@ test("clear stops future checks and is safe to repeat", async (t) => {

handle.clear();
handle.clear();
await setImmediate();
t.mock.timers.tick(10);

assert.equal(checks, 1);
assert.equal(calls, 0);
});

test("setters do not restart a cancelled controller", async (t) => {
test("setters do not restart a cancelled controller", (t) => {
let calls = 0;
const handle = when(
() => false,
Expand All @@ -34,7 +35,7 @@ test("setters do not restart a cancelled controller", async (t) => {
handle.clear();
handle.setCondition(() => true);
handle.setCode(() => calls++);
await setImmediate();
t.mock.timers.tick(10);

assert.equal(calls, 0);
});
150 changes: 150 additions & 0 deletions test/test-polling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { execFileSync } = require("node:child_process");
const when = require("../");

beforeEach((t) => t.mock.timers.enable({ apis: ["setTimeout"] }));

test("the default poll waits 10 ms after the synchronous initial check", (t) => {
let ready = false;
let checks = 0;
let calls = 0;
const handle = when(
() => {
checks++;
return ready;
},
() => calls++,
);
t.after(() => handle.clear());

assert.equal(checks, 1);
ready = true;
t.mock.timers.tick(9);
assert.equal(checks, 1);
t.mock.timers.tick(1);
assert.equal(checks, 2);
assert.equal(calls, 1);
t.mock.timers.tick(100);
assert.equal(calls, 1);
});

test("each unsuccessful poll waits the configured interval", (t) => {
let checks = 0;
const handle = when(
() => {
checks++;
return false;
},
() => {},
{ interval: 25 },
);
t.after(() => handle.clear());

t.mock.timers.tick(24);
assert.equal(checks, 1);
t.mock.timers.tick(1);
assert.equal(checks, 2);
t.mock.timers.tick(24);
assert.equal(checks, 2);
t.mock.timers.tick(1);
assert.equal(checks, 3);
});

test("clear cancels a pending timer and reset reuses the chosen interval", (t) => {
let checks = 0;
const handle = when(
() => {
checks++;
return false;
},
() => {},
{ interval: 50 },
);
t.after(() => handle.clear());

handle.clear();
t.mock.timers.tick(100);
assert.equal(checks, 1);
handle.reset();
assert.equal(checks, 2);
t.mock.timers.tick(49);
assert.equal(checks, 2);
t.mock.timers.tick(1);
assert.equal(checks, 3);
});

test("rejects invalid options before running the predicate", () => {
for (const options of [null, false, 1, "options", []]) {
let checks = 0;
assert.throws(
() =>
when(
() => {
checks++;
return true;
},
() => {},
options,
),
/options must be an object/,
);
assert.equal(checks, 0);
}
});

test("rejects intervals outside the supported timer range", () => {
for (const interval of [0, -1, 0.5, NaN, Infinity, 2147483648, "10", null]) {
assert.throws(
() =>
when(
() => true,
() => {},
{ interval },
),
{
name: "RangeError",
message: "interval must be an integer from 1 to 2147483647",
},
);
}
});

test("a Promise returned after cancellation is discarded without a rejection leak", async (t) => {
const handle = when(
() => false,
() => assert.fail("cancelled callback"),
);
t.after(() => handle.clear());
handle.setCondition(() => {
handle.clear();
return Promise.reject(new Error("discarded result"));
});
t.mock.timers.tick(10);
await new Promise(setImmediate);
});

test("a Promise returned during a later poll throws and ends that poll chain", () => {
const output = execFileSync(
process.execPath,
[
"-e",
`
const when = require(process.argv[1]);
process.once("uncaughtException", (error) => {
console.log(JSON.stringify({ name: error.name, message: error.message }));
});
let checks = 0;
when(() => ++checks === 1 ? false : Promise.resolve(false), () => {
throw new Error("unexpected callback");
});
`,
require.resolve("../"),
],
{ encoding: "utf8", timeout: 5000 },
);
assert.deepEqual(JSON.parse(output), {
name: "TypeError",
message: "condition must return a synchronous value",
});
});
29 changes: 15 additions & 14 deletions test/test-reentrancy.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const test = require("node:test");
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { setImmediate } = require("node:timers/promises");
const when = require("../");

test("clear inside a false predicate stops future checks", async (t) => {
beforeEach((t) => t.mock.timers.enable({ apis: ["setTimeout"] }));

test("clear inside a false predicate stops future checks", (t) => {
let checks = 0;
let calls = 0;
const handle = when(
Expand All @@ -16,15 +17,15 @@ test("clear inside a false predicate stops future checks", async (t) => {
);
t.after(() => handle.clear());

await setImmediate();
await setImmediate();
await setImmediate();
t.mock.timers.tick(10);
t.mock.timers.tick(10);
t.mock.timers.tick(10);

assert.equal(checks, 2);
assert.equal(calls, 0);
});

test("clear inside a truthy predicate suppresses its callback", async (t) => {
test("clear inside a truthy predicate suppresses its callback", (t) => {
let checks = 0;
let calls = 0;
const handle = when(
Expand All @@ -40,12 +41,12 @@ test("clear inside a truthy predicate suppresses its callback", async (t) => {
);
t.after(() => handle.clear());

await setImmediate();
t.mock.timers.tick(10);

assert.equal(calls, 0);
});

test("reset ignores a truthy result from the previous check", async (t) => {
test("reset ignores a truthy result from the previous check", (t) => {
let checks = 0;
let calls = 0;
const handle = when(
Expand All @@ -61,16 +62,16 @@ test("reset ignores a truthy result from the previous check", async (t) => {
);
t.after(() => handle.clear());

await setImmediate();
t.mock.timers.tick(10);

assert.equal(checks, 3);
assert.equal(calls, 0);
handle.setCondition(() => true);
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 1);
});

test("reset inside a false predicate leaves only one cancellable poll", async (t) => {
test("reset inside a false predicate leaves only one cancellable poll", (t) => {
let checks = 0;
let calls = 0;
const handle = when(
Expand All @@ -83,9 +84,9 @@ test("reset inside a false predicate leaves only one cancellable poll", async (t
);
t.after(() => handle.clear());

await setImmediate();
t.mock.timers.tick(10);
handle.clear();
await setImmediate();
t.mock.timers.tick(10);

assert.equal(checks, 3);
assert.equal(calls, 0);
Expand Down
13 changes: 7 additions & 6 deletions test/test-reset.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const test = require("node:test");
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { setImmediate } = require("node:timers/promises");
const when = require("../");

beforeEach((t) => t.mock.timers.enable({ apis: ["setTimeout"] }));

test("reset checks a cancelled controller synchronously", (t) => {
let ready = false;
let calls = 0;
Expand Down Expand Up @@ -32,7 +33,7 @@ test("reset checks a completed controller synchronously", (t) => {
assert.equal(calls, 2);
});

test("a callback can reset the controller after asynchronous success", async (t) => {
test("a callback can reset the controller after asynchronous success", (t) => {
let ready = false;
let calls = 0;
const handle = when(
Expand All @@ -48,11 +49,11 @@ test("a callback can reset the controller after asynchronous success", async (t)
t.after(() => handle.clear());

ready = true;
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 1);
ready = true;
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 2);
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 2);
});
19 changes: 10 additions & 9 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const test = require("node:test");
const { test, beforeEach } = require("node:test");
const assert = require("node:assert/strict");
const { setImmediate } = require("node:timers/promises");
const when = require("../");

beforeEach((t) => t.mock.timers.enable({ apis: ["setTimeout"] }));

test("calls the callback synchronously when the initial condition is truthy", (t) => {
let calls = 0;
const handle = when(
Expand All @@ -14,7 +15,7 @@ test("calls the callback synchronously when the initial condition is truthy", (t
assert.equal(calls, 1);
});

test("checks a false condition again and calls the callback once", async (t) => {
test("checks a false condition again and calls the callback once", (t) => {
let ready = false;
let calls = 0;
const handle = when(
Expand All @@ -25,13 +26,13 @@ test("checks a false condition again and calls the callback once", async (t) =>

assert.equal(calls, 0);
ready = true;
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 1);
await setImmediate();
t.mock.timers.tick(10);
assert.equal(calls, 1);
});

test("uses replacement condition and callback while active", async (t) => {
test("uses replacement condition and callback while active", (t) => {
let oldCalls = 0;
let newCalls = 0;
const handle = when(
Expand All @@ -42,13 +43,13 @@ test("uses replacement condition and callback while active", async (t) => {

handle.setCondition(() => true);
handle.setCode(() => newCalls++);
await setImmediate();
t.mock.timers.tick(10);

assert.equal(oldCalls, 0);
assert.equal(newCalls, 1);
});

test("setters do not restart a completed controller", async (t) => {
test("setters do not restart a completed controller", (t) => {
let calls = 0;
const handle = when(
() => true,
Expand All @@ -58,7 +59,7 @@ test("setters do not restart a completed controller", async (t) => {

handle.setCondition(() => true);
handle.setCode(() => calls++);
await setImmediate();
t.mock.timers.tick(10);

assert.equal(calls, 1);
});
Loading
Loading