@reporters/testwatch 1.0.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 +8 -0
- package/README.md +24 -0
- package/assets/cli.gif +0 -0
- package/assets/cli.gif.yml +185 -0
- package/index.js +246 -0
- package/nodeVersion.js +5 -0
- package/package.json +27 -0
- package/tests/fixtures/index.test.js +9 -0
- package/tests/fixtures/j.test.js +9 -0
- package/tests/index.test.js +230 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@reporters/testwatch)  [](https://codecov.io/gh/MoLow/reporters)
|
|
2
|
+
|
|
3
|
+
# Interactive Test Runner
|
|
4
|
+
An interactive REPL for `node:test` watch mode.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @reporters/testwatch
|
|
12
|
+
```
|
|
13
|
+
or
|
|
14
|
+
```bash
|
|
15
|
+
yarn global add @reporters/testwatch
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
Run `testwatch` in the root of your project.
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
testwatch
|
|
24
|
+
```
|
package/assets/cli.gif
ADDED
|
Binary file
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# The configurations that used for the recording, feel free to edit them
|
|
2
|
+
config:
|
|
3
|
+
|
|
4
|
+
# Specify a command to be executed
|
|
5
|
+
# like `/bin/bash -l`, `ls`, or any other commands
|
|
6
|
+
# the default is bash for Linux
|
|
7
|
+
# or powershell.exe for Windows
|
|
8
|
+
command: bash -l
|
|
9
|
+
|
|
10
|
+
# Specify the current working directory path
|
|
11
|
+
# the default is the current working directory path
|
|
12
|
+
cwd: ~
|
|
13
|
+
|
|
14
|
+
# Export additional ENV variables
|
|
15
|
+
env:
|
|
16
|
+
recording: true
|
|
17
|
+
|
|
18
|
+
# Explicitly set the number of columns
|
|
19
|
+
# or use `auto` to take the current
|
|
20
|
+
# number of columns of your shell
|
|
21
|
+
cols: 166
|
|
22
|
+
|
|
23
|
+
# Explicitly set the number of rows
|
|
24
|
+
# or use `auto` to take the current
|
|
25
|
+
# number of rows of your shell
|
|
26
|
+
rows: 23
|
|
27
|
+
|
|
28
|
+
# Amount of times to repeat GIF
|
|
29
|
+
# If value is -1, play once
|
|
30
|
+
# If value is 0, loop indefinitely
|
|
31
|
+
# If value is a positive number, loop n times
|
|
32
|
+
repeat: 0
|
|
33
|
+
|
|
34
|
+
# Quality
|
|
35
|
+
# 1 - 100
|
|
36
|
+
quality: 100
|
|
37
|
+
|
|
38
|
+
# Delay between frames in ms
|
|
39
|
+
# If the value is `auto` use the actual recording delays
|
|
40
|
+
frameDelay: auto
|
|
41
|
+
|
|
42
|
+
# Maximum delay between frames in ms
|
|
43
|
+
# Ignored if the `frameDelay` isn't set to `auto`
|
|
44
|
+
# Set to `auto` to prevent limiting the max idle time
|
|
45
|
+
maxIdleTime: 2000
|
|
46
|
+
|
|
47
|
+
# The surrounding frame box
|
|
48
|
+
# The `type` can be null, window, floating, or solid`
|
|
49
|
+
# To hide the title use the value null
|
|
50
|
+
# Don't forget to add a backgroundColor style with a null as type
|
|
51
|
+
frameBox:
|
|
52
|
+
type: floating
|
|
53
|
+
title: Terminalizer
|
|
54
|
+
style:
|
|
55
|
+
border: 0px black solid
|
|
56
|
+
boxShadow: none
|
|
57
|
+
margin: 0px
|
|
58
|
+
|
|
59
|
+
# Add a watermark image to the rendered gif
|
|
60
|
+
# You need to specify an absolute path for
|
|
61
|
+
# the image on your machine or a URL, and you can also
|
|
62
|
+
# add your own CSS styles
|
|
63
|
+
watermark:
|
|
64
|
+
imagePath: null
|
|
65
|
+
style:
|
|
66
|
+
position: absolute
|
|
67
|
+
right: 15px
|
|
68
|
+
bottom: 15px
|
|
69
|
+
width: 100px
|
|
70
|
+
opacity: 0.9
|
|
71
|
+
|
|
72
|
+
# Cursor style can be one of
|
|
73
|
+
# `block`, `underline`, or `bar`
|
|
74
|
+
cursorStyle: block
|
|
75
|
+
|
|
76
|
+
# Font family
|
|
77
|
+
# You can use any font that is installed on your machine
|
|
78
|
+
# in CSS-like syntax
|
|
79
|
+
fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace"
|
|
80
|
+
|
|
81
|
+
# The size of the font
|
|
82
|
+
fontSize: 12
|
|
83
|
+
|
|
84
|
+
# The height of lines
|
|
85
|
+
lineHeight: 1
|
|
86
|
+
|
|
87
|
+
# The spacing between letters
|
|
88
|
+
letterSpacing: 0
|
|
89
|
+
|
|
90
|
+
# Theme
|
|
91
|
+
theme:
|
|
92
|
+
background: "transparent"
|
|
93
|
+
foreground: "#afafaf"
|
|
94
|
+
cursor: "#c7c7c7"
|
|
95
|
+
black: "#232628"
|
|
96
|
+
red: "#fc4384"
|
|
97
|
+
green: "#b3e33b"
|
|
98
|
+
yellow: "#ffa727"
|
|
99
|
+
blue: "#75dff2"
|
|
100
|
+
magenta: "#ae89fe"
|
|
101
|
+
cyan: "#708387"
|
|
102
|
+
white: "#d5d5d0"
|
|
103
|
+
brightBlack: "#626566"
|
|
104
|
+
brightRed: "#ff7fac"
|
|
105
|
+
brightGreen: "#c8ed71"
|
|
106
|
+
brightYellow: "#ebdf86"
|
|
107
|
+
brightBlue: "#75dff2"
|
|
108
|
+
brightMagenta: "#ae89fe"
|
|
109
|
+
brightCyan: "#b1c6ca"
|
|
110
|
+
brightWhite: "#f9f9f4"
|
|
111
|
+
|
|
112
|
+
# Records, feel free to edit them
|
|
113
|
+
records:
|
|
114
|
+
- delay: 0
|
|
115
|
+
content: '$ '
|
|
116
|
+
- delay: 651
|
|
117
|
+
content: testwatch
|
|
118
|
+
- delay: 713
|
|
119
|
+
content: "\r\n"
|
|
120
|
+
- delay: 688
|
|
121
|
+
content: "\ec▶ testwatch\r\n \e[32m✔ should run all tests on initialization \e[90m(445.057167ms)\e[39m\e[39m\r\n \e[32m✔ should handle CTR + C \e[90m(443.270375ms)\e[39m\e[39m\r\n"
|
|
122
|
+
- delay: 123
|
|
123
|
+
content: " \e[32m✔ should handle CTR + D \e[90m(566.298666ms)\e[39m\e[39m\r\n"
|
|
124
|
+
- delay: 102
|
|
125
|
+
content: " \e[32m✔ should run all tests on \"a\" \e[90m(668.500542ms)\e[39m\e[39m\r\n"
|
|
126
|
+
- delay: 11
|
|
127
|
+
content: " \e[32m✔ should run all tests on Enter \e[90m(678.704958ms)\e[39m\e[39m\r\n \e[32m✔ should show full menu on \"w\" after running tests \e[90m(638.493708ms)\e[39m\e[39m\r\n"
|
|
128
|
+
- delay: 25
|
|
129
|
+
content: " ▶ filters\r\n \e[32m✔ should filter tests on \"t\" \e[90m(701.858542ms)\e[39m\e[39m\r\n \e[32m✔ should filter files on \"p\" \e[90m(668.653625ms)\e[39m\e[39m\r\n"
|
|
130
|
+
- delay: 201
|
|
131
|
+
content: " \e[32m✔ should filter tests and files togetheer \e[90m(902.623166ms)\e[39m\e[39m\r\n \e[32m✔ should mention when no files found \e[90m(669.483333ms)\e[39m\e[39m\r\n"
|
|
132
|
+
- delay: 234
|
|
133
|
+
content: " \e[32m✔ should clear filters on \"c\" \e[90m(1136.047417ms)\e[39m\e[39m\r\n \e[32m✔ prompt ESC should preserve previous state \e[90m(1101.547958ms)\e[39m\e[39m\r\n \e[32m✔ backspace shoud remove last character \e[90m(627.751292ms)\e[39m\e[39m\r\n \e[32m▶ \e[39mfilters \e[90m(1138.54675ms)\e[39m\r\n\r\n\e[32m▶ \e[39mtestwatch \e[90m(1143.543416ms)\e[39m\r\n\r\n"
|
|
134
|
+
- delay: 6
|
|
135
|
+
content: "\e[32m✔ j - sum \e[90m(0.458875ms)\e[39m\e[39m\r\n\e[32m✔ j - subtraction \e[90m(0.160625ms)\e[39m\e[39m\r\n\e[32m✔ index - sum \e[90m(0.454875ms)\e[39m\e[39m\r\n\e[32m✔ index - subtraction \e[90m(0.128083ms)\e[39m\e[39m\r\n\r\n\e[1mREPL Usage\e[22m\r\n\e[90m › Press \e[37m\e[1ma\e[22m\e[39m\e[90m to run all tests.\e[39m\r\n\e[90m › Press \e[37m\e[1mp\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mt\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mq\e[22m\e[39m\e[90m to quit.\e[39m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to trigger a test run.\e[39m\r\n"
|
|
136
|
+
- delay: 1523
|
|
137
|
+
content: "\ec\r\n\e[1mFilter File\e[22m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mEsc\e[22m\e[39m\e[90m to exit pattern mode.\e[39m\r\n\e[90m\e[39m\r\n\e[90m pattern › \e[39m"
|
|
138
|
+
- delay: 1000
|
|
139
|
+
content: tests/fixtures/**
|
|
140
|
+
- delay: 500
|
|
141
|
+
content: "\ec"
|
|
142
|
+
- delay: 78
|
|
143
|
+
content: "\ec\e[32m✔ j - sum \e[90m(0.430167ms)\e[39m\e[39m\r\n\e[32m✔ j - subtraction \e[90m(0.127208ms)\e[39m\e[39m\r\n"
|
|
144
|
+
- delay: 5
|
|
145
|
+
content: "\e[32m✔ index - sum \e[90m(0.430083ms)\e[39m\e[39m\r\n\e[32m✔ index - subtraction \e[90m(0.124042ms)\e[39m\e[39m\r\n\e[90m\e[39m\r\n\e[90m\e[37m\e[1mREPL Usage\e[22m\e[39m\e[90m: Press \e[37m\e[1mw\e[22m\e[39m\e[90m to show more.\e[39m"
|
|
146
|
+
- delay: 1000
|
|
147
|
+
content: "\r\n\e[1A\e[2K\e[1A\e[2K\r\n\e[37m\e[1mActive Filters:\e[22m\e[39m file name \e[90m**/\e[39m\e[33mtests/fixtures/**\e[39m\e[90m.*\e[39m\r\n\r\n\e[1mREPL Usage\e[22m\r\n\e[90m › Press \e[37m\e[1mc\e[22m\e[39m\e[90m to clear the filters.\e[39m\r\n\e[90m › Press \e[37m\e[1ma\e[22m\e[39m\e[90m to run all tests.\e[39m\r\n\e[90m › Press \e[37m\e[1mp\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mt\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mq\e[22m\e[39m\e[90m to quit.\e[39m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to trigger a test run.\e[39m\r\n"
|
|
148
|
+
- delay: 1000
|
|
149
|
+
content: "\ec\r\n\e[37m\e[1mActive Filters:\e[22m\e[39m file name \e[90m**/\e[39m\e[33mtests/fixtures/**\e[39m\e[90m.*\e[39m\r\n\r\n\e[1mFilter Test\e[22m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mEsc\e[22m\e[39m\e[90m to exit pattern mode.\e[39m\r\n\e[90m\e[39m\r\n\e[90m pattern › \e[39m"
|
|
150
|
+
- delay: 1000
|
|
151
|
+
content: sum
|
|
152
|
+
- delay: 1000
|
|
153
|
+
content: "\ec"
|
|
154
|
+
- delay: 68
|
|
155
|
+
content: "\ec\e[32m✔ j - sum \e[90m(0.44275ms)\e[39m\e[39m\r\n\e[90m﹣ j - subtraction \e[90m(0.12475ms)\e[39m # SKIP\e[39m\r\n"
|
|
156
|
+
- delay: 5
|
|
157
|
+
content: "\e[32m✔ index - sum \e[90m(0.4315ms)\e[39m\e[39m\r\n\e[90m﹣ index - subtraction \e[90m(0.12175ms)\e[39m # SKIP\e[39m\r\n\e[90m\e[39m\r\n\e[90m\e[37m\e[1mREPL Usage\e[22m\e[39m\e[90m: Press \e[37m\e[1mw\e[22m\e[39m\e[90m to show more.\e[39m"
|
|
158
|
+
- delay: 1000
|
|
159
|
+
content: "\r\n\e[1A\e[2K\e[1A\e[2K\r\n\e[37m\e[1mActive Filters:\e[22m\e[39m file name \e[90m**/\e[39m\e[33mtests/fixtures/**\e[39m\e[90m.*\e[39m, test name \e[33m/sum/\e[39m\r\n\r\n\e[1mREPL Usage\e[22m\r\n\e[90m › Press \e[37m\e[1mc\e[22m\e[39m\e[90m to clear the filters.\e[39m\r\n\e[90m › Press \e[37m\e[1ma\e[22m\e[39m\e[90m to run all tests.\e[39m\r\n\e[90m › Press \e[37m\e[1mp\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mt\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mq\e[22m\e[39m\e[90m to quit.\e[39m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to trigger a test run.\e[39m\r\n"
|
|
160
|
+
- delay: 1000
|
|
161
|
+
content: "\ec\e[32m✔ j - sum \e[90m(0.422125ms)\e[39m\e[39m\r\n\e[90m﹣ j - subtraction \e[90m(0.123084ms)\e[39m # SKIP\e[39m\r\n"
|
|
162
|
+
- delay: 7
|
|
163
|
+
content: "\e[32m✔ index - sum \e[90m(0.424667ms)\e[39m\e[39m\r\n\e[90m﹣ index - subtraction \e[90m(0.119458ms)\e[39m # SKIP\e[39m\r\n\e[90m\e[39m\r\n\e[90m\e[37m\e[1mREPL Usage\e[22m\e[39m\e[90m: Press \e[37m\e[1mw\e[22m\e[39m\e[90m to show more.\e[39m"
|
|
164
|
+
- delay: 1000
|
|
165
|
+
content: "\r\n\e[1A\e[2K\e[1A\e[2K\r\n\e[37m\e[1mActive Filters:\e[22m\e[39m file name \e[90m**/\e[39m\e[33mtests/fixtures/**\e[39m\e[90m.*\e[39m, test name \e[33m/sum/\e[39m\r\n\r\n\e[1mREPL Usage\e[22m\r\n\e[90m › Press \e[37m\e[1mc\e[22m\e[39m\e[90m to clear the filters.\e[39m\r\n\e[90m › Press \e[37m\e[1ma\e[22m\e[39m\e[90m to run all tests.\e[39m\r\n\e[90m › Press \e[37m\e[1mp\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mt\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mq\e[22m\e[39m\e[90m to quit.\e[39m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to trigger a test run.\e[39m\r\n"
|
|
166
|
+
- delay: 1000
|
|
167
|
+
content: "\ec▶ testwatch\r\n \e[32m✔ should run all tests on initialization \e[90m(377.157833ms)\e[39m\e[39m\r\n"
|
|
168
|
+
- delay: 45
|
|
169
|
+
content: " \e[32m✔ should handle CTR + C \e[90m(421.077417ms)\e[39m\e[39m\r\n"
|
|
170
|
+
- delay: 17
|
|
171
|
+
content: " \e[32m✔ should handle CTR + D \e[90m(437.436042ms)\e[39m\e[39m\r\n"
|
|
172
|
+
- delay: 190
|
|
173
|
+
content: " \e[32m✔ should run all tests on \"a\" \e[90m(626.754917ms)\e[39m\e[39m\r\n \e[32m✔ should run all tests on Enter \e[90m(628.015583ms)\e[39m\e[39m\r\n"
|
|
174
|
+
- delay: 25
|
|
175
|
+
content: " \e[32m✔ should show full menu on \"w\" after running tests \e[90m(652.595ms)\e[39m\e[39m\r\n"
|
|
176
|
+
- delay: 40
|
|
177
|
+
content: " ▶ filters\r\n \e[32m✔ should filter tests on \"t\" \e[90m(691.810209ms)\e[39m\e[39m\r\n \e[32m✔ should filter files on \"p\" \e[90m(605.502625ms)\e[39m\e[39m\r\n"
|
|
178
|
+
- delay: 136
|
|
179
|
+
content: " \e[32m✔ should filter tests and files togetheer \e[90m(826.882875ms)\e[39m\e[39m\r\n \e[32m✔ should mention when no files found \e[90m(603.837791ms)\e[39m\e[39m\r\n"
|
|
180
|
+
- delay: 202
|
|
181
|
+
content: " \e[32m✔ should clear filters on \"c\" \e[90m(1026.169ms)\e[39m\e[39m\r\n \e[32m✔ prompt ESC should preserve previous state \e[90m(1003.537917ms)\e[39m\e[39m\r\n \e[32m✔ backspace shoud remove last character \e[90m(720.0275ms)\e[39m\e[39m\r\n \e[32m▶ \e[39mfilters \e[90m(1030.538708ms)\e[39m\r\n\r\n\e[32m▶ \e[39mtestwatch \e[90m(1035.127708ms)\e[39m\r\n\r\n\e[32m✔ j - sum \e[90m(0.452833ms)\e[39m\e[39m\r\n\e[32m✔ j - subtraction \e[90m(0.136416ms)\e[39m\e[39m\r\n\e[32m✔ index - sum \e[90m(0.457583ms)\e[39m\e[39m\r\n\e[32m✔ index - subtraction \e[90m(0.132541ms)\e[39m\e[39m\r\n\e[90m\e[39m\r\n\e[90m\e[37m\e[1mREPL Usage\e[22m\e[39m\e[90m: Press \e[37m\e[1mw\e[22m\e[39m\e[90m to show more.\e[39m"
|
|
182
|
+
- delay: 1000
|
|
183
|
+
content: "\r\n\e[1A\e[2K\e[1A\e[2K\r\n\e[1mREPL Usage\e[22m\r\n\e[90m › Press \e[37m\e[1ma\e[22m\e[39m\e[90m to run all tests.\e[39m\r\n\e[90m › Press \e[37m\e[1mp\e[22m\e[39m\e[90m to filter by a file name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mt\e[22m\e[39m\e[90m to filter by a test name pattern.\e[39m\r\n\e[90m › Press \e[37m\e[1mq\e[22m\e[39m\e[90m to quit.\e[39m\r\n\e[90m › Press \e[37m\e[1mEnter\e[22m\e[39m\e[90m to trigger a test run.\e[39m\r\n"
|
|
184
|
+
- delay: 1000
|
|
185
|
+
content: "\r\n"
|
package/index.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { run } = require('node:test');
|
|
4
|
+
const { pipeline } = require('node:stream/promises');
|
|
5
|
+
const { on, once, EventEmitter } = require('node:events');
|
|
6
|
+
const { glob } = require('glob');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const { isSupported } = require('./nodeVersion');
|
|
9
|
+
|
|
10
|
+
if (!isSupported) {
|
|
11
|
+
/* c8 ignore next 3 */
|
|
12
|
+
console.log(chalk.magenta('Node.js <= 20.3.0 is not supported.'));
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
// eslint-disable-next-line import/no-unresolved, import/order
|
|
16
|
+
const { spec: SpecReporter } = require('node:test/reporters');
|
|
17
|
+
|
|
18
|
+
const KEYS = {
|
|
19
|
+
CTRLC: '\x03',
|
|
20
|
+
CTRLD: '\x04',
|
|
21
|
+
BACKSPACE: '\x7f',
|
|
22
|
+
ESC: '\x1b',
|
|
23
|
+
ENTER: '\r',
|
|
24
|
+
};
|
|
25
|
+
const KEY_NAMES = {
|
|
26
|
+
[KEYS.ENTER]: 'Enter',
|
|
27
|
+
[KEYS.ESC]: 'Esc',
|
|
28
|
+
};
|
|
29
|
+
const UnknownCommand = Symbol('UnknownKey');
|
|
30
|
+
|
|
31
|
+
process.stdout.setMaxListeners(Infinity);
|
|
32
|
+
process.setMaxListeners(Infinity);
|
|
33
|
+
process.stdin.setEncoding('utf8');
|
|
34
|
+
process.stdin.setRawMode?.(true);
|
|
35
|
+
|
|
36
|
+
class REPL {
|
|
37
|
+
#controller = new AbortController();
|
|
38
|
+
|
|
39
|
+
#filesFilter = '';
|
|
40
|
+
|
|
41
|
+
#testsFilter = '';
|
|
42
|
+
|
|
43
|
+
#mainCommands = Object.freeze({
|
|
44
|
+
c: { fn: () => this.#clearFilter(), description: 'clear the filters.', active: () => this.#filesFilter || this.#testsFilter },
|
|
45
|
+
a: { fn: () => this.#runTests(), description: 'run all tests.' },
|
|
46
|
+
p: { fn: () => this.#filterFile(), description: 'filter by a file name pattern.' },
|
|
47
|
+
t: { fn: () => this.#filterTest(), description: 'filter by a test name pattern.' },
|
|
48
|
+
q: { fn: () => this.#quit(), description: 'quit.' },
|
|
49
|
+
[KEYS.ENTER]: { fn: () => this.#runTests(), description: 'trigger a test run.' },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
#currentCommands = this.#mainCommands;
|
|
53
|
+
|
|
54
|
+
#emitter = new EventEmitter();
|
|
55
|
+
|
|
56
|
+
async #runTests() {
|
|
57
|
+
this.#controller.abort();
|
|
58
|
+
this.#controller = new AbortController();
|
|
59
|
+
const filter = this.#filesFilter ? `**/${this.#filesFilter}.*` : '**/*.test.js';
|
|
60
|
+
const files = await glob(filter, { ignore: 'node_modules/**' });
|
|
61
|
+
|
|
62
|
+
if (!files.length) {
|
|
63
|
+
this.#clear();
|
|
64
|
+
process.stdout.write(chalk.red(`\nNo files found for pattern ${filter}\n`));
|
|
65
|
+
this.#emitter.emit('drained');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let drained = true;
|
|
70
|
+
pipeline(
|
|
71
|
+
run({
|
|
72
|
+
concurrency: true,
|
|
73
|
+
files,
|
|
74
|
+
signal: this.#controller.signal,
|
|
75
|
+
watch: true,
|
|
76
|
+
testNamePatterns: this.#testsFilter,
|
|
77
|
+
}),
|
|
78
|
+
async function* (source) {
|
|
79
|
+
for await (const data of source) {
|
|
80
|
+
yield data;
|
|
81
|
+
if (data.type === 'test:start' && drained) {
|
|
82
|
+
this.#clear();
|
|
83
|
+
drained = false;
|
|
84
|
+
}
|
|
85
|
+
if (data.type === 'test:watch:drained') {
|
|
86
|
+
// eslint-disable-next-line no-loop-func
|
|
87
|
+
setImmediate(() => {
|
|
88
|
+
this.#emitter.emit('drained');
|
|
89
|
+
drained = true;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}.bind(this),
|
|
94
|
+
new SpecReporter(),
|
|
95
|
+
process.stdout,
|
|
96
|
+
{ signal: this.#controller.signal },
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line class-methods-use-this
|
|
101
|
+
#quit() {
|
|
102
|
+
process.stdout.write('\n');
|
|
103
|
+
process.stdin.setRawMode?.(false);
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line class-methods-use-this
|
|
108
|
+
#clear() {
|
|
109
|
+
process.stdout.write('\u001bc');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#back() {
|
|
113
|
+
const current = this.#currentCommands;
|
|
114
|
+
return ({ clear = true, help = true } = {}) => {
|
|
115
|
+
this.#currentCommands = current;
|
|
116
|
+
if (clear) {
|
|
117
|
+
this.#clear();
|
|
118
|
+
}
|
|
119
|
+
if (help) {
|
|
120
|
+
this.#help();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#compactHelp(char = 'w') {
|
|
126
|
+
process.stdout.write(chalk.gray(`\n${chalk.white.bold('REPL Usage')}: Press ${chalk.white.bold(char)} to show more.`));
|
|
127
|
+
const showMore = this.#back();
|
|
128
|
+
this.#currentCommands = {
|
|
129
|
+
...this.#currentCommands,
|
|
130
|
+
[char]: {
|
|
131
|
+
fn: () => {
|
|
132
|
+
// remove previous lines
|
|
133
|
+
process.stdout.write('\n\x1b[1A\x1b[2K\x1b[1A\x1b[2K');
|
|
134
|
+
showMore({ clear: false });
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#filterPrompt({ title, subtitle }) {
|
|
141
|
+
const back = this.#back();
|
|
142
|
+
let input = '';
|
|
143
|
+
return new Promise((resolve) => {
|
|
144
|
+
this.#currentCommands = {
|
|
145
|
+
[KEYS.ENTER]: {
|
|
146
|
+
fn: () => {
|
|
147
|
+
resolve(input);
|
|
148
|
+
back({ help: false });
|
|
149
|
+
},
|
|
150
|
+
description: `filter by a ${subtitle} pattern.`,
|
|
151
|
+
},
|
|
152
|
+
[KEYS.ESC]: {
|
|
153
|
+
fn: () => {
|
|
154
|
+
resolve(null);
|
|
155
|
+
back({ help: false });
|
|
156
|
+
},
|
|
157
|
+
description: 'exit pattern mode.',
|
|
158
|
+
},
|
|
159
|
+
[UnknownCommand]: {
|
|
160
|
+
fn: (cmd) => {
|
|
161
|
+
if (cmd === KEYS.BACKSPACE) {
|
|
162
|
+
input = input.slice(0, -1);
|
|
163
|
+
process.stdout.write('\x1b[1D \x1b[1D');
|
|
164
|
+
} else {
|
|
165
|
+
input += cmd;
|
|
166
|
+
process.stdout.write(cmd);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
this.#clear();
|
|
172
|
+
this.#help(title);
|
|
173
|
+
process.stdout.write(chalk.gray('\n pattern › '));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async #filterFile() {
|
|
178
|
+
const input = await this.#filterPrompt({ title: 'Filter File', subtitle: 'file name' });
|
|
179
|
+
if (input !== null) {
|
|
180
|
+
this.#filesFilter = input;
|
|
181
|
+
}
|
|
182
|
+
await this.#runTests();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async #filterTest() {
|
|
186
|
+
const input = await this.#filterPrompt({ title: 'Filter Test', subtitle: 'test name' });
|
|
187
|
+
if (input !== null) {
|
|
188
|
+
this.#testsFilter = input;
|
|
189
|
+
}
|
|
190
|
+
await this.#runTests();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#clearFilter() {
|
|
194
|
+
this.#filesFilter = '';
|
|
195
|
+
this.#testsFilter = '';
|
|
196
|
+
this.#runTests();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#help(title = 'REPL Usage') {
|
|
200
|
+
if (this.#filesFilter || this.#testsFilter) {
|
|
201
|
+
const message = `
|
|
202
|
+
${chalk.white.bold('Active Filters:')} \
|
|
203
|
+
${this.#filesFilter ? `file name ${chalk.gray('**/')}${chalk.yellow(this.#filesFilter)}${chalk.gray('.*')}` : ''}\
|
|
204
|
+
${(this.#testsFilter && this.#filesFilter) ? ', ' : ''}\
|
|
205
|
+
${this.#testsFilter ? `test name ${chalk.yellow(`/${this.#testsFilter}/`)}` : ''}
|
|
206
|
+
`;
|
|
207
|
+
process.stdout.write(message);
|
|
208
|
+
}
|
|
209
|
+
const message = `
|
|
210
|
+
${chalk.bold(title)}
|
|
211
|
+
${Object.entries(this.#currentCommands)
|
|
212
|
+
.filter(([, { active, description }]) => (!active || active()) && description)
|
|
213
|
+
.map(([key, { description }]) => chalk.gray(` › Press ${chalk.white.bold(KEY_NAMES[key] || key)} to ${description}`))
|
|
214
|
+
.join('\n')}
|
|
215
|
+
`;
|
|
216
|
+
process.stdout.write(message);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async run() {
|
|
220
|
+
await this.#runTests();
|
|
221
|
+
await once(this.#emitter, 'drained');
|
|
222
|
+
this.#emitter.on('drained', () => this.#compactHelp());
|
|
223
|
+
this.#help();
|
|
224
|
+
for await (const data of on(process.stdin, 'data')) {
|
|
225
|
+
const commands = process.stdin.isRaw ? [data[0]] : data.toString().split('');
|
|
226
|
+
for (const command of commands) {
|
|
227
|
+
if (command === KEYS.CTRLC || command === KEYS.CTRLD) {
|
|
228
|
+
this.#quit();
|
|
229
|
+
}
|
|
230
|
+
if (this.#currentCommands[command]) {
|
|
231
|
+
this.#currentCommands[command].fn();
|
|
232
|
+
} else if (this.#currentCommands[UnknownCommand]) {
|
|
233
|
+
this.#currentCommands[UnknownCommand].fn(command);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
new REPL().run()
|
|
241
|
+
.then(() => process.exit(0))
|
|
242
|
+
.catch((error) => {
|
|
243
|
+
/* c8 ignore next 2 */
|
|
244
|
+
console.error(error);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
package/nodeVersion.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reporters/testwatch",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An interactive repl for `node:test`",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"node:test",
|
|
7
|
+
"test",
|
|
8
|
+
"reporter",
|
|
9
|
+
"reporters"
|
|
10
|
+
],
|
|
11
|
+
"bin": "./index.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test-reporter=spec --test-reporter-destination=stdout --test-reporter=../github/index.js --test-reporter-destination=stdout --test tests/index.test.js"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/MoLow/reporters/issues"
|
|
17
|
+
},
|
|
18
|
+
"main": "index.js",
|
|
19
|
+
"homepage": "https://github.com/MoLow/reporters/tree/main/packages/silent",
|
|
20
|
+
"repository": "https://github.com/MoLow/reporters.git",
|
|
21
|
+
"author": "Moshe Atlow",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"chalk": "^4.1.1",
|
|
25
|
+
"glob": "^10.2.6"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
const { describe, it } = require('node:test');
|
|
2
|
+
const { spawn } = require('node:child_process');
|
|
3
|
+
const { once } = require('node:events');
|
|
4
|
+
const { setTimeout } = require('node:timers/promises');
|
|
5
|
+
const assert = require('node:assert');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { isSupported } = require('../nodeVersion');
|
|
8
|
+
|
|
9
|
+
const clear = '\x1Bc';
|
|
10
|
+
const esc = '\x1b';
|
|
11
|
+
const clearLines = '\x1B[1A\x1B[2K\x1B[1A\x1B[2K';
|
|
12
|
+
const testsRun = [
|
|
13
|
+
`✔ j - sum (*ms)
|
|
14
|
+
✔ j - subtraction (*ms)`,
|
|
15
|
+
`✔ index - sum (*ms)
|
|
16
|
+
✔ index - subtraction (*ms)`,
|
|
17
|
+
];
|
|
18
|
+
const tests = `${testsRun[0]}\n${testsRun[1]}`;
|
|
19
|
+
const mainMenu = `
|
|
20
|
+
REPL Usage
|
|
21
|
+
› Press a to run all tests.
|
|
22
|
+
› Press p to filter by a file name pattern.
|
|
23
|
+
› Press t to filter by a test name pattern.
|
|
24
|
+
› Press q to quit.
|
|
25
|
+
› Press Enter to trigger a test run.
|
|
26
|
+
`;
|
|
27
|
+
const mainMenuWithFilters = mainMenu.replace('REPL Usage', `REPL Usage
|
|
28
|
+
› Press c to clear the filters.`);
|
|
29
|
+
const compactMenu = '\nREPL Usage: Press w to show more.';
|
|
30
|
+
const filterTestsPrompt = `
|
|
31
|
+
Filter Test
|
|
32
|
+
› Press Enter to filter by a test name pattern.
|
|
33
|
+
› Press Esc to exit pattern mode.
|
|
34
|
+
|
|
35
|
+
pattern › `;
|
|
36
|
+
const filterFilesPrompt = filterTestsPrompt.replace('test', 'file').replace('Test', 'File');
|
|
37
|
+
|
|
38
|
+
async function spawnInteractive(commandSequence = 'q') {
|
|
39
|
+
let stderr = '';
|
|
40
|
+
let stdout = '';
|
|
41
|
+
const child = spawn(process.execPath, ['../../index.js'], {
|
|
42
|
+
env: { }, cwd: path.resolve(__dirname, 'fixtures'),
|
|
43
|
+
});
|
|
44
|
+
child.stdin.setEncoding('utf8');
|
|
45
|
+
let writing = false;
|
|
46
|
+
async function writeInput() {
|
|
47
|
+
if (writing) return;
|
|
48
|
+
writing = true;
|
|
49
|
+
for (const char of commandSequence) {
|
|
50
|
+
child.stdin.cork();
|
|
51
|
+
child.stdin.write(`${char}`);
|
|
52
|
+
child.stdin.uncork();
|
|
53
|
+
if (char === 'a' || char === 'c' || char === '\r' || char === esc) {
|
|
54
|
+
// wait for tests to run before writing the next command
|
|
55
|
+
// eslint-disable-next-line no-await-in-loop
|
|
56
|
+
await setTimeout(1200);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
child.stderr.setEncoding('utf8');
|
|
61
|
+
child.stderr.on('data', (data) => { stderr += data; });
|
|
62
|
+
child.stdout.setEncoding('utf8');
|
|
63
|
+
child.stdout.on('data', (data) => {
|
|
64
|
+
stdout += data;
|
|
65
|
+
if (stdout.includes(mainMenu)) {
|
|
66
|
+
writeInput();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
child.on('close', async (code, signal) => {
|
|
72
|
+
const outputs = stdout.replace(/\(.*ms\)/g, '(*ms)').split(clear);
|
|
73
|
+
resolve({
|
|
74
|
+
code, signal, stderr, outputs,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
child.on('error', (code, signal) => {
|
|
78
|
+
/* c8 ignore next 5 */
|
|
79
|
+
const outputs = stdout.replace(/\(.*ms\)/g, '(*ms)').split(clear);
|
|
80
|
+
// eslint-disable-next-line prefer-promise-reject-errors
|
|
81
|
+
reject({
|
|
82
|
+
code, signal, stderr, outputs,
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
describe('testwatch', { concurrency: true, skip: !isSupported ? 'unsupported node version' : false }, () => {
|
|
89
|
+
it('should run all tests on initialization', async () => {
|
|
90
|
+
const { outputs, stderr } = await spawnInteractive('q');
|
|
91
|
+
assert.strictEqual(stderr, '');
|
|
92
|
+
assert.deepStrictEqual(outputs, ['', `${tests}\n${mainMenu}\n`]);
|
|
93
|
+
});
|
|
94
|
+
it('should handle CTR + C', async () => {
|
|
95
|
+
const { outputs, stderr } = await spawnInteractive('\x03');
|
|
96
|
+
assert.strictEqual(stderr, '');
|
|
97
|
+
assert.deepStrictEqual(outputs, ['', `${tests}\n${mainMenu}\n`]);
|
|
98
|
+
});
|
|
99
|
+
it('should handle CTR + D', async () => {
|
|
100
|
+
const { outputs, stderr } = await spawnInteractive('\x04');
|
|
101
|
+
assert.strictEqual(stderr, '');
|
|
102
|
+
assert.deepStrictEqual(outputs, ['', `${tests}\n${mainMenu}\n`]);
|
|
103
|
+
});
|
|
104
|
+
it('should exit on sigkill', async () => {
|
|
105
|
+
const child = spawn(process.execPath, ['../../index.js'], {
|
|
106
|
+
env: { }, cwd: path.resolve(__dirname, 'fixtures'),
|
|
107
|
+
});
|
|
108
|
+
let stderr = '';
|
|
109
|
+
let stdout = '';
|
|
110
|
+
child.stderr.setEncoding('utf8');
|
|
111
|
+
child.stderr.on('data', (data) => { stderr += data; });
|
|
112
|
+
child.stdout.setEncoding('utf8');
|
|
113
|
+
child.stdout.on('data', (data) => { stdout += data; });
|
|
114
|
+
child.kill('SIGKILL');
|
|
115
|
+
const [code, signal] = await once(child, 'close');
|
|
116
|
+
assert.strictEqual(stderr, '');
|
|
117
|
+
assert.strictEqual(stdout, '');
|
|
118
|
+
assert.strictEqual(signal, 'SIGKILL');
|
|
119
|
+
assert.strictEqual(code, null);
|
|
120
|
+
});
|
|
121
|
+
it('should run all tests on "a"', async () => {
|
|
122
|
+
const { outputs, stderr } = await spawnInteractive('aq');
|
|
123
|
+
assert.strictEqual(stderr, '');
|
|
124
|
+
assert.deepStrictEqual(outputs, ['', `${tests}\n${mainMenu}`, `${tests}\n${compactMenu}\n`]);
|
|
125
|
+
});
|
|
126
|
+
it('should run all tests on Enter', async () => {
|
|
127
|
+
const { outputs, stderr } = await spawnInteractive('\rq');
|
|
128
|
+
assert.strictEqual(stderr, '');
|
|
129
|
+
assert.deepStrictEqual(outputs, ['', `${tests}\n${mainMenu}`, `${tests}\n${compactMenu}\n`]);
|
|
130
|
+
});
|
|
131
|
+
it('should show full menu on "w" after running tests', async () => {
|
|
132
|
+
const { outputs, stderr } = await spawnInteractive('awq');
|
|
133
|
+
assert.strictEqual(stderr, '');
|
|
134
|
+
assert.deepStrictEqual(outputs, [
|
|
135
|
+
'',
|
|
136
|
+
`${tests}\n${mainMenu}`,
|
|
137
|
+
`${tests}\n${compactMenu}\n${clearLines}${mainMenu}\n`,
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('filters', () => {
|
|
142
|
+
it('should filter tests on "t"', async () => {
|
|
143
|
+
const { outputs, stderr } = await spawnInteractive(['t', 'sub', '\r', 'w', 'q'].join(''));
|
|
144
|
+
const activeFilters = '\nActive Filters: test name /sub/\n';
|
|
145
|
+
assert.strictEqual(stderr, '');
|
|
146
|
+
assert.deepStrictEqual(outputs, [
|
|
147
|
+
'',
|
|
148
|
+
`${tests}\n${mainMenu}`,
|
|
149
|
+
`${filterTestsPrompt}sub`,
|
|
150
|
+
'',
|
|
151
|
+
`${tests
|
|
152
|
+
.replace('✔ j - sum (*ms)', '﹣ j - sum (*ms) # SKIP')
|
|
153
|
+
.replace('✔ index - sum (*ms)', '﹣ index - sum (*ms) # SKIP')
|
|
154
|
+
}\n${compactMenu}\n${clearLines}${activeFilters}${mainMenuWithFilters}\n`,
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should filter files on "p"', async () => {
|
|
159
|
+
const { outputs, stderr } = await spawnInteractive(['p', 'index', '\r', 'w', 'q'].join(''));
|
|
160
|
+
const activeFilters = '\nActive Filters: file name **/index.*\n';
|
|
161
|
+
assert.strictEqual(stderr, '');
|
|
162
|
+
assert.deepStrictEqual(outputs, [
|
|
163
|
+
'',
|
|
164
|
+
`${tests}\n${mainMenu}`,
|
|
165
|
+
`${filterFilesPrompt}index`,
|
|
166
|
+
'',
|
|
167
|
+
`${testsRun[1]}\n${compactMenu}\n${clearLines}${activeFilters}${mainMenuWithFilters}\n`,
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
it('should filter tests and files togetheer', async () => {
|
|
171
|
+
const { outputs, stderr } = await spawnInteractive(['p', 'index', '\r', 't', 'sum', '\r', 'w', 'q'].join(''));
|
|
172
|
+
const activeFilters = '\nActive Filters: file name **/index.*, test name /sum/\n';
|
|
173
|
+
assert.strictEqual(stderr, '');
|
|
174
|
+
assert.strictEqual(outputs.length, 8);
|
|
175
|
+
assert.strictEqual(outputs[7], `${testsRun[1].replace('✔ index - subtraction (*ms)', '﹣ index - subtraction (*ms) # SKIP')}\n${compactMenu}\n${clearLines}${activeFilters}${mainMenuWithFilters}\n`);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should mention when no files found', async () => {
|
|
179
|
+
const { outputs, stderr } = await spawnInteractive(['p', 'nothing', '\r', 'w', 'q'].join(''));
|
|
180
|
+
const activeFilters = '\nActive Filters: file name **/nothing.*\n';
|
|
181
|
+
const notFound = '\nNo files found for pattern **/nothing.*';
|
|
182
|
+
assert.strictEqual(stderr, '');
|
|
183
|
+
assert.deepStrictEqual(outputs, [
|
|
184
|
+
'',
|
|
185
|
+
`${tests}\n${mainMenu}`,
|
|
186
|
+
`${filterFilesPrompt}nothing`,
|
|
187
|
+
'',
|
|
188
|
+
`${notFound}\n${compactMenu}\n${clearLines}${activeFilters}${mainMenuWithFilters}\n`,
|
|
189
|
+
]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should clear filters on "c"', async () => {
|
|
193
|
+
const { outputs, stderr } = await spawnInteractive(['p', 'index', '\r', 'w', 't', 'sum', '\r', 'w', 'c', 'w', 'q'].join(''));
|
|
194
|
+
assert.strictEqual(stderr, '');
|
|
195
|
+
assert.strictEqual(outputs.length, 9);
|
|
196
|
+
|
|
197
|
+
assert.match(outputs[4], /Active Filters: file name \*\*\/index\.\*/);
|
|
198
|
+
assert.match(outputs[5], /Active Filters: file name \*\*\/index\.\*/);
|
|
199
|
+
assert.match(outputs[7], /Active Filters: file name \*\*\/index\.\*, test name \/sum\//);
|
|
200
|
+
assert.strictEqual(outputs[8], `${tests}\n${compactMenu}\n${clearLines}${mainMenu}\n`);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('prompt ESC should preserve previous state', async () => {
|
|
204
|
+
const { outputs } = await spawnInteractive(['p', esc, 'p', 'filter', '\r', 'p', esc, 'q'].join(''));
|
|
205
|
+
const notFound = '\nNo files found for pattern **/filter.*';
|
|
206
|
+
const activeFilters = '\nActive Filters: file name **/filter.*\n';
|
|
207
|
+
assert.deepStrictEqual(outputs, [
|
|
208
|
+
'',
|
|
209
|
+
`${tests}\n${mainMenu}`,
|
|
210
|
+
`${filterFilesPrompt}`,
|
|
211
|
+
'',
|
|
212
|
+
`${tests}\n${compactMenu}`,
|
|
213
|
+
`${filterFilesPrompt}filter`,
|
|
214
|
+
'',
|
|
215
|
+
`${notFound}\n${compactMenu}`,
|
|
216
|
+
`${activeFilters}${filterFilesPrompt}`,
|
|
217
|
+
'',
|
|
218
|
+
`${notFound}\n${compactMenu}\n`,
|
|
219
|
+
]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('backspace shoud remove last character', async () => {
|
|
223
|
+
const backspace = '\x7f';
|
|
224
|
+
const { outputs, stderr } = await spawnInteractive(['p', `noth123${backspace}${backspace}ing`, '\r', 'w', 'q'].join(''));
|
|
225
|
+
assert.strictEqual(stderr, '');
|
|
226
|
+
assert.strictEqual(outputs.length, 5);
|
|
227
|
+
assert.match(outputs[4], /No files found for pattern \*\*\/noth1ing\.\*/);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|