33 MockDynamicParametersResponse ,
44 MockDynamicParametersResponseWithError ,
55 MockPermissions ,
6+ MockPreviewParameter ,
67 MockSliderParameter ,
78 MockTemplate ,
89 MockTemplateVersionExternalAuthGithub ,
@@ -15,8 +16,11 @@ import {
1516 renderWithAuth ,
1617 waitForLoaderToBeRemoved ,
1718} from "testHelpers/renderHelpers" ;
18- import { createMockWebSocket } from "testHelpers/websockets" ;
19- import { screen , waitFor } from "@testing-library/react" ;
19+ import {
20+ createMockWebSocket ,
21+ mockDynamicParameterWebSocket ,
22+ } from "testHelpers/websockets" ;
23+ import { screen , waitFor , within } from "@testing-library/react" ;
2024import userEvent from "@testing-library/user-event" ;
2125import { API } from "api/api" ;
2226import type { DynamicParametersResponse } from "api/typesGenerated" ;
@@ -47,33 +51,7 @@ describe("CreateWorkspacePage", () => {
4751 jest . spyOn ( API , "getTemplateVersionPresets" ) . mockResolvedValue ( [ ] ) ;
4852 jest . spyOn ( API , "createWorkspace" ) . mockResolvedValue ( MockWorkspace ) ;
4953 jest . spyOn ( API , "checkAuthorization" ) . mockResolvedValue ( MockPermissions ) ;
50-
51- jest
52- . spyOn ( API , "templateVersionDynamicParameters" )
53- . mockImplementation ( ( _versionId , _ownerId , callbacks ) => {
54- const [ mockWebSocket , publisher ] = createMockWebSocket ( "ws://test" ) ;
55-
56- mockWebSocket . addEventListener ( "message" , ( event ) => {
57- callbacks . onMessage ( JSON . parse ( event . data ) ) ;
58- } ) ;
59- mockWebSocket . addEventListener ( "error" , ( ) => {
60- callbacks . onError (
61- new Error ( "Connection for dynamic parameters failed." ) ,
62- ) ;
63- } ) ;
64- mockWebSocket . addEventListener ( "close" , ( ) => {
65- callbacks . onClose ( ) ;
66- } ) ;
67-
68- publisher . publishOpen ( new Event ( "open" ) ) ;
69- publisher . publishMessage (
70- new MessageEvent ( "message" , {
71- data : JSON . stringify ( MockDynamicParametersResponse ) ,
72- } ) ,
73- ) ;
74-
75- return mockWebSocket ;
76- } ) ;
54+ mockDynamicParameterWebSocket ( MockDynamicParametersResponse ) ;
7755 } ) ;
7856
7957 afterEach ( ( ) => {
@@ -105,32 +83,9 @@ describe("CreateWorkspacePage", () => {
10583 } ) ;
10684
10785 it ( "sends parameter updates via WebSocket when form values change" , async ( ) => {
108- const [ mockWebSocket , publisher ] = createMockWebSocket ( "ws://test" ) ;
109-
110- jest
111- . spyOn ( API , "templateVersionDynamicParameters" )
112- . mockImplementation ( ( _versionId , _ownerId , callbacks ) => {
113- mockWebSocket . addEventListener ( "message" , ( event ) => {
114- callbacks . onMessage ( JSON . parse ( event . data ) ) ;
115- } ) ;
116- mockWebSocket . addEventListener ( "error" , ( ) => {
117- callbacks . onError (
118- new Error ( "Connection for dynamic parameters failed." ) ,
119- ) ;
120- } ) ;
121- mockWebSocket . addEventListener ( "close" , ( ) => {
122- callbacks . onClose ( ) ;
123- } ) ;
124-
125- publisher . publishOpen ( new Event ( "open" ) ) ;
126- publisher . publishMessage (
127- new MessageEvent ( "message" , {
128- data : JSON . stringify ( MockDynamicParametersResponse ) ,
129- } ) ,
130- ) ;
131-
132- return mockWebSocket ;
133- } ) ;
86+ const [ mockWebSocket ] = mockDynamicParameterWebSocket (
87+ MockDynamicParametersResponse ,
88+ ) ;
13489
13590 renderCreateWorkspacePage ( ) ;
13691 await waitForLoaderToBeRemoved ( ) ;
@@ -216,27 +171,9 @@ describe("CreateWorkspacePage", () => {
216171 } ) ;
217172
218173 it ( "only parameters from latest response are displayed" , async ( ) => {
219- const [ mockWebSocket , mockPublisher ] = createMockWebSocket ( "ws://test" ) ;
220- jest
221- . spyOn ( API , "templateVersionDynamicParameters" )
222- . mockImplementation ( ( _versionId , _ownerId , callbacks ) => {
223- mockWebSocket . addEventListener ( "message" , ( event ) => {
224- callbacks . onMessage ( JSON . parse ( event . data ) ) ;
225- } ) ;
226-
227- mockPublisher . publishOpen ( new Event ( "open" ) ) ;
228- mockPublisher . publishMessage (
229- new MessageEvent ( "message" , {
230- data : JSON . stringify ( {
231- id : 0 ,
232- parameters : [ MockDropdownParameter ] ,
233- diagnostics : [ ] ,
234- } ) ,
235- } ) ,
236- ) ;
237-
238- return mockWebSocket ;
239- } ) ;
174+ const [ , mockPublisher ] = mockDynamicParameterWebSocket ( [
175+ MockDropdownParameter ,
176+ ] ) ;
240177
241178 renderCreateWorkspacePage ( ) ;
242179 await waitForLoaderToBeRemoved ( ) ;
@@ -265,27 +202,89 @@ describe("CreateWorkspacePage", () => {
265202 expect ( screen . queryByText ( "CPU Count" ) ) . toBeInTheDocument ( ) ;
266203 expect ( screen . queryByText ( "Instance Type" ) ) . not . toBeInTheDocument ( ) ;
267204 } ) ;
268- } ) ;
269205
270- describe ( "Dynamic Parameter Types" , ( ) => {
271- it ( "displays parameter validation errors" , async ( ) => {
272- jest
273- . spyOn ( API , "templateVersionDynamicParameters" )
274- . mockImplementation ( ( _versionId , _ownerId , callbacks ) => {
275- const [ mockWebSocket , publisher ] = createMockWebSocket ( "ws://test" ) ;
206+ it ( "does not clobber user values" , async ( ) => {
207+ const [ , mockPublisher ] = mockDynamicParameterWebSocket ( [
208+ MockPreviewParameter ,
209+ ] ) ;
276210
277- mockWebSocket . addEventListener ( "message" , ( event ) => {
278- callbacks . onMessage ( JSON . parse ( event . data ) ) ;
279- } ) ;
211+ renderCreateWorkspacePage ( ) ;
212+ await waitForLoaderToBeRemoved ( ) ;
280213
281- publisher . publishMessage (
282- new MessageEvent ( "message" , {
283- data : JSON . stringify ( MockDynamicParametersResponseWithError ) ,
214+ const form = screen . getByTestId ( "form" ) ;
215+ const input = await within ( form ) . findByRole ( "textbox" , {
216+ name : / p a r a m e t e r 1 / i,
217+ } ) ;
218+ await userEvent . clear ( input ) ;
219+ await userEvent . type ( input , "hi there hello" ) ;
220+
221+ await waitFor ( ( ) => {
222+ expect (
223+ within ( form ) . getByDisplayValue ( "hi there hello" ) ,
224+ ) . toBeInTheDocument ( ) ;
225+ } ) ;
226+
227+ // Simulate a stale response.
228+ await act ( async ( ) => {
229+ mockPublisher . publishMessage (
230+ new MessageEvent ( "message" , {
231+ data : JSON . stringify ( {
232+ id : 1 ,
233+ parameters : [ MockPreviewParameter , MockValidationParameter ] ,
284234 } ) ,
285- ) ;
235+ } ) ,
236+ ) ;
237+ } ) ;
286238
287- return mockWebSocket ;
288- } ) ;
239+ // Should have the new field, but keep the existing user-filled values.
240+ await waitFor ( ( ) => {
241+ expect ( within ( form ) . getByDisplayValue ( "50" ) ) . toBeInTheDocument ( ) ;
242+ expect (
243+ within ( form ) . getByDisplayValue ( "hi there hello" ) ,
244+ ) . toBeInTheDocument ( ) ;
245+ } ) ;
246+ } ) ;
247+
248+ it ( "does not clobber auto-filled values" , async ( ) => {
249+ const [ , mockPublisher ] = mockDynamicParameterWebSocket ( [
250+ MockPreviewParameter ,
251+ MockSliderParameter ,
252+ ] ) ;
253+
254+ renderCreateWorkspacePage (
255+ `/templates/${ MockTemplate . name } /workspace?param.cpu_count=44¶m.parameter1=auto` ,
256+ ) ;
257+ await waitForLoaderToBeRemoved ( ) ;
258+
259+ // Simulate a stale response.
260+ await act ( async ( ) => {
261+ mockPublisher . publishMessage (
262+ new MessageEvent ( "message" , {
263+ data : JSON . stringify ( {
264+ id : 2 ,
265+ parameters : [
266+ MockPreviewParameter ,
267+ MockSliderParameter ,
268+ MockValidationParameter ,
269+ ] ,
270+ } ) ,
271+ } ) ,
272+ ) ;
273+ } ) ;
274+
275+ // Should have the new field, but keep the existing auto-filled values.
276+ const form = screen . getByTestId ( "form" ) ;
277+ await waitFor ( ( ) => {
278+ expect ( within ( form ) . getByDisplayValue ( "50" ) ) . toBeInTheDocument ( ) ;
279+ expect ( within ( form ) . getByDisplayValue ( "44" ) ) . toBeInTheDocument ( ) ;
280+ expect ( within ( form ) . getByDisplayValue ( "auto" ) ) . toBeInTheDocument ( ) ;
281+ } ) ;
282+ } ) ;
283+ } ) ;
284+
285+ describe ( "Dynamic Parameter Types" , ( ) => {
286+ it ( "displays parameter validation errors" , async ( ) => {
287+ mockDynamicParameterWebSocket ( MockDynamicParametersResponseWithError ) ;
289288
290289 renderCreateWorkspacePage ( ) ;
291290 await waitForLoaderToBeRemoved ( ) ;
@@ -329,38 +328,20 @@ describe("CreateWorkspacePage", () => {
329328 diagnostics : [ ] ,
330329 } ;
331330
332- jest
333- . spyOn ( API , "templateVersionDynamicParameters" )
334- . mockImplementation ( ( _versionId , _ownerId , callbacks ) => {
335- const [ mockWebSocket , publisher ] = createMockWebSocket ( "ws://test" ) ;
336-
337- mockWebSocket . addEventListener ( "message" , ( event ) => {
338- callbacks . onMessage ( JSON . parse ( event . data ) ) ;
339- } ) ;
340-
341- publisher . publishOpen ( new Event ( "open" ) ) ;
331+ const [ mockWebSocket , publisher ] =
332+ mockDynamicParameterWebSocket ( mockResponseInitial ) ;
333+ const originalSend = mockWebSocket . send ;
334+ mockWebSocket . send = jest . fn ( ( data ) => {
335+ originalSend . call ( mockWebSocket , data ) ;
342336
337+ if ( typeof data === "string" && data . includes ( '"200"' ) ) {
343338 publisher . publishMessage (
344339 new MessageEvent ( "message" , {
345- data : JSON . stringify ( mockResponseInitial ) ,
340+ data : JSON . stringify ( mockResponseWithError ) ,
346341 } ) ,
347342 ) ;
348-
349- const originalSend = mockWebSocket . send ;
350- mockWebSocket . send = jest . fn ( ( data ) => {
351- originalSend . call ( mockWebSocket , data ) ;
352-
353- if ( typeof data === "string" && data . includes ( '"200"' ) ) {
354- publisher . publishMessage (
355- new MessageEvent ( "message" , {
356- data : JSON . stringify ( mockResponseWithError ) ,
357- } ) ,
358- ) ;
359- }
360- } ) ;
361-
362- return mockWebSocket ;
363- } ) ;
343+ }
344+ } ) ;
364345
365346 renderCreateWorkspacePage ( ) ;
366347 await waitForLoaderToBeRemoved ( ) ;
0 commit comments