@neurodevs/node-tdd 1.0.6 → 1.0.7

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.
Files changed (179) hide show
  1. package/package.json +1 -1
  2. package/.nvmrc +0 -1
  3. package/.vscode/launch.json +0 -53
  4. package/.vscode/settings.json +0 -62
  5. package/.vscode/tasks.json +0 -99
  6. package/build/__tests__/MockFetch.d.ts +0 -18
  7. package/build/__tests__/MockFetch.js +0 -55
  8. package/build/__tests__/MockFetch.js.map +0 -1
  9. package/build/__tests__/behavioral/AbstractModuleTest.test.d.ts +0 -5
  10. package/build/__tests__/behavioral/AbstractModuleTest.test.js +0 -22
  11. package/build/__tests__/behavioral/AbstractModuleTest.test.js.map +0 -1
  12. package/build/__tests__/behavioral/utilities/AllHooksCalledEvenIfNotDefined.test.d.ts +0 -5
  13. package/build/__tests__/behavioral/utilities/AllHooksCalledEvenIfNotDefined.test.js +0 -34
  14. package/build/__tests__/behavioral/utilities/AllHooksCalledEvenIfNotDefined.test.js.map +0 -1
  15. package/build/__tests__/behavioral/utilities/Assert.test.d.ts +0 -45
  16. package/build/__tests__/behavioral/utilities/Assert.test.js +0 -645
  17. package/build/__tests__/behavioral/utilities/Assert.test.js.map +0 -1
  18. package/build/__tests__/behavioral/utilities/InstanceParentTestCanAccessParentMethods.test.d.ts +0 -4
  19. package/build/__tests__/behavioral/utilities/InstanceParentTestCanAccessParentMethods.test.js +0 -23
  20. package/build/__tests__/behavioral/utilities/InstanceParentTestCanAccessParentMethods.test.js.map +0 -1
  21. package/build/__tests__/behavioral/utilities/MockFetch.test.d.ts +0 -28
  22. package/build/__tests__/behavioral/utilities/MockFetch.test.js +0 -211
  23. package/build/__tests__/behavioral/utilities/MockFetch.test.js.map +0 -1
  24. package/build/__tests__/behavioral/utilities/StackCleaner.test.d.ts +0 -4
  25. package/build/__tests__/behavioral/utilities/StackCleaner.test.js +0 -46
  26. package/build/__tests__/behavioral/utilities/StackCleaner.test.js.map +0 -1
  27. package/build/__tests__/behavioral/utilities/StaticParentTestCanAccessParentMethods.test.d.ts +0 -7
  28. package/build/__tests__/behavioral/utilities/StaticParentTestCanAccessParentMethods.test.js +0 -32
  29. package/build/__tests__/behavioral/utilities/StaticParentTestCanAccessParentMethods.test.js.map +0 -1
  30. package/build/__tests__/behavioral/utilities/StaticTestInheritsAbstractSpruceTestProperly.test.d.ts +0 -4
  31. package/build/__tests__/behavioral/utilities/StaticTestInheritsAbstractSpruceTestProperly.test.js +0 -19
  32. package/build/__tests__/behavioral/utilities/StaticTestInheritsAbstractSpruceTestProperly.test.js.map +0 -1
  33. package/build/__tests__/behavioral/utilities/Stringify.test.d.ts +0 -5
  34. package/build/__tests__/behavioral/utilities/Stringify.test.js +0 -127
  35. package/build/__tests__/behavioral/utilities/Stringify.test.js.map +0 -1
  36. package/build/__tests__/behavioral/utilities/TestDecoratorResolver.test.d.ts +0 -7
  37. package/build/__tests__/behavioral/utilities/TestDecoratorResolver.test.js +0 -38
  38. package/build/__tests__/behavioral/utilities/TestDecoratorResolver.test.js.map +0 -1
  39. package/build/__tests__/behavioral/utilities/TestOnBasic.test.d.ts +0 -12
  40. package/build/__tests__/behavioral/utilities/TestOnBasic.test.js +0 -68
  41. package/build/__tests__/behavioral/utilities/TestOnBasic.test.js.map +0 -1
  42. package/build/__tests__/behavioral/utilities/TestOnInstance.test.d.ts +0 -28
  43. package/build/__tests__/behavioral/utilities/TestOnInstance.test.js +0 -289
  44. package/build/__tests__/behavioral/utilities/TestOnInstance.test.js.map +0 -1
  45. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTest.test.d.ts +0 -9
  46. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTest.test.js +0 -118
  47. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTest.test.js.map +0 -1
  48. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTestWithHooks.test.d.ts +0 -5
  49. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTestWithHooks.test.js +0 -78
  50. package/build/__tests__/behavioral/utilities/TestOnInstanceExtendsTestWithHooks.test.js.map +0 -1
  51. package/build/__tests__/behavioral/utilities/TestOnInstanceWithParentBeforeAll.test.d.ts +0 -4
  52. package/build/__tests__/behavioral/utilities/TestOnInstanceWithParentBeforeAll.test.js +0 -23
  53. package/build/__tests__/behavioral/utilities/TestOnInstanceWithParentBeforeAll.test.js.map +0 -1
  54. package/build/__tests__/behavioral/utilities/TestOnInstanceWithTwoLevelsOfInheritence.test.d.ts +0 -4
  55. package/build/__tests__/behavioral/utilities/TestOnInstanceWithTwoLevelsOfInheritence.test.js +0 -24
  56. package/build/__tests__/behavioral/utilities/TestOnInstanceWithTwoLevelsOfInheritence.test.js.map +0 -1
  57. package/build/__tests__/behavioral/utilities/TestResolver.test.d.ts +0 -7
  58. package/build/__tests__/behavioral/utilities/TestResolver.test.js +0 -38
  59. package/build/__tests__/behavioral/utilities/TestResolver.test.js.map +0 -1
  60. package/build/__tests__/behavioral/utilities/TestResolverOnStatic.test.d.ts +0 -4
  61. package/build/__tests__/behavioral/utilities/TestResolverOnStatic.test.js +0 -20
  62. package/build/__tests__/behavioral/utilities/TestResolverOnStatic.test.js.map +0 -1
  63. package/build/__tests__/behavioral/workspace/JestJsonParser.test.d.ts +0 -18
  64. package/build/__tests__/behavioral/workspace/JestJsonParser.test.js +0 -267
  65. package/build/__tests__/behavioral/workspace/JestJsonParser.test.js.map +0 -1
  66. package/build/__tests__/behavioral/workspace/TestReporter.test.d.ts +0 -27
  67. package/build/__tests__/behavioral/workspace/TestReporter.test.js +0 -508
  68. package/build/__tests__/behavioral/workspace/TestReporter.test.js.map +0 -1
  69. package/build/__tests__/behavioral/workspace/TestRunner.test.d.ts +0 -15
  70. package/build/__tests__/behavioral/workspace/TestRunner.test.js +0 -99
  71. package/build/__tests__/behavioral/workspace/TestRunner.test.js.map +0 -1
  72. package/build/__tests__/behavioral/workspace/Widgets.test.d.ts +0 -16
  73. package/build/__tests__/behavioral/workspace/Widgets.test.js +0 -134
  74. package/build/__tests__/behavioral/workspace/Widgets.test.js.map +0 -1
  75. package/build/__tests__/mock-terminal-kit.d.ts +0 -79
  76. package/build/__tests__/mock-terminal-kit.js +0 -103
  77. package/build/__tests__/mock-terminal-kit.js.map +0 -1
  78. package/build/__tests__/support/AbstractForInstanceTest.d.ts +0 -4
  79. package/build/__tests__/support/AbstractForInstanceTest.js +0 -9
  80. package/build/__tests__/support/AbstractForInstanceTest.js.map +0 -1
  81. package/build/__tests__/support/AbstractLevelOneTest.d.ts +0 -3
  82. package/build/__tests__/support/AbstractLevelOneTest.js +0 -4
  83. package/build/__tests__/support/AbstractLevelOneTest.js.map +0 -1
  84. package/build/__tests__/support/AbstractLevelTwoTest.d.ts +0 -7
  85. package/build/__tests__/support/AbstractLevelTwoTest.js +0 -14
  86. package/build/__tests__/support/AbstractLevelTwoTest.js.map +0 -1
  87. package/build/__tests__/support/AbstractStaticTest.d.ts +0 -7
  88. package/build/__tests__/support/AbstractStaticTest.js +0 -14
  89. package/build/__tests__/support/AbstractStaticTest.js.map +0 -1
  90. package/build/__tests__/support/AbstractTestOnInstanceTest.d.ts +0 -22
  91. package/build/__tests__/support/AbstractTestOnInstanceTest.js +0 -23
  92. package/build/__tests__/support/AbstractTestOnInstanceTest.js.map +0 -1
  93. package/build/__tests__/support/AbstractTestOnInstanceWithHooks.d.ts +0 -26
  94. package/build/__tests__/support/AbstractTestOnInstanceWithHooks.js +0 -59
  95. package/build/__tests__/support/AbstractTestOnInstanceWithHooks.js.map +0 -1
  96. package/build/__tests__/support/jest.setup.d.ts +0 -1
  97. package/build/__tests__/support/jest.setup.js +0 -4
  98. package/build/__tests__/support/jest.setup.js.map +0 -1
  99. package/build/__tests__/support/onTestFileResult.d.ts +0 -244
  100. package/build/__tests__/support/onTestFileResult.js +0 -980
  101. package/build/__tests__/support/onTestFileResult.js.map +0 -1
  102. package/eslint.config.js +0 -3
  103. package/prettier.config.js +0 -3
  104. package/src/.spruce/settings.json +0 -4
  105. package/src/__tests__/MockFetch.ts +0 -100
  106. package/src/__tests__/behavioral/AbstractModuleTest.test.ts +0 -15
  107. package/src/__tests__/behavioral/utilities/AllHooksCalledEvenIfNotDefined.test.ts +0 -31
  108. package/src/__tests__/behavioral/utilities/Assert.test.ts +0 -826
  109. package/src/__tests__/behavioral/utilities/InstanceParentTestCanAccessParentMethods.test.ts +0 -14
  110. package/src/__tests__/behavioral/utilities/MockFetch.test.ts +0 -240
  111. package/src/__tests__/behavioral/utilities/StackCleaner.test.ts +0 -46
  112. package/src/__tests__/behavioral/utilities/StaticParentTestCanAccessParentMethods.test.ts +0 -34
  113. package/src/__tests__/behavioral/utilities/StaticTestInheritsAbstractSpruceTestProperly.test.ts +0 -11
  114. package/src/__tests__/behavioral/utilities/Stringify.test.ts +0 -169
  115. package/src/__tests__/behavioral/utilities/TestDecoratorResolver.test.ts +0 -51
  116. package/src/__tests__/behavioral/utilities/TestOnBasic.test.ts +0 -62
  117. package/src/__tests__/behavioral/utilities/TestOnInstance.test.ts +0 -439
  118. package/src/__tests__/behavioral/utilities/TestOnInstanceExtendsTest.test.ts +0 -237
  119. package/src/__tests__/behavioral/utilities/TestOnInstanceExtendsTestWithHooks.test.ts +0 -85
  120. package/src/__tests__/behavioral/utilities/TestOnInstanceWithParentBeforeAll.test.ts +0 -14
  121. package/src/__tests__/behavioral/utilities/TestOnInstanceWithTwoLevelsOfInheritence.test.ts +0 -16
  122. package/src/__tests__/behavioral/utilities/TestResolver.test.ts +0 -48
  123. package/src/__tests__/behavioral/utilities/TestResolverOnStatic.test.ts +0 -12
  124. package/src/__tests__/behavioral/workspace/JestJsonParser.test.ts +0 -357
  125. package/src/__tests__/behavioral/workspace/TestReporter.test.ts +0 -584
  126. package/src/__tests__/behavioral/workspace/TestRunner.test.ts +0 -87
  127. package/src/__tests__/behavioral/workspace/Widgets.test.ts +0 -131
  128. package/src/__tests__/mock-terminal-kit.ts +0 -115
  129. package/src/__tests__/support/AbstractForInstanceTest.ts +0 -12
  130. package/src/__tests__/support/AbstractLevelOneTest.ts +0 -3
  131. package/src/__tests__/support/AbstractLevelTwoTest.ts +0 -16
  132. package/src/__tests__/support/AbstractStaticTest.ts +0 -16
  133. package/src/__tests__/support/AbstractTestOnInstanceTest.ts +0 -30
  134. package/src/__tests__/support/AbstractTestOnInstanceWithHooks.ts +0 -138
  135. package/src/__tests__/support/jest.setup.ts +0 -2
  136. package/src/__tests__/support/onTestFileResult.ts +0 -1008
  137. package/src/impl/AbstractModuleTest.ts +0 -67
  138. package/src/index.ts +0 -12
  139. package/src/utilities/AssertionError.ts +0 -14
  140. package/src/utilities/StackCleaner.ts +0 -14
  141. package/src/utilities/TestDecoratorResolver.ts +0 -118
  142. package/src/utilities/assert.ts +0 -583
  143. package/src/utilities/assert.utility.ts +0 -295
  144. package/src/utilities/decorators.ts +0 -167
  145. package/src/workspace/CommandService.ts +0 -256
  146. package/src/workspace/JestJsonParser.ts +0 -255
  147. package/src/workspace/TKButtonWidget.ts +0 -54
  148. package/src/workspace/TestLogItemGenerator.ts +0 -184
  149. package/src/workspace/TestReporter.ts +0 -880
  150. package/src/workspace/TestRunner.ts +0 -128
  151. package/src/workspace/TkBaseWidget.ts +0 -249
  152. package/src/workspace/TkInputWidget.ts +0 -83
  153. package/src/workspace/TkLayoutCellWidget.ts +0 -46
  154. package/src/workspace/TkLayoutWidget.ts +0 -181
  155. package/src/workspace/TkMenuBarWidget.ts +0 -95
  156. package/src/workspace/TkPopupWidget.ts +0 -47
  157. package/src/workspace/TkProgressBarWidget.ts +0 -54
  158. package/src/workspace/TkTextWidget.ts +0 -167
  159. package/src/workspace/TkWindowWidget.ts +0 -122
  160. package/src/workspace/WidgetFactory.ts +0 -33
  161. package/src/workspace/button.types.ts +0 -19
  162. package/src/workspace/duration.utility.ts +0 -34
  163. package/src/workspace/factory.types.ts +0 -101
  164. package/src/workspace/input.types.ts +0 -22
  165. package/src/workspace/keySelectChoices.ts +0 -134
  166. package/src/workspace/layout.types.ts +0 -40
  167. package/src/workspace/menuBar.types.ts +0 -24
  168. package/src/workspace/popup.types.ts +0 -17
  169. package/src/workspace/progressBar.types.ts +0 -13
  170. package/src/workspace/table.types.ts +0 -9
  171. package/src/workspace/termKit.utility.ts +0 -130
  172. package/src/workspace/terminal-kit.d.ts +0 -28
  173. package/src/workspace/test.types.ts +0 -34
  174. package/src/workspace/testRunner.cli.ts +0 -166
  175. package/src/workspace/text.types.ts +0 -27
  176. package/src/workspace/widget.utilities.ts +0 -35
  177. package/src/workspace/widgets.types.ts +0 -102
  178. package/src/workspace/window.types.ts +0 -22
  179. package/tsconfig.json +0 -24
