UNPKG

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