@@ -250,6 +250,152 @@ describe('shared secrets editor', () => {
250250 expect ( left ) . toBe ( true )
251251 } )
252252
253+ it . each ( [ 'personal' , 'shared' ] ) (
254+ 'resumes a skipped %s refresh after reverting the draft' ,
255+ async ( scope ) => {
256+ const original = { TOKEN : 'original' }
257+ const refreshed = { TOKEN : 'remote' , REMOTE : 'new' }
258+ const props = ( variables : Record < string , string > ) =>
259+ scope === 'shared'
260+ ? { variables }
261+ : {
262+ personal : {
263+ variables : Object . fromEntries (
264+ Object . entries ( variables ) . map ( ( [ key , value ] ) => [ key , { key, value } ] )
265+ ) ,
266+ save : mocks . save ,
267+ } ,
268+ }
269+ const selector =
270+ scope === 'shared'
271+ ? 'input[name^="workspace_env_value_TOKEN"]'
272+ : 'input[name^="env_variable_value_"]'
273+ await render ( props ( original ) )
274+ const field = container . querySelector < HTMLInputElement > ( selector )
275+ if ( ! field ) throw new Error ( 'Missing secret value' )
276+ await change ( field , 'draft' )
277+ await render ( props ( refreshed ) )
278+ const retained = container . querySelector < HTMLInputElement > ( selector )
279+ if ( ! retained ) throw new Error ( 'Missing retained draft' )
280+ expect ( retained . value ) . toBe ( 'draft' )
281+ await change ( retained , 'original' )
282+ const values = [ ...container . querySelectorAll < HTMLInputElement > ( 'input[name*="value"]' ) ] . map (
283+ ( input ) => input . value
284+ )
285+ expect ( values ) . toContain ( 'remote' )
286+ expect ( values ) . toContain ( 'new' )
287+ let left = false
288+ act ( ( ) =>
289+ useSettingsDirtyStore . getState ( ) . requestLeave ( ( ) => {
290+ left = true
291+ } )
292+ )
293+ expect ( left ) . toBe ( true )
294+ }
295+ )
296+
297+ it . each ( [ 'personal' , 'shared' ] ) (
298+ 'consumes a fresh %s snapshot received before Save without hiding remote keys' ,
299+ async ( scope ) => {
300+ const empty = { }
301+ const original = { TOKEN : 'original' }
302+ const refreshed = { TOKEN : 'remote' , REMOTE : 'new' }
303+ const personalOriginal = { TOKEN : { key : 'TOKEN' , value : 'original' } }
304+ const personalRefreshed = {
305+ TOKEN : { key : 'TOKEN' , value : 'remote' } ,
306+ REMOTE : { key : 'REMOTE' , value : 'new' } ,
307+ }
308+ const props = ( fresh : boolean ) =>
309+ scope === 'shared'
310+ ? { variables : fresh ? refreshed : original }
311+ : {
312+ variables : empty ,
313+ personal : {
314+ variables : fresh ? personalRefreshed : personalOriginal ,
315+ save : mocks . save ,
316+ } ,
317+ }
318+ const selector =
319+ scope === 'shared'
320+ ? 'input[name^="workspace_env_value_TOKEN"]'
321+ : 'input[name^="env_variable_value_"]'
322+ await render ( props ( false ) )
323+ const field = container . querySelector < HTMLInputElement > ( selector )
324+ if ( ! field ) throw new Error ( 'Missing secret value' )
325+ await change ( field , 'submitted' )
326+ await render ( props ( true ) )
327+ await act ( async ( ) => button ( 'Save' ) . click ( ) )
328+ expect ( container . querySelector < HTMLInputElement > ( selector ) ?. value ) . toBe ( 'submitted' )
329+ const values = [ ...container . querySelectorAll < HTMLInputElement > ( 'input[name*="value"]' ) ] . map (
330+ ( input ) => input . value
331+ )
332+ expect ( values ) . toContain ( 'new' )
333+ let left = false
334+ act ( ( ) =>
335+ useSettingsDirtyStore . getState ( ) . requestLeave ( ( ) => {
336+ left = true
337+ } )
338+ )
339+ expect ( left ) . toBe ( true )
340+ }
341+ )
342+
343+ it . each ( [
344+ { scope : 'personal' , refresh : 'during Save' } ,
345+ { scope : 'shared' , refresh : 'during Save' } ,
346+ { scope : 'personal' , refresh : 'after Save' } ,
347+ { scope : 'shared' , refresh : 'after Save' } ,
348+ ] ) (
349+ 'acknowledges fresh $scope values received $refresh without rolling back the save' ,
350+ async ( { scope, refresh } ) => {
351+ const request = createDeferred < void > ( )
352+ const original = { TOKEN : 'original' }
353+ const canonical = { TOKEN : 'submitted' , REMOTE : 'canonical' }
354+ const emptyShared = { }
355+ const personalOriginal = { TOKEN : { key : 'TOKEN' , value : 'original' } }
356+ const personalCanonical = {
357+ TOKEN : { key : 'TOKEN' , value : 'submitted' } ,
358+ REMOTE : { key : 'REMOTE' , value : 'canonical' } ,
359+ }
360+ const props = ( fresh : boolean , isSaving : boolean ) =>
361+ scope === 'shared'
362+ ? { variables : fresh ? canonical : original , isSaving, save : ( ) => request . promise }
363+ : {
364+ variables : emptyShared ,
365+ isSaving,
366+ personal : {
367+ variables : fresh ? personalCanonical : personalOriginal ,
368+ save : ( ) => request . promise ,
369+ } ,
370+ }
371+ const selector =
372+ scope === 'shared'
373+ ? 'input[name^="workspace_env_value_TOKEN"]'
374+ : 'input[name^="env_variable_value_"]'
375+ await render ( props ( false , false ) )
376+ const field = container . querySelector < HTMLInputElement > ( selector )
377+ if ( ! field ) throw new Error ( 'Missing secret value' )
378+ await change ( field , 'submitted' )
379+ act ( ( ) => button ( 'Save' ) . click ( ) )
380+ await render ( props ( refresh === 'during Save' , true ) )
381+ await act ( async ( ) => request . resolve ( ) )
382+ await render ( props ( refresh === 'during Save' , false ) )
383+ expect ( container . querySelector < HTMLInputElement > ( selector ) ?. value ) . toBe ( 'submitted' )
384+ if ( refresh === 'after Save' ) await render ( props ( true , false ) )
385+ const values = [ ...container . querySelectorAll < HTMLInputElement > ( 'input[name*="value"]' ) ] . map (
386+ ( input ) => input . value
387+ )
388+ expect ( values ) . toContain ( 'canonical' )
389+ let left = false
390+ act ( ( ) =>
391+ useSettingsDirtyStore . getState ( ) . requestLeave ( ( ) => {
392+ left = true
393+ } )
394+ )
395+ expect ( left ) . toBe ( true )
396+ }
397+ )
398+
253399 it ( 'keeps an edited personal secret and its navigation protection through a refresh' , async ( ) => {
254400 const personal = { variables : { TOKEN : { key : 'TOKEN' , value : 'original' } } , save : mocks . save }
255401 await render ( { personal } )
0 commit comments