Skip to content

Commit 34a6555

Browse files
committed
Remove unused export
1 parent 1dadff0 commit 34a6555

File tree

3 files changed

+520
-1
lines changed

3 files changed

+520
-1
lines changed

packages/app/src/cli/services/dev/processes/dev-session-status-manager.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import {EventEmitter} from 'events'
44
export interface DevSessionStatus {
55
isReady: boolean
66
previewURL?: string
7+
graphiqlURL?: string
78
}
89

9-
class DevSessionStatusManager extends EventEmitter {
10+
export class DevSessionStatusManager extends EventEmitter {
1011
private currentStatus: DevSessionStatus = {
1112
isReady: false,
1213
previewURL: undefined,
14+
graphiqlURL: undefined,
1315
}
1416

1517
updateStatus(status: Partial<DevSessionStatus>) {
@@ -29,6 +31,7 @@ class DevSessionStatusManager extends EventEmitter {
2931
this.currentStatus = {
3032
isReady: false,
3133
previewURL: undefined,
34+
graphiqlURL: undefined,
3235
}
3336
}
3437
}
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
import {DevSessionUI} from './DevSessionUI.js'
2+
import {DevSessionStatus, DevSessionStatusManager} from '../../processes/dev-session-status-manager.js'
3+
import {
4+
getLastFrameAfterUnmount,
5+
render,
6+
sendInputAndWait,
7+
waitForContent,
8+
waitForInputsToBeReady,
9+
} from '@shopify/cli-kit/node/testing/ui'
10+
import {AbortController} from '@shopify/cli-kit/node/abort'
11+
import React from 'react'
12+
import {beforeEach, describe, expect, test, vi} from 'vitest'
13+
import {unstyled} from '@shopify/cli-kit/node/output'
14+
import {openURL} from '@shopify/cli-kit/node/system'
15+
import {Writable} from 'stream'
16+
17+
vi.mock('@shopify/cli-kit/node/system')
18+
vi.mock('@shopify/cli-kit/node/context/local')
19+
vi.mock('@shopify/cli-kit/node/tree-kill')
20+
21+
let devSessionStatusManager: DevSessionStatusManager
22+
23+
const initialStatus: DevSessionStatus = {
24+
isReady: true,
25+
previewURL: 'https://shopify.com',
26+
graphiqlURL: 'https://graphiql.shopify.com',
27+
}
28+
29+
describe('DevSessionUI', () => {
30+
beforeEach(() => {
31+
devSessionStatusManager = new DevSessionStatusManager()
32+
devSessionStatusManager.reset()
33+
devSessionStatusManager.updateStatus(initialStatus)
34+
})
35+
36+
test('renders a stream of concurrent outputs from sub-processes, shortcuts and URLs', async () => {
37+
// Given
38+
let backendPromiseResolve: () => void
39+
let frontendPromiseResolve: () => void
40+
41+
const backendPromise = new Promise<void>(function (resolve, _reject) {
42+
backendPromiseResolve = resolve
43+
})
44+
45+
const frontendPromise = new Promise<void>(function (resolve, _reject) {
46+
frontendPromiseResolve = resolve
47+
})
48+
49+
const backendProcess = {
50+
prefix: 'backend',
51+
action: async (stdout: Writable, _stderr: Writable) => {
52+
stdout.write('first backend message')
53+
stdout.write('second backend message')
54+
stdout.write('third backend message')
55+
56+
backendPromiseResolve()
57+
},
58+
}
59+
60+
const frontendProcess = {
61+
prefix: 'frontend',
62+
action: async (stdout: Writable, _stderr: Writable) => {
63+
await backendPromise
64+
65+
stdout.write('first frontend message')
66+
stdout.write('second frontend message')
67+
stdout.write('third frontend message')
68+
69+
frontendPromiseResolve()
70+
},
71+
}
72+
73+
// When
74+
const renderInstance = render(
75+
<DevSessionUI
76+
processes={[backendProcess, frontendProcess]}
77+
abortController={new AbortController()}
78+
devSessionStatusManager={devSessionStatusManager}
79+
/>,
80+
)
81+
82+
await frontendPromise
83+
84+
// Then
85+
expect(unstyled(renderInstance.lastFrame()!).replace(/\d/g, '0')).toMatchInlineSnapshot(`
86+
"00:00:00 │ backend │ first backend message
87+
00:00:00 │ backend │ second backend message
88+
00:00:00 │ backend │ third backend message
89+
00:00:00 │ frontend │ first frontend message
90+
00:00:00 │ frontend │ second frontend message
91+
00:00:00 │ frontend │ third frontend message
92+
93+
────────────────────────────────────────────────────────────────────────────────────────────────────
94+
95+
› Press g │ open GraphiQL (Admin API) in your browser
96+
› Press p │ preview in your browser
97+
› Press q │ quit
98+
99+
Preview URL: https://shopify.com
100+
GraphiQL URL: https://graphiql.shopify.com
101+
"
102+
`)
103+
104+
renderInstance.unmount()
105+
})
106+
107+
test('opens the previewURL when p is pressed', async () => {
108+
// When
109+
const renderInstance = render(
110+
<DevSessionUI
111+
processes={[]}
112+
abortController={new AbortController()}
113+
devSessionStatusManager={devSessionStatusManager}
114+
/>,
115+
)
116+
117+
await waitForInputsToBeReady()
118+
await sendInputAndWait(renderInstance, 100, 'p')
119+
120+
// Then
121+
expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://shopify.com')
122+
123+
renderInstance.unmount()
124+
})
125+
126+
test('opens the graphiqlURL when g is pressed', async () => {
127+
// When
128+
const renderInstance = render(
129+
<DevSessionUI
130+
processes={[]}
131+
abortController={new AbortController()}
132+
devSessionStatusManager={devSessionStatusManager}
133+
/>,
134+
)
135+
136+
await waitForInputsToBeReady()
137+
await sendInputAndWait(renderInstance, 100, 'g')
138+
139+
// Then
140+
expect(vi.mocked(openURL)).toHaveBeenNthCalledWith(1, 'https://graphiql.shopify.com')
141+
142+
renderInstance.unmount()
143+
})
144+
145+
test('quits when q is pressed', async () => {
146+
// Given
147+
const abortController = new AbortController()
148+
const abort = vi.spyOn(abortController, 'abort')
149+
150+
// When
151+
const renderInstance = render(
152+
<DevSessionUI
153+
processes={[]}
154+
abortController={abortController}
155+
devSessionStatusManager={devSessionStatusManager}
156+
/>,
157+
)
158+
159+
const promise = renderInstance.waitUntilExit()
160+
161+
await waitForInputsToBeReady()
162+
renderInstance.stdin.write('q')
163+
164+
await promise
165+
166+
// Then
167+
expect(abort).toHaveBeenCalledOnce()
168+
169+
renderInstance.unmount()
170+
})
171+
172+
test('shows shutting down message when aborted', async () => {
173+
// Given
174+
const abortController = new AbortController()
175+
176+
// When
177+
const renderInstance = render(
178+
<DevSessionUI
179+
processes={[]}
180+
abortController={abortController}
181+
devSessionStatusManager={devSessionStatusManager}
182+
/>,
183+
)
184+
185+
const promise = renderInstance.waitUntilExit()
186+
187+
abortController.abort()
188+
189+
expect(unstyled(renderInstance.lastFrame()!).replace(/\d/g, '0')).toContain('Shutting down dev ...')
190+
191+
await promise
192+
193+
expect(unstyled(getLastFrameAfterUnmount(renderInstance)!).replace(/\d/g, '0')).toMatchInlineSnapshot(`
194+
""
195+
`)
196+
197+
// unmount so that polling is cleared after every test
198+
renderInstance.unmount()
199+
})
200+
201+
test('shows error shutting down message when aborted with error', async () => {
202+
// Given
203+
const abortController = new AbortController()
204+
205+
const backendProcess: any = {
206+
prefix: 'backend',
207+
action: async (stdout: Writable, _stderr: Writable, _signal: AbortSignal) => {
208+
stdout.write('first backend message')
209+
stdout.write('second backend message')
210+
stdout.write('third backend message')
211+
212+
// await promise that never resolves
213+
await new Promise(() => {})
214+
},
215+
}
216+
217+
// When
218+
const renderInstance = render(
219+
<DevSessionUI
220+
processes={[backendProcess]}
221+
abortController={abortController}
222+
devSessionStatusManager={devSessionStatusManager}
223+
/>,
224+
)
225+
226+
const promise = renderInstance.waitUntilExit()
227+
228+
abortController.abort('something went wrong')
229+
230+
expect(unstyled(renderInstance.lastFrame()!).replace(/\d/g, '0')).toMatchInlineSnapshot(`
231+
"00:00:00 │ backend │ first backend message
232+
00:00:00 │ backend │ second backend message
233+
00:00:00 │ backend │ third backend message
234+
235+
────────────────────────────────────────────────────────────────────────────────────────────────────
236+
237+
› Press g │ open GraphiQL (Admin API) in your browser
238+
› Press p │ preview in your browser
239+
› Press q │ quit
240+
241+
Shutting down dev because of an error ...
242+
"
243+
`)
244+
245+
await promise
246+
247+
expect(unstyled(getLastFrameAfterUnmount(renderInstance)!).replace(/\d/g, '0')).toMatchInlineSnapshot(`
248+
"00:00:00 │ backend │ first backend message
249+
00:00:00 │ backend │ second backend message
250+
00:00:00 │ backend │ third backend message
251+
"
252+
`)
253+
254+
// unmount so that polling is cleared after every test
255+
renderInstance.unmount()
256+
})
257+
258+
test('updates UI when status changes through devSessionStatusManager', async () => {
259+
// Given
260+
devSessionStatusManager.reset()
261+
262+
// When
263+
const renderInstance = render(
264+
<DevSessionUI
265+
processes={[]}
266+
abortController={new AbortController()}
267+
devSessionStatusManager={devSessionStatusManager}
268+
/>,
269+
)
270+
271+
await waitForInputsToBeReady()
272+
273+
// Initial state
274+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('preview in your browser')
275+
276+
// When status updates
277+
devSessionStatusManager.updateStatus({
278+
isReady: true,
279+
previewURL: 'https://new-preview-url.shopify.com',
280+
graphiqlURL: 'https://new-graphiql.shopify.com',
281+
})
282+
283+
await waitForContent(renderInstance, 'preview in your browser')
284+
285+
// Then
286+
expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://new-preview-url.shopify.com')
287+
expect(unstyled(renderInstance.lastFrame()!)).toContain('GraphiQL URL: https://new-graphiql.shopify.com')
288+
renderInstance.unmount()
289+
})
290+
291+
test('updates UI when devSessionEnabled changes from false to true', async () => {
292+
// Given
293+
devSessionStatusManager.updateStatus({isReady: false})
294+
295+
const renderInstance = render(
296+
<DevSessionUI
297+
processes={[]}
298+
abortController={new AbortController()}
299+
devSessionStatusManager={devSessionStatusManager}
300+
/>,
301+
)
302+
303+
await waitForInputsToBeReady()
304+
305+
// Then
306+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Press p')
307+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Press g')
308+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('Preview URL')
309+
expect(unstyled(renderInstance.lastFrame()!)).not.toContain('GraphiQL URL')
310+
311+
// When
312+
devSessionStatusManager.updateStatus({isReady: true})
313+
314+
await waitForInputsToBeReady()
315+
316+
// Then
317+
expect(unstyled(renderInstance.lastFrame()!)).toContain('Press p')
318+
expect(unstyled(renderInstance.lastFrame()!)).toContain('Press g')
319+
expect(unstyled(renderInstance.lastFrame()!)).toContain('Preview URL: https://shopify.com')
320+
expect(unstyled(renderInstance.lastFrame()!)).toContain('GraphiQL URL: https://graphiql.shopify.com')
321+
renderInstance.unmount()
322+
})
323+
324+
test('handles process errors by aborting', async () => {
325+
// Given
326+
const abortController = new AbortController()
327+
const abort = vi.spyOn(abortController, 'abort')
328+
const errorProcess = {
329+
prefix: 'error',
330+
action: async () => {
331+
throw new Error('Test error')
332+
},
333+
}
334+
335+
// When
336+
const renderInstance = render(
337+
<DevSessionUI
338+
processes={[errorProcess]}
339+
abortController={abortController}
340+
devSessionStatusManager={devSessionStatusManager}
341+
/>,
342+
)
343+
344+
await expect(renderInstance.waitUntilExit()).rejects.toThrow('Test error')
345+
346+
// Then
347+
expect(abort).toHaveBeenCalledWith(new Error('Test error'))
348+
349+
renderInstance.unmount()
350+
})
351+
})

0 commit comments

Comments
 (0)