-
Notifications
You must be signed in to change notification settings - Fork 34
/
matchers.js
131 lines (115 loc) · 3.69 KB
/
matchers.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import diff from 'jest-diff'
import getType from 'jest-get-type'
/**
* Generates nSample random real numbers between min and max
*
* @param {number} min interval lower limit
* @param {number} max interval upper limit
* @param {number} nSamples number of random samples
* @return {Object[]} random samples
*/
function randomReals(min, max, nSamples) {
return Array.from( { length: nSamples } ).map(() => {
return min + (max - min) * Math.random()
} )
}
/**
* generates nSamples random samples, each sample is an array of length
* sampleLength
*
* @param {number} min sample comonent lower limit
* @param {number} max sample comonent upper limit
* @param {number} nSamples number of samples
* @param {number} sampleLength length of each sample
* @return {Object[]} array of samples
*/
function genSamples(min, max, nSamples, sampleLength) {
return Array.from( { length: nSamples } ).map(() => {
return randomReals(min, max, sampleLength)
} )
}
/**
* recursively checks whether a and b are nearly equal:
* - for numbers, approximate equality
* - for functions, approximate equal at random samples
* - for arrays/objects, approximate equality of all keys
* - fallback to strict equality === as default case
*
* @param {any} a [description]
* @param {[type]} b [description]
* @param {Object} [config={}] [description]
* @return {Boolean} [description]
*/
export function isNearlyEqual(a, b, config = {} ) {
const {
numDigits = 6,
nSamples = 5,
range = [1, 3]
} = config
if (getType(a) !== getType(b)) {
return false
}
switch (getType(a)) {
case 'number': {
return Math.abs(a - b) <= 10 ** -numDigits
}
case 'array': {
return a.length === b.length &&
a.every((item, index) => isNearlyEqual(item, b[index], config))
}
case 'function': {
// by using max arity, we allow that functions of different arity might be
// nearly equal. For example, x => 5 and (x, y) => 5
const arity = Math.max(a.length, b.length)
const [min, max] = range
const samples = genSamples(min, max, nSamples, arity)
return isNearlyEqual(
samples.map(sample => a(...sample)),
samples.map(sample => b(...sample)),
config)
}
case 'object': {
if (Object.keys(a).length !== Object.keys(b).length) {
return false
}
const sameKeys = Object.keys(a).sort().every((key, index) => (
key === Object.keys(b).sort()[index]
))
return sameKeys && Object.keys(a).every((key, index) => (
isNearlyEqual(a[key], b[key], config)
))
}
default: {
return a === b
}
}
}
export function toNearlyEqual(received, expected, config = {} ) {
const pass = isNearlyEqual(received, expected, config)
const jestMessage = (pass, received, expected) => {
return pass
? () =>
this.utils.matcherHint('.not.toNearlyEqual') +
'\n\n' +
`Expected value to not be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(received)}`
: () => {
const diffString = '\t Approximate difference not implemented, Sorry!'
return (
this.utils.matcherHint('.toNearlyEqual') +
'\n\n' +
`Expected value to be:\n` +
` ${this.utils.printExpected(expected)}\n` +
`Received:\n` +
` ${this.utils.printReceived(received)}` +
(diffString ? `\n\nDifference:\n\n${diffString}` : '')
)
}
}
return {
pass,
message: jestMessage(pass, received, expected)
}
}