@webex/contact-center 3.12.0-task-refactor.7 → 3.12.0-task-refactor.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/services/task/Task.js +32 -0
  2. package/dist/services/task/Task.js.map +1 -1
  3. package/dist/services/task/TaskUtils.js +3 -1
  4. package/dist/services/task/TaskUtils.js.map +1 -1
  5. package/dist/services/task/state-machine/TaskStateMachine.js +76 -0
  6. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  7. package/dist/services/task/state-machine/actions.js +113 -23
  8. package/dist/services/task/state-machine/actions.js.map +1 -1
  9. package/dist/services/task/state-machine/uiControlsComputer.js +99 -21
  10. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  11. package/dist/types/services/task/Task.d.ts +10 -0
  12. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +110 -0
  13. package/dist/types/services/task/state-machine/actions.d.ts +2 -0
  14. package/dist/webex.js +1 -1
  15. package/package.json +1 -1
  16. package/src/services/task/Task.ts +34 -0
  17. package/src/services/task/TaskUtils.ts +5 -3
  18. package/src/services/task/state-machine/TaskStateMachine.ts +104 -0
  19. package/src/services/task/state-machine/actions.ts +151 -25
  20. package/src/services/task/state-machine/uiControlsComputer.ts +173 -30
  21. package/test/unit/spec/services/task/Task.ts +61 -0
  22. package/test/unit/spec/services/task/TaskUtils.ts +65 -0
  23. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +676 -0
  24. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +597 -0
  25. package/umd/contact-center.min.js +2 -2
  26. package/umd/contact-center.min.js.map +1 -1
@@ -471,6 +471,606 @@ describe('Task state machine', () => {
471
471
  expect(snapshotAfterEnd.context.consultDestinationAgentJoined).toBe(false);
472
472
  });
473
473
 
