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

TotalTechGeek / pineapple / 5215358360

pending completion
5215358360

Pull #26

github

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

2119 of 2290 branches covered (92.53%)

Branch coverage included in aggregate %.

179 of 179 new or added lines in 9 files covered. (100.0%)

9030 of 9407 relevant lines covered (95.99%)

1489.38 hits per line

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

84.86
/methods.js
1
// @ts-check
12✔
2
import { AsyncLogicEngine, Compiler } from 'json-logic-engine'
12✔
3
import { splitEvery, equals, omit, pick, dissocPath } from 'ramda'
12✔
4
import Ajv from 'ajv'
12✔
5
import chalk from 'chalk'
12✔
6
import { SpecialHoF } from './symbols.js'
12✔
7
import { askSnapshotUpdate, askSnapshot } from './inputs.js'
12✔
8
import { diff } from './utils.js'
12✔
9
import { serialize } from './snapshot.js'
12✔
10
import fc from 'fast-check'
12✔
11

12✔
12
const engine = new AsyncLogicEngine()
12✔
13
const ajv = new Ajv()
12✔
14

12✔
15
engine.addMethod('pick', ([a, b]) => pick(a, b), { sync: true, deterministic: true })
12✔
16
engine.addMethod('omit', ([a, b]) => omit(a, b), { sync: true, deterministic: true })
12✔
17
engine.addMethod('omitDeep', ([a, b]) => {
12✔
18
  let res = a
96✔
19
  for (const path of b) res = dissocPath(path.split('.'), res)
96✔
20
  return res
96✔
21
}, { sync: true, deterministic: true })
12✔
22
engine.addMethod('**', ([a, b]) => a ** b, { sync: true, deterministic: true })
12✔
23
engine.addMethod('===', ([a, b]) => equals(a, b), {
12✔
24
  sync: true,
12✔
25
  deterministic: true
12✔
26
})
12✔
27
engine.addMethod('bigint', i => BigInt(i))
12✔
28
engine.addMethod('date', i => i ? new Date(i) : new Date())
12✔
29
engine.addMethod('as', {
12✔
30
  asyncMethod: async ([item, schema], context, above, engine) => {
12✔
31
    if (schema === 'function') return typeof item === 'function'
6,003!
32
    return ajv.validate(schema, item)
6,003✔
33
  },
3✔
34
  traverse: true
12✔
35
})
12✔
36

12✔
37
engine.addMethod('typeof', i => {
12✔
38
  const type = typeof i
72✔
39
  if (type === 'object') {
72!
40
    if (i === null) return 'null'
×
41
    if (Array.isArray(i)) return 'array'
×
42
    // get the constructor name
×
43
    if (i.constructor) return i.constructor.name.toLowerCase()
×
44
  }
×
45
  return type
72✔
46
}, { sync: true, deterministic: true })
12✔
47

12✔
48
engine.addMethod('combine', (data) => Object.assign({}, ...data), {
12✔
49
  sync: true,
12✔
50
  deterministic: true
12✔
51
})
12✔
52

12✔
53
engine.addMethod('list', {
12✔
54
  method: i => i ? [].concat(i) : [],
12!
55
  deterministic: true,
12✔
56
  traverse: true
12✔
57
}, { sync: true })
12✔
58

12✔
59
engine.addMethod('obj', {
12✔
60
  method: (items) => {
12✔
61
    return items ? splitEvery(2, items).reduce((accumulator, [variable, value]) => ({ ...accumulator, [variable]: value }), {}) : {}
1,425!
62
  },
3✔
63
  traverse: true,
12✔
64
  // @ts-ignore
12✔
65
  compile: (data, buildState) => {
12✔
66
    if (!data) return '({})'
81!
67
    data = [].concat(data)
81✔
68
    if (!(data.length % 2)) { return false }
81✔
69
    const items = splitEvery(2, data).map(([variable, item]) => {
×
70
      return `[${Compiler.buildString(variable, buildState)}]: ${Compiler.buildString(item, buildState)}`
×
71
    })
×
72
    return `({ ${items.join(', ')} })`
×
73
  }
81✔
74
})
12✔
75

12✔
76
function generateErrorText (error) {
12✔
77
  if (error && error.constructor.name === 'Error' && error.message) return chalk.red(`"${error.message}"`)
12✔
78
  if (error.errors) return chalk.red(error.errors.map(e => e.message).join('\n'))
×
79
  if (error instanceof Error) {
×
80
    return chalk.red(`${error.constructor.name}: "${error.message}"`)
×
81
  }
×
82
  return chalk.red(JSON.stringify(error))
×
83
}
12✔
84

