/
create-chain.js
116 lines (100 loc) 路 4.25 KB
/
create-chain.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
'use strict';
const chainRegistry = new WeakMap();
function startChain(name, call, defaults) {
const fn = (...args) => {
call(Object.assign({}, defaults), args);
};
Object.defineProperty(fn, 'name', {value: name});
chainRegistry.set(fn, {call, defaults, fullName: name});
return fn;
}
function extendChain(prev, name, flag) {
if (!flag) {
flag = name;
}
const fn = (...args) => {
callWithFlag(prev, flag, args);
};
const fullName = `${chainRegistry.get(prev).fullName}.${name}`;
Object.defineProperty(fn, 'name', {value: fullName});
prev[name] = fn;
chainRegistry.set(fn, {flag, fullName, prev});
return fn;
}
function callWithFlag(prev, flag, args) {
const combinedFlags = {[flag]: true};
do {
const step = chainRegistry.get(prev);
if (step.call) {
step.call(Object.assign({}, step.defaults, combinedFlags), args);
prev = null;
} else {
combinedFlags[step.flag] = true;
prev = step.prev;
}
} while (prev);
}
function createHookChain(hook, isAfterHook) {
// Hook chaining rules:
// * `always` comes immediately after "after hooks"
// * `skip` must come at the end
// * no `only`
// * no repeating
extendChain(hook, 'cb', 'callback');
extendChain(hook, 'skip', 'skipped');
extendChain(hook.cb, 'skip', 'skipped');
if (isAfterHook) {
extendChain(hook, 'always');
extendChain(hook.always, 'cb', 'callback');
extendChain(hook.always, 'skip', 'skipped');
extendChain(hook.always.cb, 'skip', 'skipped');
}
return hook;
}
function createChain(fn, defaults, meta) {
// Test chaining rules:
// * `serial` must come at the start
// * `only` and `skip` must come at the end
// * `failing` must come at the end, but can be followed by `only` and `skip`
// * `only` and `skip` cannot be chained together
// * no repeating
const root = startChain('test', fn, Object.assign({}, defaults, {type: 'test'}));
extendChain(root, 'cb', 'callback');
extendChain(root, 'failing');
extendChain(root, 'only', 'exclusive');
extendChain(root, 'serial');
extendChain(root, 'skip', 'skipped');
extendChain(root.cb, 'failing');
extendChain(root.cb, 'only', 'exclusive');
extendChain(root.cb, 'skip', 'skipped');
extendChain(root.cb.failing, 'only', 'exclusive');
extendChain(root.cb.failing, 'skip', 'skipped');
extendChain(root.failing, 'only', 'exclusive');
extendChain(root.failing, 'skip', 'skipped');
extendChain(root.serial, 'cb', 'callback');
extendChain(root.serial, 'failing');
extendChain(root.serial, 'only', 'exclusive');
extendChain(root.serial, 'skip', 'skipped');
extendChain(root.serial.cb, 'failing');
extendChain(root.serial.cb, 'only', 'exclusive');
extendChain(root.serial.cb, 'skip', 'skipped');
extendChain(root.serial.cb.failing, 'only', 'exclusive');
extendChain(root.serial.cb.failing, 'skip', 'skipped');
extendChain(root.serial.failing, 'only', 'exclusive');
extendChain(root.serial.failing, 'skip', 'skipped');
root.after = createHookChain(startChain('test.after', fn, Object.assign({}, defaults, {type: 'after'})), true);
root.afterEach = createHookChain(startChain('test.afterEach', fn, Object.assign({}, defaults, {type: 'afterEach'})), true);
root.before = createHookChain(startChain('test.before', fn, Object.assign({}, defaults, {type: 'before'})), false);
root.beforeEach = createHookChain(startChain('test.beforeEach', fn, Object.assign({}, defaults, {type: 'beforeEach'})), false);
root.serial.after = createHookChain(startChain('test.after', fn, Object.assign({}, defaults, {serial: true, type: 'after'})), true);
root.serial.afterEach = createHookChain(startChain('test.afterEach', fn, Object.assign({}, defaults, {serial: true, type: 'afterEach'})), true);
root.serial.before = createHookChain(startChain('test.before', fn, Object.assign({}, defaults, {serial: true, type: 'before'})), false);
root.serial.beforeEach = createHookChain(startChain('test.beforeEach', fn, Object.assign({}, defaults, {serial: true, type: 'beforeEach'})), false);
// "todo" tests cannot be chained. Allow todo tests to be flagged as needing
// to be serial.
root.todo = startChain('test.todo', fn, Object.assign({}, defaults, {type: 'test', todo: true}));
root.serial.todo = startChain('test.serial.todo', fn, Object.assign({}, defaults, {serial: true, type: 'test', todo: true}));
root.meta = meta;
return root;
}
module.exports = createChain;