Skip to content

Commit 4e24645

Browse files
test: improve code coverage with additional test cases
1 parent 2a304ae commit 4e24645

File tree

5 files changed

+361
-0
lines changed

5 files changed

+361
-0
lines changed

lib/__tests__/DefaultArgs.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
"use strict";
5+
6+
const AsyncParallelBailHook = require("../AsyncParallelBailHook");
7+
const AsyncParallelHook = require("../AsyncParallelHook");
8+
const AsyncSeriesBailHook = require("../AsyncSeriesBailHook");
9+
const AsyncSeriesHook = require("../AsyncSeriesHook");
10+
const AsyncSeriesLoopHook = require("../AsyncSeriesLoopHook");
11+
const AsyncSeriesWaterfallHook = require("../AsyncSeriesWaterfallHook");
12+
const SyncBailHook = require("../SyncBailHook");
13+
const SyncHook = require("../SyncHook");
14+
const SyncLoopHook = require("../SyncLoopHook");
15+
const SyncWaterfallHook = require("../SyncWaterfallHook");
16+
17+
describe("Hooks without explicit args", () => {
18+
it("should construct SyncHook without args", () => {
19+
const hook = new SyncHook();
20+
const mock = jest.fn();
21+
hook.tap("A", mock);
22+
hook.call();
23+
expect(mock).toHaveBeenCalledTimes(1);
24+
});
25+
26+
it("should construct SyncBailHook without args", () => {
27+
const hook = new SyncBailHook();
28+
hook.tap("A", () => "bailed");
29+
expect(hook.call()).toBe("bailed");
30+
});
31+
32+
it("should construct SyncLoopHook without args", () => {
33+
const hook = new SyncLoopHook();
34+
let count = 0;
35+
hook.tap("A", () => {
36+
if (count++ < 2) return true;
37+
});
38+
hook.call();
39+
expect(count).toBeGreaterThanOrEqual(3);
40+
});
41+
42+
it("should throw if SyncWaterfallHook is constructed without args", () => {
43+
expect(() => new SyncWaterfallHook()).toThrow(
44+
/Waterfall hooks must have at least one argument/
45+
);
46+
expect(() => new SyncWaterfallHook([])).toThrow(
47+
/Waterfall hooks must have at least one argument/
48+
);
49+
});
50+
51+
it("should throw if AsyncSeriesWaterfallHook is constructed without args", () => {
52+
expect(() => new AsyncSeriesWaterfallHook()).toThrow(
53+
/Waterfall hooks must have at least one argument/
54+
);
55+
expect(() => new AsyncSeriesWaterfallHook([])).toThrow(
56+
/Waterfall hooks must have at least one argument/
57+
);
58+
});
59+
60+
it("should construct AsyncParallelHook without args", async () => {
61+
const hook = new AsyncParallelHook();
62+
const mock = jest.fn((cb) => cb());
63+
hook.tapAsync("A", mock);
64+
await new Promise((resolve) => {
65+
hook.callAsync(() => resolve());
66+
});
67+
expect(mock).toHaveBeenCalledTimes(1);
68+
});
69+
70+
it("should construct AsyncParallelBailHook without args", async () => {
71+
const hook = new AsyncParallelBailHook();
72+
const mock = jest.fn((cb) => cb());
73+
hook.tapAsync("A", mock);
74+
await new Promise((resolve) => {
75+
hook.callAsync(() => resolve());
76+
});
77+
expect(mock).toHaveBeenCalledTimes(1);
78+
});
79+
80+
it("should construct AsyncSeriesHook without args", async () => {
81+
const hook = new AsyncSeriesHook();
82+
const mock = jest.fn((cb) => cb());
83+
hook.tapAsync("A", mock);
84+
await new Promise((resolve) => {
85+
hook.callAsync(() => resolve());
86+
});
87+
expect(mock).toHaveBeenCalledTimes(1);
88+
});
89+
90+
it("should construct AsyncSeriesBailHook without args", async () => {
91+
const hook = new AsyncSeriesBailHook();
92+
const mock = jest.fn((cb) => cb());
93+
hook.tapAsync("A", mock);
94+
await new Promise((resolve) => {
95+
hook.callAsync(() => resolve());
96+
});
97+
expect(mock).toHaveBeenCalledTimes(1);
98+
});
99+
100+
it("should construct AsyncSeriesLoopHook without args", () => {
101+
const hook = new AsyncSeriesLoopHook();
102+
let calls = 0;
103+
hook.tapAsync("A", (cb) => {
104+
calls++;
105+
cb();
106+
});
107+
return new Promise((resolve) => {
108+
hook.callAsync(() => resolve());
109+
}).then(() => {
110+
expect(calls).toBeGreaterThanOrEqual(1);
111+
});
112+
});
113+
});

