1 | 'use strict';
|
2 |
|
3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
4 |
|
5 | var tap = _interopDefault(require('zora-tap-reporter'));
|
6 | var deepEqual = _interopDefault(require('deep-equal'));
|
7 |
|
8 | const getAssertionLocation = () => {
|
9 | const err = new Error();
|
10 | const stack = (err.stack || '').split('\n');
|
11 | return (stack[3] || '').trim().replace(/^at/i, '');
|
12 | };
|
13 | const assertMethodHook = fn => function (...args) {
|
14 | const assertResult = fn(...args);
|
15 |
|
16 | if (assertResult.pass === false) {
|
17 | assertResult.at = getAssertionLocation();
|
18 | }
|
19 |
|
20 | this.collect(assertResult);
|
21 | return assertResult;
|
22 | };
|
23 |
|
24 | const Assertion = {
|
25 | ok: assertMethodHook((val, message = 'should be truthy') => ({
|
26 | pass: Boolean(val),
|
27 | actual: val,
|
28 | expected: true,
|
29 | message,
|
30 | operator: 'ok'
|
31 | })),
|
32 | deepEqual: assertMethodHook((actual, expected, message = 'should be equivalent') => ({
|
33 | pass: deepEqual(actual, expected),
|
34 | actual,
|
35 | expected,
|
36 | message,
|
37 | operator: 'deepEqual'
|
38 | })),
|
39 | equal: assertMethodHook((actual, expected, message = 'should be equal') => ({
|
40 | pass: actual === expected,
|
41 | actual,
|
42 | expected,
|
43 | message,
|
44 | operator: 'equal'
|
45 | })),
|
46 | notOk: assertMethodHook((val, message = 'should not be truthy') => ({
|
47 | pass: !val,
|
48 | expected: false,
|
49 | actual: val,
|
50 | message,
|
51 | operator: 'notOk'
|
52 | })),
|
53 | notDeepEqual: assertMethodHook((actual, expected, message = 'should not be equivalent') => ({
|
54 | pass: !deepEqual(actual, expected),
|
55 | actual,
|
56 | expected,
|
57 | message,
|
58 | operator: 'notDeepEqual'
|
59 | })),
|
60 | notEqual: assertMethodHook((actual, expected, message = 'should not be equal') => ({
|
61 | pass: actual !== expected,
|
62 | actual,
|
63 | expected,
|
64 | message,
|
65 | operator: 'notEqual'
|
66 | })),
|
67 | throws: assertMethodHook((func, expected, message) => {
|
68 | let caught;
|
69 | let pass;
|
70 | let actual;
|
71 | if (typeof expected === 'string') {
|
72 | [expected, message] = [message, expected];
|
73 | }
|
74 | try {
|
75 | func();
|
76 | } catch (err) {
|
77 | caught = {error: err};
|
78 | }
|
79 | pass = caught !== undefined;
|
80 | actual = caught && caught.error;
|
81 | if (expected instanceof RegExp) {
|
82 | pass = expected.test(actual) || expected.test(actual && actual.message);
|
83 | expected = String(expected);
|
84 | } else if (typeof expected === 'function' && caught) {
|
85 | pass = actual instanceof expected;
|
86 | actual = actual.constructor;
|
87 | }
|
88 | return {
|
89 | pass,
|
90 | expected,
|
91 | actual,
|
92 | operator: 'throws',
|
93 | message: message || 'should throw'
|
94 | };
|
95 | }),
|
96 | doesNotThrow: assertMethodHook((func, expected, message) => {
|
97 | let caught;
|
98 | if (typeof expected === 'string') {
|
99 | [expected, message] = [message, expected];
|
100 | }
|
101 | try {
|
102 | func();
|
103 | } catch (err) {
|
104 | caught = {error: err};
|
105 | }
|
106 | return {
|
107 | pass: caught === undefined,
|
108 | expected: 'no thrown error',
|
109 | actual: caught && caught.error,
|
110 | operator: 'doesNotThrow',
|
111 | message: message || 'should not throw'
|
112 | };
|
113 | }),
|
114 | fail: assertMethodHook((message = 'fail called') => ({
|
115 | pass: false,
|
116 | actual: 'fail called',
|
117 | expected: 'fail not called',
|
118 | message,
|
119 | operator: 'fail'
|
120 | }))
|
121 | };
|
122 |
|
123 | var assert = collect => Object.create(Assertion, {collect: {value: collect}});
|
124 |
|
125 | const noop = () => {};
|
126 |
|
127 | const skip = description => test('SKIPPED - ' + description, noop);
|
128 |
|
129 | const Test = {
|
130 | async run() {
|
131 | const collect = assertion => this.items.push(assertion);
|
132 | const start = Date.now();
|
133 | await Promise.resolve(this.spec(assert(collect)));
|
134 | const executionTime = Date.now() - start;
|
135 | return Object.assign(this, {
|
136 | executionTime
|
137 | });
|
138 | },
|
139 | skip() {
|
140 | return skip(this.description);
|
141 | }
|
142 | };
|
143 |
|
144 | function test(description, spec, {only = false} = {}) {
|
145 | return Object.create(Test, {
|
146 | items: {value: []},
|
147 | only: {value: only},
|
148 | spec: {value: spec},
|
149 | description: {value: description}
|
150 | });
|
151 | }
|
152 |
|
153 |
|
154 | const onNextTick = val => new Promise(resolve => setTimeout(() => resolve(val), 0));
|
155 |
|
156 | const PlanProto = {
|
157 | [Symbol.iterator]() {
|
158 | return this.items[Symbol.iterator]();
|
159 | },
|
160 | test(description, spec, opts) {
|
161 | if (!spec && description.test) {
|
162 |
|
163 | this.items.push(...description);
|
164 | } else {
|
165 | this.items.push(test(description, spec, opts));
|
166 | }
|
167 | return this;
|
168 | },
|
169 | only(description, spec) {
|
170 | return this.test(description, spec, {only: true});
|
171 | },
|
172 | skip(description, spec) {
|
173 | if (!spec && description.test) {
|
174 |
|
175 | for (const t of description) {
|
176 | this.items.push(t.skip());
|
177 | }
|
178 | } else {
|
179 | this.items.push(skip(description));
|
180 | }
|
181 | return this;
|
182 | }
|
183 | };
|
184 |
|
185 | const runnify = fn => async function (sink = tap()) {
|
186 | const sinkIterator = typeof sink[Symbol.iterator] === 'function' ?
|
187 | sink[Symbol.iterator]() :
|
188 | sink();
|
189 | sinkIterator.next();
|
190 | try {
|
191 | const hasOnly = this.items.some(t => t.only);
|
192 | const tests = hasOnly ? this.items.map(t => t.only ? t : t.skip()) : this.items;
|
193 | await fn(tests, sinkIterator);
|
194 | } catch (err) {
|
195 | sinkIterator.throw(err);
|
196 | } finally {
|
197 | sinkIterator.return();
|
198 | }
|
199 | };
|
200 |
|
201 | function factory({sequence = false} = {sequence: false}) {
|
202 |
|
203 | const exec = sequence === true ? async (tests, sinkIterator) => {
|
204 | for (const t of tests) {
|
205 | const result = await onNextTick(t.run());
|
206 | sinkIterator.next(result);
|
207 | }
|
208 | } : async (tests, sinkIterator) => {
|
209 | const runningTests = tests.map(t => t.run());
|
210 | for (const r of runningTests) {
|
211 | const executedTest = await onNextTick(r);
|
212 | sinkIterator.next(executedTest);
|
213 | }
|
214 | };
|
215 |
|
216 |
|
217 | return Object.assign(Object.create(PlanProto, {
|
218 | items: {value: []}, length: {
|
219 | get() {
|
220 | return this.items.length;
|
221 | }
|
222 | }
|
223 | }), {
|
224 | run: runnify(exec)
|
225 | });
|
226 | }
|
227 |
|
228 | module.exports = factory;
|