UNPKG

4.67 kBJavaScriptView Raw
1'use strict'
2
3const debug = require('debug')('snap-shot')
4const stackSites = require('stack-sites')
5const callsites = require('callsites')
6const la = require('lazy-ass')
7const is = require('check-more-types')
8const getSpecFunction = require('find-test-caller')
9const {strip} = require('./utils')
10const snapShotCore = require('snap-shot-core')
11const compare = require('./compare')
12
13const isNode = Boolean(require('fs').existsSync)
14const isBrowser = !isNode
15const isCypress = isBrowser && typeof cy === 'object'
16
17let fs
18if (isNode) {
19 fs = require('./file-system')
20} else if (isCypress) {
21 fs = require('./cypress-system')
22} else {
23 fs = require('./browser-system')
24}
25
26const opts = {
27 show: Boolean(process.env.SHOW),
28 dryRun: Boolean(process.env.DRY),
29 update: Boolean(process.env.UPDATE),
30 ci: Boolean(process.env.CI)
31}
32
33const SNAP_SHOT_EXTENSION = '.snap-shot'
34
35function getSpec ({file, line}) {
36 return getSpecFunction({file, line, fs})
37}
38
39function dataDriven (fn, inputs) {
40 la(is.fn(fn), 'expected a function for data-driven test', fn)
41 la(is.array(inputs),
42 'expected list of inputs', inputs, 'to function', fn.name)
43 const name = fn.name
44 const behavior = inputs.map(given => {
45 const args = Array.isArray(given) ? given : [given]
46 return {
47 given,
48 expect: fn.apply(null, args)
49 }
50 })
51 return {name, behavior}
52}
53
54function snapshot (what, update) {
55 const sites = stackSites()
56 if (sites.length < 3) {
57 // hmm, maybe there is test (like we are inside Cypress)
58 if (this && this.test && this.test.title) {
59 debug('no callsite, but have test title "%s"', this.test.title)
60 return this.test.title
61 }
62 debug(sites)
63 const msg = 'Do not have caller function callsite'
64 throw new Error(msg)
65 }
66 debug('%d callsite(s)', sites.length)
67
68 const caller = sites[2]
69 const file = caller.filename
70 // TODO report function name
71 // https://github.com/bahmutov/stack-sites/issues/1
72 const line = caller.line
73 const column = caller.column
74 const message = `
75 file: ${file}
76 line: ${line},
77 column: ${column}
78 `
79 debug(message)
80 let {specName, specSource, startLine} = getSpec({file, line, column})
81
82 // maybe the "snapshot" function was part of composition
83 // TODO handle arbitrary long chains by walking up to library code
84 if (!specName) {
85 const caller = sites[3]
86 const file = caller.filename
87 const line = caller.line
88 const column = caller.column
89 debug('trying to get snapshot from %s %d,%d', file, line, column)
90 const out = getSpec({file, line, column})
91 specName = out.specName
92 specSource = out.specSource
93 startLine = out.startLine
94 }
95
96 if (!specName) {
97 // make the file was transpiled. Try callsites search
98 const sites = callsites()
99 const caller = sites[1]
100 const file = caller.getFileName()
101 const line = caller.getLineNumber()
102 const column = caller.getColumnNumber()
103 debug('trying to get snapshot from callsite %s %d,%d', file, line, column)
104 const out = getSpec({file, line, column})
105 specName = out.specName
106 specSource = out.specSource
107 startLine = out.startLine
108 }
109
110 if (!specName) {
111 console.error('Problem finding caller')
112 console.trace()
113
114 const relativeName = fs.fromCurrentFolder(file)
115 const msg = `Could not determine test for ${relativeName}
116 line ${line} column ${column}`
117 throw new Error(msg)
118 }
119 debug(`found spec name "${specName}" for line ${line} column ${column}`)
120 la(is.unemptyString(specSource), 'could not get spec source from',
121 file, 'line', line, 'column', column, 'named', specName)
122 la(is.number(startLine), 'could not determine spec function start line',
123 file, 'line', line, 'column', column, 'named', specName)
124
125 // is this data-driven test? for example
126 // snapshot(isPrime, 1, 2, 3, 4, 5)
127 // or
128 // snapshot(add, [1, 2], [-5, 5], [4, 6])
129 if (is.fn(what) && arguments.length > 1) {
130 debug(`data-driven test for ${what.name}`)
131 la(is.unemptyString(what.name),
132 'input function is missing name', what.toString())
133 what = dataDriven(what, Array.from(arguments).slice(1))
134 }
135
136 const setOrCheckValue = any => {
137 const value = strip(any)
138 snapShotCore({
139 what: value,
140 file,
141 specName,
142 compare: compare,
143 ext: SNAP_SHOT_EXTENSION,
144 opts
145 })
146 return value
147 }
148
149 if (is.promise(what)) {
150 return what.then(setOrCheckValue)
151 } else {
152 return setOrCheckValue(what)
153 }
154}
155
156if (isBrowser) {
157 // there might be async step to load test source code in the browser
158 la(is.fn(fs.init), 'browser file system is missing init', fs)
159 snapshot.init = fs.init
160}
161
162module.exports = snapshot