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