hooks: Add unit tests for `callFirst()`, `aCallFirst()`

pull/4698/head
Richard Hansen 2021-02-01 18:31:50 -05:00 committed by John McLear
parent 6f30ea7c38
commit ba0544ea9e
1 changed files with 240 additions and 0 deletions

View File

@ -389,6 +389,90 @@ describe(__filename, function () {
});
});
describe('hooks.callFirst', function () {
it('no registered hooks (undefined) -> []', async function () {
delete plugins.hooks.testHook;
assert.deepEqual(hooks.callFirst(hookName), []);
});
it('no registered hooks (empty list) -> []', async function () {
testHooks.length = 0;
assert.deepEqual(hooks.callFirst(hookName), []);
});
it('passes hook name => {}', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
hooks.callFirst(hookName);
});
it('undefined context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hooks.callFirst(hookName);
});
it('null context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
hooks.callFirst(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
hooks.callFirst(hookName, wantContext);
});
it('predicate never satisfied -> calls all in order', async function () {
const gotCalls = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
hook.hook_fn = () => { gotCalls.push(i); };
testHooks.push(hook);
}
assert.deepEqual(hooks.callFirst(hookName), []);
assert.deepEqual(gotCalls, [0, 1, 2]);
});
it('stops when predicate is satisfied', async function () {
testHooks.length = 0;
testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
});
it('skips values that do not satisfy predicate (undefined)', async function () {
testHooks.length = 0;
testHooks.push(makeHook(), makeHook('val1'));
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
});
it('skips values that do not satisfy predicate (empty list)', async function () {
testHooks.length = 0;
testHooks.push(makeHook([]), makeHook('val1'));
assert.deepEqual(hooks.callFirst(hookName), ['val1']);
});
it('null satisifes the predicate', async function () {
testHooks.length = 0;
testHooks.push(makeHook(null), makeHook('val1'));
assert.deepEqual(hooks.callFirst(hookName), [null]);
});
it('non-empty arrays are returned unmodified', async function () {
const want = ['val1'];
testHooks.length = 0;
testHooks.push(makeHook(want), makeHook(['val2']));
assert.equal(hooks.callFirst(hookName), want); // Note: *NOT* deepEqual!
});
it('value can be passed via callback', async function () {
const want = {};
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
const got = hooks.callFirst(hookName);
assert.deepEqual(got, [want]);
assert.equal(got[0], want); // Note: *NOT* deepEqual!
});
});
describe('callHookFnAsync', function () {
const callHookFnAsync = hooks.exportedForTestingOnly.callHookFnAsync; // Convenience shorthand.
@ -883,4 +967,160 @@ describe(__filename, function () {
});
});
});
describe('hooks.aCallFirst', function () {
it('no registered hooks (undefined) -> []', async function () {
delete plugins.hooks.testHook;
assert.deepEqual(await hooks.aCallFirst(hookName), []);
});
it('no registered hooks (empty list) -> []', async function () {
testHooks.length = 0;
assert.deepEqual(await hooks.aCallFirst(hookName), []);
});
it('passes hook name => {}', async function () {
hook.hook_fn = (hn) => { assert.equal(hn, hookName); };
await hooks.aCallFirst(hookName);
});
it('undefined context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
await hooks.aCallFirst(hookName);
});
it('null context => {}', async function () {
hook.hook_fn = (hn, ctx) => { assert.deepEqual(ctx, {}); };
await hooks.aCallFirst(hookName, null);
});
it('context unmodified', async function () {
const wantContext = {};
hook.hook_fn = (hn, ctx) => { assert.equal(ctx, wantContext); };
await hooks.aCallFirst(hookName, wantContext);
});
it('default predicate: predicate never satisfied -> calls all in order', async function () {
const gotCalls = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
hook.hook_fn = () => { gotCalls.push(i); };
testHooks.push(hook);
}
assert.deepEqual(await hooks.aCallFirst(hookName), []);
assert.deepEqual(gotCalls, [0, 1, 2]);
});
it('calls hook functions serially', async function () {
const gotCalls = [];
testHooks.length = 0;
for (let i = 0; i < 3; i++) {
const hook = makeHook();
hook.hook_fn = async () => {
gotCalls.push(i);
// Check gotCalls asynchronously to ensure that the next hook function does not start
// executing before this hook function has resolved.
return await new Promise((resolve) => {
setImmediate(() => {
assert.deepEqual(gotCalls, [...Array(i + 1).keys()]);
resolve();
});
});
};
testHooks.push(hook);
}
assert.deepEqual(await hooks.aCallFirst(hookName), []);
assert.deepEqual(gotCalls, [0, 1, 2]);
});
it('default predicate: stops when satisfied', async function () {
testHooks.length = 0;
testHooks.push(makeHook(), makeHook('val1'), makeHook('val2'));
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
});
it('default predicate: skips values that do not satisfy (undefined)', async function () {
testHooks.length = 0;
testHooks.push(makeHook(), makeHook('val1'));
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
});
it('default predicate: skips values that do not satisfy (empty list)', async function () {
testHooks.length = 0;
testHooks.push(makeHook([]), makeHook('val1'));
assert.deepEqual(await hooks.aCallFirst(hookName), ['val1']);
});
it('default predicate: null satisifes', async function () {
testHooks.length = 0;
testHooks.push(makeHook(null), makeHook('val1'));
assert.deepEqual(await hooks.aCallFirst(hookName), [null]);
});
it('custom predicate: called for each hook function', async function () {
testHooks.length = 0;
testHooks.push(makeHook(0), makeHook(1), makeHook(2));
let got = 0;
await hooks.aCallFirst(hookName, null, null, (val) => { ++got; return false; });
assert.equal(got, 3);
});
it('custom predicate: boolean false/true continues/stops iteration', async function () {
testHooks.length = 0;
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
let nCall = 0;
const predicate = (val) => {
assert.deepEqual(val, [++nCall]);
return nCall === 2;
};
assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
assert.equal(nCall, 2);
});
it('custom predicate: non-boolean falsy/truthy continues/stops iteration', async function () {
testHooks.length = 0;
testHooks.push(makeHook(1), makeHook(2), makeHook(3));
let nCall = 0;
const predicate = (val) => {
assert.deepEqual(val, [++nCall]);
return nCall === 2 ? {} : null;
};
assert.deepEqual(await hooks.aCallFirst(hookName, null, null, predicate), [2]);
assert.equal(nCall, 2);
});
it('custom predicate: array value passed unmodified to predicate', async function () {
const want = [0];
hook.hook_fn = () => want;
const predicate = (got) => { assert.equal(got, want); }; // Note: *NOT* deepEqual!
await hooks.aCallFirst(hookName, null, null, predicate);
});
it('custom predicate: normalized value passed to predicate (undefined)', async function () {
const predicate = (got) => { assert.deepEqual(got, []); };
await hooks.aCallFirst(hookName, null, null, predicate);
});
it('custom predicate: normalized value passed to predicate (null)', async function () {
hook.hook_fn = () => null;
const predicate = (got) => { assert.deepEqual(got, [null]); };
await hooks.aCallFirst(hookName, null, null, predicate);
});
it('non-empty arrays are returned unmodified', async function () {
const want = ['val1'];
testHooks.length = 0;
testHooks.push(makeHook(want), makeHook(['val2']));
assert.equal(await hooks.aCallFirst(hookName), want); // Note: *NOT* deepEqual!
});
it('value can be passed via callback', async function () {
const want = {};
hook.hook_fn = (hn, ctx, cb) => { cb(want); };
const got = await hooks.aCallFirst(hookName);
assert.deepEqual(got, [want]);
assert.equal(got[0], want); // Note: *NOT* deepEqual!
});
});
});