1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
|
6 |
|
7 | var equal$1 = _interopDefault(require('fast-deep-equal'));
|
8 |
|
9 | const startTestMessage = (test, offset) => ({
|
10 | type: "TEST_START" ,
|
11 | data: test,
|
12 | offset
|
13 | });
|
14 | const assertionMessage = (assertion, offset) => ({
|
15 | type: "ASSERTION" ,
|
16 | data: assertion,
|
17 | offset
|
18 | });
|
19 | const endTestMessage = (test, offset) => ({
|
20 | type: "TEST_END" ,
|
21 | data: test,
|
22 | offset
|
23 | });
|
24 | const bailout = (error, offset) => ({
|
25 | type: "BAIL_OUT" ,
|
26 | data: error,
|
27 | offset
|
28 | });
|
29 |
|
30 | const 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 | });
|
52 | const 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 |
|
98 | const defaultTestOptions = Object.freeze({
|
99 | offset: 0,
|
100 | skip: false,
|
101 | runOnly: false
|
102 | });
|
103 | const noop = () => {
|
104 | };
|
105 | const TesterPrototype = {
|
106 | [Symbol.asyncIterator]: async function* () {
|
107 | await this.routine;
|
108 | for (const assertion of this.assertions) {
|
109 | if (assertion[Symbol.asyncIterator]) {
|
110 |
|
111 | yield startTestMessage({ description: assertion.description }, this.offset);
|
112 | yield* assertion;
|
113 | if (assertion.error !== null) {
|
114 |
|
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 | };
|
129 | const 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 | };
|
162 | const testerFactory = testerLikeProvider();
|
163 | const map = fn => async function* (stream) {
|
164 | for await (const m of stream) {
|
165 | yield fn(m);
|
166 | }
|
167 | };
|
168 |
|
169 | const 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 |
|
213 | const isAssertionResult = (result) => {
|
214 | return 'operator' in result;
|
215 | };
|
216 | const specFnRegexp = /zora_spec_fn/;
|
217 | const nodeInternal = /node_modules\/.*|\(internal\/.*/;
|
218 | const 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 | };
|
229 | const assertMethodHook = (fn) => function (...args) {
|
230 |
|
231 | return this.collect(fn(...args));
|
232 | };
|
233 | const aliasMethodHook = (methodName) => function (...args) {
|
234 | return this[methodName](...args);
|
235 | };
|
236 | const 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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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" ,
|
344 | description: description || 'should not throw'
|
345 | };
|
346 | })
|
347 | };
|
348 | const 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 |
|
384 | const flatten = map(m => {
|
385 | m.offset = 0;
|
386 | return m;
|
387 | });
|
388 | const print = (message, offset = 0) => {
|
389 | console.log(message.padStart(message.length + (offset * 4)));
|
390 | };
|
391 | const stringifySymbol = (key, value) => {
|
392 | if (typeof value === 'symbol') {
|
393 | return value.toString();
|
394 | }
|
395 | return value;
|
396 | };
|
397 | const 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 | };
|
405 | const comment = (value, offset) => {
|
406 | print(`# ${value}`, offset);
|
407 | };
|
408 | const subTestPrinter = (prefix = '') => (message) => {
|
409 | const { data } = message;
|
410 | const value = `${prefix}${data.description}`;
|
411 | comment(value, message.offset);
|
412 | };
|
413 | const indentedSubTest = subTestPrinter('Subtest: ');
|
414 | const flatSubTest = subTestPrinter();
|
415 | const testEnd = (message) => {
|
416 | const length = message.data.length;
|
417 | const { offset } = message;
|
418 | print(`1..${length}`, offset);
|
419 | };
|
420 | const bailOut = (message) => {
|
421 | print('Bail out! Unhandled error.');
|
422 | };
|
423 | const indentedDiagnostic = ({ expected, pass, description, actual, operator, at = 'N/A', ...rest }) => ({
|
424 | wanted: expected,
|
425 | found: actual,
|
426 | at,
|
427 | operator,
|
428 | ...rest
|
429 | });
|
430 | const flatDiagnostic = ({ pass, description, ...rest }) => rest;
|
431 | const 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 | };
|
447 | const 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 | };
|
463 | const indentedReport = (message, id) => {
|
464 | switch (message.type) {
|
465 | case "TEST_START" :
|
466 | id.fork();
|
467 | indentedSubTest(message);
|
468 | break;
|
469 | case "ASSERTION" :
|
470 | indentedAssertion(message, id);
|
471 | break;
|
472 | case "TEST_END" :
|
473 | id.merge();
|
474 | testEnd(message);
|
475 | break;
|
476 | case "BAIL_OUT" :
|
477 | bailOut();
|
478 | throw message.data;
|
479 | }
|
480 | };
|
481 | const flatReport = (message, id) => {
|
482 | switch (message.type) {
|
483 | case "TEST_START" :
|
484 | flatSubTest(message);
|
485 | break;
|
486 | case "ASSERTION" :
|
487 | flatAssertion(message, id);
|
488 | break;
|
489 | case "BAIL_OUT" :
|
490 | bailOut();
|
491 | throw message.data;
|
492 | }
|
493 | };
|
494 | const 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 | };
|
501 | const id = function* () {
|
502 | let i = 0;
|
503 | while (true) {
|
504 | yield ++i;
|
505 | }
|
506 | };
|
507 | const 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 | };
|
524 | const 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 | };
|
534 | const 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 |
|
543 | const 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 |
|
570 | const findConfigurationFlag = (name) => {
|
571 | if (typeof process !== 'undefined') {
|
572 | return process.env[name] === 'true';
|
573 |
|
574 | }
|
575 | else if (typeof window !== 'undefined') {
|
576 |
|
577 | return Boolean(window[name]);
|
578 | }
|
579 | return false;
|
580 | };
|
581 | const defaultTestHarness = harnessFactory({
|
582 | runOnly: findConfigurationFlag('RUN_ONLY')
|
583 | });
|
584 | let autoStart = true;
|
585 | let indent = findConfigurationFlag('INDENT');
|
586 | const rootTest = defaultTestHarness.test.bind(defaultTestHarness);
|
587 | rootTest.indent = () => {
|
588 | console.warn('indent function is deprecated, use "INDENT" configuration flag instead');
|
589 | indent = true;
|
590 | };
|
591 | const test = rootTest;
|
592 | const skip = defaultTestHarness.skip.bind(defaultTestHarness);
|
593 | const only = defaultTestHarness.only.bind(defaultTestHarness);
|
594 | rootTest.skip = skip;
|
595 | const equal = defaultTestHarness.equal.bind(defaultTestHarness);
|
596 | const equals = equal;
|
597 | const eq = equal;
|
598 | const deepEqual = equal;
|
599 | const notEqual = defaultTestHarness.notEqual.bind(defaultTestHarness);
|
600 | const notEquals = notEqual;
|
601 | const notEq = notEqual;
|
602 | const notDeepEqual = notEqual;
|
603 | const is = defaultTestHarness.is.bind(defaultTestHarness);
|
604 | const same = is;
|
605 | const isNot = defaultTestHarness.isNot.bind(defaultTestHarness);
|
606 | const notSame = isNot;
|
607 | const ok = defaultTestHarness.ok.bind(defaultTestHarness);
|
608 | const truthy = ok;
|
609 | const notOk = defaultTestHarness.notOk.bind(defaultTestHarness);
|
610 | const falsy = notOk;
|
611 | const fail = defaultTestHarness.fail.bind(defaultTestHarness);
|
612 | const throws = defaultTestHarness.throws.bind(defaultTestHarness);
|
613 | const doesNotThrow = defaultTestHarness.doesNotThrow.bind(defaultTestHarness);
|
614 | const createHarness = (opts = {}) => {
|
615 | autoStart = false;
|
616 | return harnessFactory(opts);
|
617 | };
|
618 | const start = () => {
|
619 | if (autoStart) {
|
620 | defaultTestHarness.report(indent ? mochaTapLike : tapeTapLike);
|
621 | }
|
622 | };
|
623 |
|
624 |
|
625 | if (typeof window === 'undefined') {
|
626 | setTimeout(start, 0);
|
627 | }
|
628 | else {
|
629 |
|
630 | window.addEventListener('load', start);
|
631 | }
|
632 |
|
633 | exports.AssertPrototype = AssertPrototype;
|
634 | exports.createHarness = createHarness;
|
635 | exports.deepEqual = deepEqual;
|
636 | exports.doesNotThrow = doesNotThrow;
|
637 | exports.eq = eq;
|
638 | exports.equal = equal;
|
639 | exports.equals = equals;
|
640 | exports.fail = fail;
|
641 | exports.falsy = falsy;
|
642 | exports.is = is;
|
643 | exports.isNot = isNot;
|
644 | exports.mochaTapLike = mochaTapLike;
|
645 | exports.notDeepEqual = notDeepEqual;
|
646 | exports.notEq = notEq;
|
647 | exports.notEqual = notEqual;
|
648 | exports.notEquals = notEquals;
|
649 | exports.notOk = notOk;
|
650 | exports.notSame = notSame;
|
651 | exports.ok = ok;
|
652 | exports.only = only;
|
653 | exports.same = same;
|
654 | exports.skip = skip;
|
655 | exports.tapeTapLike = tapeTapLike;
|
656 | exports.test = test;
|
657 | exports.throws = throws;
|
658 | exports.truthy = truthy;
|