lib/__tests__/Hook.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,84 @@
44
*/
55
"use strict";
66

7+
const Hook = require("../Hook");
78
const SyncHook = require("../SyncHook");
89

910
describe("Hook", () => {
11+
it("should throw when compile is not overridden", () => {
12+
const hook = new Hook(["arg"]);
13+
expect(() =>
14+
hook.compile({ taps: [], interceptors: [], args: [], type: "sync" })
15+
).toThrow(/Abstract: should be overridden/);
16+
});
17+
18+
it("should throw when tap options are not a string or object", () => {
19+
const hook = new SyncHook();
20+
expect(() => hook.tap(42, () => {})).toThrow(
21+
new Error("Invalid tap options")
22+
);
23+
expect(() => hook.tap(null, () => {})).toThrow(
24+
new Error("Invalid tap options")
25+
);
26+
expect(() => hook.tap(undefined, () => {})).toThrow(
27+
new Error("Invalid tap options")
28+
);
29+
expect(() => hook.tap(true, () => {})).toThrow(
30+
new Error("Invalid tap options")
31+
);
32+
});
33+
34+
it("should expose name/isUsed/intercept/withOptions from withOptions wrapper", () => {
35+
const hook = new SyncHook(["a"], "myHook");
36+
const wrapped = hook.withOptions({ stage: 10 });
37+
38+
expect(wrapped.name).toBe("myHook");
39+
expect(wrapped.isUsed()).toBe(false);
40+
41+
const interceptorCalls = [];
42+
wrapped.intercept({ call: (x) => interceptorCalls.push(x) });
43+
44+
const calls = [];
45+
wrapped.tap("A", (x) => calls.push(["A", x]));
46+
wrapped.tap({ name: "B" }, (x) => calls.push(["B", x]));
47+
48+
expect(wrapped.isUsed()).toBe(true);
49+
50+
hook.call(1);
51+
expect(calls).toEqual([
52+
["A", 1],
53+
["B", 1]
54+
]);
55+
expect(interceptorCalls).toEqual([1]);
56+
});
57+
58+
it("should allow nested withOptions to merge options", () => {
59+
const hook = new SyncHook();
60+
const nested = hook.withOptions({ stage: -5 }).withOptions({ before: "Z" });
61+
nested.tap("A", () => {});
62+
expect(hook.taps[0].stage).toBe(-5);
63+
expect(hook.taps[0].before).toBe("Z");
64+
});
65+
66+
it("should keep the tap options unchanged when an interceptor's register returns undefined", () => {
67+
const hook = new SyncHook();
68+
hook.intercept({ register: () => undefined });
69+
hook.tap("A", () => {});
70+
expect(hook.taps[0].name).toBe("A");
71+
});
72+
73+
it("should throw when options.name is missing entirely on an object tap", () => {
74+
const hook = new SyncHook();
75+
expect(() => hook.tap({}, () => {})).toThrow(
76+
new Error("Missing name for tap")
77+
);
78+
});
79+
80+
it("should accept the optional hook name argument in the Hook constructor", () => {
81+
const hook = new SyncHook(["a"], "namedHook");
82+
expect(hook.name).toBe("namedHook");
83+
});
84+
1085
it("should allow to insert hooks before others and in stages", () => {
1186
const hook = new SyncHook();
1287

lib/__tests__/HookMap.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
"use strict";
5+
6+
const HookMap = require("../HookMap");
7+
const SyncHook = require("../SyncHook");
8+
9+
describe("HookMap", () => {
10+
it("should return undefined from get when the key is unknown", () => {
11+
const map = new HookMap(() => new SyncHook());
12+
expect(map.get("missing")).toBeUndefined();
13+
});
14+
15+
it("should lazily create hooks through for(...) and cache them", () => {
16+
const factory = jest.fn(() => new SyncHook(["a"]));
17+
const map = new HookMap(factory, "myMap");
18+
19+
expect(map.name).toBe("myMap");
20+
21+
const hook1 = map.for("key1");
22+
const hook2 = map.for("key1");
23+
const hook3 = map.for("key2");
24+
25+
expect(hook1).toBe(hook2);
26+
expect(hook1).not.toBe(hook3);
27+
expect(factory).toHaveBeenCalledTimes(2);
28+
expect(factory).toHaveBeenNthCalledWith(1, "key1");
29+
expect(factory).toHaveBeenNthCalledWith(2, "key2");
30+
31+
expect(map.get("key1")).toBe(hook1);
32+
});
33+
34+
it("should apply interceptor factories when creating hooks", () => {
35+
const map = new HookMap(() => new SyncHook());
36+
const wrapped = new SyncHook();
37+
38+
map.intercept({
39+
factory: (key, hook) => {
40+
expect(key).toBe("foo");
41+
expect(hook).toBeDefined();
42+
return wrapped;
43+
}
44+
});
45+
46+
expect(map.for("foo")).toBe(wrapped);
47+
});
48+
49+
it("should default the interceptor factory to pass-through", () => {
50+
const map = new HookMap(() => new SyncHook());
51+
map.intercept({});
52+
const hook = map.for("bar");
53+
expect(hook).toBeDefined();
54+
expect(map.get("bar")).toBe(hook);
55+
});
56+
57+
it("should forward deprecated tap helpers to the underlying hook", () => {
58+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
59+
const map = new HookMap(() => new SyncHook(["a"]));
60+
61+
const syncMock = jest.fn();
62+
map.tap("k", "plugin-sync", syncMock);
63+
map.for("k").call(1);
64+
expect(syncMock).toHaveBeenCalledWith(1);
65+
66+
const asyncMap = new HookMap(
67+
() => new (require("../AsyncSeriesHook"))(["a"])
68+
);
69+
const asyncMock = jest.fn((_a, cb) => cb());
70+
asyncMap.tapAsync("k", "plugin-async", asyncMock);
71+
72+
return new Promise((resolve) => {
73+
asyncMap.for("k").callAsync(2, () => {
74+
expect(asyncMock).toHaveBeenCalled();
75+
76+
const promiseMap = new HookMap(
77+
() => new (require("../AsyncSeriesHook"))(["a"])
78+
);
79+
const promiseMock = jest.fn(() => Promise.resolve());
80+
promiseMap.tapPromise("k", "plugin-promise", promiseMock);
81+
82+
promiseMap
83+
.for("k")
84+
.promise(3)
85+
.then(() => {
86+
expect(promiseMock).toHaveBeenCalledWith(3);
87+
warn.mockRestore();
88+
resolve();
89+
});
90+
});
91+
});
92+
});
93+
});

lib/__tests__/SyncLoopHook.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
"use strict";
5+
6+
const SyncLoopHook = require("../SyncLoopHook");
7+
8+
describe("SyncLoopHook", () => {
9+
it("should throw on tapAsync", () => {
10+
const hook = new SyncLoopHook(["a"]);
11+
expect(() => hook.tapAsync("A", () => {})).toThrow(
12+
/tapAsync is not supported on a SyncLoopHook/
13+
);
14+
});
15+
16+
it("should throw on tapPromise", () => {
17+
const hook = new SyncLoopHook(["a"]);
18+
expect(() => hook.tapPromise("A", () => {})).toThrow(
19+
/tapPromise is not supported on a SyncLoopHook/
20+
);
21+
});
22+
23+
it("should loop through taps until all return undefined", () => {
24+
const hook = new SyncLoopHook(["counter"]);
25+
let firstCalls = 0;
26+
let secondCalls = 0;
27+
hook.tap("first", () => {
28+
if (firstCalls++ < 2) return true;
29+
});
30+
hook.tap("second", () => {
31+
if (secondCalls++ < 1) return true;
32+
});
33+
hook.call();
34+
expect(firstCalls).toBeGreaterThanOrEqual(3);
35+
expect(secondCalls).toBeGreaterThanOrEqual(2);
36+
});
37+
38+
it("should be callable without arguments using default args", () => {
39+
const hook = new SyncLoopHook();
40+
const mock = jest.fn();
41+
hook.tap("A", mock);
42+
hook.call();
43+
expect(mock).toHaveBeenCalledTimes(1);
44+
});
45+
});

lib/__tests__/UtilBrowser.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
"use strict";
5+
6+
const utilBrowser = require("../util-browser");
7+
8+
describe("util-browser", () => {
9+
it("should warn only once and forward arguments", () => {
10+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
11+
const inner = jest.fn((...args) => args.reduce((a, b) => a + b, 0));
12+
const wrapped = utilBrowser.deprecate(inner, "do not use");
13+
14+
expect(wrapped(1, 2, 3)).toBe(6);
15+
expect(wrapped(4, 5)).toBe(9);
16+
17+
expect(inner).toHaveBeenCalledTimes(2);
18+
expect(warn).toHaveBeenCalledTimes(1);
19+
expect(warn).toHaveBeenCalledWith("DeprecationWarning: do not use");
20+
21+
warn.mockRestore();
22+
});
23+
24+
it("should preserve `this` when invoked as a method", () => {
25+
const warn = jest.spyOn(console, "warn").mockImplementation(() => {});
26+
const obj = {
27+
value: 42,
28+
run: utilBrowser.deprecate(function run() {
29+
return this.value;
30+
}, "method deprecated")
31+
};
32+
expect(obj.run()).toBe(42);
33+
warn.mockRestore();
34+
});
35+
});

0 commit comments

Comments
 (0)