1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const lodash_1 = require("./wrap/lodash");
|
4 | const proxy_safe_clone_deep_with_1 = require("./wrap/proxy-safe-clone-deep-with");
|
5 | const calls_1 = require("./store/calls");
|
6 | const store_1 = require("./store");
|
7 | const arguments_1 = require("./stringify/arguments");
|
8 | const stubbings_1 = require("./store/stubbings");
|
9 | const symbols_1 = require("./symbols");
|
10 | function explain(testDouble) {
|
11 | if (lodash_1.default.isFunction(testDouble)) {
|
12 | return explainFunction(testDouble);
|
13 | }
|
14 | else if (lodash_1.default.isObject(testDouble)) {
|
15 | return explainObject(testDouble);
|
16 | }
|
17 | else {
|
18 | return explainNonTestDouble(testDouble);
|
19 | }
|
20 | }
|
21 | exports.default = explain;
|
22 | function explainObject(obj) {
|
23 | const { explanations, children } = explainChildren(obj);
|
24 | return {
|
25 | name: null,
|
26 | callCount: 0,
|
27 | calls: [],
|
28 | description: describeObject(explanations),
|
29 | children,
|
30 | isTestDouble: explanations.length > 0
|
31 | };
|
32 | }
|
33 | function explainChildren(thing) {
|
34 | const explanations = [];
|
35 | const children = (0, proxy_safe_clone_deep_with_1.default)(thing, (val, key, obj, stack) => {
|
36 | if (lodash_1.default.isFunction(val) && stack) {
|
37 | return lodash_1.default.tap(explainFunction(val), (explanation) => {
|
38 | if (explanation.isTestDouble)
|
39 | explanations.push(explanation);
|
40 | });
|
41 | }
|
42 | });
|
43 | return { explanations, children };
|
44 | }
|
45 | function describeObject(explanations) {
|
46 | const count = explanations.length;
|
47 | if (count === 0)
|
48 | return 'This object contains no test doubles';
|
49 | return `This object contains ${count} test double function${count > 1 ? 's' : ''}: [${lodash_1.default.map(explanations, e => `"${e.name}"`).join(', ')}]`;
|
50 | }
|
51 | function explainFunction(testDouble) {
|
52 | if (store_1.default.for(testDouble, false) == null) {
|
53 | return explainNonTestDouble(testDouble);
|
54 | }
|
55 | const calls = calls_1.default.for(testDouble);
|
56 | const stubs = stubbings_1.default.for(testDouble);
|
57 | const { children } = explainChildren(testDouble);
|
58 | return {
|
59 | name: store_1.default.for(testDouble).name,
|
60 | callCount: calls.length,
|
61 | calls,
|
62 | description: testdoubleDescription(testDouble, stubs, calls) +
|
63 | stubbingDescription(stubs) +
|
64 | callDescription(calls),
|
65 | children,
|
66 | isTestDouble: true
|
67 | };
|
68 | }
|
69 | function explainNonTestDouble(thing) {
|
70 | return ({
|
71 | name: undefined,
|
72 | callCount: 0,
|
73 | calls: [],
|
74 | description: `This is not a test double${lodash_1.default.isFunction(thing) ? ' function' : ''}.`,
|
75 | isTestDouble: false
|
76 | });
|
77 | }
|
78 | function testdoubleDescription(testDouble, stubs, calls) {
|
79 | return `This test double ${stringifyName(testDouble)}has ${stubs.length} stubbings and ${calls.length} invocations.`;
|
80 | }
|
81 | function stubbingDescription(stubs) {
|
82 | return stubs.length > 0
|
83 | ? lodash_1.default.reduce(stubs, (desc, stub) => desc + `\n - when called with \`(${(0, arguments_1.default)(stub.args)})\`, then ${planFor(stub)} ${argsFor(stub)}.`, '\n\nStubbings:')
|
84 | : '';
|
85 | }
|
86 | function planFor(stub) {
|
87 | switch (stub.config.plan) {
|
88 | case 'thenCallback': return 'callback';
|
89 | case 'thenResolve': return 'resolve';
|
90 | case 'thenReject': return 'reject';
|
91 | default: return 'return';
|
92 | }
|
93 | }
|
94 | function argsFor(stub) {
|
95 | switch (stub.config.plan) {
|
96 | case 'thenCallback': return `\`(${(0, arguments_1.default)(stub.stubbedValues, ', ')})\``;
|
97 | default: return (0, arguments_1.default)(stub.stubbedValues, ', then ', '`');
|
98 | }
|
99 | }
|
100 | function callDescription(calls) {
|
101 | return calls.length > 0
|
102 | ? lodash_1.default.reduce(calls, (desc, call) => {
|
103 | let argDescription;
|
104 | if (call.cloneArgs !== symbols_1.default.uncloneable) {
|
105 | argDescription = `\`(${(0, arguments_1.default)(call.cloneArgs)})\`.`;
|
106 | }
|
107 | else {
|
108 | argDescription = `\`(${(0, arguments_1.default)(call.args)})\` [Cloning argument values failed; displaying current references]`;
|
109 | }
|
110 | return desc + `\n - called with ${argDescription}`;
|
111 | }, '\n\nInvocations:')
|
112 | : '';
|
113 | }
|
114 | function stringifyName(testDouble) {
|
115 | const name = store_1.default.for(testDouble).name;
|
116 | return name ? `\`${name}\` ` : '';
|
117 | }
|