12✔
85
engine.addMethod('snapshot', {
12✔
86
  asyncMethod: async ([inputs, extract], context) => {
12✔
87
    inputs = await engine.run(inputs, context)
372✔
88

372✔
89
    let result = null
372✔
90
    let promise = false
372✔
91
    // workaround to get the actualError to the output.
372✔
92
    let actualError = null
372✔
93
    try {
372✔
94
      const call = getDataSpecialSnapshot(context.func.apply(null, inputs))
372✔
95
      if (call && call.then) {
372✔
96
        promise = true
24✔
97
      }
24✔
98
      result = { value: await call }
372✔
99
    } catch (err) {
372✔
100
      const errorInfo = err instanceof Error
3✔
101
        ? (err.constructor.name === 'Error' ? err.message : err.constructor.name)
3!
102
        : err
3!
103
      actualError = err
3✔
104
      result = {
3✔
105
        error: errorInfo,
3✔
106
        ...(err.message !== errorInfo && err.message && { message: err.message })
3✔
107
      }
3✔
108
    }
3✔
109

372✔
110
    if (result.value && extract) {
372✔
111
      result.value = await engine.run(extract, { ...context, data: result.value })
9✔
112
    }
9✔
113

372✔
114
    // @ts-ignore
372✔
115
    result.async = promise
372✔
116

372✔
117
    // @ts-ignore
372✔
118
    if (!process.env.OMIT_SNAPSHOT_INPUTS && context.fuzzed) result.input = inputs
372✔
119

372✔
120
    const { exists, value, meta } = await context.snap.find(context.id)
372✔
121

372✔
122
    // @ts-ignore
372✔
123
    if (!exists && await askSnapshot({ item: result, rule: context.rule, id: context.id, file: context.file })) {
372!
124
      await context.snap.set(context.id, result)
×
125
      return [result, true]
×
126
    }
×
127

372✔
128
    let checkError = false
372✔
129
    if (meta?.check) checkError = !await engine.run(meta.check, { expected: value, actual: result })
372✔
130
    const compareResult = meta?.transform ? await engine.run(meta.transform, result) : result
372✔
131
    const compareValue = meta?.transform ? await engine.run(meta.transform, value) : value
372✔
132

372✔
133
    if (!checkError && equals(compareValue, compareResult)) return [result, true]
372✔
134

×
135
    if (exists) {
×
136
      const answer = await askSnapshotUpdate({ item: result, value, rule: context.rule, id: context.id, file: context.file })
×
137
      // We have a snapshot, but it's different, so we need to ask the user if they want to update the snapshot
×
138
      if (answer) {
×
139
        await context.snap.set(context.id, result, typeof answer === 'object' ? answer : undefined)
×
140
        return [result, true]
×
141
      }
×
142
      return [{ ...result, actualError }, false, diff(value, result)]
×
143
    }
×
144

×
145
    return [{ ...result, actualError }, false, 'There is no snapshot for this test.']
×
146
  },
3✔
147
  traverse: false
12✔
148
}, {
12✔
149
  useContext: true
12✔
150
}
12✔
151
)
12✔
152

12✔
153
engine.addMethod('to', async ([inputs, output], context) => {
12✔
154
  try {
27,156✔
155
    const result = getDataSpecial(context.func.apply(null, inputs))
27,156✔
156
    if (!equals(result, output)) {
27,156✔
157
      if (result && result.then) {
3✔
158
        return [await result, false, `Expected ${chalk.green(`${serialize(output)} `)}\n\nReceived ${chalk.red(`Promise<${serialize(await result)}>`)}`]
3✔
159
      }
3✔
160
      return [result, false, diff(output, result)]
×
161
    }
×
162
    return [result, true]
27,153✔
163
  } catch (err) {
27,156!
164
    return [err, false, `Expected ${chalk.green(serialize(output))} but function threw ${chalk.red(generateErrorText(err))}`]
×
165
  }
×
166
}, {
12✔
167
  useContext: true
12✔
168
})
12✔
169

12✔
170
function getDataSpecial (data) {
45,282✔
171
  if (data && data[SpecialHoF]) return data.result
45,282✔
172
  return data
45,225✔
173
}
45,282✔
174

12✔
175
function getDataSpecialSnapshot (data) {
372✔
176
  if (data && data[SpecialHoF]) return { result: data.result, instance: data.instance }
372!
177
  return data
372✔
178
}
372✔
179

12✔
180
engine.addMethod('toParse', {
12✔
181
  asyncMethod: async ([inputs, output], context, above, engine) => {
12✔
182
    try {
12,078✔
183
      inputs = await engine.run(inputs, context)
12,078✔
184
      const result = getDataSpecial(context.func.apply(null, inputs))
12,078✔
185
      if (result && result.then) return [result.catch(err => err), false, 'Function call returns a promise.']
12,078!
186
      return [result, Boolean(await engine.run(output, { data: result, context: context.func.instance, args: inputs }))]
12,075✔
187
    } catch (err) {
12,078✔
188
      return [err, false, `Could not execute condition as function threw ${generateErrorText(err)}`]
3✔
189
    }
3✔
190
  },
3✔
191
  traverse: false
12✔
192
}, {
12✔
193
  useContext: true
12✔
194
})
12✔
195

12✔
196
engine.addMethod('resolves', async ([inputs, output], context) => {
12✔
197
  try {
3,006✔
198
    const result = getDataSpecial(context.func.apply(null, inputs))
3,006✔
199

3,006✔
200
    if (!result || !result.then) {
3,006✔
201
      return [await result, false, `Expected ${chalk.green(`Promise<${serialize(output)}>`)}\n\nReceived ${chalk.red(`${serialize(await result)}`)}`]
3✔
202
    }
3✔
203

3,003✔
204
    if (!equals(await result, output)) {
3,006!
205
      return [result, false, diff(output, await result)]
×
206
    }
×
207

3,003✔
208
    return [await result, true]
3,003✔
209
  } catch (err) {
3,006!
210
    return [err, false, `Expected ${serialize(output)} but function rejected with ${generateErrorText(err)}`]
×
211
  }
×
212
}, {
12✔
213
  useContext: true
12✔
214
})
12✔
215

12✔
216
engine.addMethod('resolvesParse', {
12✔
217
  asyncMethod: async ([inputs, output], context, above, engine) => {
12✔
218
    try {
3,030✔
219
      inputs = await engine.run(inputs, context)
3,030✔
220
      const result = getDataSpecial(context.func.apply(null, inputs))
3,030✔
221
      if (!result || !result.then) return [result, false, `Expected ${chalk.green('Promise<T>')}\nReceived ${chalk.red(serialize(result))}`]
3,030!
222
      return [await result, Boolean(await engine.run(output, { data: await result, context: context.func.instance, args: inputs }))]
3,030✔
223
    } catch (err) {
3,030✔
224
      return [err, false, `Could not execute condition as function rejected with ${generateErrorText(err)}`]
6✔
225
    }
6✔
226
  },
3✔
227
  traverse: false
12✔
228
}, {
12✔
229
  useContext: true
12✔
230
})
12✔
231

12✔
232
engine.addMethod('execute', {
12✔
233
  asyncMethod: async ([inputs], context, above, engine) => {
12✔
234
    try {
180✔
235
      const result = await context.func.apply(null, await engine.run(inputs, context))
180✔
236
      return [result, true]
177✔
237
    } catch (err) {
180✔
238
      return [err, false, `Could not execute as function threw ${generateErrorText(err)}`]
3✔
239
    }
3✔
240
  },
3✔
241
  traverse: false
12✔
242
}, {
12✔
243
  useContext: true
12✔
244
})
12✔
245

12✔
246
engine.addMethod('throws', async ([inputs, output], context) => {
12✔
247
  try {
15,027✔
248
    const result = getDataSpecial(context.func.apply(null, inputs))
15,027✔
249

15,027✔
250
    if (result && result.then) {
15,027✔
251
      try {
6✔
252
        return [await result, false, `Expected to throw but got ${chalk.red(`Promise<${serialize(await result)}>`)}`]
6✔
253
      } catch (err2) {
3✔
254
        return [err2, false, `Expected to throw but got rejected Promise instead. (${chalk.red(err2 && (err2.message || err2.constructor.name || err2))})`]
3!
255
      }
3✔
256
    }
6✔
257
    return [result, false, 'Did not throw.']
3✔
258
  } catch (err) {
15,027✔
259
    const errorName = err.constructor.name
15,018✔
260
    const errorMessage = err.message
15,018✔
261
    if (output && !equals(errorName, output) && !equals(errorMessage, output)) { return [err, false, `Error name or message did not match. Expected '${output}' but got class '${errorName}' or message '${errorMessage}'`] }
15,018!
262
    return [err, true]
15,018✔
263
  }
15,018✔
264
})
3✔
265

12✔
266
engine.addMethod('rejects', async ([inputs, output], context) => {
12✔
267
  try {
12✔
268
    let result
12✔
269
    try { result = getDataSpecial(context.func.apply(null, inputs)) } catch (err2) {
12✔
270
      return [err2, false, 'Async call threw synchronously.']
3✔
271
    }
3✔
272
    if (!result || !result.then) return [result, false, `Expected ${chalk.green('a rejected Promise')}\nReceived ${chalk.red(serialize(result))}`]
12✔
273
    return [await result, false, 'Did not throw.']
12✔
274
  } catch (err) {
3✔
275
    const errorName = err.constructor.name
3✔
276
    const errorMessage = err.message
3✔
277
    if (output && !equals(errorName, output) && !equals(errorMessage, output)) { return [err, false, `Error name or message did not match. Expected '${output}' but got class '${errorName}' or message '${errorMessage}'`] }
3!
278

3✔
279
    return [err, true]
3✔
280
  }
3✔
281
})
3✔
282

12✔
283
engine.addMethod('__fails__', async ([func, inputs, output], context, above, engine) => {
12✔
284
  const data = await engine.run({ [func.join('')]: [inputs, output] }, context)
36✔
285
  return [data, !data[1]]
36✔
286
})
3✔
287

12✔
288
export const createArbitrary = method => data => {
12✔
289
  if (data === undefined) return method()
156✔
290
  return method(...[].concat(data))
84✔
291
}
156✔
292

12✔
293
Object.keys(fc).filter(i => typeof fc[i] === 'function' && i[0] === i[0].toLowerCase()).forEach(addition => {
12✔
294
  engine.addMethod('#' + addition, createArbitrary(fc[addition]), { sync: true })
1,332✔
295
})
1,002✔
296

12✔
297
engine.addMethod('#number', createArbitrary(fc.nat), { sync: true })
12✔
298

12✔
299
export default engine
12✔
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