@@ -1,880 +0,0 @@
1
- import chalk from 'chalk'
2
- import durationUtil from './duration.utility.js'
3
- import { ButtonWidget } from './button.types.js'
4
- import { InputWidget } from './input.types.js'
5
- import { LayoutWidget } from './layout.types.js'
6
- import { MenuBarWidget } from './menuBar.types.js'
7
- import { PopupWidget } from './popup.types.js'
8
- import { ProgressBarWidget } from './progressBar.types.js'
9
- import { TextWidget } from './text.types.js'
10
- import { WindowWidget } from './window.types.js'
11
- import WidgetFactory from './WidgetFactory.js'
12
- import { TestResults, TestRunnerStatus } from './test.types.js'
13
- import TestLogItemGenerator from './TestLogItemGenerator.js'
14
-
15
- export default class TestReporter {
16
- private started = false
17
- private table?: any
18
- private bar!: ProgressBarWidget
19
- private bottomLayout!: LayoutWidget
20
- private testLog!: TextWidget
21
- private errorLog?: TextWidget
22
- private errorLogItemGenerator: TestLogItemGenerator
23
- private lastResults: TestReporterResults = {
24
- totalTestFiles: 0,
25
- customErrors: [],
26
- }
27
- private updateInterval?: any
28
- private menu!: MenuBarWidget
29
- private statusBar!: TextWidget
30
- private window!: WindowWidget
31
- private widgets: WidgetFactory
32
- private selectTestPopup?: PopupWidget
33
- private topLayout!: LayoutWidget
34
- private filterInput!: InputWidget
35
- private filterPattern?: string
36
- private clearFilterPatternButton!: ButtonWidget
37
- private isDebugging = false
38
- private watchMode: WatchMode = 'off'
39
- private status: TestRunnerStatus = 'ready'
40
- private countDownTimeInterval?: any
41
- private cwd: string | undefined
42
- private orientation: TestReporterOrientation = 'landscape'
43
-
44
- private handleStartStop?: () => void
45
- private handleRestart?: () => void
46
- private handleQuit?: () => void
47
- private handleRerunTestFile?: (fileName: string) => void
48
- private handleFilterChange?: (pattern?: string) => void
49
- private handleOpenTestFile?: (testFile: string) => void
50
- private handleToggleDebug?: () => void
51
- private handletoggleStandardWatch?: () => void
52
- private handleToggleSmartWatch?: () => any
53
- private minWidth = 50
54
-
55
- public constructor(options?: TestReporterOptions) {
56
- this.cwd = options?.cwd
57
- this.filterPattern = options?.filterPattern
58
- this.handleRestart = options?.handleRestart
59
- this.handleStartStop = options?.handleStartStop
60
- this.handleQuit = options?.handleQuit
61
- this.handleRerunTestFile = options?.handleRerunTestFile
62
- this.handleOpenTestFile = options?.handleOpenTestFile
63
- this.handleFilterChange = options?.handleFilterPatternChange
64
- this.status = options?.status ?? 'ready'
65
- this.handleToggleDebug = options?.handleToggleDebug
66
- this.handletoggleStandardWatch = options?.handletoggleStandardWatch
67
- this.isDebugging = options?.isDebugging ?? false
68
- this.watchMode = options?.watchMode ?? 'off'
69
- this.handleToggleSmartWatch = options?.handleToggleSmartWatch
70
-
71
- this.errorLogItemGenerator = new TestLogItemGenerator()
72
- this.widgets = new WidgetFactory()
73
- }
74
-
75
- public setFilterPattern(pattern: string | undefined) {
76
- this.filterPattern = pattern
77
- this.filterInput.setValue(pattern ?? '')
78
- this.clearFilterPatternButton.setText(buildPatternButtonText(pattern))
79
- }
80
-
81
- public setIsDebugging(isDebugging: boolean) {
82
- this.setLabelStatus('toggleDebug', 'Debug', isDebugging)
83
- this.isDebugging = isDebugging
84
- }
85
-
86
- public setWatchMode(watchMode: WatchMode) {
87
- this.watchMode = watchMode
88
- if (!this.countDownTimeInterval) {
89
- let label =
90
- watchMode === 'smart' ? 'Smart Watch ' : 'Standard Watch'
91
- if (watchMode === 'off') {
92
- label = 'Not Watching '
93
- }
94
- this.setWatchLabel(label)
95
- }
96
- }
97
-
98
- private setWatchLabel(label: string) {
99
- const isEnabled = this.watchMode !== 'off'
100
- this.setLabelStatus('watchDropdown', label, isEnabled)
101
-
102
- this.menu.setTextForItem(
103
- 'toggleStandardWatch',
104
- this.watchMode === 'standard' ? '√ Standard' : 'Standard'
105
- )
106
-
107
- this.menu.setTextForItem(
108
- 'toggleSmartWatch',
109
- this.watchMode === 'smart' ? '√ Smart' : 'Smart'
110
- )
111
- }
112
-
113
- private setLabelStatus(menuKey: string, label: string, isEnabled: boolean) {
114
- this.menu.setTextForItem(
115
- menuKey,
116
- `${label} ^${isEnabled ? 'k' : 'w'}^#^${isEnabled ? 'g' : 'r'}${isEnabled ? ' • ' : ' • '}^`
117
- )
118
- }
119
-
120
- public startCountdownTimer(durationSec: number) {
121
- clearInterval(this.countDownTimeInterval)
122
- this.countDownTimeInterval = undefined
123
-
124
- let remaining = durationSec
125
-
126
- function renderCountdownTime(time: number) {
127
- return `Starting ${time} `
128
- }
129
-
130
- this.setWatchLabel(renderCountdownTime(remaining))
131
-
132
- this.countDownTimeInterval = setInterval(() => {
133
- remaining--
134
-
135
- if (remaining < 0) {
136
- this.stopCountdownTimer()
137
- } else {
138
- this.setWatchLabel(renderCountdownTime(remaining))
139
- }
140
- }, 1000) as any
141
- }
142
-
143
- public stopCountdownTimer() {
144
- clearInterval(this.countDownTimeInterval)
145
- this.countDownTimeInterval = undefined
146
- this.setWatchMode(this.watchMode)
147
- }
148
-
149
- public async start() {
150
- this.started = true
151
-
152
- this.window = this.widgets.Widget('window', {})
153
- this.window.hideCursor()
154
-
155
- const { width } = this.window.getFrame()
156
- if (width < this.minWidth) {
157
- throw new Error(
158
- `Your screen must be at least ${this.minWidth} characters wide.`
159
- )
160
- }
161
-
162
- void this.window.on('key', this.handleGlobalKeypress.bind(this))
163
- void this.window.on('kill', (payload: { code: any }) => {
164
- if (payload.code instanceof Error) {
165
- const term = (this.window as any).term as any
166
- const doc = (this.window as any).getTermKitElement?.() as any
167
- term?.grabInput?.({ mouse: 'button' })
168
- doc?.draw?.()
169
- } else {
170
- void this.destroy()
171
- }
172
- })
173
- void this.window.on('resize', this.handleWindowResize.bind(this))
174
-
175
- this.dropInTopLayout()
176
- this.dropInProgressBar()
177
- this.dropInMenu()
178
- this.dropInBottomLayout()
179
- this.dropInStatusBar()
180
- this.dropInTestLog()
181
- this.dropInFilterControls()
182
-
183
- this.updateOrientation()
184
-
185
- this.setIsDebugging(this.isDebugging)
186
- this.setWatchMode(this.watchMode)
187
- this.setStatus(this.status)
188
-
189
- this.updateInterval = setInterval(
190
- this.handleUpdateInterval.bind(this),
191
- 1000
192
- )
193
- }
194
-
195
- private handleWindowResize() {
196
- this.updateOrientation()
197
- }
198
-
199
- private updateOrientation() {
200
- const frame = this.window.getFrame()
201
-
202
- if (frame.width * 0.4 > frame.height) {
203
- this.orientation = 'landscape'
204
- } else {
205
- this.orientation = 'portrait'
206
- }
207
- }
208
-
209
- private dropInMenu() {
210
- this.menu = this.widgets.Widget('menuBar', {
211
- parent: this.window,
212
- left: 0,
213
- top: 0,
214
- shouldLockWidthWithParent: true,
215
- items: [
216
- {
217
- label: 'Restart ',
218
- value: 'restart',
219
- },
220
- {
221
- label: 'Debug ',
222
- value: 'toggleDebug',
223
- },
224
- {
225
- label: 'Not Watching ',
226
- value: 'watchDropdown',
227
- items: [
228
- {
229
- label: 'Watch all',
230
- value: 'toggleStandardWatch',
231
- },
232
- {
233
- label: 'Smart watch',
234
- value: 'toggleSmartWatch',
235
- },
236
- ],
237
- },
238
- {
239
- label: 'Quit',
240
- value: 'quit',
241
- },
242
- ],
243
- })
244
-
245
- void this.menu.on('select', this.handleMenuSelect.bind(this))
246
- }
247
-
248
- public setStatus(status: TestRunnerStatus) {
249
- this.status = status
250
-
251
- this.updateMenuLabels()
252
- this.closeSelectTestPopup()
253
- this.bottomLayout.updateLayout()
254
-
255
- if (status === 'ready') {
256
- this.setStatusLabel('Starting...')
257
- } else if (this.status === 'stopped') {
258
- this.refreshResults()
259
- this.testLog?.scrollToTop()
260
- this.setStatusLabel('')
261
- } else if (this.status === 'running') {
262
- this.setStatusLabel('Running tests...')
263
- }
264
- }
265
-
266
- private updateMenuLabels() {
267
- let restartLabel = 'Start ^#^r › ^'
268
- switch (this.status) {
269
- case 'running':
270
- restartLabel = 'Stop ^k^#^g › ^'
271
- break
272
- case 'stopped':
273
- restartLabel = `Start ^w^#^r › ^`
274
- break
275
- case 'ready':
276
- restartLabel = 'Booting ^#^K › ^'
277
- break
278
- }
279
-
280
- this.menu.setTextForItem('restart', restartLabel)
281
- }
282
-
283
- private handleMenuSelect(payload: { value: string }) {
284
- switch (payload.value) {
285
- case 'quit':
286
- this.handleQuit?.()
287
- break
288
- case 'restart':
289
- this.handleStartStop?.()
290
- break
291
- case 'toggleDebug':
292
- this.handleToggleDebug?.()
293
- break
294
- case 'toggleStandardWatch':
295
- this.handletoggleStandardWatch?.()
296
- break
297
- case 'toggleSmartWatch':
298
- this.handleToggleSmartWatch?.()
299
- break
300
- }
301
- }
302
-
303
- private handleUpdateInterval() {
304
- try {
305
- if (this.status !== 'stopped') {
306
- this.refreshResults()
307
- }
308
- } catch {
309
- // prevent uncaughtException from crashing the TUI
310
- }
311
- }
312
-
313
- private refreshResults() {
314
- if (this.lastResults) {
315
- this.updateLogs()
316
- }
317
- }
318
-
319
- private async handleGlobalKeypress(payload: { key: string }) {
320
- if (this.window.getFocusedWidget() === this.filterInput) {
321
- return
322
- }
323
-
324
- switch (payload.key) {
325
- case 'ENTER':
326
- this.handleRestart?.()
327
- break
328
- case 'CTRL_C':
329
- this.handleQuit?.()
330
- process.exit()
331
- break
332
- }
333
- }
334
-
335
- private dropInTestLog() {
336
- const parent = this.bottomLayout.getChildById('results')!
337
-
338
- this.testLog = this.widgets.Widget('text', {
339
- parent,
340
- isScrollEnabled: true,
341
- wordWrap: false,
342
- left: 0,
343
- top: 0,
344
- height: '100%',
345
- width: '100%',
346
- shouldLockHeightWithParent: true,
347
- shouldLockWidthWithParent: true,
348
- })
349
-
350
- void this.testLog.on('click', this.handleClickTestLog.bind(this))
351
- }
352
-
353
- private async handleClickTestLog(payload: { row: number; column: number }) {
354
- const testFile = this.getFileForLine(payload.row)
355
- const { row, column } = payload
356
-
357
- this.closeSelectTestPopup()
358
-
359
- if (testFile) {
360
- this.dropInSelectTestPopup({ testFile, column, row })
361
- }
362
- }
363
-
364
- public async showAlert(options: { title: string; message: string }) {
365
- const { title, message } = options
366
-
367
- const windowFrame = this.window.getFrame()
368
- const popupHeight = Math.min(windowFrame.height - 4, 25)
369
-
370
- return new Promise<void>((resolve) => {
371
- const popup = this.widgets.Widget('popup', {
372
- parent: this.window,
373
- top: 2,
374
- left: 4,
375
- width: Math.min(windowFrame.width - 8, 80),
376
- height: popupHeight,
377
- })
378
-
379
- this.widgets.Widget('text', {
380
- parent: popup,
381
- left: 2,
382
- top: 1,
383
- height: 1,
384
- width: popup.getFrame().width - 4,
385
- text: title,
386
- })
387
-
388
- this.widgets.Widget('text', {
389
- parent: popup,
390
- left: 2,
391
- top: 3,
392
- height: popupHeight - 7,
393
- width: popup.getFrame().width - 4,
394
- text: message,
395
- isScrollEnabled: true,
396
- wordWrap: true,
397
- })
398
-
399
- const okButton = this.widgets.Widget('button', {
400
- parent: popup,
401
- left: Math.floor(popup.getFrame().width / 2) - 4,
402
- top: popupHeight - 3,
403
- text: ' OK ',
404
- })
405
-
406
- void okButton.on('click', () => {
407
- void popup.destroy()
408
- resolve()
409
- })
410
- })
411
- }
412
-
413
- private closeSelectTestPopup() {
414
- if (this.selectTestPopup) {
415
- void this.selectTestPopup.destroy()
416
- this.selectTestPopup = undefined
417
- }
418
- }
419
-
420
- private dropInSelectTestPopup(options: {
421
- testFile: string
422
- column: number
423
- row: number
424
- }) {
425
- const { testFile, row, column } = options
426
-
427
- this.selectTestPopup = this.widgets.Widget('popup', {
428
- parent: this.window,
429
- left: Math.max(1, column - 25),
430
- top: Math.max(4, row - 2),
431
- width: 50,
432
- height: 10,
433
- })
434
-
435
- this.widgets.Widget('text', {
436
- parent: this.selectTestPopup,
437
- left: 1,
438
- top: 1,
439
- height: 4,
440
- width: this.selectTestPopup.getFrame().width - 2,
441
- text: `Selected file:\n\n${testFile}`,
442
- })
443
-
444
- const open = this.widgets.Widget('button', {
445
- parent: this.selectTestPopup,
446
- left: 1,
447
- top: 6,
448
- text: 'Open',
449
- })
450
-
451
- const rerun = this.widgets.Widget('button', {
452
- parent: this.selectTestPopup,
453
- left: 20,
454
- top: 6,
455
- text: 'Test',
456
- })
457
-
458
- const cancel = this.widgets.Widget('button', {
459
- parent: this.selectTestPopup,
460
- left: 37,
461
- top: 6,
462
- text: 'Nevermind',
463
- })
464
-
465
- void rerun.on('click', () => {
466
- this.handleRerunTestFile?.(testFile)
467
- this.closeSelectTestPopup()
468
- })
469
- void cancel.on('click', this.closeSelectTestPopup.bind(this))
470
- void open.on('click', () => {
471
- this.openTestFile(testFile)
472
- })
473
- }
474
-
475
- private openTestFile(testFile: string) {
476
- this.handleOpenTestFile?.(testFile)
477
- this.closeSelectTestPopup()
478
- }
479
-
480
- public getFileForLine(row: number): string | undefined {
481
- let line = this.testLog.getScrollY()
482
-
483
- for (const file of this.lastResults.testFiles ?? []) {
484
- const minRow = line
485
- const lineCount =
486
- 1 +
487
- (file.tests ?? []).length +
488
- (file.status === 'running' ? 1 : 0)
489
- const maxRow = line + lineCount - 1
490
-
491
- if (row >= minRow && row <= maxRow) {
492
- return file.path
493
- }
494
-
495
- line = maxRow + 1
496
- }
497
-
498
- return undefined
499
- }
500
-
501
- private dropInProgressBar() {
502
- const parent = this.topLayout.getChildById('progress') ?? this.window
503
- this.bar = this.widgets.Widget('progressBar', {
504
- parent,
505
- left: 0,
506
- top: 0,
507
- width: parent.getFrame().width,
508
- shouldLockWidthWithParent: true,
509
- label: 'Ready and waiting...',
510
- progress: 0,
511
- })
512
- }
513
-
514
- private dropInFilterControls() {
515
- const parent = this.topLayout.getChildById('filter') ?? this.window
516
-
517
- const buttonWidth = 3
518
- this.filterInput = this.widgets.Widget('input', {
519
- parent,
520
- left: 0,
521
- label: 'Pattern',
522
- width: parent.getFrame().width - buttonWidth,
523
- height: 1,
524
- shouldLockWidthWithParent: true,
525
- value: this.filterPattern,
526
- })
527
-
528
- void this.filterInput.on('cancel', () => {
529
- this.filterInput.setValue(this.filterPattern ?? '')
530
- })
531
-
532
- void this.filterInput.on('submit', (payload) => {
533
- this.handleFilterChange?.(payload?.value ?? undefined)
534
- })
535
-
536
- this.clearFilterPatternButton = this.widgets.Widget('button', {
537
- parent,
538
- left: this.filterInput.getFrame().width,
539
- width: buttonWidth,
540
- top: 0,
541
- text: buildPatternButtonText(this.filterPattern),
542
- shouldLockRightWithParent: true,
543
- })
544
-
545
- void this.clearFilterPatternButton.on('click', () => {
546
- if (this.filterPattern || this.filterPattern?.length === 0) {
547
- this.handleFilterChange?.(undefined)
548
- } else {
549
- this.filterInput.setValue('')
550
- }
551
- })
552
- }
553
-
554
- private dropInBottomLayout() {
555
- this.bottomLayout = this.widgets.Widget('layout', {
556
- parent: this.window,
557
- width: '100%',
558
- top: 4,
559
- height: this.window.getFrame().height - 5,
560
- shouldLockWidthWithParent: true,
561
- shouldLockHeightWithParent: true,
562
- rows: [
563
- {
564
- height: '100%',
565
- columns: [
566
- {
567
- id: 'results',
568
- width: '100%',
569
- },
570
- ],
571
- },
572
- ],
573
- })
574
- }
575
-
576
- private dropInStatusBar() {
577
- this.statusBar = this.widgets.Widget('text', {
578
- parent: this.window,
579
- top: this.window.getFrame().height - 1,
580
- width: '100%',
581
- shouldLockWidthWithParent: true,
582
- shouldLockBottomWithParent: true,
583
- backgroundColor: 'yellow',
584
- foregroundColor: 'black',
585
- text: '...',
586
- })
587
- }
588
-
589
- private dropInTopLayout() {
590
- this.topLayout = this.widgets.Widget('layout', {
591
- parent: this.window,
592
- width: '100%',
593
- top: 1,
594
- height: 3,
595
- shouldLockWidthWithParent: true,
596
- shouldLockHeightWithParent: false,
597
- rows: [
598
- {
599
- height: '100%',
600
- columns: [
601
- {
602
- id: 'progress',
603
- width: 50,
604
- },
605
- {
606
- id: 'filter',
607
- },
608
- ],
609
- },
610
- ],
611
- })
612
- }
613
-
614
- public updateResults(results: TestResults) {
615
- if (!this.started) {
616
- throw new Error('You must call start() before anything else.')
617
- }
618
-
619
- this.lastResults = {
620
- ...this.lastResults,
621
- ...results,
622
- }
623
-
624
- this.updateProgressBar(results)
625
-
626
- const percentPassing = this.generatePercentPassing(results)
627
- const percentComplete = this.generatePercentComplete(results)
628
-
629
- this.window.setTitle(
630
- `Testing: ${percentComplete}% complete.${
631
- percentComplete > 0 ? ` ${percentPassing}% passing.` : ''
632
- }`
633
- )
634
-
635
- this.updateLogs()
636
- }
637
-
638
- private updateLogs() {
639
- if (this.selectTestPopup) {
640
- return
641
- }
642
-
643
- const { logContent, errorContent } = this.resultsToLogContents(
644
- this.lastResults
645
- )
646
-
647
- this.testLog.setText(logContent)
648
-
649
- if (!errorContent) {
650
- this.errorLog?.setText(' Nothing to report...')
651
- } else {
652
- !this.errorLog && this.dropInErrorLog()
653
- const cleanedLog = this.cwd
654
- ? errorContent.replace(new RegExp(this.cwd + '/', 'gim'), '')
655
- : errorContent
656
-
657
- this.errorLog?.setText(cleanedLog)
658
- }
659
- }
660
-
661
- private resultsToLogContents(results: TestResults) {
662
- let logContent = ''
663
- let errorContent = ''
664
-
665
- const files = [...(results.testFiles ?? [])]
666
-
667
- if (this.status === 'stopped') {
668
- files.sort((a, b) => {
669
- const aFailed = a.status === 'failed' ? 0 : 1
670
- const bFailed = b.status === 'failed' ? 0 : 1
671
- return aFailed - bFailed
672
- })
673
- }
674
-
675
- files.forEach((file) => {
676
- logContent += this.errorLogItemGenerator.generateLogItemForFile(
677
- file,
678
- this.status
679
- )
680
- errorContent +=
681
- this.errorLogItemGenerator.generateErrorLogItemForFile(file)
682
- })
683
-
684
- if (this.lastResults.customErrors.length > 0) {
685
- errorContent =
686
- this.lastResults.customErrors
687
- .map((err) => chalk.red(err))
688
- .join(`\n`) + `\n${errorContent}`
689
- }
690
-
691
- return { logContent, errorContent }
692
- }
693
-
694
- private dropInErrorLog() {
695
- if (this.bottomLayout.getRows().length === 1) {
696
- if (this.orientation === 'portrait') {
697
- this.bottomLayout.addRow({
698
- id: 'row_2',
699
- columns: [{ id: 'errors', width: '100%' }],
700
- })
701
-
702
- this.bottomLayout.setRowHeight(0, '60%')
703
- this.bottomLayout.setRowHeight(1, '40%')
704
- } else {
705
- this.bottomLayout.addColumn(0, { id: 'errors', width: '40%' })
706
- this.bottomLayout.setColumnWidth({
707
- rowIdx: 0,
708
- columnIdx: 0,
709
- width: '60%',
710
- })
711
- }
712
-
713
- this.bottomLayout.updateLayout()
714
-
715
- const cell = this.bottomLayout.getChildById('errors')
716
-
717
- if (!cell) {
718
- throw new Error('Pulling child error')
719
- }
720
-
721
- this.errorLog = this.widgets.Widget('text', {
722
- parent: cell,
723
- width: '100%',
724
- height: '100%',
725
- isScrollEnabled: true,
726
- shouldAutoScrollWhenAppendingContent: false,
727
- shouldLockHeightWithParent: true,
728
- shouldLockWidthWithParent: true,
729
- padding: { left: 1 },
730
- focusable: false,
731
- })
732
- }
733
- }
734
-
735
- private updateProgressBar(results: TestResults) {
736
- if ((results.totalTestFilesComplete ?? 0) > 0) {
737
- const testsRemaining =
738
- results.totalTestFiles - (results.totalTestFilesComplete ?? 0)
739
-
740
- if (testsRemaining === 0) {
741
- const { percent, totalTests, totalPassedTests, totalTime } =
742
- this.generateProgressStats(results)
743
-
744
- this.bar.setLabel(
745
- `Finished! ${totalPassedTests} of ${totalTests} (${percent}%) passed in ${durationUtil.msToFriendly(
746
- totalTime
747
- )}.${percent < 100 ? ` Don't give up!` : ''}`
748
- )
749
- } else {
750
- this.bar.setLabel(
751
- `${results.totalTestFilesComplete} of ${
752
- results.totalTestFiles
753
- } (${this.generatePercentComplete(
754
- results
755
- )}%) complete. ${testsRemaining} remaining...`
756
- )
757
- }
758
- } else {
759
- this.bar.setLabel('0%')
760
- }
761
-
762
- this.bar.setProgress(this.generatePercentComplete(results) / 100)
763
- }
764
-
765
- private generateProgressStats(results: TestResults): {
766
- percent: number
767
- totalTests: number
768
- totalPassedTests: number
769
- totalTime: number
770
- } {
771
- let totalTests = 0
772
- let totalPassedTests = 0
773
- let totalTime = 0
774
-
775
- results.testFiles?.forEach((file) => {
776
- file.tests?.forEach((test) => {
777
- totalTime += test.duration
778
- if (test.status === 'passed') {
779
- totalPassedTests++
780
- }
781
-
782
- if (test.status === 'passed' || test.status === 'failed') {
783
- totalTests++
784
- }
785
- })
786
- })
787
-
788
- const percent = Math.floor((totalPassedTests / totalTests) * 100)
789
- return {
790
- percent: percent > 0 ? percent : 0,
791
- totalTests,
792
- totalPassedTests,
793
- totalTime,
794
- }
795
- }
796
-
797
- private generatePercentComplete(results: TestResults): number {
798
- const percent =
799
- (results.totalTestFilesComplete ?? 0) / results.totalTestFiles
800
- if (isNaN(percent)) {
801
- return 0
802
- }
803
- return Math.round(percent * 100)
804
- }
805
-
806
- private generatePercentPassing(results: TestResults): number {
807
- const percent =
808
- (results.totalPassed ?? 0) / this.getTotalTestFilesRun(results)
809
-
810
- if (isNaN(percent)) {
811
- return 0
812
- }
813
-
814
- return Math.floor(percent * 100)
815
- }
816
-
817
- private getTotalTestFilesRun(results: TestResults) {
818
- return (
819
- (results.totalTests ?? 0) -
820
- (results.totalSkipped ?? 0) -
821
- (results.totalTodo ?? 0)
822
- )
823
- }
824
-
825
- public render() {
826
- this.table?.computeCells()
827
- this.table?.draw()
828
- }
829
-
830
- public async destroy() {
831
- clearInterval(this.updateInterval)
832
- await this.window.destroy()
833
- }
834
-
835
- public reset() {
836
- this.testLog.setText('')
837
- this.lastResults = {
838
- totalTestFiles: 0,
839
- customErrors: [],
840
- }
841
- this.errorLogItemGenerator.resetStartTimes()
842
- }
843
-
844
- public setStatusLabel(text: string) {
845
- this.statusBar.setText(text)
846
- }
847
-
848
- public appendError(message: string) {
849
- this.lastResults.customErrors.push(message)
850
- }
851
- }
852
-
853
- function buildPatternButtonText(pattern: string | undefined): string {
854
- return pattern ? ' x ' : ' - '
855
- }
856
-
857
- export interface TestReporterOptions {
858
- handleStartStop?: () => void
859
- handleRestart?: () => void
860
- handleQuit?: () => void
861
- onRequestOpenTestFile?: () => void
862
- handleRerunTestFile?: (fileName: string) => void
863
- handleOpenTestFile?: (fileName: string) => void
864
- handleFilterPatternChange?: (pattern?: string) => void
865
- handleToggleDebug?: () => void
866
- handletoggleStandardWatch?: () => void
867
- handleToggleSmartWatch?: () => void
868
- filterPattern?: string
869
- isDebugging?: boolean
870
- watchMode?: WatchMode
871
- status?: TestRunnerStatus
872
- cwd?: string
873
- }
874
-
875
- type TestReporterResults = TestResults & {
876
- customErrors: string[]
877
- }
878
-
879
- export type TestReporterOrientation = 'landscape' | 'portrait'
880
- export type WatchMode = 'off' | 'standard' | 'smart'