@small-tech/tap-monkey 1.1.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.4.0] - 2022-05-24
8
+
9
+ Testy monkey.
10
+
11
+ ## Added
12
+
13
+ - 100% code coverage. Now using tape and *drumroll* Tap Monkey itself for the tests.
14
+
15
+ ## [1.3.0] - 2021-03-05
16
+
17
+ Debug monkey.
18
+
19
+ ## Added
20
+
21
+ - Now displays console output from your app if there is any (useful for debugging).
22
+
23
+ ## [1.2.0] - 2021-03-04
24
+
25
+ Bail-out monkey.
26
+
27
+ ## Added
28
+
29
+ - Handles bail-out events.
30
+
31
+ ## Updated
32
+
33
+ - tap-out: version 3.1.0 → 3.2.0
34
+
7
35
  ## [1.1.1] - 2021-03-03
8
36
 
9
37
  Fix typos.
package/README.md CHANGED
@@ -64,7 +64,7 @@ e.g., using [c8](https://github.com/bcoe/c8):
64
64
 
65
65
  ```json
66
66
  "scripts" : {
67
- "test": "c8 tape test/**/*js | tap-monkey"
67
+ "coverage": "c8 tape test/**/*js | tap-monkey"
68
68
  }
69
69
  ```
70
70
 
@@ -76,6 +76,53 @@ While passing tests are displayed ephemerally in the status line so as not to fi
76
76
 
77
77
  (When running tests, we don’t care about passing tests, only failing ones.)
78
78
 
79
+ ## Debug output
80
+
81
+ Any console output that is generated by your program (e.g., from `console.log()` statements) is displayed in full and separate from the current test status line.
82
+
83
+ This is useful while debugging. (I like to have regular and debug test tasks in my code and use the latter when debugging an issue encountered in a test.)
84
+
85
+ e.g., my regular tests define an environment variable that silences console output but I have a separate debug test task that doesn’t include this.
86
+
87
+ ```json
88
+ "scripts" : {
89
+ "test": "QUIET=true tape test/**/*js | tap-monkey",
90
+ "test-debug": "tape test/**/*js | tap-monkey"
91
+ }
92
+ ```
93
+
94
+ Then, all you need is to use a conditional log function in your app that checks for that environment variable. e.g.,
95
+
96
+ ```js
97
+ // Conditionally log to console.
98
+ export default function log (...args) {
99
+ if (process.env.QUIET) {
100
+ return
101
+ }
102
+ console.log(...args)
103
+ }
104
+ ```
105
+
106
+ ## Error behaviour
107
+
108
+ If your test runner (e.g., [ESM Tape Runner](https://github.com/small-tech/esm-tape-runner)) encounters an error in your app’s source code (e.g., a syntax error in one of your classes), it should bail out (and hopefully also log the error so you know what it is). Tap Monkey will respond to bail out events by displaying them and exiting (your test runner should have exit also so you should not get any pipe errors).
109
+
110
+ ## Testing Tap Monkey
111
+
112
+ Tap Monkey itself, of course, comes with unit tests displayed by none other than _\*drumroll\*_ Tap Monkey!
113
+
114
+ ### Run tests
115
+
116
+ ```shell
117
+ npm run -s test
118
+ ```
119
+
120
+ ### Run coverage
121
+
122
+ ```shell
123
+ npm run -s coverage
124
+ ```
125
+
79
126
  ## Like this? Fund us!
80
127
 
81
128
  [Small Technology Foundation](https://small-tech.org) is a tiny, independent not-for-profit.
package/index.js CHANGED
@@ -16,14 +16,15 @@ import Ora from 'ora'
16
16
  import chalk from 'chalk'
17
17
  import { performance } from 'perf_hooks'
18
18
  import tapOut from '@small-tech/tap-out'
19
-
20
- let hasFailures = false
19
+ import os from 'os'
21
20
 
22
21
  // The formatter has a --quiet option that stops status updates being
23
22
  // printed until there is a failure or until the aggregate statistics is
24
23
  // being shown. People using screen readers and other assistive technologies
25
24
  // might want to use this if the number of status updates becomes overwhelming.
26
- let quiet = (process.argv.length === 3 && process.argv[2] === '--quiet')
25
+ export const context = {
26
+ quiet: (process.argv.length === 3 && process.argv[2] === '--quiet')
27
+ }
27
28
 
28
29
  // Due to the 300ms frame duration of the monkey animation, not every
29
30
  // status update we receive about new test suites and test passes will be
@@ -54,23 +55,22 @@ let printingCoverage = false
54
55
  let coverageBorderCount = 0
55
56
  let currentTest = ''
56
57
 
57
- parser.on('test', test => {
58
- spinner.start()
59
- if (!quiet) {
60
- currentTest = test.name
61
- spinner.text = `Running ${chalk.underline(currentTest)} tests`
58
+ const passHandler = (assert => {
59
+ if (!context.quiet) {
60
+ spinner.text = `${chalk.green('✔')} ${assert.name}`
62
61
  }
63
62
  })
64
63
 
65
- parser.on('pass', assert => {
66
- if (!quiet) {
67
- spinner.text = `${chalk.green('✔')} ${assert.name}`
64
+ const testHandler = (test => {
65
+ spinner.start()
66
+ if (!context.quiet) {
67
+ currentTest = test.name
68
+ spinner.text = `Running ${chalk.underline(currentTest)} tests`
68
69
  }
69
70
  })
70
71
 
71
- parser.on('fail', assert => {
72
+ const failHandler = (assert => {
72
73
  // Stop the spinner and output failures in full.
73
- hasFailures = true
74
74
  spinner.stop()
75
75
 
76
76
  const e = assert.error
@@ -80,50 +80,57 @@ parser.on('fail', assert => {
80
80
  if (e.operator !== undefined) console.log(` operator:`, e.operator)
81
81
  if (e.expected !== undefined) console.log(` ${chalk.green(`expected: ${e.expected}`)}`)
82
82
  if (e.actual !== undefined) console.log(` ${chalk.red(`actual : ${e.actual}`)}`)
83
- if (e.at !== undefined) console.log(` ${chalk.yellow(`at : ${e.at}`)}`)
83
+ if (e.at !== undefined) console.log(` ${chalk.yellow(`at : ${e.at.file.replace(os.homedir(), '~')}:${e.at.line}:${e.at.character}`)}`)
84
84
 
85
85
  console.log()
86
86
 
87
87
  e.stack.split('\n').forEach(line => {
88
- console.log(' ', chalk.gray(line))
88
+ console.log(' ', chalk.red(line))
89
89
  })
90
90
 
91
91
  spinner.start()
92
92
  })
93
93
 
94
- parser.on('comment', comment => {
95
- let commentText = comment.raw
96
-
97
- if (commentText.startsWith('----')) {
98
- // We take the first comment that includes a border to signal the
99
- // start of the coverage section and stop the spinner permanently
100
- // from there on.
101
- if (!printingCoverage) {
102
- printingCoverage = true
103
- spinner.stop()
104
- }
94
+ const bailOutHandler = (event => {
95
+ // If the test runner has emitted a bail out event, it has signaled
96
+ // that it cannot continue. So we notify the person and exit.
97
+ spinner.stop()
98
+ console.error(chalk.red(event.raw))
99
+ console.error()
100
+ process.exit(1)
101
+ })
105
102
 
106
- coverageBorderCount++
107
- switch(coverageBorderCount) {
108
- case 1: commentText = `╭─${commentText.replace(/\-\|\-/g, '─┬─')}─╮`; break
109
- case 2: commentText = `├─${commentText.replace(/\-\|\-/g, '─┼─')}─┤`; break
110
- case 3: commentText = `╰─${commentText.replace(/\-\|\-/g, '─┴─')}─╯\n`; break
111
- default: throw new Error('Too many borders found in coverage. Panic!')
112
- }
113
- } else {
114
- commentText = `│ ${commentText} │`
115
- }
103
+ const commentHandler = (comment => {
104
+ spinner.stop()
105
+ let commentText = comment.raw
106
+
107
+ const isCoverageBorder = commentText.startsWith('----')
108
+ if (isCoverageBorder) { printingCoverage = true }
116
109
 
117
110
  if (printingCoverage) {
111
+ if (isCoverageBorder) {
112
+ coverageBorderCount++
113
+ switch(coverageBorderCount) {
114
+ case 1: commentText = `╭─${commentText.replace(/\-\|\-/g, '─┬─')}─╮`; break
115
+ case 2: commentText = `├─${commentText.replace(/\-\|\-/g, '─┼─')}─┤`; break
116
+ case 3: commentText = `╰─${commentText.replace(/\-\|\-/g, '─┴─')}─╯\n`; break
117
+ default: throw new Error('Too many borders found in coverage. Panic!')
118
+ }
119
+ } else {
120
+ // Printing coverage but this line isn’t a border, just surround it with vertical borders.
121
+ commentText = `│ ${commentText} │`
122
+ }
123
+ // Replace any inner borders that there might be with proper box-drawing characters.
118
124
  console.log(commentText.replace(/\|/g, '│').replace(/\-/g, '─'))
119
125
  } else {
120
- // We haven’t started printing coverage yet so this must be some other TAP comment.
121
- // Display it in the status line.
122
- spinner.text = commentText
126
+ // We aren’t printing coverage yet so this must be a regular TAP comment.
127
+ // Display it fully.
128
+ console.log(chalk.yellow(' 🢂 '), commentText.trim())
129
+ spinner.start()
123
130
  }
124
131
  })
125
132
 
126
- parser.on('output', results => {
133
+ const outputHandler = (results => {
127
134
  const duration = ((performance.now() - startTime)/1000).toFixed(2)
128
135
  spinner.stop()
129
136
 
@@ -131,7 +138,9 @@ parser.on('output', results => {
131
138
  const passing = results.pass.length
132
139
  const failing = results.fail.length
133
140
 
134
- if (hasFailures) {
141
+ // TODO: Handle edge case of zero total tests.
142
+
143
+ if (failing > 0) {
135
144
  console.log(` 🙊️ ${chalk.magenta('There are failed tests.')}`)
136
145
  } else {
137
146
  console.log(` 🍌️ ${chalk.green('All tests passing!')}`)
@@ -143,3 +152,13 @@ parser.on('output', results => {
143
152
  console.log(chalk.red( ` Failing ${failing}`))
144
153
  console.log(chalk.gray( ` Duration ${duration} secs`))
145
154
  })
155
+
156
+ parser.on('test', testHandler)
157
+ parser.on('pass', passHandler)
158
+ parser.on('fail', failHandler)
159
+ parser.on('bailOut', bailOutHandler)
160
+ parser.on('comment', commentHandler)
161
+ parser.on('output', outputHandler)
162
+
163
+ export default { testHandler, passHandler, failHandler, bailOutHandler, commentHandler, outputHandler, parser, spinner }
164
+
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@small-tech/tap-monkey",
3
- "version": "1.1.1",
3
+ "version": "1.4.0",
4
4
  "description": "A tap formatter that’s also a monkey.",
5
- "keywords": ["tap", "formatter", "tape", "nyc", "c8", "test anything protocol", "accessible", "a11y"],
5
+ "keywords": [
6
+ "tap",
7
+ "formatter",
8
+ "tape",
9
+ "nyc",
10
+ "c8",
11
+ "test anything protocol",
12
+ "accessible",
13
+ "a11y"
14
+ ],
6
15
  "license": "ISC",
7
16
  "main": "index.js",
8
17
  "type": "module",
@@ -10,7 +19,8 @@
10
19
  "tap-monkey": "./index.js"
11
20
  },
12
21
  "scripts": {
13
- "test": "echo \"Error: no test specified\" && exit 1"
22
+ "test": "tape tests/index.js | node index.js",
23
+ "coverage": "c8 tape tests/index.js | node index.js"
14
24
  },
15
25
  "author": {
16
26
  "name": "Aral Balkan",
@@ -24,8 +34,13 @@
24
34
  "url": "https://github.com/small-tech/tap-monkey.git"
25
35
  },
26
36
  "dependencies": {
27
- "@small-tech/tap-out": "^3.1.0",
37
+ "@small-tech/tap-out": "^3.2.0",
28
38
  "chalk": "^4.1.0",
29
39
  "ora": "^5.3.0"
40
+ },
41
+ "devDependencies": {
42
+ "c8": "^7.11.3",
43
+ "strip-ansi": "^7.0.1",
44
+ "tape": "^5.5.3"
30
45
  }
31
46
  }
package/tests/index.js ADDED
@@ -0,0 +1,295 @@
1
+ import test from 'tape'
2
+ import tapMonkey from '../index.js'
3
+ import { context } from '../index.js'
4
+ import strip from 'strip-ansi'
5
+
6
+ // Since Tap Monkey pipes stdin, this will leave a handle open.
7
+ // We have to destroy stdin when all tests are done for the
8
+ // runner to exit properly.
9
+ // See https://github.com/nodejs/node/issues/32291
10
+
11
+ test.onFinish(() => {
12
+ process.stdin.destroy()
13
+ })
14
+
15
+ //
16
+ // Test handler and general tests.
17
+ //
18
+
19
+ test('test handler', t => {
20
+ context.quiet = false
21
+ tapMonkey.testHandler({name: 'mock'})
22
+ tapMonkey.spinner.stop()
23
+
24
+ t.strictEquals(tapMonkey.spinner._spinner.interval, 300, 'spinner interval is as expected')
25
+ t.strictEquals(tapMonkey.spinner._spinner.frames[0], ' 🙈 ', 'animation frame 1 is correct')
26
+ t.strictEquals(tapMonkey.spinner._spinner.frames[1], ' 🙈 ', 'animation frame 2 is correct')
27
+ t.strictEquals(tapMonkey.spinner._spinner.frames[2], ' 🙉 ', 'animation frame 3 is correct')
28
+ t.strictEquals(tapMonkey.spinner._spinner.frames[3], ' 🙊 ', 'animation frame 4 is correct')
29
+
30
+ t.true(strip(tapMonkey.spinner.text).includes('Running mock tests'), 'test name is displayed correctly')
31
+
32
+ context.quiet = true
33
+ tapMonkey.testHandler({name: 'quiet mock'})
34
+ tapMonkey.spinner.stop()
35
+
36
+ t.false(strip(tapMonkey.spinner.text).includes('Running quiet mock tests'), 'test name not displayed in quiet mode')
37
+
38
+ t.end()
39
+ })
40
+
41
+ //
42
+ // Pass handler tests.
43
+ //
44
+
45
+ test('pass handler', t => {
46
+ // Quiet passes (the default)
47
+ context.quiet = true
48
+ const quietPass = 'a quiet pass'
49
+ tapMonkey.passHandler({ name: quietPass })
50
+ t.false(tapMonkey.spinner.text.includes(quietPass), 'quiet mode should not display passed tests')
51
+
52
+ // Loud passes.
53
+ context.quiet = false
54
+ const loudPass = 'a loud pass'
55
+ tapMonkey.passHandler({ name: loudPass })
56
+ t.true(tapMonkey.spinner.text.includes(loudPass), 'passed tests should display when quiet mode is off')
57
+
58
+ t.end()
59
+ })
60
+
61
+ //
62
+ // Fail handler tests.
63
+ //
64
+
65
+ test('fail handler', t => {
66
+ // Capture console log temporarily.
67
+ const originalConsoleLog = console.log
68
+ let output = ''
69
+ const capturedConsoleLog = (...args) => output += args.join(' ')
70
+ console.log = capturedConsoleLog
71
+
72
+ const mockAssertionWithTypeError = {
73
+ type: 'assert',
74
+ raw: 'not ok 2 TypeError: Cannot convert undefined or null to object',
75
+ ok: false,
76
+ number: 2,
77
+ name: 'TypeError: Cannot convert undefined or null to object',
78
+ error: {
79
+ operator: 'error',
80
+ expected: undefined,
81
+ actual: undefined,
82
+ at: {
83
+ file: '/var/home/aral/Projects/nodekit/node_modules/tape-promise/node_modules/onetime/index.js',
84
+ line: '30',
85
+ character: '12'
86
+ },
87
+ stack: 'TypeError: Cannot convert undefined or null to object\n' +
88
+ 'at Function.keys (<anonymous>)\n' +
89
+ 'at sortResults (file:///var/home/aral/Projects/nodekit/tests/files.js:17:10)\n' +
90
+ 'at file:///var/home/aral/Projects/nodekit/tests/files.js:101:58\n' +
91
+ 'at processTicksAndRejections (node:internal/process/task_queues:96:5)\n',
92
+ raw: ' operator: error\n' +
93
+ ' at: bound (/var/home/aral/Projects/nodekit/node_modules/tape-promise/node_modules/onetime/index.js:30:12)\n' +
94
+ ' stack: |-\n' +
95
+ 'TypeError: Cannot convert undefined or null to object\n' +
96
+ 'at Function.keys (<anonymous>)\n' +
97
+ 'at sortResults (file:///var/home/aral/Projects/nodekit/tests/files.js:17:10)\n' +
98
+ 'at file:///var/home/aral/Projects/nodekit/tests/files.js:101:58\n' +
99
+ 'at processTicksAndRejections (node:internal/process/task_queues:96:5)'
100
+ },
101
+ test: 10
102
+ }
103
+
104
+ tapMonkey.failHandler(mockAssertionWithTypeError)
105
+ tapMonkey.spinner.stop()
106
+ console.log = originalConsoleLog
107
+
108
+ t.true(output.includes('TypeError: Cannot convert undefined or null to object'), 'output includes main error message')
109
+ t.true(output.includes('~/Projects/nodekit/node_modules/tape-promise/node_modules/onetime/index.js:30:12'), 'error location shown')
110
+
111
+ // Test a regular assertion failure.
112
+
113
+ output = ''
114
+ console.log = capturedConsoleLog
115
+
116
+ const regularFailedAssertion = {
117
+ type: 'assert',
118
+ raw: 'not ok 8 quiet mode should not display passed tests',
119
+ ok: false,
120
+ number: 8,
121
+ name: 'quiet mode should not display passed tests',
122
+ error: {
123
+ operator: 'ok',
124
+ expected: 'true',
125
+ actual: 'false',
126
+ at: undefined,
127
+ stack: 'Error: quiet mode should not display passed tests\n' +
128
+ 'at Test.assert [as _assert] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:314:54)\n' +
129
+ 'at Test.bound [as _assert] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
130
+ 'at Test.assert (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:433:10)\n' +
131
+ 'at Test.bound [as true] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
132
+ 'at Test.<anonymous> (file:///var/home/aral/Projects/tap-monkey/tests/index.js:50:9)\n' +
133
+ 'at Test.bound [as _cb] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
134
+ 'at Test.run (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:117:31)\n' +
135
+ 'at Test.bound [as run] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
136
+ 'at Immediate.next [as _onImmediate] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/results.js:88:19)\n' +
137
+ 'at processImmediate (node:internal/timers:466:21)\n',
138
+ raw: ' operator: ok\n' +
139
+ ' expected: true\n' +
140
+ ' actual: false\n' +
141
+ ' stack: |-\n' +
142
+ 'Error: quiet mode should not display passed tests\n' +
143
+ 'at Test.assert [as _assert] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:314:54)\n' +
144
+ 'at Test.bound [as _assert] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
145
+ 'at Test.assert (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:433:10)\n' +
146
+ 'at Test.bound [as true] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
147
+ 'at Test.<anonymous> (file:///var/home/aral/Projects/tap-monkey/tests/index.js:50:9)\n' +
148
+ 'at Test.bound [as _cb] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
149
+ 'at Test.run (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:117:31)\n' +
150
+ 'at Test.bound [as run] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/test.js:99:32)\n' +
151
+ 'at Immediate.next [as _onImmediate] (/var/home/aral/Projects/tap-monkey/node_modules/tape/lib/results.js:88:19)\n' +
152
+ 'at processImmediate (node:internal/timers:466:21)'
153
+ },
154
+ test: 2
155
+ }
156
+
157
+ tapMonkey.failHandler(regularFailedAssertion)
158
+ tapMonkey.spinner.stop()
159
+ console.log = originalConsoleLog
160
+
161
+ t.true(output.includes('operator: ok'), 'output should include operator')
162
+ t.true(output.includes('expected: true'), 'output should include expected field')
163
+ t.true(output.includes('actual : false'), 'output should include actual field')
164
+
165
+ t.end()
166
+ })
167
+
168
+ //
169
+ // Bail out handler tests.
170
+ //
171
+
172
+ test('bailout handler', t => {
173
+ // Setup.
174
+ const _error = console.error
175
+ let output = ''
176
+ console.error = string => output += string
177
+
178
+ const _exit = process.exit
179
+ let exitCalledWithCorrectCode = false
180
+ process.exit = code => exitCalledWithCorrectCode = code === 1
181
+
182
+ // Test.
183
+ tapMonkey.spinner.start()
184
+ const mockRaw = 'mock raw event contents'
185
+ tapMonkey.bailOutHandler({ raw: mockRaw })
186
+
187
+ t.strictEquals(tapMonkey.spinner.isSpinning, false, 'spinner has stopped')
188
+ t.true(output.includes(mockRaw), 'output includes mock event raw string')
189
+ t.true(exitCalledWithCorrectCode, 'exit is called')
190
+
191
+ // Tear down.
192
+ console.error = _error
193
+ process.exit = _exit
194
+
195
+ t.end()
196
+ })
197
+
198
+ //
199
+ // Comment handler tests.
200
+ //
201
+
202
+ test('comment handler', t => {
203
+ // Setup.
204
+ const originalConsoleLog = console.log
205
+ const capturedConsoleLog = (...args) => output += args.join(' ')
206
+
207
+ console.log = capturedConsoleLog
208
+ let output = ''
209
+
210
+ // Test regular comment.
211
+ const regularCommentPrefix = ' 🢂 '
212
+ const regularComment = 'a regular comment'
213
+
214
+ tapMonkey.commentHandler({ raw: regularComment })
215
+ tapMonkey.spinner.stop()
216
+ console.log = originalConsoleLog
217
+
218
+ t.equals(output, `${regularCommentPrefix} ${regularComment}`, 'regular comment is formatted correctly')
219
+
220
+ // Test coverage comments.
221
+ output = ''
222
+ console.log = capturedConsoleLog
223
+
224
+ const coverageTap = [
225
+ '----------|---------|----------|---------|---------|-----------------------',
226
+ ' File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ',
227
+ '----------|---------|----------|---------|---------|-----------------------',
228
+ 'All files | 70.37 | 71.42 | 50 | 70.37 | ',
229
+ ' index.js | 70.37 | 71.42 | 50 | 70.37 | 59-61,105-131,135-152 ',
230
+ '----------|---------|----------|---------|---------|-----------------------'
231
+ ]
232
+
233
+ const expectedCoverageOutput = '╭───────────┬─────────┬──────────┬─────────┬─────────┬────────────────────────╮│ File │ % Stmts │ % Branch │ % Funcs │ % Lines │ Uncovered Line #s │├───────────┼─────────┼──────────┼─────────┼─────────┼────────────────────────┤│ All files │ 70.37 │ 71.42 │ 50 │ 70.37 │ ││ index.js │ 70.37 │ 71.42 │ 50 │ 70.37 │ 59─61,105─131,135─152 │╰───────────┴─────────┴──────────┴─────────┴─────────┴────────────────────────╯'
234
+
235
+ coverageTap.forEach(line => {
236
+ tapMonkey.commentHandler({ raw: line })
237
+ })
238
+ tapMonkey.spinner.stop()
239
+ console.log = originalConsoleLog
240
+
241
+ // Assertions.
242
+ t.equals(output.trim(), expectedCoverageOutput, 'formatted coverage output is correct')
243
+
244
+ // Test too many borders in coverage error.
245
+ t.throws(() => {
246
+ tapMonkey.commentHandler({ raw: coverageTap[0] })
247
+ }, 'too many borders in coverage output error should throw')
248
+
249
+ t.end()
250
+ })
251
+
252
+ //
253
+ // Output handler tests.
254
+ //
255
+
256
+ test ('output handler', t => {
257
+ const vacuumPack = string => string.replace(/\s/g, '')
258
+ const originalConsoleLog = console.log
259
+ const capturedConsoleLog = (...args) => output += args.join(' ')
260
+
261
+ console.log = capturedConsoleLog
262
+ let output = ''
263
+
264
+ // Test: all tests passing.
265
+
266
+ const mockAllTestsPassingResults = {
267
+ asserts: [1, 2, 3],
268
+ pass: [1, 2, 3],
269
+ fail: []
270
+ }
271
+
272
+ tapMonkey.outputHandler(mockAllTestsPassingResults)
273
+ console.log = originalConsoleLog
274
+
275
+ t.strictEquals(tapMonkey.spinner.isSpinning, false, 'spinner has stopped')
276
+ t.true(vacuumPack(output).includes('Alltestspassing!Total3Passing3Failing0'))
277
+
278
+ // Test: failing tests.
279
+
280
+ output = ''
281
+ console.log = capturedConsoleLog
282
+
283
+ const mockFailingTests = {
284
+ asserts: [1, 2, 3],
285
+ pass: [1],
286
+ fail: [2, 3]
287
+ }
288
+
289
+ tapMonkey.outputHandler(mockFailingTests)
290
+ console.log = originalConsoleLog
291
+
292
+ t.true(vacuumPack(output).includes('Therearefailedtests.Total3Passing1Failing2'))
293
+ t.end()
294
+ })
295
+