UNPKG

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