474
+ it('returns to HELD with main-leg controls after AgentConsultEnded from Stable Prod while CONSULTING', () => {
475
+ const service = startMachine();
476
+ const baseTaskData = createTaskData({
477
+ agentId: 'agent-1',
478
+ mediaResourceId: 'interaction-1',
479
+ });
480
+
481
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: baseTaskData});
482
+ service.send({type: TaskEvent.ASSIGN, taskData: baseTaskData});
483
+ service.send({
484
+ type: TaskEvent.CONSULT,
485
+ destination: 'agent-2',
486
+ destinationType: 'agent',
487
+ });
488
+ service.send({type: TaskEvent.CONSULT_SUCCESS});
489
+ service.send({type: TaskEvent.CONSULTING_ACTIVE, consultDestinationAgentJoined: true});
490
+ expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
491
+
492
+ const consultEndedTaskData = createTaskData({
493
+ agentId: 'agent-1',
494
+ mediaResourceId: 'interaction-1',
495
+ type: 'AgentConsultEnded' as any,
496
+ isConsulted: false,
497
+ interaction: {
498
+ state: 'connected',
499
+ interactionId: 'interaction-1',
500
+ mainInteractionId: 'interaction-1',
501
+ owner: 'agent-1',
502
+ participants: {
503
+ 'agent-1': {
504
+ id: 'agent-1',
505
+ pType: 'Agent',
506
+ hasLeft: false,
507
+ consultState: 'consultCompleted',
508
+ isConsulted: false,
509
+ },
510
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
511
+ },
512
+ media: {
513
+ 'interaction-1': {
514
+ mediaResourceId: 'interaction-1',
515
+ mType: 'mainCall',
516
+ isHold: true,
517
+ participants: ['customer-1', 'agent-1'],
518
+ },
519
+ },
520
+ } as any,
521
+ });
522
+
523
+ service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
524
+
525
+ const snapshot = service.getSnapshot();
526
+ expect(snapshot.value).toBe(TaskState.HELD);
527
+ expect(snapshot.context.consultInitiator).toBe(false);
528
+ expect(snapshot.context.consultCallHeld).toBe(false);
529
+ expect(snapshot.context.consultDestinationAgentJoined).toBe(false);
530
+ expect(snapshot.context.uiControls.activeLeg).toBe('main');
531
+ expect(snapshot.context.uiControls.consult.endConsult).toEqual({
532
+ isVisible: false,
533
+ isEnabled: false,
534
+ });
535
+ expect(snapshot.context.uiControls.main.hold).toEqual({
536
+ isVisible: true,
537
+ isEnabled: true,
538
+ });
539
+ expect(snapshot.context.uiControls.main.consult).toEqual({
540
+ isVisible: true,
541
+ isEnabled: true,
542
+ });
543
+ expect(snapshot.context.uiControls.main.transfer).toEqual({
544
+ isVisible: true,
545
+ isEnabled: true,
546
+ });
547
+ expect(snapshot.context.uiControls.main.recording).toEqual({
548
+ isVisible: true,
549
+ isEnabled: true,
550
+ });
551
+ });
552
+
553
+ it('enables main consult after ending consult before consultee answers (CONSULT_INITIATING -> CONSULT_END)', () => {
554
+ const service = startMachine();
555
+ const baseTaskData = createTaskData({
556
+ agentId: 'agent-1',
557
+ mediaResourceId: 'interaction-1',
558
+ });
559
+
560
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: baseTaskData});
561
+ service.send({type: TaskEvent.ASSIGN, taskData: baseTaskData});
562
+ service.send({
563
+ type: TaskEvent.CONSULT,
564
+ destination: 'agent-2',
565
+ destinationType: 'agent',
566
+ });
567
+ // Consult requested but consultee (agent-2) has not answered yet.
568
+ expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
569
+
570
+ // Agent 1 ends the consult before agent-2 answers. Backend AgentConsultEnded:
571
+ // main on hold, self consultCompleted, no consult media, consultee not in participants.
572
+ const consultEndedTaskData = createTaskData({
573
+ agentId: 'agent-1',
574
+ mediaResourceId: 'interaction-1',
575
+ consultMediaResourceId: 'consult-media',
576
+ destAgentId: 'agent-2',
577
+ destinationType: 'Agent',
578
+ type: 'AgentConsultEnded' as any,
579
+ isConsulted: false,
580
+ interaction: {
581
+ state: 'connected',
582
+ interactionId: 'interaction-1',
583
+ mainInteractionId: 'interaction-1',
584
+ owner: 'agent-1',
585
+ participants: {
586
+ 'agent-1': {
587
+ id: 'agent-1',
588
+ pType: 'Agent',
589
+ hasLeft: false,
590
+ hasJoined: true,
591
+ consultState: 'consultCompleted',
592
+ isConsulted: false,
593
+ },
594
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
595
+ },
596
+ media: {
597
+ 'interaction-1': {
598
+ mediaResourceId: 'interaction-1',
599
+ mType: 'mainCall',
600
+ isHold: true,
601
+ participants: ['customer-1', 'agent-1'],
602
+ },
603
+ },
604
+ } as any,
605
+ });
606
+
607
+ service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
608
+
609
+ const snapshot = service.getSnapshot();
610
+ expect(snapshot.value).toBe(TaskState.HELD);
611
+ expect(snapshot.context.consultInitiator).toBe(false);
612
+ expect(snapshot.context.consultDestinationAgentJoined).toBe(false);
613
+ expect(snapshot.context.uiControls.activeLeg).toBe('main');
614
+ expect(snapshot.context.uiControls.main.consult).toEqual({
615
+ isVisible: true,
616
+ isEnabled: true,
617
+ });
618
+ expect(snapshot.context.uiControls.consult.endConsult).toEqual({
619
+ isVisible: false,
620
+ isEnabled: false,
621
+ });
622
+ });
623
+
624
+ it('stays HELD and clears consult UI when AgentConsultEnded arrives on HELD state', () => {
625
+ const service = startMachine();
626
+ const heldTaskData = createTaskData({
627
+ agentId: 'agent-1',
628
+ mediaResourceId: 'interaction-1',
629
+ interaction: {
630
+ state: 'hold',
631
+ interactionId: 'interaction-1',
632
+ mainInteractionId: 'interaction-1',
633
+ owner: 'agent-1',
634
+ participants: {
635
+ 'agent-1': {
636
+ id: 'agent-1',
637
+ pType: 'Agent',
638
+ hasLeft: false,
639
+ consultState: 'consulting',
640
+ isConsulted: false,
641
+ },
642
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
643
+ },
644
+ media: {
645
+ 'interaction-1': {
646
+ mediaResourceId: 'interaction-1',
647
+ mType: 'mainCall',
648
+ isHold: true,
649
+ participants: ['customer-1', 'agent-1'],
650
+ },
651
+ },
652
+ } as any,
653
+ });
654
+
655
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: heldTaskData});
656
+ service.send({type: TaskEvent.ASSIGN, taskData: heldTaskData});
657
+ service.send({
658
+ type: TaskEvent.HOLD_INITIATED,
659
+ mediaResourceId: heldTaskData.mediaResourceId,
660
+ });
661
+ service.send({
662
+ type: TaskEvent.HOLD_SUCCESS,
663
+ mediaResourceId: heldTaskData.mediaResourceId,
664
+ taskData: heldTaskData,
665
+ });
666
+ expect(service.getSnapshot().value).toBe(TaskState.HELD);
667
+
668
+ const consultEndedTaskData = createTaskData({
669
+ agentId: 'agent-1',
670
+ mediaResourceId: 'interaction-1',
671
+ type: 'AgentConsultEnded' as any,
672
+ isConsulted: false,
673
+ interaction: {
674
+ state: 'hold',
675
+ interactionId: 'interaction-1',
676
+ mainInteractionId: 'interaction-1',
677
+ owner: 'agent-1',
678
+ participants: {
679
+ 'agent-1': {
680
+ id: 'agent-1',
681
+ pType: 'Agent',
682
+ hasLeft: false,
683
+ consultState: 'consultCompleted',
684
+ isConsulted: false,
685
+ },
686
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
687
+ },
688
+ media: {
689
+ 'interaction-1': {
690
+ mediaResourceId: 'interaction-1',
691
+ mType: 'mainCall',
692
+ isHold: true,
693
+ participants: ['customer-1', 'agent-1'],
694
+ },
695
+ },
696
+ } as any,
697
+ });
698
+
699
+ service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
700
+
701
+ const snapshot = service.getSnapshot();
702
+ expect(snapshot.value).toBe(TaskState.HELD);
703
+ expect(snapshot.context.consultInitiator).toBe(false);
704
+ expect(snapshot.context.uiControls.activeLeg).toBe('main');
705
+ expect(snapshot.context.uiControls.main.hold).toEqual({
706
+ isVisible: true,
707
+ isEnabled: true,
708
+ });
709
+ expect(snapshot.context.uiControls.consult.endConsult).toEqual({
710
+ isVisible: false,
711
+ isEnabled: false,
712
+ });
713
+ });
714
+
715
+ it('keeps main.consult enabled on HELD after AgentConsultFailed then AgentConsultEnded (RONA)', () => {
716
+ const service = startMachine();
717
+ const heldTaskData = createTaskData({
718
+ agentId: 'agent-1',
719
+ mediaResourceId: 'interaction-1',
720
+ type: 'AgentContactHeld' as any,
721
+ interaction: {
722
+ state: 'hold',
723
+ interactionId: 'interaction-1',
724
+ mainInteractionId: 'interaction-1',
725
+ owner: 'agent-1',
726
+ participants: {
727
+ 'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
728
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
729
+ } as any,
730
+ media: {
731
+ 'interaction-1': {
732
+ mediaResourceId: 'interaction-1',
733
+ mType: 'mainCall',
734
+ isHold: true,
735
+ participants: ['customer-1', 'agent-1'],
736
+ },
737
+ } as any,
738
+ } as any,
739
+ });
740
+
741
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: heldTaskData});
742
+ service.send({type: TaskEvent.ASSIGN, taskData: heldTaskData});
743
+ service.send({
744
+ type: TaskEvent.HOLD_INITIATED,
745
+ mediaResourceId: heldTaskData.mediaResourceId,
746
+ });
747
+ service.send({
748
+ type: TaskEvent.HOLD_SUCCESS,
749
+ mediaResourceId: heldTaskData.mediaResourceId,
750
+ taskData: heldTaskData,
751
+ });
752
+ expect(service.getSnapshot().value).toBe(TaskState.HELD);
753
+
754
+ const consultCreatedTaskData = createTaskData({
755
+ agentId: 'agent-1',
756
+ mediaResourceId: 'interaction-1',
757
+ consultMediaResourceId: 'consult-media',
758
+ consultingAgentId: 'agent-1',
759
+ destAgentId: 'agent-2',
760
+ isConsulted: false,
761
+ type: 'AgentConsultCreated' as any,
762
+ interaction: {
763
+ state: 'consult',
764
+ interactionId: 'interaction-1',
765
+ mainInteractionId: 'interaction-1',
766
+ owner: 'agent-1',
767
+ participants: {
768
+ 'agent-1': {
769
+ id: 'agent-1',
770
+ pType: 'Agent',
771
+ hasLeft: false,
772
+ consultState: 'consultInitiated',
773
+ isConsulted: false,
774
+ },
775
+ 'agent-2': {
776
+ id: 'agent-2',
777
+ pType: 'Agent',
778
+ hasLeft: false,
779
+ consultState: 'consultReserved',
780
+ isConsulted: true,
781
+ },
782
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
783
+ } as any,
784
+ media: {
785
+ 'interaction-1': {
786
+ mediaResourceId: 'interaction-1',
787
+ mType: 'mainCall',
788
+ isHold: true,
789
+ participants: ['customer-1', 'agent-1'],
790
+ },
791
+ 'consult-media': {
792
+ mediaResourceId: 'consult-media',
793
+ mType: 'consult',
794
+ participants: ['agent-2', 'agent-1'],
795
+ },
796
+ } as any,
797
+ } as any,
798
+ });
799
+ service.send({type: TaskEvent.CONSULT_CREATED, taskData: consultCreatedTaskData});
800
+
801
+ const consultFailedTaskData = createTaskData({
802
+ agentId: 'agent-1',
803
+ mediaResourceId: 'interaction-1',
804
+ consultMediaResourceId: 'consult-media',
805
+ consultingAgentId: 'agent-1',
806
+ destAgentId: 'agent-2',
807
+ isConsulted: false,
808
+ type: 'AgentConsultFailed' as any,
809
+ interaction: {
810
+ state: 'connected',
811
+ interactionId: 'interaction-1',
812
+ mainInteractionId: 'interaction-1',
813
+ owner: 'agent-1',
814
+ participants: {
815
+ 'agent-1': {
816
+ id: 'agent-1',
817
+ pType: 'Agent',
818
+ hasLeft: false,
819
+ consultState: 'consultCompleted',
820
+ isConsulted: false,
821
+ },
822
+ 'agent-2': {
823
+ id: 'agent-2',
824
+ pType: 'Agent',
825
+ hasLeft: false,
826
+ consultState: 'consultReserved',
827
+ isConsulted: true,
828
+ },
829
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
830
+ } as any,
831
+ media: {
832
+ 'interaction-1': {
833
+ mediaResourceId: 'interaction-1',
834
+ mType: 'mainCall',
835
+ isHold: true,
836
+ participants: ['customer-1', 'agent-1'],
837
+ },
838
+ } as any,
839
+ } as any,
840
+ });
841
+ service.send({type: TaskEvent.CONSULT_FAILED, taskData: consultFailedTaskData});
842
+
843
+ const consultEndedTaskData = createTaskData({
844
+ agentId: 'agent-1',
845
+ mediaResourceId: 'interaction-1',
846
+ consultMediaResourceId: 'consult-media',
847
+ isConsulted: false,
848
+ type: 'AgentConsultEnded' as any,
849
+ interaction: consultFailedTaskData.interaction,
850
+ });
851
+ service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
852
+
853
+ const snapshot = service.getSnapshot();
854
+ expect(snapshot.value).toBe(TaskState.HELD);
855
+ expect(snapshot.context.consultInitiator).toBe(false);
856
+ expect(snapshot.context.uiControls.activeLeg).toBe('main');
857
+ expect(snapshot.context.uiControls.main.consult).toEqual({
858
+ isVisible: true,
859
+ isEnabled: true,
860
+ });
861
+ expect(snapshot.context.uiControls.main.hold).toEqual({
862
+ isVisible: true,
863
+ isEnabled: true,
864
+ });
865
+ expect(snapshot.context.uiControls.main.transfer).toEqual({
866
+ isVisible: true,
867
+ isEnabled: true,
868
+ });
869
+ });
870
+
871
+ it('returns to main leg (HELD) and does not clear the task after AgentConsultFailed then AgentConsultEnded while CONSULTING (RONA)', () => {
872
+ const service = startMachine();
873
+ const heldTaskData = createTaskData({
874
+ agentId: 'agent-1',
875
+ mediaResourceId: 'interaction-1',
876
+ type: 'AgentContactHeld' as any,
877
+ interaction: {
878
+ state: 'hold',
879
+ interactionId: 'interaction-1',
880
+ mainInteractionId: 'interaction-1',
881
+ owner: 'agent-1',
882
+ participants: {
883
+ 'agent-1': {id: 'agent-1', pType: 'Agent', hasLeft: false},
884
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
885
+ } as any,
886
+ media: {
887
+ 'interaction-1': {
888
+ mediaResourceId: 'interaction-1',
889
+ mType: 'mainCall',
890
+ isHold: true,
891
+ participants: ['customer-1', 'agent-1'],
892
+ },
893
+ } as any,
894
+ } as any,
895
+ });
896
+
897
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: heldTaskData});
898
+ service.send({type: TaskEvent.ASSIGN, taskData: heldTaskData});
899
+ service.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId: heldTaskData.mediaResourceId});
900
+ service.send({
901
+ type: TaskEvent.HOLD_SUCCESS,
902
+ mediaResourceId: heldTaskData.mediaResourceId,
903
+ taskData: heldTaskData,
904
+ });
905
+ expect(service.getSnapshot().value).toBe(TaskState.HELD);
906
+
907
+ // Agent 1 initiates a consult and AgentConsulting arrives during ringing, moving the
908
+ // initiator into CONSULTING before the consultee answers.
909
+ service.send({type: TaskEvent.CONSULT, destination: 'agent-2', destinationType: 'agent'});
910
+ expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
911
+ service.send({type: TaskEvent.CONSULT_SUCCESS});
912
+ expect(service.getSnapshot().value).toBe(TaskState.CONSULTING);
913
+ expect(service.getSnapshot().context.consultInitiator).toBe(true);
914
+
915
+ // Consultee RONAs: AgentConsultFailed arrives while still in CONSULTING. Main stays held.
916
+ const consultFailedTaskData = createTaskData({
917
+ agentId: 'agent-1',
918
+ mediaResourceId: 'interaction-1',
919
+ consultMediaResourceId: 'consult-media',
920
+ consultingAgentId: 'agent-1',
921
+ destAgentId: 'agent-2',
922
+ isConsulted: false,
923
+ type: 'AgentConsultFailed' as any,
924
+ interaction: {
925
+ state: 'consult',
926
+ interactionId: 'interaction-1',
927
+ mainInteractionId: 'interaction-1',
928
+ owner: 'agent-1',
929
+ participants: {
930
+ 'agent-1': {
931
+ id: 'agent-1',
932
+ pType: 'Agent',
933
+ hasLeft: false,
934
+ consultState: 'consultCompleted',
935
+ isConsulted: false,
936
+ },
937
+ 'agent-2': {
938
+ id: 'agent-2',
939
+ pType: 'Agent',
940
+ hasLeft: false,
941
+ hasJoined: false,
942
+ consultState: 'consultReserved',
943
+ isConsulted: true,
944
+ },
945
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
946
+ } as any,
947
+ media: {
948
+ 'interaction-1': {
949
+ mediaResourceId: 'interaction-1',
950
+ mType: 'mainCall',
951
+ isHold: true,
952
+ participants: ['customer-1', 'agent-1'],
953
+ },
954
+ 'consult-media': {
955
+ mediaResourceId: 'consult-media',
956
+ mType: 'consult',
957
+ participants: ['agent-2', 'agent-1'],
958
+ },
959
+ } as any,
960
+ } as any,
961
+ });
962
+ service.send({type: TaskEvent.CONSULT_FAILED, taskData: consultFailedTaskData});
963
+ // The initiator must leave CONSULTING and fall back to the held main leg (not stay in
964
+ // CONSULTING, which would let the trailing AgentConsultEnded terminate the task).
965
+ expect(service.getSnapshot().value).toBe(TaskState.HELD);
966
+
967
+ // AgentConsultEnded closes out the consult leg.
968
+ const consultEndedTaskData = createTaskData({
969
+ agentId: 'agent-1',
970
+ mediaResourceId: 'interaction-1',
971
+ consultMediaResourceId: 'consult-media',
972
+ isConsulted: false,
973
+ type: 'AgentConsultEnded' as any,
974
+ interaction: {
975
+ state: 'connected',
976
+ interactionId: 'interaction-1',
977
+ mainInteractionId: 'interaction-1',
978
+ owner: 'agent-1',
979
+ participants: {
980
+ 'agent-1': {
981
+ id: 'agent-1',
982
+ pType: 'Agent',
983
+ hasLeft: false,
984
+ consultState: 'consultCompleted',
985
+ isConsulted: false,
986
+ },
987
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
988
+ } as any,
989
+ media: {
990
+ 'interaction-1': {
991
+ mediaResourceId: 'interaction-1',
992
+ mType: 'mainCall',
993
+ isHold: true,
994
+ participants: ['customer-1', 'agent-1'],
995
+ },
996
+ } as any,
997
+ } as any,
998
+ });
999
+ service.send({type: TaskEvent.CONSULT_END, taskData: consultEndedTaskData});
1000
+
1001
+ const snapshot = service.getSnapshot();
1002
+ // The task must remain on the main leg (HELD) and never reach TERMINATED.
1003
+ expect(snapshot.value).toBe(TaskState.HELD);
1004
+ expect(snapshot.value).not.toBe(TaskState.TERMINATED);
1005
+ expect(snapshot.context.consultInitiator).toBe(false);
1006
+ expect(snapshot.context.uiControls.activeLeg).toBe('main');
1007
+ expect(snapshot.context.uiControls.main.consult).toEqual({
1008
+ isVisible: true,
1009
+ isEnabled: true,
1010
+ });
1011
+ });
1012
+
1013
+ it('transitions CONSULT_INITIATING to CONSULTING on CONSULTING_ACTIVE and marks destination joined', () => {
1014
+ const service = startMachine();
1015
+ const baseTaskData = createTaskData();
1016
+ const consultingTaskData = createTaskData({
1017
+ agentId: 'agent-1',
1018
+ isConsulted: false,
1019
+ consultMediaResourceId: 'consult-media',
1020
+ interaction: {
1021
+ state: 'consulting',
1022
+ interactionId: 'interaction-1',
1023
+ mainInteractionId: 'interaction-1',
1024
+ participants: {
1025
+ 'agent-1': {
1026
+ id: 'agent-1',
1027
+ pType: 'Agent',
1028
+ hasLeft: false,
1029
+ consultState: 'consulting',
1030
+ isConsulted: false,
1031
+ },
1032
+ 'agent-2': {
1033
+ id: 'agent-2',
1034
+ pType: 'Agent',
1035
+ hasLeft: false,
1036
+ hasJoined: true,
1037
+ consultState: 'consulting',
1038
+ isConsulted: true,
1039
+ },
1040
+ } as any,
1041
+ media: {
1042
+ 'interaction-1': {mediaResourceId: 'interaction-1', mType: 'mainCall', isHold: true},
1043
+ 'consult-media': {
1044
+ mediaResourceId: 'consult-media',
1045
+ mType: 'consult',
1046
+ participants: ['agent-1', 'agent-2'],
1047
+ },
1048
+ } as any,
1049
+ } as any,
1050
+ });
1051
+
1052
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: baseTaskData});
1053
+ service.send({type: TaskEvent.ASSIGN, taskData: baseTaskData});
1054
+ service.send({
1055
+ type: TaskEvent.CONSULT,
1056
+ destination: 'agent-2',
1057
+ destinationType: 'agent',
1058
+ });
1059
+
1060
+ expect(service.getSnapshot().value).toBe(TaskState.CONSULT_INITIATING);
1061
+
1062
+ service.send({
1063
+ type: TaskEvent.CONSULTING_ACTIVE,
1064
+ consultDestinationAgentJoined: true,
1065
+ taskData: consultingTaskData,
1066
+ });
1067
+
1068
+ const snapshot = service.getSnapshot();
1069
+ expect(snapshot.value).toBe(TaskState.CONSULTING);
1070
+ expect(snapshot.context.consultInitiator).toBe(true);
1071
+ expect(snapshot.context.consultDestinationAgentJoined).toBe(true);
1072
+ });
1073
+
474
1074
  it('keeps consultDestinationAgentJoined false while consultee is only reserved, then sets true on actual join', () => {
475
1075
  const service = startMachine();
476
1076
  const pendingTaskData = createTaskData({
@@ -708,6 +1308,82 @@ describe('Task state machine', () => {
708
1308
  expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
709
1309
  });
710
1310
 
1311
+ it('transitions CONFERENCING to CONSULTING on CONSULTING_ACTIVE and marks DN consult joined', () => {
1312
+ const service = startMachine();
1313
+ const conferenceTaskData = createConferenceConsultTaskData({
1314
+ interactionState: 'conference',
1315
+ includeSecondAgent: true,
1316
+ conferenceHoldParticipant: false,
1317
+ });
1318
+
1319
+ service.send({type: TaskEvent.TASK_INCOMING, taskData: conferenceTaskData});
1320
+ service.send({type: TaskEvent.ASSIGN, taskData: conferenceTaskData});
1321
+ service.send({type: TaskEvent.CONFERENCE_START, taskData: conferenceTaskData});
1322
+ expect(service.getSnapshot().value).toBe(TaskState.CONFERENCING);
1323
+
1324
+ const dnConsultTaskData = createTaskData({
1325
+ type: 'AgentConsulting' as any,
1326
+ agentId: 'agent-1',
1327
+ consultingAgentId: 'agent-1',
1328
+ isConsulted: false,
1329
+ destinationType: 'DN',
1330
+ consultMediaResourceId: 'consult-media-1',
1331
+ interaction: {
1332
+ state: 'conference',
1333
+ mainInteractionId: 'interaction-1',
1334
+ interactionId: 'interaction-1',
1335
+ participants: {
1336
+ 'agent-1': {
1337
+ id: 'agent-1',
1338
+ pType: 'Agent',
1339
+ hasLeft: false,
1340
+ consultState: 'consulting',
1341
+ isConsulted: false,
1342
+ },
1343
+ 'agent-2': {
1344
+ id: 'agent-2',
1345
+ pType: 'Agent',
1346
+ hasLeft: false,
1347
+ consultState: 'conferencing',
1348
+ },
1349
+ 'dn-dest': {
1350
+ id: 'dn-dest',
1351
+ pType: 'DN',
1352
+ hasLeft: false,
1353
+ hasJoined: true,
1354
+ },
1355
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false},
1356
+ },
1357
+ media: {
1358
+ 'interaction-1': {
1359
+ mediaResourceId: 'interaction-1',
1360
+ mType: 'mainCall',
1361
+ participants: ['agent-1', 'agent-2', 'customer-1'],
1362
+ isHold: false,
1363
+ },
1364
+ 'consult-media-1': {
1365
+ mediaResourceId: 'consult-media-1',
1366
+ mType: 'consult',
1367
+ participants: ['agent-1', 'dn-dest'],
1368
+ isHold: false,
1369
+ },
1370
+ },
1371
+ } as any,
1372
+ });
1373
+
1374
+ service.send({
1375
+ type: TaskEvent.CONSULTING_ACTIVE,
1376
+ consultDestinationAgentJoined: true,
1377
+ taskData: dnConsultTaskData,
1378
+ });
1379
+
1380
+ const snapshot = service.getSnapshot();
1381
+ expect(snapshot.value).toBe(TaskState.CONSULTING);
1382
+ expect(snapshot.context.consultDestinationAgentJoined).toBe(true);
1383
+ expect(snapshot.context.consultFromConference).toBe(true);
1384
+ expect(snapshot.context.consultDestinationType).toBe('entryPoint');
1385
+ });
1386
+
711
1387
  it('transitions to conferencing when merge event is received', () => {
712
1388
  const service = startMachine();
713
1389
  const taskData = createTaskData({consultingAgentId: 'agent-1'});