UNPKG

19.4 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', { value: true });
4
5function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
7var equal$1 = _interopDefault(require('fast-deep-equal'));
8
9const startTestMessage = (test, offset) => ({
10 type: "TEST_START" /* TEST_START */,
11 data: test,
12 offset
13});
14const assertionMessage = (assertion, offset) => ({
15 type: "ASSERTION" /* ASSERTION */,
16 data: assertion,
17 offset
18});
19const endTestMessage = (test, offset) => ({
20 type: "TEST_END" /* TEST_END */,
21 data: test,
22 offset
23});
24const bailout = (error, offset) => ({
25 type: "BAIL_OUT" /* BAIL_OUT */,
26 data: error,
27 offset
28});
29
30const delegateToCounter = (counter) => (target) => Object.defineProperties(target, {
31 skipCount: {
32 get() {
33 return counter.skipCount;
34 }
35 },
36 failureCount: {
37 get() {
38 return counter.failureCount;
39 }
40 },
41 successCount: {
42 get() {
43 return counter.successCount;
44 }
45 },
46 count: {
47 get() {
48 return counter.count;
49 }
50 }
51});
52const counter = () => {
53 let success = 0;
54 let failure = 0;
55 let skip = 0;
56 return Object.defineProperties({
57 update(assertion) {
58 const { pass, skip: isSkipped } = assertion;
59 if (isSkipped) {
60 skip++;
61 }
62 else if (!isAssertionResult(assertion)) {
63 skip += assertion.skipCount;
64 success += assertion.successCount;
65 failure += assertion.failureCount;
66 }
67 else if (pass) {
68 success++;
69 }
70 else {
71 failure++;
72 }
73 }
74 }, {
75 successCount: {
76 get() {
77 return success;
78 }
79 },
80 failureCount: {
81 get() {
82 return failure;
83 }
84 },
85 skipCount: {
86 get() {
87 return skip;
88 }
89 },
90 count: {
91 get() {
92 return skip + success + failure;
93 }
94 }
95 });
96};
97
98const defaultTestOptions = Object.freeze({
99 offset: 0,
100 skip: false,
101 runOnly: false
102});
103const noop = () => {
104};
105const TesterPrototype = {
106 [Symbol.asyncIterator]: async function* () {
107 await this.routine;
108 for (const assertion of this.assertions) {
109 if (assertion[Symbol.asyncIterator]) {
110 // Sub test
111 yield startTestMessage({ description: assertion.description }, this.offset);
112 yield* assertion;
113 if (assertion.error !== null) {
114 // Bubble up the error and return
115 this.error = assertion.error;
116 this.pass = false;
117 return;
118 }
119 }
120 yield assertionMessage(assertion, this.offset);
121 this.pass = this.pass && assertion.pass;
122 this.counter.update(assertion);
123 }
124 return this.error !== null ?
125 yield bailout(this.error, this.offset) :
126 yield endTestMessage(this, this.offset);
127 }
128};
129const testerLikeProvider = (BaseProto = TesterPrototype) => (assertions, routine, offset) => {
130 const testCounter = counter();
131 const withTestCounter = delegateToCounter(testCounter);
132 let pass = true;
133 return withTestCounter(Object.create(BaseProto, {
134 routine: {
135 value: routine
136 },
137 assertions: {
138 value: assertions
139 },
140 offset: {
141 value: offset
142 },
143 counter: {
144 value: testCounter
145 },
146 length: {
147 get() {
148 return assertions.length;
149 }
150 },
151 pass: {
152 enumerable: true,
153 get() {
154 return pass;
155 },
156 set(val) {
157 pass = val;
158 }
159 }
160 }));
161};
162const testerFactory = testerLikeProvider();
163const map = fn => async function* (stream) {
164 for await (const m of stream) {
165 yield fn(m);
166 }
167};
168
169const tester = (description, spec, { offset = 0, skip = false, runOnly = false } = defaultTestOptions) => {
170 let executionTime = 0;
171 let error = null;
172 const assertions = [];
173 const collect = item => assertions.push(item);
174 const specFunction = skip === true ? noop : function zora_spec_fn() {
175 return spec(assert(collect, offset, runOnly));
176 };
177 const testRoutine = (async function () {
178 try {
179 const start = Date.now();
180 const result = await specFunction();
181 executionTime = Date.now() - start;
182 return result;
183 }
184 catch (e) {
185 error = e;
186 }
187 })();
188 return Object.defineProperties(testerFactory(assertions, testRoutine, offset), {
189 error: {
190 get() {
191 return error;
192 },
193 set(val) {
194 error = val;
195 }
196 },
197 executionTime: {
198 enumerable: true,
199 get() {
200 return executionTime;
201 }
202 },
203 skip: {
204 value: skip
205 },
206 description: {
207 enumerable: true,
208 value: description
209 }
210 });
211};
212
213const isAssertionResult = (result) => {
214 return 'operator' in result;
215};
216const specFnRegexp = /zora_spec_fn/;
217const nodeInternal = /node_modules\/.*|\(internal\/.*/;
218const getAssertionLocation = () => {
219 const err = new Error();
220 const stack = (err.stack || '')
221 .split('\n')
222 .filter(l => !nodeInternal.test(l) && l !== '');
223 const userLandIndex = stack.findIndex(l => specFnRegexp.test(l));
224 return (userLandIndex >= 1 ?
225 stack[userLandIndex - 1] : (stack[stack.length - 1] || 'N/A'))
226 .trim()
227 .replace(/^at|^@/, '');
228};
229const assertMethodHook = (fn) => function (...args) {
230 // @ts-ignore
231 return this.collect(fn(...args));
232};
233const aliasMethodHook = (methodName) => function (...args) {
234 return this[methodName](...args);
235};
236const AssertPrototype = {
237 equal: assertMethodHook((actual, expected, description = 'should be equivalent') => ({
238 pass: equal$1(actual, expected),
239 actual,
240 expected,
241 description,
242 operator: "equal" /* EQUAL */
243 })),
244 equals: aliasMethodHook('equal'),
245 eq: aliasMethodHook('equal'),
246 deepEqual: aliasMethodHook('equal'),
247 notEqual: assertMethodHook((actual, expected, description = 'should not be equivalent') => ({
248 pass: !equal$1(actual, expected),
249 actual,
250 expected,
251 description,
252 operator: "notEqual" /* NOT_EQUAL */
253 })),
254 notEquals: aliasMethodHook('notEqual'),
255 notEq: aliasMethodHook('notEqual'),
256 notDeepEqual: aliasMethodHook('notEqual'),
257 is: assertMethodHook((actual, expected, description = 'should be the same') => ({
258 pass: Object.is(actual, expected),
259 actual,
260 expected,
261 description,
262 operator: "is" /* IS */
263 })),
264 same: aliasMethodHook('is'),
265 isNot: assertMethodHook((actual, expected, description = 'should not be the same') => ({
266 pass: !Object.is(actual, expected),
267 actual,
268 expected,
269 description,
270 operator: "isNot" /* IS_NOT */
271 })),
272 notSame: aliasMethodHook('isNot'),
273 ok: assertMethodHook((actual, description = 'should be truthy') => ({
274 pass: Boolean(actual),
275 actual,
276 expected: 'truthy value',
277 description,
278 operator: "ok" /* OK */
279 })),
280 truthy: aliasMethodHook('ok'),
281 notOk: assertMethodHook((actual, description = 'should be falsy') => ({
282 pass: !Boolean(actual),
283 actual,
284 expected: 'falsy value',
285 description,
286 operator: "notOk" /* NOT_OK */
287 })),
288 falsy: aliasMethodHook('notOk'),
289 fail: assertMethodHook((description = 'fail called') => ({
290 pass: false,
291 actual: 'fail called',
292 expected: 'fail not called',
293 description,
294 operator: "fail" /* FAIL */
295 })),
296 throws: assertMethodHook((func, expected, description) => {
297 let caught;
298 let pass;
299 let actual;
300 if (typeof expected === 'string') {
301 [expected, description] = [description, expected];
302 }
303 try {
304 func();
305 }
306 catch (err) {
307 caught = { error: err };
308 }
309 pass = caught !== undefined;
310 actual = caught && caught.error;
311 if (expected instanceof RegExp) {
312 pass = expected.test(actual) || expected.test(actual && actual.message);
313 actual = actual && actual.message || actual;
314 expected = String(expected);
315 }
316 else if (typeof expected === 'function' && caught) {
317 pass = actual instanceof expected;
318 actual = actual.constructor;
319 }
320 return {
321 pass,
322 actual,
323 expected,
324 description: description || 'should throw',
325 operator: "throws" /* THROWS */
326 };
327 }),
328 doesNotThrow: assertMethodHook((func, expected, description) => {
329 let caught;
330 if (typeof expected === 'string') {
331 [expected, description] = [description, expected];
332 }
333 try {
334 func();
335 }
336 catch (err) {
337 caught = { error: err };
338 }
339 return {
340 pass: caught === undefined,
341 expected: 'no thrown error',
342 actual: caught && caught.error,
343 operator: "doesNotThrow" /* DOES_NOT_THROW */,
344 description: description || 'should not throw'
345 };
346 })
347};
348const assert = (collect, offset, runOnly = false) => {
349 const actualCollect = item => {
350 if (!item.pass) {
351 item.at = getAssertionLocation();
352 }
353 collect(item);
354 return item;
355 };
356 const test = (description, spec, opts) => {
357 const options = Object.assign({}, defaultTestOptions, opts, { offset: offset + 1, runOnly });
358 const subTest = tester(description, spec, options);
359 collect(subTest);
360 return subTest.routine;
361 };
362 const skip = (description, spec, opts) => {
363 return test(description, spec, Object.assign({}, opts, { skip: true }));
364 };
365 return Object.assign(Object.create(AssertPrototype, { collect: { value: actualCollect } }), {
366 test(description, spec, opts = {}) {
367 if (runOnly) {
368 return skip(description, spec, opts);
369 }
370 return test(description, spec, opts);
371 },
372 skip(description, spec = noop, opts = {}) {
373 return skip(description, spec, opts);
374 },
375 only(description, spec, opts = {}) {
376 const specFn = runOnly === false ? _ => {
377 throw new Error(`Can not use "only" method when not in run only mode`);
378 } : spec;
379 return test(description, specFn, opts);
380 }
381 });
382};
383
384const flatten = map(m => {
385 m.offset = 0;
386 return m;
387});
388const print = (message, offset = 0) => {
389 console.log(message.padStart(message.length + (offset * 4))); // 4 white space used as indent (see tap-parser)
390};
391const stringifySymbol = (key, value) => {
392 if (typeof value === 'symbol') {
393 return value.toString();
394 }
395 return value;
396};
397const printYAML = (obj, offset = 0) => {
398 const YAMLOffset = offset + 0.5;
399 print('---', YAMLOffset);
400 for (const [prop, value] of Object.entries(obj)) {
401 print(`${prop}: ${JSON.stringify(value, stringifySymbol)}`, YAMLOffset + 0.5);
402 }
403 print('...', YAMLOffset);
404};
405const comment = (value, offset) => {
406 print(`# ${value}`, offset);
407};
408const subTestPrinter = (prefix = '') => (message) => {
409 const { data } = message;
410 const value = `${prefix}${data.description}`;
411 comment(value, message.offset);
412};
413const indentedSubTest = subTestPrinter('Subtest: ');
414const flatSubTest = subTestPrinter();
415const testEnd = (message) => {
416 const length = message.data.length;
417 const { offset } = message;
418 print(`1..${length}`, offset);
419};
420const bailOut = (message) => {
421 print('Bail out! Unhandled error.');
422};
423const indentedDiagnostic = ({ expected, pass, description, actual, operator, at = 'N/A', ...rest }) => ({
424 wanted: expected,
425 found: actual,
426 at,
427 operator,
428 ...rest
429});
430const flatDiagnostic = ({ pass, description, ...rest }) => rest;
431const indentedAssertion = (message, idIter) => {
432 const { data, offset } = message;
433 const { pass, description } = data;
434 const label = pass === true ? 'ok' : 'not ok';
435 const { value: id } = idIter.next();
436 if (isAssertionResult(data)) {
437 print(`${label} ${id} - ${description}`, offset);
438 if (pass === false) {
439 printYAML(indentedDiagnostic(data), offset);
440 }
441 }
442 else {
443 const comment = data.skip === true ? 'SKIP' : `${data.executionTime}ms`;
444 print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # ${comment}`, message.offset);
445 }
446};
447const flatAssertion = (message, idIter) => {
448 const { data, offset } = message;
449 const { pass, description } = data;
450 const label = pass === true ? 'ok' : 'not ok';
451 if (isAssertionResult(data)) {
452 const { value: id } = idIter.next();
453 print(`${label} ${id} - ${description}`, offset);
454 if (pass === false) {
455 printYAML(flatDiagnostic(data), offset);
456 }
457 }
458 else if (data.skip) {
459 const { value: id } = idIter.next();
460 print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # SKIP`, offset);
461 }
462};
463const indentedReport = (message, id) => {
464 switch (message.type) {
465 case "TEST_START" /* TEST_START */:
466 id.fork();
467 indentedSubTest(message);
468 break;
469 case "ASSERTION" /* ASSERTION */:
470 indentedAssertion(message, id);
471 break;
472 case "TEST_END" /* TEST_END */:
473 id.merge();
474 testEnd(message);
475 break;
476 case "BAIL_OUT" /* BAIL_OUT */:
477 bailOut();
478 throw message.data;
479 }
480};
481const flatReport = (message, id) => {
482 switch (message.type) {
483 case "TEST_START" /* TEST_START */:
484 flatSubTest(message);
485 break;
486 case "ASSERTION" /* ASSERTION */:
487 flatAssertion(message, id);
488 break;
489 case "BAIL_OUT" /* BAIL_OUT */:
490 bailOut();
491 throw message.data;
492 }
493};
494const summary = (harness) => {
495 print('', 0);
496 comment(harness.pass ? 'ok' : 'not ok', 0);
497 comment(`success: ${harness.successCount}`, 0);
498 comment(`skipped: ${harness.skipCount}`, 0);
499 comment(`failure: ${harness.failureCount}`, 0);
500};
501const id = function* () {
502 let i = 0;
503 while (true) {
504 yield ++i;
505 }
506};
507const idGen = () => {
508 let stack = [id()];
509 return {
510 [Symbol.iterator]() {
511 return this;
512 },
513 next() {
514 return stack[0].next();
515 },
516 fork() {
517 stack.unshift(id());
518 },
519 merge() {
520 stack.shift();
521 }
522 };
523};
524const tapeTapLike = async (stream) => {
525 const src = flatten(stream);
526 const id = idGen();
527 print('TAP version 13');
528 for await (const message of src) {
529 flatReport(message, id);
530 }
531 print(`1..${stream.count}`, 0);
532 summary(stream);
533};
534const mochaTapLike = async (stream) => {
535 print('TAP version 13');
536 const id = idGen();
537 for await (const message of stream) {
538 indentedReport(message, id);
539 }
540 summary(stream);
541};
542
543const harnessFactory = ({ runOnly = false, indent = false } = {
544 runOnly: false,
545 indent: false
546}) => {
547 const tests = [];
548 const rootOffset = 0;
549 const collect = item => tests.push(item);
550 const api = assert(collect, rootOffset, runOnly);
551 let error = null;
552 const factory = testerLikeProvider(Object.assign(api, TesterPrototype, {
553 report: async function (reporter) {
554 const rep = reporter || (indent ? mochaTapLike : tapeTapLike);
555 return rep(this);
556 }
557 }));
558 return Object.defineProperties(factory(tests, Promise.resolve(), rootOffset), {
559 error: {
560 get() {
561 return error;
562 },
563 set(val) {
564 error = val;
565 }
566 }
567 });
568};
569
570const findConfigurationFlag = (name) => {
571 if (typeof process !== 'undefined') {
572 return process.env[name] === 'true';
573 // @ts-ignore
574 }
575 else if (typeof window !== 'undefined') {
576 // @ts-ignore
577 return Boolean(window[name]);
578 }
579 return false;
580};
581const defaultTestHarness = harnessFactory({
582 runOnly: findConfigurationFlag('RUN_ONLY')
583});
584let autoStart = true;
585let indent = findConfigurationFlag('INDENT');
586const rootTest = defaultTestHarness.test.bind(defaultTestHarness);
587rootTest.indent = () => {
588 console.warn('indent function is deprecated, use "INDENT" configuration flag instead');
589 indent = true;
590};
591const test = rootTest;
592const skip = defaultTestHarness.skip.bind(defaultTestHarness);
593const only = defaultTestHarness.only.bind(defaultTestHarness);
594rootTest.skip = skip;
595const equal = defaultTestHarness.equal.bind(defaultTestHarness);
596const equals = equal;
597const eq = equal;
598const deepEqual = equal;
599const notEqual = defaultTestHarness.notEqual.bind(defaultTestHarness);
600const notEquals = notEqual;
601const notEq = notEqual;
602const notDeepEqual = notEqual;
603const is = defaultTestHarness.is.bind(defaultTestHarness);
604const same = is;
605const isNot = defaultTestHarness.isNot.bind(defaultTestHarness);
606const notSame = isNot;
607const ok = defaultTestHarness.ok.bind(defaultTestHarness);
608const truthy = ok;
609const notOk = defaultTestHarness.notOk.bind(defaultTestHarness);
610const falsy = notOk;
611const fail = defaultTestHarness.fail.bind(defaultTestHarness);
612const throws = defaultTestHarness.throws.bind(defaultTestHarness);
613const doesNotThrow = defaultTestHarness.doesNotThrow.bind(defaultTestHarness);
614const createHarness = (opts = {}) => {
615 autoStart = false;
616 return harnessFactory(opts);
617};
618const start = () => {
619 if (autoStart) {
620 defaultTestHarness.report(indent ? mochaTapLike : tapeTapLike);
621 }
622};
623// on next tick start reporting
624// @ts-ignore
625if (typeof window === 'undefined') {
626 setTimeout(start, 0);
627}
628else {
629 // @ts-ignore
630 window.addEventListener('load', start);
631}
632
633exports.AssertPrototype = AssertPrototype;
634exports.createHarness = createHarness;
635exports.deepEqual = deepEqual;
636exports.doesNotThrow = doesNotThrow;
637exports.eq = eq;
638exports.equal = equal;
639exports.equals = equals;
640exports.fail = fail;
641exports.falsy = falsy;
642exports.is = is;
643exports.isNot = isNot;
644exports.mochaTapLike = mochaTapLike;
645exports.notDeepEqual = notDeepEqual;
646exports.notEq = notEq;
647exports.notEqual = notEqual;
648exports.notEquals = notEquals;
649exports.notOk = notOk;
650exports.notSame = notSame;
651exports.ok = ok;
652exports.only = only;
653exports.same = same;
654exports.skip = skip;
655exports.tapeTapLike = tapeTapLike;
656exports.test = test;
657exports.throws = throws;
658exports.truthy = truthy;