1 | 'use strict'
|
2 |
|
3 | const debug = require('debug')('snap-shot')
|
4 | const debugSave = require('debug')('save')
|
5 | const stackSites = require('stack-sites')
|
6 | const callsites = require('callsites')
|
7 | const la = require('lazy-ass')
|
8 | const is = require('check-more-types')
|
9 | const utils = require('./utils')
|
10 | const {snapshotIndex, strip} = utils
|
11 | const {train} = require('validate-by-example')
|
12 |
|
13 | const isNode = Boolean(require('fs').existsSync)
|
14 | const isBrowser = !isNode
|
15 | const isCypress = isBrowser && typeof cy === 'object'
|
16 |
|
17 | let fs
|
18 | if (isNode) {
|
19 | fs = require('./file-system')
|
20 | } else if (isCypress) {
|
21 | fs = require('./cypress-system')
|
22 | } else {
|
23 | fs = require('./browser-system')
|
24 | }
|
25 |
|
26 |
|
27 | const snapshotsPerTest = {}
|
28 |
|
29 | const shouldUpdate = Boolean(process.env.UPDATE)
|
30 | const shouldShow = Boolean(process.env.SHOW)
|
31 |
|
32 | function getSpecFunction ({file, line}) {
|
33 | return utils.getSpecFunction({file, line, fs})
|
34 | }
|
35 |
|
36 | const formKey = (specName, oneIndex) =>
|
37 | `${specName} ${oneIndex}`
|
38 |
|
39 | function findStoredValue ({file, specName, index = 1}) {
|
40 | const relativePath = fs.fromCurrentFolder(file)
|
41 | if (shouldUpdate) {
|
42 |
|
43 | return
|
44 | }
|
45 |
|
46 | debug('loading snapshots from %s for spec %s', file, relativePath)
|
47 | const snapshots = fs.loadSnapshots(file)
|
48 | if (!snapshots) {
|
49 | return
|
50 | }
|
51 |
|
52 | const key = formKey(specName, index)
|
53 | if (!(key in snapshots)) {
|
54 | return
|
55 | }
|
56 |
|
57 | return snapshots[key]
|
58 | }
|
59 |
|
60 | function storeValue ({file, specName, index, value}) {
|
61 | la(value !== undefined, 'cannot store undefined value')
|
62 | la(is.unemptyString(file), 'missing filename', file)
|
63 | la(is.unemptyString(specName), 'missing spec name', specName)
|
64 | la(is.positive(index), 'missing snapshot index', file, specName, index)
|
65 |
|
66 | const snapshots = fs.loadSnapshots(file)
|
67 | const key = formKey(specName, index)
|
68 | snapshots[key] = value
|
69 |
|
70 | if (shouldShow) {
|
71 | const relativeName = fs.fromCurrentFolder(file)
|
72 | console.log('saving snapshot "%s" for file %s', key, relativeName)
|
73 | console.log(value)
|
74 | }
|
75 |
|
76 | fs.saveSnapshots(file, snapshots)
|
77 | debug('saved updated snapshot %d for spec %s', index, specName)
|
78 |
|
79 | debugSave('Saved for "%s %d" snapshot\n%s',
|
80 | specName, index, JSON.stringify(value, null, 2))
|
81 | }
|
82 |
|
83 | const isPromise = x => is.object(x) && is.fn(x.then)
|
84 |
|
85 | function snapshot (what, update) {
|
86 | const sites = stackSites()
|
87 | if (sites.length < 3) {
|
88 |
|
89 | if (this && this.test && this.test.title) {
|
90 | debug('no callsite, but have test title "%s"', this.test.title)
|
91 | return this.test.title
|
92 | }
|
93 | const msg = 'Do not have caller function callsite'
|
94 | throw new Error(msg)
|
95 | }
|
96 | debug('%d callsite(s)', sites.length)
|
97 |
|
98 | const caller = sites[2]
|
99 | const file = caller.filename
|
100 |
|
101 |
|
102 | const line = caller.line
|
103 | const column = caller.column
|
104 | const message = `
|
105 | file: ${file}
|
106 | line: ${line},
|
107 | column: ${column}
|
108 | `
|
109 | debug(message)
|
110 | let {specName, specSource, startLine} =
|
111 | getSpecFunction({file, line, column})
|
112 |
|
113 |
|
114 |
|
115 | if (!specName) {
|
116 | const caller = sites[3]
|
117 | const file = caller.filename
|
118 | const line = caller.line
|
119 | const column = caller.column
|
120 | debug('trying to get snapshot from %s %d,%d', file, line, column)
|
121 | const out = getSpecFunction({file, line, column})
|
122 | specName = out.specName
|
123 | specSource = out.specSource
|
124 | startLine = out.startLine
|
125 | }
|
126 |
|
127 | if (!specName) {
|
128 |
|
129 | const sites = callsites()
|
130 | const caller = sites[1]
|
131 | const file = caller.getFileName()
|
132 | const line = caller.getLineNumber()
|
133 | const column = caller.getColumnNumber()
|
134 | debug('trying to get snapshot from callsite %s %d,%d', file, line, column)
|
135 | const out = getSpecFunction({file, line, column})
|
136 | specName = out.specName
|
137 | specSource = out.specSource
|
138 | startLine = out.startLine
|
139 | }
|
140 |
|
141 | if (!specName) {
|
142 | console.error('Problem finding caller')
|
143 | console.trace()
|
144 |
|
145 | const relativeName = fs.fromCurrentFolder(file)
|
146 | const msg = `Could not determine test for ${relativeName}
|
147 | line ${line} column ${column}`
|
148 | throw new Error(msg)
|
149 | }
|
150 | debug(`found spec name "${specName}" for line ${line} column ${column}`)
|
151 | la(is.unemptyString(specSource), 'could not get spec source from',
|
152 | file, 'line', line, 'column', column, 'named', specName)
|
153 | la(is.number(startLine), 'could not determine spec function start line',
|
154 | file, 'line', line, 'column', column, 'named', specName)
|
155 |
|
156 | const setOrCheckValue = any => {
|
157 | const index = snapshotIndex({specName, counters: snapshotsPerTest})
|
158 | la(is.positive(index), 'invalid snapshot index', index,
|
159 | 'for\n', specName, '\ncounters', snapshotsPerTest)
|
160 | debug('spec "%s" snapshot is #%d',
|
161 | specName, index)
|
162 |
|
163 | const value = strip(any)
|
164 | const expected = findStoredValue({file, specName, index})
|
165 | if (update || expected === undefined) {
|
166 | const schema = train(value)
|
167 | storeValue({file, specName, index, value: schema})
|
168 | } else {
|
169 | debug('found schema snapshot for "%s", value', specName, expected)
|
170 | fs.raiseIfDifferent({
|
171 | value,
|
172 | expected,
|
173 | specName
|
174 | })
|
175 | }
|
176 |
|
177 | return value
|
178 | }
|
179 |
|
180 | if (isPromise(what)) {
|
181 | return what.then(setOrCheckValue)
|
182 | } else {
|
183 | return setOrCheckValue(what)
|
184 | }
|
185 | }
|
186 |
|
187 | if (isBrowser) {
|
188 |
|
189 | la(is.fn(fs.init), 'browser file system is missing init', fs)
|
190 | snapshot.init = fs.init
|
191 | }
|
192 |
|
193 | module.exports = snapshot
|