@@ -10,6 +10,7 @@ vi.mock("laravel-echo", () => {
1010 leaveChannel : vi . fn ( ) ,
1111 listen : vi . fn ( ) ,
1212 stopListening : vi . fn ( ) ,
13+ notification : vi . fn ( ) ,
1314 } ;
1415
1516 const mockPublicChannel = {
@@ -854,3 +855,261 @@ describe("useEchoPresence hook", async () => {
854855 expect ( result . current . channel ) . not . toBeNull ( ) ;
855856 } ) ;
856857} ) ;
858+
859+ describe ( "useEchoNotification hook" , async ( ) => {
860+ let echoModule : typeof import ( "../src/hooks/use-echo" ) ;
861+ let configModule : typeof import ( "../src/config/index" ) ;
862+ let echoInstance : Echo < "null" > ;
863+
864+ beforeEach ( async ( ) => {
865+ vi . resetModules ( ) ;
866+
867+ echoInstance = new Echo ( {
868+ broadcaster : "null" ,
869+ } ) ;
870+
871+ echoModule = await getEchoModule ( ) ;
872+ configModule = await getConfigModule ( ) ;
873+
874+ configModule . configureEcho ( {
875+ broadcaster : "null" ,
876+ } ) ;
877+ } ) ;
878+
879+ afterEach ( ( ) => {
880+ vi . clearAllMocks ( ) ;
881+ } ) ;
882+
883+ it ( "subscribes to a private channel and listens for notifications" , async ( ) => {
884+ const mockCallback = vi . fn ( ) ;
885+ const channelName = "test-channel" ;
886+
887+ const { result } = renderHook ( ( ) =>
888+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
889+ ) ;
890+
891+ expect ( result . current ) . toHaveProperty ( "leaveChannel" ) ;
892+ expect ( typeof result . current . leave ) . toBe ( "function" ) ;
893+ expect ( result . current ) . toHaveProperty ( "leave" ) ;
894+ expect ( typeof result . current . leaveChannel ) . toBe ( "function" ) ;
895+ expect ( result . current ) . toHaveProperty ( "listen" ) ;
896+ expect ( typeof result . current . listen ) . toBe ( "function" ) ;
897+ expect ( result . current ) . toHaveProperty ( "stopListening" ) ;
898+ expect ( typeof result . current . stopListening ) . toBe ( "function" ) ;
899+ } ) ;
900+
901+ it ( "sets up a notification listener on a channel" , async ( ) => {
902+ const mockCallback = vi . fn ( ) ;
903+ const channelName = "test-channel" ;
904+
905+ renderHook ( ( ) =>
906+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
907+ ) ;
908+
909+ expect ( echoInstance . private ) . toHaveBeenCalledWith ( channelName ) ;
910+
911+ const channel = echoInstance . private ( channelName ) ;
912+ expect ( channel . notification ) . toHaveBeenCalled ( ) ;
913+ } ) ;
914+
915+ it ( "handles notification filtering by event type" , async ( ) => {
916+ const mockCallback = vi . fn ( ) ;
917+ const channelName = "test-channel" ;
918+ const eventType = "specific-type" ;
919+
920+ renderHook ( ( ) =>
921+ echoModule . useEchoNotification (
922+ channelName ,
923+ mockCallback ,
924+ eventType ,
925+ ) ,
926+ ) ;
927+
928+ const channel = echoInstance . private ( channelName ) ;
929+ expect ( channel . notification ) . toHaveBeenCalled ( ) ;
930+
931+ const notificationCallback = vi . mocked ( channel . notification ) . mock
932+ . calls [ 0 ] [ 0 ] ;
933+
934+ const matchingNotification = {
935+ type : eventType ,
936+ data : { message : "test" } ,
937+ } ;
938+ const nonMatchingNotification = {
939+ type : "other-type" ,
940+ data : { message : "test" } ,
941+ } ;
942+
943+ notificationCallback ( matchingNotification ) ;
944+ notificationCallback ( nonMatchingNotification ) ;
945+
946+ expect ( mockCallback ) . toHaveBeenCalledWith ( matchingNotification ) ;
947+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 1 ) ;
948+ expect ( mockCallback ) . not . toHaveBeenCalledWith ( nonMatchingNotification ) ;
949+ } ) ;
950+
951+ it ( "handles multiple notification event types" , async ( ) => {
952+ const mockCallback = vi . fn ( ) ;
953+ const channelName = "test-channel" ;
954+ const events = [ "type1" , "type2" ] ;
955+
956+ renderHook ( ( ) =>
957+ echoModule . useEchoNotification ( channelName , mockCallback , events ) ,
958+ ) ;
959+
960+ const channel = echoInstance . private ( channelName ) ;
961+ expect ( channel . notification ) . toHaveBeenCalled ( ) ;
962+
963+ const notificationCallback = vi . mocked ( channel . notification ) . mock
964+ . calls [ 0 ] [ 0 ] ;
965+
966+ const notification1 = { type : events [ 0 ] , data : { } } ;
967+ const notification2 = { type : events [ 1 ] , data : { } } ;
968+ const notification3 = { type : "type3" , data : { } } ;
969+
970+ notificationCallback ( notification1 ) ;
971+ notificationCallback ( notification2 ) ;
972+ notificationCallback ( notification3 ) ;
973+
974+ expect ( mockCallback ) . toHaveBeenCalledWith ( notification1 ) ;
975+ expect ( mockCallback ) . toHaveBeenCalledWith ( notification2 ) ;
976+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 2 ) ;
977+ expect ( mockCallback ) . not . toHaveBeenCalledWith ( notification3 ) ;
978+ } ) ;
979+
980+ it ( "accepts all notifications when no event types specified" , async ( ) => {
981+ const mockCallback = vi . fn ( ) ;
982+ const channelName = "test-channel" ;
983+
984+ renderHook ( ( ) =>
985+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
986+ ) ;
987+
988+ const channel = echoInstance . private ( channelName ) ;
989+ expect ( channel . notification ) . toHaveBeenCalled ( ) ;
990+
991+ const notificationCallback = vi . mocked ( channel . notification ) . mock
992+ . calls [ 0 ] [ 0 ] ;
993+
994+ const notification1 = { type : "type1" , data : { } } ;
995+ const notification2 = { type : "type2" , data : { } } ;
996+
997+ notificationCallback ( notification1 ) ;
998+ notificationCallback ( notification2 ) ;
999+
1000+ expect ( mockCallback ) . toHaveBeenCalledWith ( notification1 ) ;
1001+ expect ( mockCallback ) . toHaveBeenCalledWith ( notification2 ) ;
1002+
1003+ expect ( mockCallback ) . toHaveBeenCalledTimes ( 2 ) ;
1004+ } ) ;
1005+
1006+ it ( "cleans up subscriptions on unmount" , async ( ) => {
1007+ const mockCallback = vi . fn ( ) ;
1008+ const channelName = "test-channel" ;
1009+
1010+ const { unmount } = renderHook ( ( ) =>
1011+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1012+ ) ;
1013+
1014+ expect ( echoInstance . private ) . toHaveBeenCalledWith ( channelName ) ;
1015+
1016+ expect ( ( ) => unmount ( ) ) . not . toThrow ( ) ;
1017+
1018+ expect ( echoInstance . leaveChannel ) . toHaveBeenCalledWith (
1019+ `private-${ channelName } ` ,
1020+ ) ;
1021+ } ) ;
1022+
1023+ it ( "won't subscribe multiple times to the same channel" , async ( ) => {
1024+ const mockCallback = vi . fn ( ) ;
1025+ const channelName = "test-channel" ;
1026+
1027+ const { unmount : unmount1 } = renderHook ( ( ) =>
1028+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1029+ ) ;
1030+
1031+ const { unmount : unmount2 } = renderHook ( ( ) =>
1032+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1033+ ) ;
1034+
1035+ expect ( echoInstance . private ) . toHaveBeenCalledTimes ( 1 ) ;
1036+
1037+ expect ( ( ) => unmount1 ( ) ) . not . toThrow ( ) ;
1038+ expect ( echoInstance . leaveChannel ) . not . toHaveBeenCalled ( ) ;
1039+
1040+ expect ( ( ) => unmount2 ( ) ) . not . toThrow ( ) ;
1041+ expect ( echoInstance . leaveChannel ) . toHaveBeenCalledWith (
1042+ `private-${ channelName } ` ,
1043+ ) ;
1044+ } ) ;
1045+
1046+ it ( "can leave a channel" , async ( ) => {
1047+ const mockCallback = vi . fn ( ) ;
1048+ const channelName = "test-channel" ;
1049+
1050+ const { result } = renderHook ( ( ) =>
1051+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1052+ ) ;
1053+
1054+ result . current . leaveChannel ( ) ;
1055+
1056+ expect ( echoInstance . leaveChannel ) . toHaveBeenCalledWith (
1057+ `private-${ channelName } ` ,
1058+ ) ;
1059+ } ) ;
1060+
1061+ it ( "can leave all channel variations" , async ( ) => {
1062+ const mockCallback = vi . fn ( ) ;
1063+ const channelName = "test-channel" ;
1064+
1065+ const { result } = renderHook ( ( ) =>
1066+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1067+ ) ;
1068+
1069+ result . current . leave ( ) ;
1070+
1071+ expect ( echoInstance . leave ) . toHaveBeenCalledWith ( channelName ) ;
1072+ } ) ;
1073+
1074+ it ( "can manually start and stop listening" , async ( ) => {
1075+ const mockCallback = vi . fn ( ) ;
1076+ const channelName = "test-channel" ;
1077+
1078+ const { result } = renderHook ( ( ) =>
1079+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1080+ ) ;
1081+
1082+ const channel = echoInstance . private ( channelName ) ;
1083+ expect ( channel . notification ) . toHaveBeenCalledTimes ( 1 ) ;
1084+
1085+ result . current . stopListening ( ) ;
1086+ result . current . listen ( ) ;
1087+
1088+ expect ( channel . notification ) . toHaveBeenCalledTimes ( 1 ) ;
1089+ } ) ;
1090+
1091+ it ( "stopListening prevents new notification listeners" , async ( ) => {
1092+ const mockCallback = vi . fn ( ) ;
1093+ const channelName = "test-channel" ;
1094+
1095+ const { result } = renderHook ( ( ) =>
1096+ echoModule . useEchoNotification ( channelName , mockCallback ) ,
1097+ ) ;
1098+
1099+ result . current . stopListening ( ) ;
1100+
1101+ expect ( result . current . stopListening ) . toBeDefined ( ) ;
1102+ expect ( typeof result . current . stopListening ) . toBe ( "function" ) ;
1103+ } ) ;
1104+
1105+ it ( "callback and events are optional" , async ( ) => {
1106+ const channelName = "test-channel" ;
1107+
1108+ const { result } = renderHook ( ( ) =>
1109+ echoModule . useEchoNotification ( channelName ) ,
1110+ ) ;
1111+
1112+ expect ( result . current ) . toHaveProperty ( "channel" ) ;
1113+ expect ( result . current . channel ) . not . toBeNull ( ) ;
1114+ } ) ;
1115+ } ) ;
0 commit comments