@@ -7,6 +7,8 @@ test.use({ aiAvailable: true })
77const aiPendingNode = ( page : Page ) => page . locator ( '.AiPendingNode' )
88const aiPendingStatus = ( node : ReturnType < typeof aiPendingNode > ) =>
99 node . locator ( '[data-testid="ai-pending-status"]' )
10+ const aiPendingRefresh = ( node : ReturnType < typeof aiPendingNode > ) =>
11+ node . locator ( '[data-testid="ai-pending-refresh"]' )
1012
1113/** Switch the AI mock into deferred mode so each `generateComponent` waits for the test to drive it. */
1214async function enableDeferredAiMock ( page : Page ) : Promise < void > {
@@ -319,3 +321,107 @@ test('after a cancel, a freshly submitted prompt is dispatched and completes', a
319321 await expect ( placeholders ) . toHaveCount ( 0 )
320322 await expect ( locate . graphNodeByBinding ( page , 'ai_component1' ) ) . toBeVisible ( )
321323} )
324+
325+ test ( 'refresh button is hidden while queued and visible once running or failed' , async ( {
326+ editorPage,
327+ page,
328+ } ) => {
329+ await editorPage
330+ await enableDeferredAiMock ( page )
331+
332+ // Two prompts: first runs, second sits queued behind it.
333+ await openAiPrompt ( page , 'first' )
334+ await openAiPrompt ( page , 'second' )
335+ const placeholders = aiPendingNode ( page )
336+ await expect ( placeholders ) . toHaveCount ( 2 )
337+
338+ // Queued placeholder must not offer refresh.
339+ await expect ( aiPendingRefresh ( placeholders . nth ( 1 ) ) ) . toHaveCount ( 0 )
340+ // The first placeholder is still queued (no `started` event yet) — also hidden.
341+ await expect ( aiPendingRefresh ( placeholders . nth ( 0 ) ) ) . toHaveCount ( 0 )
342+
343+ // Drive `started` on the first; refresh appears for the now-running entry only.
344+ const probe = await probeAiMock ( page )
345+ await emitProgress ( page , { requestId : probe . ids [ 0 ] ! , kind : 'started' } )
346+ await expect ( aiPendingRefresh ( placeholders . nth ( 0 ) ) ) . toHaveCount ( 1 )
347+ await expect ( aiPendingRefresh ( placeholders . nth ( 1 ) ) ) . toHaveCount ( 0 )
348+
349+ // Fail the first; refresh stays visible on the failed entry.
350+ await failAi ( page , probe . ids [ 0 ] ! , 'Mock failure' )
351+ const failed = page . locator ( '.AiPendingNode.failed' )
352+ await expect ( failed ) . toHaveCount ( 1 )
353+ await expect ( aiPendingRefresh ( failed ) ) . toHaveCount ( 1 )
354+ } )
355+
356+ test ( 'refresh on a failed placeholder re-queues the same prompt' , async ( { editorPage, page } ) => {
357+ await editorPage
358+ await enableDeferredAiMock ( page )
359+
360+ await openAiPrompt ( page , 'first' )
361+ const placeholders = aiPendingNode ( page )
362+ await expect ( placeholders ) . toHaveCount ( 1 )
363+ const { ids } = await probeAiMock ( page )
364+ const failedId = ids [ 0 ] !
365+ await failAi ( page , failedId , 'Mock failure for testing' )
366+
367+ const failed = page . locator ( '.AiPendingNode.failed' )
368+ await expect ( failed ) . toHaveCount ( 1 )
369+
370+ // Click refresh: the failed placeholder is dismissed and a fresh request is enqueued.
371+ await aiPendingRefresh ( failed ) . click ( )
372+ await expect ( failed ) . toHaveCount ( 0 )
373+ await expect ( placeholders ) . toHaveCount ( 1 )
374+
375+ // A new request id appears in the mock — proves a fresh IPC went out.
376+ await expect
377+ . poll (
378+ async ( ) => {
379+ const p = await probeAiMock ( page )
380+ return p . ids . find ( ( id ) => id !== failedId )
381+ } ,
382+ { timeout : 10_000 } ,
383+ )
384+ . not . toBeUndefined ( )
385+ const probe = await probeAiMock ( page )
386+ const newId = probe . ids . find ( ( id ) => id !== failedId ) !
387+
388+ await emitProgress ( page , { requestId : newId , kind : 'started' } )
389+ await resolveAi ( page , newId )
390+ await expect ( placeholders ) . toHaveCount ( 0 )
391+ await expect ( locate . graphNodeByBinding ( page , 'ai_component1' ) ) . toBeVisible ( )
392+ } )
393+
394+ test ( 'refresh on a running placeholder cancels the in-flight prompt and re-queues it' , async ( {
395+ editorPage,
396+ page,
397+ } ) => {
398+ await editorPage
399+ await enableDeferredAiMock ( page )
400+
401+ await openAiPrompt ( page , 'first' )
402+ const placeholders = aiPendingNode ( page )
403+ await expect ( placeholders ) . toHaveCount ( 1 )
404+ const firstProbe = await probeAiMock ( page )
405+ const firstId = firstProbe . ids [ 0 ] !
406+ await emitProgress ( page , { requestId : firstId , kind : 'started' } )
407+
408+ await aiPendingRefresh ( placeholders . nth ( 0 ) ) . click ( )
409+ // Original request is cancelled and a new one is dispatched.
410+ await expect
411+ . poll (
412+ async ( ) => {
413+ const p = await probeAiMock ( page )
414+ return p . ids . find ( ( id ) => id !== firstId )
415+ } ,
416+ { timeout : 10_000 } ,
417+ )
418+ . not . toBeUndefined ( )
419+ const afterRefresh = await probeAiMock ( page )
420+ expect ( afterRefresh . cancels ) . toEqual ( [ firstId ] )
421+ const secondId = afterRefresh . ids . find ( ( id ) => id !== firstId ) !
422+
423+ await emitProgress ( page , { requestId : secondId , kind : 'started' } )
424+ await resolveAi ( page , secondId )
425+ await expect ( placeholders ) . toHaveCount ( 0 )
426+ await expect ( locate . graphNodeByBinding ( page , 'ai_component1' ) ) . toBeVisible ( )
427+ } )
0 commit comments