1 | 'use strict';
|
2 |
|
3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
4 |
|
5 | var deepEqual = _interopDefault(require('deep-equal'));
|
6 |
|
7 | const getAssertionLocation = () => {
|
8 | const err = new Error();
|
9 | const stack = (err.stack || '').split('\n');
|
10 | return (stack[3] || '').trim().replace(/^at/i, '');
|
11 | };
|
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, description = 'should be truthy') => ({
|
26 | pass: Boolean(val),
|
27 | actual: val,
|
28 | expected: true,
|
29 | description,
|
30 | operator: 'ok'
|
31 | })),
|
32 | deepEqual: assertMethodHook((actual, expected, description = 'should be equivalent') => ({
|
33 | pass: deepEqual(actual, expected),
|
34 | actual,
|
35 | expected,
|
36 | description,
|
37 | operator: 'deepEqual'
|
38 | })),
|
39 | equal: assertMethodHook((actual, expected, description = 'should be equal') => ({
|
40 | pass: actual === expected,
|
41 | actual,
|
42 | expected,
|
43 | description,
|
44 | operator: 'equal'
|
45 | })),
|
46 | notOk: assertMethodHook((val, description = 'should not be truthy') => ({
|
47 | pass: !val,
|
48 | expected: false,
|
49 | actual: val,
|
50 | description,
|
51 | operator: 'notOk'
|
52 | })),
|
53 | notDeepEqual: assertMethodHook((actual, expected, description = 'should not be equivalent') => ({
|
54 | pass: !deepEqual(actual, expected),
|
55 | actual,
|
56 | expected,
|
57 | description,
|
58 | operator: 'notDeepEqual'
|
59 | })),
|
60 | notEqual: assertMethodHook((actual, expected, description = 'should not be equal') => ({
|
61 | pass: actual !== expected,
|
62 | actual,
|
63 | expected,
|
64 | description,
|
65 | operator: 'notEqual'
|
66 | })),
|
67 | throws: assertMethodHook((func, expected, description) => {
|
68 | let caught;
|
69 | let pass;
|
70 | let actual;
|
71 | if (typeof expected === 'string') {
|
72 | [expected, description] = [description, 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 | description: description || 'should throw'
|
94 | };
|
95 | }),
|
96 | doesNotThrow: assertMethodHook((func, expected, description) => {
|
97 | let caught;
|
98 | if (typeof expected === 'string') {
|
99 | [expected, description] = [description, 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 | description: description || 'should not throw'
|
112 | };
|
113 | }),
|
114 | fail: assertMethodHook((description = 'fail called') => ({
|
115 | pass: false,
|
116 | actual: 'fail called',
|
117 | expected: 'fail not called',
|
118 | description,
|
119 | operator: 'fail'
|
120 | }))
|
121 | };
|
122 |
|
123 | var assert = (collect, test) => Object.assign(
|
124 | Object.create(Assertion, {collect: {value: collect}}), {
|
125 | async test(description, spec) {
|
126 |
|
127 | return test(description, spec).task;
|
128 | }
|
129 | });
|
130 |
|
131 | const tester = (collect, {offset = 0} = {}) => (description, spec) => {
|
132 | const buffer = [{type: 'title', data: description, offset}];
|
133 | const result = {count: 0, pass: true, description, spec};
|
134 | let done = false;
|
135 |
|
136 | const createAssertion = item => {
|
137 | result.pass = result.pass && item.pass;
|
138 | return {type: 'assert', data: item, offset};
|
139 | };
|
140 |
|
141 | const collector = item => {
|
142 | result.count++;
|
143 | item.id = result.count;
|
144 | if (item[Symbol.asyncIterator] === undefined) {
|
145 |
|
146 | buffer.push(createAssertion(item));
|
147 | } else {
|
148 |
|
149 | buffer.push(item);
|
150 | }
|
151 | };
|
152 |
|
153 | const handleDelegate = async delegate => {
|
154 | const {value, done} = await delegate.next();
|
155 |
|
156 |
|
157 | if (done === true) {
|
158 | const {executionTime, pass, description} = value;
|
159 | const subTestAssertion = Object.assign(createAssertion({
|
160 | pass,
|
161 | description,
|
162 | id: delegate.id,
|
163 | executionTime
|
164 | }), {type: 'testAssert'});
|
165 | buffer.shift();
|
166 | buffer.unshift(subTestAssertion);
|
167 | return instance.next();
|
168 | }
|
169 | return {value, done};
|
170 | };
|
171 |
|
172 | const subTest = tester(collector, {offset: offset + 1});
|
173 |
|
174 | const start = Date.now();
|
175 |
|
176 | const assertFn = assert(collector, subTest);
|
177 | const task = new Promise(resolve => resolve(spec(assertFn)))
|
178 | .then(() => {
|
179 |
|
180 | result.executionTime = Date.now() - start;
|
181 | buffer.push({type: 'plan', data: {start: 1, end: result.count}, offset});
|
182 | buffer.push({type: 'time', data: result.executionTime, offset});
|
183 | done = true;
|
184 | return result;
|
185 | })
|
186 | .catch(err => {
|
187 |
|
188 | buffer.push({type: 'assert', data: {pass: false, description}});
|
189 | buffer.push({type: 'comment', data: 'Unhandled exception'});
|
190 | buffer.push({type: 'bailout', data: err, offset});
|
191 | done = true;
|
192 | });
|
193 |
|
194 | const instance = {
|
195 | test: subTest,
|
196 | task,
|
197 | [Symbol.asyncIterator]() {
|
198 | return this;
|
199 | },
|
200 | async next() {
|
201 | if (buffer.length === 0) {
|
202 | if (done === true) {
|
203 | return {done: true, value: result};
|
204 | }
|
205 |
|
206 | await task;
|
207 | return this.next();
|
208 | }
|
209 |
|
210 | const next = buffer[0];
|
211 |
|
212 |
|
213 | if (next[Symbol.asyncIterator] !== undefined) {
|
214 | return handleDelegate(next);
|
215 | }
|
216 |
|
217 | return {value: buffer.shift(), done: false};
|
218 | }
|
219 | };
|
220 |
|
221 |
|
222 | collect(instance);
|
223 |
|
224 | return instance;
|
225 | };
|
226 |
|
227 | const print = (message, offset = 0) => {
|
228 | console.log(message.padStart(message.length + (offset * 4)));
|
229 | };
|
230 |
|
231 | const toYaml = print => (obj, offset = 0) => {
|
232 | for (const [prop, value] of Object.entries(obj)) {
|
233 | print(`${prop}: ${JSON.stringify(value)}`, offset + 0.5);
|
234 | }
|
235 | };
|
236 |
|
237 | const tap = print => {
|
238 | const yaml = toYaml(print);
|
239 | return {
|
240 | version(version = 13) {
|
241 | print(`TAP version ${version}`);
|
242 | },
|
243 | title(value, offset = 0) {
|
244 | const message = offset > 0 ? `Subtest: ${value}` : value;
|
245 | this.comment(message, offset);
|
246 | },
|
247 | assert(value, offset = 0) {
|
248 | const {pass, description, id, executionTime, expected = '', actual = '', at = '', operator = ''} = value;
|
249 | const label = pass === true ? 'ok' : 'not ok';
|
250 | print(`${label} ${id} - ${description}${executionTime ? ` # time=${executionTime}ms` : ''}`, offset);
|
251 | if (pass === false && value.operator) {
|
252 | print('---', offset + 0.5);
|
253 | yaml({expected, actual, at, operator}, offset);
|
254 | print('...', offset + 0.5);
|
255 | }
|
256 | },
|
257 | plan(value, offset = 0) {
|
258 | print(`1..${value.end}`, offset);
|
259 | },
|
260 | time(value, offset = 0) {
|
261 | this.comment(`time=${value}ms`, offset);
|
262 | },
|
263 | comment(value, offset = 0) {
|
264 | print(`# ${value}`, offset);
|
265 | },
|
266 | bailout(value = 'Unhandled exception') {
|
267 | print(`Bail out! ${value}`);
|
268 | },
|
269 | testAssert(value, offset = 0) {
|
270 | return this.assert(value, offset);
|
271 | }
|
272 | };
|
273 | };
|
274 |
|
275 | var tap$1 = (printFn = print) => {
|
276 | const reporter = tap(printFn);
|
277 | return (toPrint = {}) => {
|
278 | const {data, type, offset = 0} = toPrint;
|
279 | if (typeof reporter[type] === 'function') {
|
280 | reporter[type](data, offset);
|
281 | }
|
282 |
|
283 | };
|
284 | };
|
285 |
|
286 |
|
287 |
|
288 |
|
289 | const asyncIterator = behavior => Object.assign({
|
290 | [Symbol.asyncIterator]() {
|
291 | return this;
|
292 | }
|
293 | }, behavior);
|
294 |
|
295 | const filter = predicate => iterator => asyncIterator({
|
296 | async next() {
|
297 | const {done, value} = await iterator.next();
|
298 |
|
299 | if (done === true) {
|
300 | return {done};
|
301 | }
|
302 |
|
303 | if (!predicate(value)) {
|
304 | return this.next();
|
305 | }
|
306 |
|
307 | return {done, value};
|
308 | }
|
309 | });
|
310 |
|
311 | const map = mapFn => iterator => asyncIterator({
|
312 | [Symbol.asyncIterator]() {
|
313 | return this;
|
314 | },
|
315 | async next() {
|
316 | const {done, value} = await iterator.next();
|
317 | if (done === true) {
|
318 | return {done};
|
319 | }
|
320 | return {done, value: mapFn(value)};
|
321 | }
|
322 | });
|
323 |
|
324 | const stream = asyncIterator => Object.assign(asyncIterator, {
|
325 | map(fn) {
|
326 | return stream(map(fn)(asyncIterator));
|
327 | },
|
328 | filter(fn) {
|
329 | return stream(filter(fn)(asyncIterator));
|
330 | }
|
331 | });
|
332 |
|
333 | const combine = (...iterators) => {
|
334 | const [...pending] = iterators;
|
335 | let current = pending.shift();
|
336 |
|
337 | return asyncIterator({
|
338 | async next() {
|
339 | if (current === undefined) {
|
340 | return {done: true};
|
341 | }
|
342 |
|
343 | const {done, value} = await current.next();
|
344 |
|
345 | if (done === true) {
|
346 | current = pending.shift();
|
347 | return this.next();
|
348 | }
|
349 |
|
350 | return {done, value};
|
351 | }
|
352 | });
|
353 | };
|
354 |
|
355 | let flatten = true;
|
356 | const tests = [];
|
357 | const test = tester(t => tests.push(t));
|
358 |
|
359 |
|
360 | const subTest = (test('Root', () => {})).test;
|
361 | test.test = (description, spec) => {
|
362 | flatten = false;
|
363 | return subTest(description, spec);
|
364 | };
|
365 |
|
366 | const start = async ({reporter = tap$1()} = {}) => {
|
367 | let count = 0;
|
368 | let failure = 0;
|
369 | reporter({type: 'version', data: 13});
|
370 |
|
371 |
|
372 | await tests[0].next();
|
373 |
|
374 | let outputStream = stream(combine(...tests));
|
375 | outputStream = flatten ? outputStream
|
376 | .filter(({type}) => type !== 'testAssert')
|
377 | .map(item => Object.assign(item, {offset: 0})) :
|
378 | outputStream;
|
379 |
|
380 | const filterOutAtRootLevel = ['plan', 'time'];
|
381 | outputStream = outputStream
|
382 | .filter(item => item.offset > 0 || !filterOutAtRootLevel.includes(item.type))
|
383 | .map(item => {
|
384 | if (item.offset > 0 || (item.type !== 'assert' && item.type !== 'testAssert')) {
|
385 | return item;
|
386 | }
|
387 |
|
388 | count++;
|
389 | item.data.id = count;
|
390 | failure += item.data.pass ? 0 : 1;
|
391 | return item;
|
392 | });
|
393 |
|
394 |
|
395 | while (true) {
|
396 | const {done, value} = await outputStream.next();
|
397 |
|
398 | if (done === true) {
|
399 | break;
|
400 | }
|
401 |
|
402 | reporter(value);
|
403 |
|
404 | if (value.type === 'bailout') {
|
405 | throw value.data;
|
406 | }
|
407 | }
|
408 |
|
409 | reporter({type: 'plan', data: {start: 1, end: count}});
|
410 | reporter({type: 'comment', data: failure > 0 ? `failed ${failure} of ${count} tests` : 'ok'});
|
411 | };
|
412 |
|
413 |
|
414 | if (typeof window === 'undefined') {
|
415 | setTimeout(start, 0);
|
416 | } else {
|
417 | window.addEventListener('load', start);
|
418 | }
|
419 |
|
420 | module.exports = test;
|