UNPKG

4.21 kBJavaScriptView Raw
1const { resolve } = require('path');
2const erte = require('@artdeco/erte');
3const { equal } = require('assert');
4const { confirm } = require('reloquent');
5const { inspect } = require('util');
6const read = require('@wrote/read');
7const write = require('@wrote/write');
8const ensurePath = require('@wrote/ensure-path');
9const erotic = require('erotic');
10const frame = require('frame-of-mind');
11
12const isJSON = p => /\.json$/.test(p)
13
14/**
15 * SnapshotContext allows to match the test result against a snapshot. The default snapshot location is `test/snapshot`.
16 */
17class SnapshotContext {
18 constructor() {
19 this.snapshotsDir = 'test/snapshot'
20 }
21 /**
22 * Set the directory to save snapshots in.
23 * @param {string} dir The directory.
24 */
25 setDir(dir) {
26 this.snapshotsDir = dir
27 }
28 async save(path, snapshot) {
29 const p = resolve(this.snapshotsDir, path)
30 await ensurePath(p)
31 if (isJSON(p)) {
32 const data = JSON.stringify(snapshot, null, 2)
33 await write(p, data)
34 } else {
35 await write(p, snapshot)
36 }
37 }
38 async prompt(snapshot, name) {
39 if (typeof snapshot == 'string') {
40 let maxWidth = snapshot.split(/\r?\n/).reduce((acc, current) => {
41 if (current.length > acc) return current.length
42 }, 0)
43 if (process.stdout.isTTY && process.stdout.columns - 4 >= maxWidth) {
44 console.log(frame(snapshot)) // eslint-disable-line no-console
45 } else {
46 console.log(snapshot) // eslint-disable-line no-console
47 }
48 } else {
49 console.log(inspect(snapshot, { colors: true })) // eslint-disable-line
50 }
51 const answer = await confirm(`Save snapshot${name ? ` for ${name}` : ''}?`)
52 return answer
53 }
54 async promptAndSave(path, actual, name) {
55 if (!actual) throw new Error('Give snapshot to save')
56 const res = await this.prompt(actual, name)
57 if (res) {
58 await this.save(path, actual)
59 } else {
60 throw new Error('Could not test missing snapshot')
61 }
62 }
63 async read(path) {
64 const p = resolve(this.snapshotsDir, path)
65 if (isJSON(p)) {
66 const data = await read(p)
67 const snapshot = JSON.parse(data)
68 return snapshot
69 } else {
70 const snapshot = await read(p)
71 return snapshot
72 }
73 }
74 /**
75 * Test the snapshot by reading the file and matching it against the given actual value. If filename ends with `.json`, the data will be serialised as a JSON, and then parsed back and deep-equal will be performed. Otherwise, string comparison is made with red/green highlighting. If no file exists, a prompt will be shown to save a snapshot. Answer with **y** to accept the snapshot and pass the test. There's no update possibility which means files must be deleted by hand and new snapshots taken.
76 * @param {string} path Path to the file
77 * @param {string} actual Expected result
78 * @param {string} name The name of the test.
79 * @param {boolean} [interactive] Whether to ask to update the test in interactive mode.
80 */
81 async test(path, actual, name, interactive = false) {
82 if (!actual) throw new Error('Pass the actual value for snapshot.')
83 const cb = erotic(true)
84 const json = isJSON(path)
85 let expected
86 try {
87 expected = await this.read(path)
88 if (json) {
89 const { deepEqual } = require(/*dpck*/'@zoroaster/assert')
90 deepEqual(actual, expected)
91 } else {
92 equal(actual, expected)
93 }
94 } catch (err) {
95 if (err.code == 'ENOENT') {
96 await this.promptAndSave(path, actual, name)
97 return
98 }
99 let erteString
100 if (!json) {
101 erteString = erte(expected, actual)
102 }
103 if (interactive) {
104 if (json) console.log(err.message)
105 else console.log(erteString)
106 const upd = await confirm(`Update snapshot${name ? ` for ${name}` : ''}?`)
107 if (upd) {
108 await this.save(path, actual)
109 return
110 }
111 }
112 if (!json) {
113 !interactive && console.log(erteString) // eslint-disable-line no-console
114 const e = cb('The string didn\'t match the snapshot.')
115 e.erte = erteString
116 throw e
117 }
118 const e = cb(err)
119 throw e
120 }
121 }
122}
123
124
125module.exports = SnapshotContext
\No newline at end of file