11import { LoadingBar } from './LoadingBar.js'
2+ import { Stdout } from '../../ui.js'
23import { render } from '../../testing/ui.js'
34import { shouldDisplayColors , unstyled } from '../../../../public/node/output.js'
45import useLayout from '../hooks/use-layout.js'
@@ -16,7 +17,6 @@ vi.mock('../../../../public/node/output.js', async () => {
1617} )
1718
1819beforeEach ( ( ) => {
19- // Default terminal width
2020 vi . mocked ( useLayout ) . mockReturnValue ( {
2121 twoThirds : 53 ,
2222 oneThird : 27 ,
@@ -25,196 +25,151 @@ beforeEach(() => {
2525 vi . mocked ( shouldDisplayColors ) . mockReturnValue ( true )
2626} )
2727
28+ /**
29+ * Creates a Stdout test double simulating a TTY stream.
30+ * On real Node streams, isTTY is only present as an own property when the
31+ * stream IS a TTY.
32+ */
33+ function createTTYStdout ( columns = 100 ) {
34+ const stdout = new Stdout ( { columns} ) as Stdout & { isTTY : boolean }
35+ stdout . isTTY = true
36+ return stdout
37+ }
38+
39+ /**
40+ * Renders LoadingBar with a TTY stdout so the animated progress bar renders.
41+ */
42+ function renderWithTTY ( element : React . ReactElement ) {
43+ const stdout = createTTYStdout ( )
44+ const instance = render ( element , { stdout} )
45+ return { lastFrame : stdout . lastFrame , unmount : instance . unmount }
46+ }
47+
2848describe ( 'LoadingBar' , ( ) => {
2949 test ( 'renders loading bar with default colored characters' , async ( ) => {
30- // Given
31- const title = 'Loading content'
32-
33- // When
34- const { lastFrame} = render ( < LoadingBar title = { title } /> )
50+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Loading content" /> )
3551
36- // Then
3752 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
3853 "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
3954 Loading content ..."
4055 ` )
4156 } )
4257
4358 test ( 'renders loading bar with hill pattern when noColor prop is true' , async ( ) => {
44- // Given
45- const title = 'Processing files'
46-
47- // When
48- const { lastFrame} = render ( < LoadingBar title = { title } noColor /> )
59+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Processing files" noColor /> )
4960
50- // Then
5161 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
5262 "▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
5363 Processing files ..."
5464 ` )
5565 } )
5666
5767 test ( 'renders loading bar with hill pattern when shouldDisplayColors returns false' , async ( ) => {
58- // Given
5968 vi . mocked ( shouldDisplayColors ) . mockReturnValue ( false )
60- const title = ' Downloading packages'
69+ const { lastFrame } = renderWithTTY ( < LoadingBar title = " Downloading packages" /> )
6170
62- // When
63- const { lastFrame} = render ( < LoadingBar title = { title } /> )
64-
65- // Then
6671 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
6772 "▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
6873 Downloading packages ..."
6974 ` )
7075 } )
7176
7277 test ( 'handles narrow terminal width correctly' , async ( ) => {
73- // Given
74- vi . mocked ( useLayout ) . mockReturnValue ( {
75- twoThirds : 20 ,
76- oneThird : 10 ,
77- fullWidth : 30 ,
78- } )
79- const title = 'Building app'
80-
81- // When
82- const { lastFrame} = render ( < LoadingBar title = { title } /> )
83-
84- // Then
78+ vi . mocked ( useLayout ) . mockReturnValue ( { twoThirds : 20 , oneThird : 10 , fullWidth : 30 } )
79+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Building app" /> )
80+
8581 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
8682 "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
8783 Building app ..."
8884 ` )
8985 } )
9086
9187 test ( 'handles narrow terminal width correctly in no-color mode' , async ( ) => {
92- // Given
93- vi . mocked ( useLayout ) . mockReturnValue ( {
94- twoThirds : 15 ,
95- oneThird : 8 ,
96- fullWidth : 23 ,
97- } )
98- const title = 'Installing'
99-
100- // When
101- const { lastFrame} = render ( < LoadingBar title = { title } noColor /> )
102-
103- // Then
88+ vi . mocked ( useLayout ) . mockReturnValue ( { twoThirds : 15 , oneThird : 8 , fullWidth : 23 } )
89+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Installing" noColor /> )
90+
10491 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
10592 "▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇
10693 Installing ..."
10794 ` )
10895 } )
10996
11097 test ( 'handles very narrow terminal width in no-color mode' , async ( ) => {
111- // Given
112- vi . mocked ( useLayout ) . mockReturnValue ( {
113- twoThirds : 5 ,
114- oneThird : 3 ,
115- fullWidth : 8 ,
116- } )
117- const title = 'Wait'
118-
119- // When
120- const { lastFrame} = render ( < LoadingBar title = { title } noColor /> )
121-
122- // Then
98+ vi . mocked ( useLayout ) . mockReturnValue ( { twoThirds : 5 , oneThird : 3 , fullWidth : 8 } )
99+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Wait" noColor /> )
100+
123101 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
124102 "▁▁▁▂▂
125103 Wait ..."
126104 ` )
127105 } )
128106
129107 test ( 'handles wide terminal width correctly' , async ( ) => {
130- // Given
131- vi . mocked ( useLayout ) . mockReturnValue ( {
132- twoThirds : 100 ,
133- oneThird : 50 ,
134- fullWidth : 150 ,
135- } )
136- const title = 'Synchronizing data'
137-
138- // When
139- const { lastFrame} = render ( < LoadingBar title = { title } /> )
140-
141- // Then
108+ vi . mocked ( useLayout ) . mockReturnValue ( { twoThirds : 100 , oneThird : 50 , fullWidth : 150 } )
109+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Synchronizing data" /> )
110+
142111 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
143112 "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
144113 Synchronizing data ..."
145114 ` )
146115 } )
147116
148117 test ( 'handles wide terminal width correctly in no-color mode with pattern repetition' , async ( ) => {
149- // Given
150- vi . mocked ( useLayout ) . mockReturnValue ( {
151- twoThirds : 90 ,
152- oneThird : 45 ,
153- fullWidth : 135 ,
154- } )
155- const title = 'Analyzing dependencies'
156-
157- // When
158- const { lastFrame} = render ( < LoadingBar title = { title } noColor /> )
159-
160- // Then
118+ vi . mocked ( useLayout ) . mockReturnValue ( { twoThirds : 90 , oneThird : 45 , fullWidth : 135 } )
119+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Analyzing dependencies" noColor /> )
120+
161121 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
162122 "▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁
163123 Analyzing dependencies ..."
164124 ` )
165125 } )
166126
167127 test ( 'renders correctly with empty title' , async ( ) => {
168- // Given
169- const title = ''
170-
171- // When
172- const { lastFrame} = render ( < LoadingBar title = { title } /> )
128+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "" /> )
173129
174- // Then
175130 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
176131 "▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
177132 ..."
178133 ` )
179134 } )
180135
181136 test ( 'noColor prop overrides shouldDisplayColors when both would show colors' , async ( ) => {
182- // Given
183137 vi . mocked ( shouldDisplayColors ) . mockReturnValue ( true )
184- const title = ' Testing override'
138+ const { lastFrame } = renderWithTTY ( < LoadingBar title = " Testing override" noColor /> )
185139
186- // When
187- const { lastFrame} = render ( < LoadingBar title = { title } noColor /> )
188-
189- // Then
190140 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `
191141 "▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅▄▄▃▃▂▂▁▁▁▁▂▂▃▃▄▄▅▅▆▆▇▇██▇▇▆▆▅▅
192142 Testing override ..."
193143 ` )
194144 } )
195145
196146 test ( 'renders consistently with same props' , async ( ) => {
197- // Given
198- const title = 'Consistent test'
199- const props = { title, noColor : false }
200-
201- // When
202- const { lastFrame : frame1 } = render ( < LoadingBar { ...props } /> )
203- const { lastFrame : frame2 } = render ( < LoadingBar { ...props } /> )
147+ const props = { title : 'Consistent test' , noColor : false }
148+ const { lastFrame : frame1 } = renderWithTTY ( < LoadingBar { ...props } /> )
149+ const { lastFrame : frame2 } = renderWithTTY ( < LoadingBar { ...props } /> )
204150
205- // Then
206151 expect ( frame1 ( ) ) . toBe ( frame2 ( ) )
207152 } )
208153
209154 test ( 'hides progress bar when noProgressBar is true' , async ( ) => {
210- // Given
211155 vi . mocked ( shouldDisplayColors ) . mockReturnValue ( true )
212- const title = 'task 1'
213-
214- // When
215- const { lastFrame} = render ( < LoadingBar title = { title } noProgressBar /> )
156+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "task 1" noProgressBar /> )
216157
217- // Then
218158 expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `"task 1 ..."` )
219159 } )
160+
161+ test ( 'shows only static title text when output stream is not a TTY' , async ( ) => {
162+ // Default test Stdout has no isTTY property, simulating a non-TTY stream
163+ const { lastFrame} = render ( < LoadingBar title = "Installing dependencies" /> )
164+
165+ expect ( unstyled ( lastFrame ( ) ! ) ) . toMatchInlineSnapshot ( `"Installing dependencies ..."` )
166+ } )
167+
168+ test ( 'shows animated progress bar when output stream is a TTY' , async ( ) => {
169+ const { lastFrame} = renderWithTTY ( < LoadingBar title = "Uploading theme" /> )
170+
171+ const frame = unstyled ( lastFrame ( ) ! )
172+ expect ( frame ) . toContain ( '▀' )
173+ expect ( frame ) . toContain ( 'Uploading theme ...' )
174+ } )
220175} )
0 commit comments