Skip to content

Commit 8363edc

Browse files
Harsh1925Tobbe
andauthored
fix(tui): validate spinner.characters (≥2) and frameInterval (>0); add first tests (#12088)
Co-authored-by: Tobbe Lundberg <tobbe@tlundberg.com>
1 parent 349ab4a commit 8363edc

File tree

2 files changed

+80
-7
lines changed

2 files changed

+80
-7
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 & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
//
2-
31
import stream from 'stream'
42

53
import boxen from 'boxen'
@@ -66,9 +64,33 @@ export class ReactiveTUIContent {
6664
RedwoodStyling.redwood(c),
6765
),
6866
}
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+
6979
this.spinner = { ...defaultSpinner, ...options.spinner }
7080
this.boxen = { ...options.boxen }
71-
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+
}
7294

7395
if (options.outStream) {
7496
this.setOutStream(options.outStream)
@@ -97,7 +119,16 @@ export class ReactiveTUIContent {
97119
this.content = options.content
98120
}
99121
if (options.spinner) {
100-
// 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+
}
101132
this.spinner = { ...this.spinner, ...options.spinner }
102133
}
103134
if (options.boxen) {
@@ -106,8 +137,14 @@ export class ReactiveTUIContent {
106137
if (options.outStream) {
107138
this.setOutStream(options.outStream)
108139
}
109-
if (options.frameInterval) {
110-
// 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+
}
111148
this.frameInterval = options.frameInterval
112149
}
113150
}
@@ -171,7 +208,19 @@ export interface RedwoodTUIConfig {
171208
}
172209

173210
/**
174-
* 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)
175224
*/
176225
export class RedwoodTUI {
177226
private manager: UpdateManager

0 commit comments

Comments
 (0)