• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TotalTechGeek / pineapple / 5214826938

pending completion
5214826938

Pull #26

github

web-flow
Merge 738aa6380 into 5bafc7bbf
Pull Request #26: Add more arbitraries and omissions

2032 of 2187 branches covered (92.91%)

Branch coverage included in aggregate %.

108 of 108 new or added lines in 8 files covered. (100.0%)

9000 of 9336 relevant lines covered (96.4%)

1404.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.29
/utils.js
1
import chalk from 'chalk'
12✔
2
import { diffStringsUnified } from 'jest-diff'
12✔
3
import engine from './methods.js'
12✔
4
import { serialize } from './snapshot.js'
12✔
5
import fc from 'fast-check'
12✔
6
import { ConstantFunc } from './symbols.js'
12✔
7

12✔
8
const basicSub = { var: '' }
12✔
9

12✔
10
// The following is used as a "simple way" to keep track of whether a complex structure generated from arbitraries is actually constant.
12✔
11
// This allows us to avoid a bunch of duplicate test effort when someone writes something like:
12✔
12
// { name: 'Jesse', image: #commonPicture }, where #commonPicture is defined as a constant.
12✔
13
const constantStructures = new Set()
12✔
14

12✔
15
/**
12✔
16
 * Checks if a function in the Logic Engine is logged as constant.
12✔
17
 * @param {string} name
12✔
18
 * @returns {boolean}
12✔
19
 */
12✔
20
function checkConstantFunction (name) {
177✔
21
  return engine.methods[name] && engine.methods[name].method && engine.methods[name].method[ConstantFunc]
177✔
22
}
177✔
23

12✔
24
/**
12✔
25
 * It takes the output of the diff function and if it contains the string "Comparing two different
12✔
26
 * types", it replaces the output with a more detailed message
12✔
27
 * @param expected - The expected value.
12✔
28
 * @param received - The value that was actually returned from the test.
12✔
29
 * @returns The function diffTouchup is being returned.
12✔
30
 */
12✔
31
export function diff (expected, received) {
12✔
32
  if (typeof expected !== typeof received) {
×
33
    return `Comparing two different types of values. Expected ${chalk.green(typeof expected)} but received ${chalk.red(typeof received)}.\n${chalk.green(`- Expected: ${serialize(expected).replace(/\n/g, '\n  ')}`)}\n${chalk.red(`- Received: ${serialize(received).replace(/\n/g, '\n  ')}`)}`
×
34
  }
×
35

×
36
  const result = diffStringsUnified(
×
37
    serialize(expected),
×
38
    serialize(received),
×
39
    {
×
40
      changeColor: i => i
×
41
    }
×
42
  )
×
43

×
44
  return result
×
45
}
×
46

12✔
47
const tupleConstApplication = item => {
12✔
48
  if (item && typeof item === 'object') {
54✔
49
    const key = Object.keys(item)[0]
33✔
50
    if (key.startsWith('#')) {
33✔
51
      return item
33✔
52
    }
33✔
53
  }
33✔
54
  return { '#constant': item }
30✔
55
}
54✔
56

12✔
57
const simplifyArbitraries = new Set(['#oneof'])
12✔
58

12✔
59
/**
12✔
60
 * Used to touch up the logic so that statements that aren't completely valid with arbitraries are made valid,
12✔
61
 * like #record({ id: #integer }) and { id: #integer } become the same.
12✔
62
 * @param {*} item
12✔
63
 * @returns
12✔
64
 */
