UNPKG

4.42 kBJavaScriptView Raw
1'use strict'
2
3const la = require('lazy-ass')
4const is = require('check-more-types')
5const falafel = require('falafel')
6const crypto = require('crypto')
7const debug = require('debug')('snap-shot')
8const path = require('path')
9
10function isTestFunctionName (name) {
11 return ['it', 'test'].includes(name)
12}
13
14function isTestFunction (node) {
15 if (node.type === 'Identifier') {
16 return isTestFunctionName(node.name)
17 }
18 if (node.type === 'MemberExpression') {
19 return isTestFunctionName(node.object.name)
20 }
21}
22
23function sha256 (string) {
24 la(is.unemptyString(string), 'missing string to SHA', string)
25 const hash = crypto.createHash('sha256')
26 hash.update(string)
27 return hash.digest('hex')
28}
29
30function snapshotIndex ({counters, specName}) {
31 la(is.object(counters), 'expected counters', counters)
32 la(is.unemptyString(specName), 'expected specName', specName)
33 if (!(specName in counters)) {
34 counters[specName] = 1
35 } else {
36 counters[specName] += 1
37 }
38 return counters[specName]
39}
40
41function shouldTranspile () {
42 const exists = require('fs').existsSync
43 if (!exists) {
44 return false
45 }
46 return exists(path.join(process.cwd(), '.babelrc'))
47}
48
49function transpile (filename) {
50 debug('transpiling %s', filename)
51 const babel = require('babel-core')
52 const {transformFileSync} = babel
53 // trick to keep the line numbers same as original code
54 const opts = {
55 sourceMaps: false,
56 retainLines: true
57 }
58 const {code} = transformFileSync(filename, opts)
59 return code
60}
61
62function findInSource ({source, file, line}) {
63 la(is.string(source), 'could not get source from', file)
64
65 let foundSpecName, specSource, startLine
66 const options = {
67 locations: true,
68 sourceType: 'module'
69 }
70
71 falafel(source, options, node => {
72 if (foundSpecName) {
73 // already found
74 return
75 }
76 // nested IFs make debugging easier
77 if (node.type === 'CallExpression') {
78 if (isTestFunction(node.callee)) {
79 if (node.loc.start.line < line &&
80 node.loc.end.line > line) {
81 debug('found test function around snapshot at line %d', line)
82
83 if (node.arguments.length !== 2) {
84 throw new Error('Cannot get test name for ' + node.source())
85 }
86
87 specSource = node.arguments[1].source()
88 startLine = node.loc.start.line
89
90 // TODO handle tests where just a single function argument was used
91 // it(function testThis() {...})
92 let specName
93
94 const nameNode = node.arguments[0]
95 if (nameNode.type === 'TemplateLiteral') {
96 specName = nameNode.source().replace(/`/g, '')
97 debug('template literal name "%s"', specName)
98 } else if (nameNode.type === 'Literal') {
99 specName = nameNode.value
100 debug('regular string name "%s", specName')
101 } else {
102 // TODO handle single function
103 }
104
105 if (node.arguments.length === 2 &&
106 node.arguments[1].type === 'FunctionExpression' &&
107 node.arguments[1].id &&
108 is.unemptyString(node.arguments[1].id.name)) {
109 specName = node.arguments[1].id.name
110 debug('callback function has a name "%s"', specName)
111 }
112
113 foundSpecName = specName
114
115 if (!foundSpecName) {
116 const hash = sha256(specSource)
117 debug('using source hash %s for found spec in %s line %d',
118 hash, file, line)
119 foundSpecName = hash
120 }
121 }
122 }
123 }
124 })
125 return {
126 specName: foundSpecName,
127 specSource,
128 startLine
129 }
130}
131
132function getSpecFunction ({fs, file, line}) {
133 // TODO can be cached efficiently
134 la(is.unemptyString(file), 'missing file', file)
135 la(is.number(line), 'missing line number', line)
136
137 let found
138 try {
139 const source = fs.readFileSync(file, 'utf8')
140 found = findInSource({source, file, line})
141 } catch (e) {
142 debug('problem finding without transpiling %s %d', file, line)
143 }
144
145 if (found) {
146 return found
147 }
148
149 // maybe we need transpiling?
150 if (shouldTranspile()) {
151 const source = transpile(file)
152 found = findInSource({source, file, line})
153 return found
154 }
155}
156
157// make sure values in the object are "safe" to be serialized
158// and compared from loaded value
159function strip (o) {
160 return JSON.parse(JSON.stringify(o))
161}
162
163module.exports = {
164 snapshotIndex,
165 getSpecFunction,
166 strip
167}