@@ -35,6 +35,7 @@ import ArrayStore from 'common/data/array_store';
3535import {
3636 CHAT_EDITING_PREVIEW_CLASS ,
3737 CHAT_EDITING_PREVIEW_CANCEL_BUTTON_CLASS ,
38+ CHAT_EDITING_PREVIEW_HIDING_CLASS ,
3839} from '__internal/ui/chat/message_box/editing_preview' ;
3940import { CHAT_CONFIRMATION_POPUP_WRAPPER_CLASS } from '__internal/ui/chat/confirmationpopup' ;
4041import { POPUP_CLASS } from '__internal/ui/popup/popup' ;
@@ -80,6 +81,16 @@ const waitForCondition = (condition, timeout = ANIMATION_TIMEOUT * 8, interval =
8081 } , interval ) ;
8182} ) ;
8283
84+ // The editing preview removes its DOM node only on its CSS hide animation's animationend,
85+ // which is unreliable under CI load. Wait for the hiding to start, then fire animationend
86+ // so the node is removed synchronously.
87+ const waitForEditingPreviewToHide = async ( getEditingPreview ) => {
88+ await waitForCondition ( ( ) => getEditingPreview ( ) . length === 0
89+ || getEditingPreview ( ) . hasClass ( CHAT_EDITING_PREVIEW_HIDING_CLASS ) ) ;
90+
91+ getEditingPreview ( ) . get ( 0 ) ?. dispatchEvent ( new Event ( 'animationend' ) ) ;
92+ } ;
93+
8394export const MOCK_COMPANION_USER_ID = 'COMPANION_USER_ID' ;
8495export const MOCK_CURRENT_USER_ID = 'CURRENT_USER_ID' ;
8596export const NOW = 1721747399083 ;
@@ -1076,15 +1087,17 @@ QUnit.module('Chat', () => {
10761087
10771088 this . $sendButton . trigger ( 'dxclick' ) ;
10781089
1079- const expectedLength = cancel ? 1 : 0 ;
1090+ if ( cancel ) {
1091+ // The update is cancelled, so the preview must stay visible. Give a bounded
1092+ // window for an erroneous hide to start, then confirm it is still shown.
1093+ await waitForCondition ( ( ) => this . getEditingPreview ( ) . hasClass ( CHAT_EDITING_PREVIEW_HIDING_CLASS ) , ANIMATION_TIMEOUT ) ;
10801094
1081- await waitForCondition ( ( ) => this . getEditingPreview ( ) . length === expectedLength ) ;
1095+ assert . strictEqual ( this . getEditingPreview ( ) . length , 1 , `Editing preview remains when cancel=${ cancel } ` ) ;
1096+ } else {
1097+ await waitForEditingPreviewToHide ( ( ) => this . getEditingPreview ( ) ) ;
10821098
1083- assert . strictEqual (
1084- this . getEditingPreview ( ) . length ,
1085- expectedLength ,
1086- `Editing preview ${ cancel ? 'remains' : 'is hidden' } when cancel=${ cancel } `
1087- ) ;
1099+ assert . strictEqual ( this . getEditingPreview ( ) . length , 0 , `Editing preview is hidden when cancel=${ cancel } ` ) ;
1100+ }
10881101 } ) ;
10891102 } ) ;
10901103 } ) ;
@@ -1158,9 +1171,11 @@ QUnit.module('Chat', () => {
11581171
11591172 $applyButton . trigger ( 'dxclick' ) ;
11601173
1174+ // The preview hides via a CSS animation; drive its removal deterministically.
1175+ await waitForEditingPreviewToHide ( ( ) => this . getEditingPreview ( ) ) ;
1176+
11611177 // The input is refocused asynchronously, only after the confirmation popup hides.
1162- await waitForCondition ( ( ) => this . getEditingPreview ( ) . length === 0
1163- && this . textArea . option ( 'value' ) === ''
1178+ await waitForCondition ( ( ) => this . textArea . option ( 'value' ) === ''
11641179 && this . $textArea . hasClass ( FOCUSED_STATE_CLASS ) ) ;
11651180
11661181 assert . strictEqual ( this . getEditingPreview ( ) . length , 0 ) ;
@@ -1227,16 +1242,18 @@ QUnit.module('Chat', () => {
12271242 const $editButton = this . getContextMenuItems ( ) . eq ( 0 ) ;
12281243 $editButton . trigger ( 'dxclick' ) ;
12291244
1230- // The input is focused asynchronously, only after the context menu finishes hiding.
1231- // Wait until the editing mode is fully established (preview shown and input focused)
1232- // before sending, so the focus assertion does not race with the menu hide animation.
1233- await waitForCondition ( ( ) => this . getEditingPreview ( ) . length === 1
1234- && this . $textArea . hasClass ( FOCUSED_STATE_CLASS ) ) ;
1245+ // Make sure the editing mode is established before sending.
1246+ await waitForCondition ( ( ) => this . getEditingPreview ( ) . length === 1 ) ;
12351247
12361248 this . $sendButton . trigger ( 'dxclick' ) ;
12371249
1238- await waitForCondition ( ( ) => this . getEditingPreview ( ) . length === 0
1239- && this . textArea . option ( 'value' ) === '' ) ;
1250+ // The rejected cancel promise lets the update proceed, clearing the preview; its DOM
1251+ // node is removed only when the CSS hide animation ends, so drive that deterministically.
1252+ await waitForEditingPreviewToHide ( ( ) => this . getEditingPreview ( ) ) ;
1253+
1254+ // The input is focused asynchronously, only after the context menu finishes hiding.
1255+ await waitForCondition ( ( ) => this . textArea . option ( 'value' ) === ''
1256+ && this . $textArea . hasClass ( FOCUSED_STATE_CLASS ) ) ;
12401257
12411258 assert . strictEqual ( this . getEditingPreview ( ) . length , 0 ) ;
12421259 assert . strictEqual ( this . textArea . option ( 'value' ) , '' , 'input is empty' ) ;
0 commit comments