Skip to content

Commit be86892

Browse files
Harsh1925Tobbe
andcommitted
fix(tui): validate spinner.characters (≥2) and frameInterval (>0); add first tests
redwoodjs/graphql#12088 Co-authored-by: Tobbe Lundberg <[email protected]>
1 parent c592f02 commit be86892

File tree

2 files changed

+80
-5
lines changed

2 files changed

+80
-5
lines changed

packages/tui/src/index.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ReactiveTUIContent } from './index'
2+
3+
describe('ReactiveTUIContent validation', () => {
4+
it('throws if spinner.characters has fewer than 2 entries', () => {
5+
const r = new ReactiveTUIContent({})
6+
expect(() =>
7+
r.update({ spinner: { enabled: true, characters: ['-'] } }),
8+
).toThrow(/at least 2/i)
9+
})
10+
11+
it('throws if frameInterval is 0 or negative', () => {
12+
const r = new ReactiveTUIContent({})
13+
expect(() => r.update({ frameInterval: 0 })).toThrow(/frameInterval.*> 0/i)
14+
expect(() => r.update({ frameInterval: -5 })).toThrow(/frameInterval.*> 0/i)
15+
})
16+
17+
it('accepts valid spinner.characters and frameInterval', () => {
18+
const r = new ReactiveTUIContent({})
19+
expect(() =>
20+
r.update({ spinner: { enabled: true, characters: ['-', '\\'] } }),
21+
).not.toThrow()
22+
expect(() => r.update({ frameInterval: 50 })).not.toThrow()
23+
})
24+
})

packages/tui/src/index.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,33 @@ export class ReactiveTUIContent {
6464
RedwoodStyling.redwood(c),
6565
),
6666
}
67+
68+
// Validate spinner.characters if provided
69+
if (
70+
options.spinner?.characters !== undefined &&
71+
(!Array.isArray(options.spinner.characters) ||
72+
options.spinner.characters.length < 2)
73+
) {
74+
throw new Error(
75+
'tui: `spinner.characters` must be an array with at least 2 entries',
76+
)
77+
}
78+
6779
this.spinner = { ...defaultSpinner, ...options.spinner }
6880
this.boxen = { ...options.boxen }
69-
this.frameInterval = options.frameInterval || 80
81+
82+
// Validate frameInterval if provided
83+
if (options.frameInterval !== undefined) {
84+
if (
85+
typeof options.frameInterval !== 'number' ||
86+
options.frameInterval <= 0
87+
) {
88+
throw new Error('tui: `frameInterval` must be a number > 0')
89+
}
90+
this.frameInterval = options.frameInterval
91+
} else {
92+
this.frameInterval = 80
93+
}
7094

7195
if (options.outStream) {
7296
this.setOutStream(options.outStream)
@@ -95,7 +119,16 @@ export class ReactiveTUIContent {
95119
this.content = options.content
96120
}
97121
if (options.spinner) {
98-
// TODO: Validate characters array has at least two characters
122+
// Validate characters array has at least two characters (if provided)
123+
if (
124+
options.spinner.characters !== undefined &&
125+
(!Array.isArray(options.spinner.characters) ||
126+
options.spinner.characters.length < 2)
127+
) {
128+
throw new Error(
129+
'tui: `spinner.characters` must be an array with at least 2 entries',
130+
)
131+
}
99132
this.spinner = { ...this.spinner, ...options.spinner }
100133
}
101134
if (options.boxen) {
@@ -104,8 +137,14 @@ export class ReactiveTUIContent {
104137
if (options.outStream) {
105138
this.setOutStream(options.outStream)
106139
}
107-
if (options.frameInterval) {
108-
// TODO: Validate > 0
140+
if (options.frameInterval !== undefined) {
141+
// Validate > 0
142+
if (
143+
typeof options.frameInterval !== 'number' ||
144+
options.frameInterval <= 0
145+
) {
146+
throw new Error('tui: `frameInterval` must be a number > 0')
147+
}
109148
this.frameInterval = options.frameInterval
110149
}
111150
}
@@ -169,7 +208,19 @@ export interface RedwoodTUIConfig {
169208
}
170209

171210
/**
172-
* TODO: Documentation for this
211+
* RedwoodTUI
212+
*
213+
* A thin wrapper around stdout/stderr that renders static text and
214+
* "reactive" content (e.g., spinners) to the terminal. It coordinates a
215+
* shared UpdateManager, handles TTY/non-TTY output, and provides helpers
216+
* for prompting and boxed messages.
217+
*
218+
* Typical usage:
219+
* const tui = new RedwoodTUI()
220+
* const content = new ReactiveTUIContent({ spinner: { enabled: true } })
221+
* tui.startReactive(content)
222+
* // ...work...
223+
* tui.stopReactive(true)
173224
*/
174225
export class RedwoodTUI {
175226
private manager: UpdateManager

0 commit comments

Comments
 (0)