12✔
65
function touchUpArbitrary (item, force = false) {
1,392✔
66
  if (!item) return item
1,392✔
67
  if (typeof item === 'number') return item
1,392✔
68
  if (typeof item === 'string') return item
1,392✔
69
  if (typeof item === 'boolean') return item
1,392✔
70
  if (typeof item === 'bigint') return item
1,392!
71

312✔
72
  const key = Object.keys(item)[0]
312✔
73

312✔
74
  if (key.startsWith('#')) {
1,392✔
75
    if (item[key] && item[key].obj) {
159✔
76
      return {
18✔
77
        [key]: {
18✔
78
          obj: item[key].obj.map(touchUpArbitrary)
18✔
79
        }
18✔
80
      }
18✔
81
    }
18✔
82

150✔
83
    if (item[key] && item[key].list) {
159!
84
      return {
9✔
85
        [key]: {
9✔
86
          list: item[key].list.map(touchUpArbitrary)
9✔
87
        }
9✔
88
      }
9✔
89
    }
9✔
90

150✔
91
    // todo: improve the testing around this
150✔
92
    if (item[key] && item[key].map && simplifyArbitraries.has(key)) {
159✔
93
      return {
12✔
94
        [key]: item[key].map(touchUpArbitrary).map(i => {
12✔
95
        // detect if any are not objects and wrap them in #constant
33✔
96
          if (typeof i !== 'object') return { '#constant': i }
33✔
97
          if (i === null) return { '#constant': null }
33✔
98
          if (i === undefined) return { '#constant': undefined }
33!
99
          return touchUpArbitrary(i, true)
21✔
100
        })
12✔
101
      }
12✔
102
    }
12✔
103
  }
159✔
104

300✔
105
  if (key === 'obj') {
1,392✔
106
    if (force === true || item[key].some(i => (Object.keys(i || {})[0] || '').startsWith('#'))) {
66!
107
      const result = {
27✔
108
        '#record': {
27✔
109
          obj: item[key]
27✔
110
            .map(touchUpArbitrary)
27✔
111
            // apply a fix to the values of the obj so that they're all wrapped in #constant,
27✔
112
            // this makes the sugar simpler.
27✔
113
            .map((i, x) => (x & 1) ? tupleConstApplication(i) : i)
27✔
114
        }
27✔
115
      }
27✔
116

27✔
117
      // detect & track if the result is purely constant.
27✔
118
      if (result['#record'].obj.every((i, x) => !(x & 1) || constantStructures.has(i) || typeof i['#constant'] !== 'undefined' || checkConstantFunction(Object.keys(i)[0]))) {
27✔
119
        constantStructures.add(result)
18✔
120
      }
18✔
121

27✔
122
      return result
27✔
123
    }
27✔
124
  }
66✔
125

282✔
126
  if (key === 'list') {
1,392✔
127
    if (force === true || item[key].some(i => (Object.keys(i || {})[0] || '').startsWith('#'))) {
69!
128
      const result = {
21✔
129
        '#tuple': {
21✔
130
          list: item[key]
21✔
131
            .map(touchUpArbitrary)
21✔
132
            // makes the sugar simpler by applying #constant to the constant values of an inferred tuple.
21✔
133
            .map(tupleConstApplication)
21✔
134
        }
21✔
135
      }
21✔
136

21✔
137
      // detect & track if the result is purely constant.
21✔
138
      if (result['#tuple'].list.every((i) => typeof i['#constant'] !== 'undefined' || checkConstantFunction(Object.keys(i)[0]))) {
21✔
139
        constantStructures.add(result)
18✔
140
      }
18✔
141

21✔
142
      return result
21✔
143
    }
21✔
144
  }
69✔
145

270✔
146
  const count = arbitraryBranchCount(item)
270✔
147

270✔
148
  if (count === 0) {
1,392✔
149
    return item
123✔
150
  } else if (count === 1) {
1,392✔
151
    const [map, arbitraries] = traverseSubstitute(item)
156✔
152
    return {
156✔
153
      ...arbitraries[0],
156✔
154
      map
156✔
155
    }
156✔
156
  } else throw new Error('You may not use more than one arbitrary in an argument, did you intend to use #record or #tuple?')
156!
157
}
1,392✔
158

12✔
159
/**
12✔
160
 * Count the number of references to arbitraries in the logic tree.
12✔
161
 * (Nested Arbitraries still count as one, because it terminates the traversal
12✔
162
 * when it reaches one; this is by design!).
12✔
163
 * @param {*} obj
12✔
164
 */
12✔
165
function arbitraryBranchCount (obj) {
567✔
166
  if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
567✔
167
    const key = Object.keys(obj)[0]
309✔
168
    if (key.startsWith('#')) return 1
309✔
169

162✔
170
    if (!Array.isArray(obj[key])) {
309✔
171
      if (obj[key] && typeof obj[key] === 'object') {
36!
172
        return arbitraryBranchCount(obj[key])
9✔
173
      }
9✔
174
      return 0
36✔
175
    }
36✔
176

135✔
177
    return obj[key].reduce((acc, i) => {
135✔
178
      acc += arbitraryBranchCount(i)
306✔
179
      return acc
306✔
180
    }, 0)
135✔
181
  }
135✔
182
  return 0
267✔
183
}
567✔
184

12✔
185
/**
12✔
186
 * Substitute any arbitraries with a "var" expression so that we can perform a map operation
12✔
187
 * after building the relevant logic. :)
12✔
188
 * @param {*} obj
12✔
189
 * @param {*} sub
12✔
190
 */
12✔
191
export function traverseSubstitute (obj, sub = []) {
12✔
192
  if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
174✔
193
    const key = Object.keys(obj)[0]
165✔
194
    if (key.startsWith('#')) {
165✔
195
      sub.push(obj)
156✔
196
      return [basicSub, sub]
156✔
197
    }
156✔
198

18✔
199
    return [{
18✔
200
      [key]: Array.isArray(obj[key]) ? obj[key].map(i => traverseSubstitute(i, sub)[0]) : traverseSubstitute(obj[key], sub)[0]
165!
201
    }, sub]
165✔
202
  }
165✔
203
  return [obj, sub]
18✔
204
}
174✔
205

12✔
206
/**
12✔
207
 * Convert the arguments for a test case into arbitraries.
12✔
208
 * @param  {...any} args
12✔
209
 */
12✔
210
export async function argumentsToArbitraries (data, ...args) {
12✔
211
  args = args.map(touchUpArbitrary)
792✔
212
  let constant = true
792✔
213
  const list = []
792✔
214
  for (const i of args) {
792✔
215
    if (i && typeof i === 'object') {
1,260✔
216
      const keys = Object.keys(i)
264✔
217

264✔
218
      if (keys[0].startsWith('#')) {
264✔
219
        if (!constantStructures.has(i) && keys[0] !== '#constant' && !checkConstantFunction(keys[0])) constant = false
162✔
220
        const result = await (await engine.build(i))(data)
162✔
221
        if (keys[1] === 'map' && i.map !== basicSub) {
162✔
222
          list.push(result.map(engine.fallback.build(i.map)))
18✔
223
          continue
18✔
224
        }
18✔
225
        list.push(result)
153✔
226
        continue
153✔
227
      }
153✔
228
    }
264✔
229

1,107✔
230
    list.push(fc.constant(await (await engine.build(i))(data)))
1,107✔
231
  }
1,107✔
232

792✔
233
  list.constant = constant
792✔
234
  return list
792✔
235
}
792✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc