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

TotalTechGeek / pineapple / 5214823182

pending completion
5214823182

Pull #26

github

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

1800 of 1954 branches covered (92.12%)

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

86.27
/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
12✔
19
  for (const path of b) res = dissocPath(path.split('.'), res)
12✔
20
  return res
12✔
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('combine', (data) => Object.assign({}, ...data), {
12✔
38
  sync: true,
12✔
39
  deterministic: true
12✔
40
})
12✔
41

12✔
42
engine.addMethod('list', {
12✔
43
  method: i => i ? [].concat(i) : [],
12!
44
  deterministic: true,
12✔
45
  traverse: true
12✔
46
}, { sync: true })
12✔
47

12✔
48
engine.addMethod('obj', {
12✔
49
  method: (items) => {
12✔
50
    return items ? splitEvery(2, items).reduce((accumulator, [variable, value]) => ({ ...accumulator, [variable]: value }), {}) : {}
912!
51
  },
3✔
52
  traverse: true,
12✔
53
  // @ts-ignore
12✔
54
  compile: (data, buildState) => {
12✔
55
    if (!data) return '({})'
81!
56
    data = [].concat(data)
81✔
57
    if (!(data.length % 2)) { return false }
81✔
58
    const items = splitEvery(2, data).map(([variable, item]) => {
×
59
      return `[${Compiler.buildString(variable, buildState)}]: ${Compiler.buildString(item, buildState)}`
×
60
    })
×
61
    return `({ ${items.join(', ')} })`
×
62
  }
81✔
63
})
12✔
64

12✔
65
function generateErrorText (error) {
12✔
66
  if (error && error.constructor.name === 'Error' && error.message) return chalk.red(`"${error.message}"`)
12✔
67
  if (error.errors) return chalk.red(error.errors.map(e => e.message).join('\n'))
×
68
  if (error instanceof Error) {
×
69
    return chalk.red(`${error.constructor.name}: "${error.message}"`)
×
70
  }
×
71
  return chalk.red(JSON.stringify(error))
×
72
}
12✔
73

12✔
74
engine.addMethod('snapshot', {
12✔
75
  asyncMethod: async ([inputs, extract], context) => {
12✔
76
    inputs = await engine.run(inputs, context)
312✔
77

312✔
78
    let result = null
312✔
79
    let promise = false
312✔
80
    // workaround to get the actualError to the output.
312✔
81
    let actualError = null
312✔
82
    try {
312✔
83
      const call = getDataSpecialSnapshot(context.func.apply(null, inputs))
312✔
84
      if (call && call.then) {
312✔
85
        promise = true
24✔
86
      }
24✔
87
      result = { value: await call }
312✔
88
    } catch (err) {
312✔
89
      const errorInfo = err instanceof Error
3✔
90
        ? (err.constructor.name === 'Error' ? err.message : err.constructor.name)
3!
91
        : err
3!
92
      actualError = err
3✔
93
      result = {
3✔
94
        error: errorInfo,
3✔
95
        ...(err.message !== errorInfo && err.message && { message: err.message })
3✔
96
      }
3✔
97
    }
3✔
98

312✔
99
    if (result.value && extract) {
312✔
100
      result.value = await engine.run(extract, { ...context, data: result.value })
9✔
101
    }
9✔
102

312✔
103
    // @ts-ignore
312✔
104
    result.async = promise
312✔
105

312✔
106
    // @ts-ignore
312✔
107
    if (!process.env.OMIT_SNAPSHOT_INPUTS && context.fuzzed) result.input = inputs
312✔
108

312✔
109
    const { exists, value, transform } = await context.snap.find(context.id)
312✔
110

312✔
111
    // @ts-ignore
312✔
112
    if (!exists && await askSnapshot({ item: result, rule: context.rule, id: context.id, file: context.file })) {
312!
113
      await context.snap.set(context.id, result)
×
114
      return [result, true]
×
115
    }
×
116

312✔
117
    const compareResult = transform ? await engine.run(transform, result) : result
312✔
118
    const compareValue = transform ? await engine.run(transform, value) : value
312✔
119

312✔
120
    if (equals(compareValue, compareResult)) return [result, true]
312✔
121

×
122
    if (exists) {
×
123
      const answer = await askSnapshotUpdate({ item: result, value, rule: context.rule, id: context.id, file: context.file })
×
124
      // We have a snapshot, but it's different, so we need to ask the user if they want to update the snapshot
×
125
      if (answer) {
×
126
        await context.snap.set(context.id, result, typeof answer === 'object' ? answer : undefined)
×
127
        return [result, true]
×
128
      }
×
129
      return [{ ...result, actualError }, false, diff(value, result)]
×
130
    }
×
131

×
132
    return [{ ...result, actualError }, false, 'There is no snapshot for this test.']
×
133
  },
3✔
134
  traverse: false
12✔
135
}, {
12✔
136
  useContext: true
12✔
137
}
12✔
138
)
12✔
139

12✔
140
engine.addMethod('to', async ([inputs, output], context) => {
12✔
141
  try {
27,156✔
142
    const result = getDataSpecial(context.func.apply(null, inputs))
27,156✔
143
    if (!equals(result, output)) {
27,156✔
144
      if (result && result.then) {
3✔
145
        return [await result, false, `Expected ${chalk.green(`${serialize(output)} `)}\n\nReceived ${chalk.red(`Promise<${serialize(await result)}>`)}`]
3✔
146
      }
3✔
147
      return [result, false, diff(output, result)]
×
148
    }
×
149
    return [result, true]
27,153✔
150
  } catch (err) {
27,156!
151
    return [err, false, `Expected ${chalk.green(serialize(output))} but function threw ${chalk.red(generateErrorText(err))}`]
×
152
  }
×
153
}, {
12✔
154
  useContext: true
12✔
155
})
12✔
156

12✔
157
function getDataSpecial (data) {
45,282✔
158
  if (data && data[SpecialHoF]) return data.result
45,282✔
159
  return data
45,225✔
160
}
45,282✔
161

12✔
162
function getDataSpecialSnapshot (data) {
312✔
163
  if (data && data[SpecialHoF]) return { result: data.result, instance: data.instance }
312!
164
  return data
312✔
165
}
312✔
166

12✔
167
engine.addMethod('toParse', {
12✔
168
  asyncMethod: async ([inputs, output], context, above, engine) => {
12✔
169
    try {
12,078✔
170
      inputs = await engine.run(inputs, context)
12,078✔
171
      const result = getDataSpecial(context.func.apply(null, inputs))
12,078✔
172
      if (result && result.then) return [result.catch(err => err), false, 'Function call returns a promise.']
12,078!
173
      return [result, Boolean(await engine.run(output, { data: result, context: context.func.instance, args: inputs }))]
12,075✔
174
    } catch (err) {
12,078✔
175
      return [err, false, `Could not execute condition as function threw ${generateErrorText(err)}`]
3✔
176
    }
3✔
177
  },
3✔
178
  traverse: false
12✔
179
}, {
12✔
180
  useContext: true
12✔
181
})
12✔
182

12✔
183
engine.addMethod('resolves', async ([inputs, output], context) => {
12✔
184
  try {
3,006✔
185
    const result = getDataSpecial(context.func.apply(null, inputs))
3,006✔
186

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

3,003✔
191
    if (!equals(await result, output)) {
3,006!
192
      return [result, false, diff(output, await result)]
×
193
    }
×
194

3,003✔
195
    return [await result, true]
3,003✔
196
  } catch (err) {
3,006!
197
    return [err, false, `Expected ${serialize(output)} but function rejected with ${generateErrorText(err)}`]
×
198
  }
×
199
}, {
12✔
200
  useContext: true
12✔
201
})
12✔
202

12✔
203
engine.addMethod('resolvesParse', {
12✔
204
  asyncMethod: async ([inputs, output], context, above, engine) => {
12✔
205
    try {
3,030✔
206
      inputs = await engine.run(inputs, context)
3,030✔
207
      const result = getDataSpecial(context.func.apply(null, inputs))
3,030✔
208
      if (!result || !result.then) return [result, false, `Expected ${chalk.green('Promise<T>')}\nReceived ${chalk.red(serialize(result))}`]
3,030!
209
      return [await result, Boolean(await engine.run(output, { data: await result, context: context.func.instance, args: inputs }))]
3,030✔
210
    } catch (err) {
3,030✔
211
      return [err, false, `Could not execute condition as function rejected with ${generateErrorText(err)}`]
6✔
212
    }
6✔
213
  },
3✔
214
  traverse: false
12✔
215
}, {
12✔
216
  useContext: true
12✔
217
})
12✔
218

12✔
219
engine.addMethod('execute', {
12✔
220
  asyncMethod: async ([inputs], context, above, engine) => {
12✔
221
    try {
180✔
222
      const result = await context.func.apply(null, await engine.run(inputs, context))
180✔
223
      return [result, true]
177✔
224
    } catch (err) {
180✔
225
      return [err, false, `Could not execute as function threw ${generateErrorText(err)}`]
3✔
226
    }
3✔
227
  },
3✔
228
  traverse: false
12✔
229
}, {
12✔
230
  useContext: true
12✔
231
})
12✔
232

12✔
233
engine.addMethod('throws', async ([inputs, output], context) => {
12✔
234
  try {
15,027✔
235
    const result = getDataSpecial(context.func.apply(null, inputs))
15,027✔
236

15,027✔
237
    if (result && result.then) {
15,027✔
238
      try {
6✔
239
        return [await result, false, `Expected to throw but got ${chalk.red(`Promise<${serialize(await result)}>`)}`]
6✔
240
      } catch (err2) {
3✔
241
        return [err2, false, `Expected to throw but got rejected Promise instead. (${chalk.red(err2 && (err2.message || err2.constructor.name || err2))})`]
3!
242
      }
3✔
243
    }
6✔
244
    return [result, false, 'Did not throw.']
3✔
245
  } catch (err) {
15,027✔
246
    const errorName = err.constructor.name
15,018✔
247
    const errorMessage = err.message
15,018✔
248
    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!
249
    return [err, true]
15,018✔
250
  }
15,018✔
251
})
3✔
252

12✔
253
engine.addMethod('rejects', async ([inputs, output], context) => {
12✔
254
  try {
12✔
255
    let result
12✔
256
    try { result = getDataSpecial(context.func.apply(null, inputs)) } catch (err2) {
12✔
257
      return [err2, false, 'Async call threw synchronously.']
3✔
258
    }
3✔
259
    if (!result || !result.then) return [result, false, `Expected ${chalk.green('a rejected Promise')}\nReceived ${chalk.red(serialize(result))}`]
12✔
260
    return [await result, false, 'Did not throw.']
12✔
261
  } catch (err) {
3✔
262
    const errorName = err.constructor.name
3✔
263
    const errorMessage = err.message
3✔
264
    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!
265

3✔
266
    return [err, true]
3✔
267
  }
3✔
268
})
3✔
269

12✔
270
engine.addMethod('__fails__', async ([func, inputs, output], context, above, engine) => {
12✔
271
  const data = await engine.run({ [func.join('')]: [inputs, output] }, context)
36✔
272
  return [data, !data[1]]
36✔
273
})
3✔
274

12✔
275
export const createArbitrary = method => data => {
12✔
276
  if (data === undefined) return method()
150✔
277
  return method(...[].concat(data))
84✔
278
}
150✔
279

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

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

12✔
286
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

© 2025 Coveralls, Inc