@@ -7,13 +7,14 @@ import {
77} from '@mongodb-js/testing-library-compass' ;
88import { AssistantChat } from './assistant-chat' ;
99import { expect } from 'chai' ;
10- import { createMockChat } from '../test/utils' ;
10+ import { createMockChat } from '../../ test/utils' ;
1111import type { ConnectionInfo } from '@mongodb-js/connection-info' ;
1212import {
1313 AssistantActionsContext ,
1414 type AssistantMessage ,
15- } from './compass-assistant-provider' ;
15+ } from '.. /compass-assistant-provider' ;
1616import sinon from 'sinon' ;
17+ import type { TextPart } from 'ai' ;
1718
1819describe ( 'AssistantChat' , function ( ) {
1920 const mockMessages : AssistantMessage [ ] = [
@@ -527,4 +528,235 @@ describe('AssistantChat', function () {
527528 expect ( screen . queryByLabelText ( 'Thumbs Down Icon' ) ) . to . not . exist ;
528529 } ) ;
529530 } ) ;
531+
532+ describe ( 'messages with confirmation' , function ( ) {
533+ let mockConfirmationMessage : AssistantMessage ;
534+
535+ beforeEach ( function ( ) {
536+ mockConfirmationMessage = {
537+ id : 'confirmation-test' ,
538+ role : 'assistant' ,
539+ parts : [ { type : 'text' , text : 'This is a confirmation message.' } ] ,
540+ metadata : {
541+ confirmation : {
542+ state : 'pending' ,
543+ description : 'Are you sure you want to proceed with this action?' ,
544+ } ,
545+ } ,
546+ } ;
547+ } ) ;
548+
549+ it ( 'renders confirmation message when message has confirmation metadata' , function ( ) {
550+ renderWithChat ( [ mockConfirmationMessage ] ) ;
551+
552+ expect ( screen . getByText ( 'Please confirm your request' ) ) . to . exist ;
553+ expect (
554+ screen . getByText ( 'Are you sure you want to proceed with this action?' )
555+ ) . to . exist ;
556+ expect ( screen . getByText ( 'Confirm' ) ) . to . exist ;
557+ expect ( screen . getByText ( 'Cancel' ) ) . to . exist ;
558+ } ) ;
559+
560+ it ( 'does not render regular message content when confirmation metadata exists' , function ( ) {
561+ renderWithChat ( [ mockConfirmationMessage ] ) ;
562+
563+ // Should not show the message text content when confirmation is present
564+ expect ( screen . queryByText ( 'This is a confirmation message.' ) ) . to . not
565+ . exist ;
566+ } ) ;
567+
568+ it ( 'shows confirmation as pending when it is the last message' , function ( ) {
569+ renderWithChat ( [ mockConfirmationMessage ] ) ;
570+
571+ expect ( screen . getByText ( 'Confirm' ) ) . to . exist ;
572+ expect ( screen . getByText ( 'Cancel' ) ) . to . exist ;
573+ expect ( screen . queryByText ( 'Request confirmed' ) ) . to . not . exist ;
574+ expect ( screen . queryByText ( 'Request cancelled' ) ) . to . not . exist ;
575+ } ) ;
576+
577+ it ( 'shows confirmation as rejected when it is not the last message' , function ( ) {
578+ const messages : AssistantMessage [ ] = [
579+ mockConfirmationMessage ,
580+ {
581+ id : 'newer-message' ,
582+ role : 'user' as const ,
583+ parts : [ { type : 'text' , text : 'Another message' } ] ,
584+ } ,
585+ ] ;
586+
587+ renderWithChat ( messages ) ;
588+
589+ // The confirmation message (first one) should show as rejected since it's not the last
590+ expect ( screen . queryByText ( 'Confirm' ) ) . to . not . exist ;
591+ expect ( screen . queryByText ( 'Cancel' ) ) . to . not . exist ;
592+ expect ( screen . getByText ( 'Request cancelled' ) ) . to . exist ;
593+ } ) ;
594+
595+ it ( 'adds new confirmed message when confirmation is confirmed' , function ( ) {
596+ const { chat, ensureOptInAndSendStub } = renderWithChat ( [
597+ mockConfirmationMessage ,
598+ ] ) ;
599+
600+ const confirmButton = screen . getByText ( 'Confirm' ) ;
601+ userEvent . click ( confirmButton ) ;
602+
603+ // Should add a new message without confirmation metadata
604+ expect ( chat . messages ) . to . have . length ( 2 ) ;
605+ const newMessage = chat . messages [ 1 ] ;
606+ expect ( newMessage . id ) . to . equal ( 'confirmation-test-confirmed' ) ;
607+ expect ( newMessage . metadata ?. confirmation ) . to . be . undefined ;
608+ expect ( newMessage . parts ) . to . deep . equal ( mockConfirmationMessage . parts ) ;
609+
610+ // Should call ensureOptInAndSend to send the new message
611+ expect ( ensureOptInAndSendStub . calledOnce ) . to . be . true ;
612+ } ) ;
613+
614+ it ( 'updates confirmation state to confirmed and adds a new message when confirm button is clicked' , function ( ) {
615+ const { chat } = renderWithChat ( [ mockConfirmationMessage ] ) ;
616+
617+ const confirmButton = screen . getByText ( 'Confirm' ) ;
618+ userEvent . click ( confirmButton ) ;
619+
620+ // Original message should have updated confirmation state
621+ const originalMessage = chat . messages [ 0 ] ;
622+ expect ( originalMessage . metadata ?. confirmation ?. state ) . to . equal (
623+ 'confirmed'
624+ ) ;
625+
626+ expect ( chat . messages ) . to . have . length ( 2 ) ;
627+
628+ expect (
629+ screen . getByText ( ( mockConfirmationMessage . parts [ 0 ] as TextPart ) . text )
630+ ) . to . exist ;
631+ } ) ;
632+
633+ it ( 'updates confirmation state to rejected and does not add a new message when cancel button is clicked' , function ( ) {
634+ const { chat, ensureOptInAndSendStub } = renderWithChat ( [
635+ mockConfirmationMessage ,
636+ ] ) ;
637+
638+ const cancelButton = screen . getByText ( 'Cancel' ) ;
639+ userEvent . click ( cancelButton ) ;
640+
641+ // Original message should have updated confirmation state
642+ const originalMessage = chat . messages [ 0 ] ;
643+ expect ( originalMessage . metadata ?. confirmation ?. state ) . to . equal (
644+ 'rejected'
645+ ) ;
646+
647+ // Should not add a new message
648+ expect ( chat . messages ) . to . have . length ( 1 ) ;
649+
650+ // Should not call ensureOptInAndSend
651+ expect ( ensureOptInAndSendStub . notCalled ) . to . be . true ;
652+ } ) ;
653+
654+ it ( 'shows confirmed status after confirmation is confirmed' , function ( ) {
655+ const { chat } = renderWithChat ( [ mockConfirmationMessage ] ) ;
656+
657+ // Verify buttons are initially present
658+ expect ( screen . getByText ( 'Confirm' ) ) . to . exist ;
659+ expect ( screen . getByText ( 'Cancel' ) ) . to . exist ;
660+
661+ const confirmButton = screen . getByText ( 'Confirm' ) ;
662+ userEvent . click ( confirmButton ) ;
663+
664+ // The state update should be immediate - check the chat messages
665+ const updatedMessage = chat . messages [ 0 ] ;
666+ expect ( updatedMessage . metadata ?. confirmation ?. state ) . to . equal (
667+ 'confirmed'
668+ ) ;
669+ } ) ;
670+
671+ it ( 'shows cancelled status after confirmation is rejected' , function ( ) {
672+ const { chat } = renderWithChat ( [ mockConfirmationMessage ] ) ;
673+
674+ // Verify buttons are initially present
675+ expect ( screen . getByText ( 'Confirm' ) ) . to . exist ;
676+ expect ( screen . getByText ( 'Cancel' ) ) . to . exist ;
677+
678+ const cancelButton = screen . getByText ( 'Cancel' ) ;
679+ userEvent . click ( cancelButton ) ;
680+
681+ // The state update should be immediate - check the chat messages
682+ const updatedMessage = chat . messages [ 0 ] ;
683+ expect ( updatedMessage . metadata ?. confirmation ?. state ) . to . equal ( 'rejected' ) ;
684+ } ) ;
685+
686+ it ( 'handles multiple confirmation messages correctly' , function ( ) {
687+ const confirmationMessage1 : AssistantMessage = {
688+ id : 'confirmation-1' ,
689+ role : 'assistant' ,
690+ parts : [ { type : 'text' , text : 'First confirmation' } ] ,
691+ metadata : {
692+ confirmation : {
693+ state : 'pending' ,
694+ description : 'First confirmation description' ,
695+ } ,
696+ } ,
697+ } ;
698+
699+ const confirmationMessage2 : AssistantMessage = {
700+ id : 'confirmation-2' ,
701+ role : 'assistant' ,
702+ parts : [ { type : 'text' , text : 'Second confirmation' } ] ,
703+ metadata : {
704+ confirmation : {
705+ state : 'pending' ,
706+ description : 'Second confirmation description' ,
707+ } ,
708+ } ,
709+ } ;
710+
711+ renderWithChat ( [ confirmationMessage1 , confirmationMessage2 ] ) ;
712+
713+ expect ( screen . getAllByText ( 'Request cancelled' ) ) . to . have . length ( 1 ) ;
714+
715+ expect ( screen . getAllByText ( 'Confirm' ) ) . to . have . length ( 1 ) ;
716+ expect ( screen . getAllByText ( 'Cancel' ) ) . to . have . length ( 1 ) ;
717+ expect ( screen . getByText ( 'Second confirmation description' ) ) . to . exist ;
718+ } ) ;
719+
720+ it ( 'preserves other metadata when creating confirmed message' , function ( ) {
721+ const messageWithExtraMetadata : AssistantMessage = {
722+ id : 'confirmation-with-metadata' ,
723+ role : 'assistant' ,
724+ parts : [ { type : 'text' , text : 'Message with extra metadata' } ] ,
725+ metadata : {
726+ confirmation : {
727+ state : 'pending' ,
728+ description : 'Confirmation description' ,
729+ } ,
730+ displayText : 'Custom display text' ,
731+ isPermanent : true ,
732+ } ,
733+ } ;
734+
735+ const { chat } = renderWithChat ( [ messageWithExtraMetadata ] ) ;
736+
737+ const confirmButton = screen . getByText ( 'Confirm' ) ;
738+ userEvent . click ( confirmButton ) ;
739+
740+ // New confirmed message should preserve other metadata
741+ const newMessage = chat . messages [ 1 ] ;
742+ expect ( newMessage . metadata ?. displayText ) . to . equal ( 'Custom display text' ) ;
743+ expect ( newMessage . metadata ?. isPermanent ) . to . equal ( true ) ;
744+ expect ( newMessage . metadata ?. confirmation ) . to . be . undefined ;
745+ } ) ;
746+
747+ it ( 'does not render confirmation component for regular messages' , function ( ) {
748+ const regularMessage : AssistantMessage = {
749+ id : 'regular' ,
750+ role : 'assistant' ,
751+ parts : [ { type : 'text' , text : 'This is a regular message' } ] ,
752+ } ;
753+
754+ renderWithChat ( [ regularMessage ] ) ;
755+
756+ expect ( screen . queryByText ( 'Please confirm your request' ) ) . to . not . exist ;
757+ expect ( screen . queryByText ( 'Confirm' ) ) . to . not . exist ;
758+ expect ( screen . queryByText ( 'Cancel' ) ) . to . not . exist ;
759+ expect ( screen . getByText ( 'This is a regular message' ) ) . to . exist ;
760+ } ) ;
761+ } ) ;
530762} ) ;
0 commit comments