Skip to content

Commit d8b1f2e

Browse files
committed
change renderSingleTask to use TokenizedString and implemented in kitchen-sink
1 parent 900ff09 commit d8b1f2e

File tree

5 files changed

+70
-34
lines changed

5 files changed

+70
-34
lines changed

packages/cli-kit/src/private/node/ui/components/SingleTask.test.tsx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import {SingleTask} from './SingleTask.js'
22
import {render} from '../../testing/ui.js'
3+
import {TokenizedString} from '../../../../public/node/output.js'
34
import React from 'react'
45
import {describe, expect, test} from 'vitest'
56

67
describe('SingleTask', () => {
78
test('unmounts when promise resolves successfully', async () => {
89
// Given
9-
const title = 'Uploading files'
10+
const title = new TokenizedString('Uploading files')
1011
let resolvePromise: (value: string) => void
1112
const task = () =>
1213
new Promise<string>((resolve) => {
@@ -31,7 +32,7 @@ describe('SingleTask', () => {
3132

3233
test('unmounts when promise rejects', async () => {
3334
// Given
34-
const title = 'Failed task'
35+
const title = new TokenizedString('Failed task')
3536
let rejectPromise: (error: Error) => void
3637
const task = () =>
3738
new Promise<string>((resolve, reject) => {
@@ -53,7 +54,7 @@ describe('SingleTask', () => {
5354

5455
test('handles promise that resolves immediately', async () => {
5556
// Given
56-
const title = 'Instant task'
57+
const title = new TokenizedString('Instant task')
5758
const task = () => Promise.resolve('immediate success')
5859

5960
// When
@@ -66,7 +67,7 @@ describe('SingleTask', () => {
6667

6768
test('handles promise that rejects immediately', async () => {
6869
// Given
69-
const title = 'Instant failure'
70+
const title = new TokenizedString('Instant failure')
7071
const task = () => Promise.reject(new Error('Immediate error'))
7172

7273
// When
@@ -81,7 +82,11 @@ describe('SingleTask', () => {
8182
let stringResult: string | undefined
8283
const stringTask = () => Promise.resolve('task completed')
8384
const stringRender = render(
84-
<SingleTask title="String task" task={stringTask} onComplete={(result) => (stringResult = result)} />,
85+
<SingleTask
86+
title={new TokenizedString('String task')}
87+
task={stringTask}
88+
onComplete={(result) => (stringResult = result)}
89+
/>,
8590
)
8691
await stringRender.waitUntilExit()
8792
expect(stringRender.lastFrame()).toBeDefined()
@@ -91,7 +96,11 @@ describe('SingleTask', () => {
9196
let objectResult: {id: number; name: string} | undefined
9297
const objectTask = () => Promise.resolve({id: 1, name: 'test'})
9398
const objectRender = render(
94-
<SingleTask title="Object task" task={objectTask} onComplete={(result) => (objectResult = result)} />,
99+
<SingleTask
100+
title={new TokenizedString('Object task')}
101+
task={objectTask}
102+
onComplete={(result) => (objectResult = result)}
103+
/>,
95104
)
96105
await objectRender.waitUntilExit()
97106
expect(objectRender.lastFrame()).toBeDefined()
@@ -100,7 +109,11 @@ describe('SingleTask', () => {
100109
let numberResult: number | undefined
101110
const numberTask = () => Promise.resolve(42)
102111
const numberRender = render(
103-
<SingleTask title="Number task" task={numberTask} onComplete={(result) => (numberResult = result)} />,
112+
<SingleTask
113+
title={new TokenizedString('Number task')}
114+
task={numberTask}
115+
onComplete={(result) => (numberResult = result)}
116+
/>,
104117
)
105118
await numberRender.waitUntilExit()
106119
expect(numberRender.lastFrame()).toBeDefined()
@@ -110,7 +123,11 @@ describe('SingleTask', () => {
110123
let booleanResult: boolean | undefined
111124
const booleanTask = () => Promise.resolve(true)
112125
const booleanRender = render(
113-
<SingleTask title="Boolean task" task={booleanTask} onComplete={(result) => (booleanResult = result)} />,
126+
<SingleTask
127+
title={new TokenizedString('Boolean task')}
128+
task={booleanTask}
129+
onComplete={(result) => (booleanResult = result)}
130+
/>,
114131
)
115132
await booleanRender.waitUntilExit()
116133
expect(booleanRender.lastFrame()).toBeDefined()
@@ -119,7 +136,7 @@ describe('SingleTask', () => {
119136

120137
test('handles promise with delayed resolution', async () => {
121138
// Given
122-
const title = 'Delayed task'
139+
const title = new TokenizedString('Delayed task')
123140
const task = () =>
124141
new Promise<string>((resolve) => {
125142
setTimeout(() => resolve('completed'), 100)
@@ -137,7 +154,7 @@ describe('SingleTask', () => {
137154

138155
test('handles promise with delayed rejection', async () => {
139156
// Given
140-
const title = 'Delayed failure'
157+
const title = new TokenizedString('Delayed failure')
141158
const task = () =>
142159
new Promise<string>((resolve, reject) => {
143160
setTimeout(() => reject(new Error('delayed error')), 100)
@@ -163,7 +180,7 @@ describe('SingleTask', () => {
163180
const task = () => Promise.reject(customError)
164181

165182
// When
166-
const renderInstance = render(<SingleTask title="Custom error task" task={task} />)
183+
const renderInstance = render(<SingleTask title={new TokenizedString('Custom error task')} task={task} />)
167184

168185
// Then - should preserve the exact error
169186
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Custom error message')
@@ -175,8 +192,8 @@ describe('SingleTask', () => {
175192
const slowPromise = () => new Promise((resolve) => setTimeout(() => resolve('slow'), 150))
176193

177194
// When
178-
const fastRender = render(<SingleTask title="Fast task" task={fastPromise} />)
179-
const slowRender = render(<SingleTask title="Slow task" task={slowPromise} />)
195+
const fastRender = render(<SingleTask title={new TokenizedString('Fast task')} task={fastPromise} />)
196+
const slowRender = render(<SingleTask title={new TokenizedString('Slow task')} task={slowPromise} />)
180197

181198
// Then - Both should complete successfully
182199
await fastRender.waitUntilExit()
@@ -188,7 +205,7 @@ describe('SingleTask', () => {
188205

189206
test('passes noColor prop to LoadingBar component', async () => {
190207
// Given
191-
const title = 'No color task'
208+
const title = new TokenizedString('No color task')
192209
const task = () => Promise.resolve()
193210

194211
// When - Test that noColor prop doesn't break the component
@@ -201,7 +218,7 @@ describe('SingleTask', () => {
201218

202219
test('updates status message during task execution', async () => {
203220
// Given
204-
const initialTitle = 'Starting task'
221+
const initialTitle = new TokenizedString('Starting task')
205222
let step1Resolve: () => void
206223
let step2Resolve: () => void
207224
let step3Resolve: () => void
@@ -216,14 +233,14 @@ describe('SingleTask', () => {
216233
step3Resolve = resolve
217234
})
218235

219-
const task = async (updateStatus: (status: string) => void) => {
220-
updateStatus('Running (1 complete)...')
236+
const task = async (updateStatus: (status: TokenizedString) => void) => {
237+
updateStatus(new TokenizedString('Running (1 complete)...'))
221238
await step1Promise
222239

223-
updateStatus('Running (2 complete)...')
240+
updateStatus(new TokenizedString('Running (2 complete)...'))
224241
await step2Promise
225242

226-
updateStatus('Running (3 complete)...')
243+
updateStatus(new TokenizedString('Running (3 complete)...'))
227244
await step3Promise
228245

229246
return 'completed'

packages/cli-kit/src/private/node/ui/components/SingleTask.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {LoadingBar} from './LoadingBar.js'
22
import {useExitOnCtrlC} from '../hooks/use-exit-on-ctrl-c.js'
3+
import {TokenizedString} from '../../../../public/node/output.js'
34
import React, {useEffect, useState} from 'react'
45
import {useApp} from 'ink'
56

67
interface SingleTaskProps<T> {
7-
title: string
8-
task: (updateStatus: (status: string) => void) => Promise<T>
8+
title: TokenizedString
9+
task: (updateStatus: (status: TokenizedString) => void) => Promise<T>
910
onComplete?: (result: T) => void
1011
noColor?: boolean
1112
}
@@ -34,7 +35,7 @@ const SingleTask = <T,>({task, title, onComplete, noColor}: SingleTaskProps<T>)
3435
return null
3536
}
3637

37-
return <LoadingBar title={status} noColor={noColor} />
38+
return <LoadingBar title={status.value} noColor={noColor} />
3839
}
3940

4041
export {SingleTask}

packages/cli-kit/src/public/node/ui.test.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import {AbortSignal} from './abort.js'
1212
import {BugError, FatalError, AbortError, FatalErrorType} from './error.js'
1313
import {mockAndCaptureOutput} from './testing/output.js'
14+
import {TokenizedString} from './output.js'
1415
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
1516
import supportsHyperlinks from 'supports-hyperlinks'
1617
import {Writable} from 'stream'
@@ -426,7 +427,7 @@ describe('renderSingleTask', async () => {
426427
// Given
427428
const expectedResult = {id: 123, name: 'test-result'}
428429
const task = () => Promise.resolve(expectedResult)
429-
const title = 'Processing data'
430+
const title = new TokenizedString('Processing data')
430431

431432
// When
432433
const result = await renderSingleTask({title, task})
@@ -439,7 +440,7 @@ describe('renderSingleTask', async () => {
439440
// Given
440441
const expectedResult = {id: 123, name: 'test-result'}
441442
const task = () => Promise.resolve(expectedResult)
442-
const title = 'Processing data'
443+
const title = new TokenizedString('Processing data')
443444

444445
// When
445446
const result = await renderSingleTask({title, task})
@@ -451,7 +452,7 @@ describe('renderSingleTask', async () => {
451452
test('returns undefined when task resolves with undefined', async () => {
452453
// Given
453454
const task = () => Promise.resolve(undefined)
454-
const title = 'Void task'
455+
const title = new TokenizedString('Void task')
455456

456457
// When
457458
const result = await renderSingleTask({title, task})
@@ -464,7 +465,7 @@ describe('renderSingleTask', async () => {
464465
// Given
465466
const expectedError = new Error('Task failed with error')
466467
const task = () => Promise.reject(expectedError)
467-
const title = 'Failing task'
468+
const title = new TokenizedString('Failing task')
468469

469470
// When & Then
470471
await expect(renderSingleTask({title, task})).rejects.toThrow('Task failed with error')
@@ -477,7 +478,7 @@ describe('renderSingleTask', async () => {
477478
new Promise((resolve, reject) => {
478479
setTimeout(() => reject(expectedError), 100)
479480
})
480-
const title = 'Slow failing task'
481+
const title = new TokenizedString('Slow failing task')
481482

482483
// When & Then
483484
await expect(renderSingleTask({title, task})).rejects.toThrow('Delayed failure')
@@ -491,9 +492,9 @@ describe('renderSingleTask', async () => {
491492

492493
// When
493494
const [result1, result2, result3] = await Promise.all([
494-
renderSingleTask({title: 'Task 1', task: task1}),
495-
renderSingleTask({title: 'Task 2', task: task2}),
496-
renderSingleTask({title: 'Task 3', task: task3}),
495+
renderSingleTask({title: new TokenizedString('Task 1'), task: task1}),
496+
renderSingleTask({title: new TokenizedString('Task 2'), task: task2}),
497+
renderSingleTask({title: new TokenizedString('Task 3'), task: task3}),
497498
])
498499

499500
// Then

packages/cli-kit/src/public/node/ui.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-non-null-assertion */
22
/* eslint-disable tsdoc/syntax */
33
import {AbortError, AbortSilentError, FatalError as Fatal} from './error.js'
4-
import {outputContent, outputDebug, outputToken} from './output.js'
4+
import {outputContent, outputDebug, outputToken, TokenizedString} from './output.js'
55
import {terminalSupportsPrompting} from './system.js'
66
import {AbortController} from './abort.js'
77
import {runWithTimer} from './metadata.js'
@@ -488,8 +488,8 @@ export async function renderTasks<TContext>(
488488
}
489489

490490
export interface RenderSingleTaskOptions<T> {
491-
title: string
492-
task: (updateStatus: (status: string) => void) => Promise<T>
491+
title: TokenizedString
492+
task: (updateStatus: (status: TokenizedString) => void) => Promise<T>
493493
renderOptions?: RenderOptions
494494
}
495495

packages/cli/src/cli/services/kitchen-sink/async.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import {renderConcurrent, renderTasks} from '@shopify/cli-kit/node/ui'
1+
import {renderConcurrent, renderSingleTask, renderTasks} from '@shopify/cli-kit/node/ui'
22
import {AbortSignal} from '@shopify/cli-kit/node/abort'
3+
import {outputContent, outputToken, TokenizedString} from '@shopify/cli-kit/node/output'
34
import {Writable} from 'stream'
45

56
export async function asyncTasks() {
@@ -58,4 +59,20 @@ export async function asyncTasks() {
5859
]
5960

6061
await renderTasks(tasks)
62+
63+
// renderSingleTask
64+
await renderSingleTask({
65+
title: new TokenizedString('Importing data'),
66+
task: async (updateStatus: (status: TokenizedString) => void) => {
67+
for (let i = 1; i <= 10; i++) {
68+
// eslint-disable-next-line no-await-in-loop
69+
await new Promise((resolve) => setTimeout(resolve, 500))
70+
const status = outputContent`Importing data ${outputToken.italic(
71+
outputContent`(${outputToken.green(i.toString())} complete)`,
72+
)}`
73+
updateStatus(status)
74+
}
75+
return 'completed'
76+
},
77+
})
6178
}

0 commit comments

Comments
 (0)