UNPKG

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