@@ -13,6 +13,7 @@ import {
1313 LocalMessage ,
1414 ReactionResponse ,
1515 SortParamRequest ,
16+ StreamAPIError ,
1617 UserResponse ,
1718} from 'stream-chat' ;
1819import { ChannelService } from './channel.service' ;
@@ -33,6 +34,17 @@ import { NotificationService } from './notification.service';
3334import { toLocalMessage } from './local-message' ;
3435import { AttachmentUpload , MessageInput , StreamMessage } from './types' ;
3536
37+ // The error stream-chat v10 throws for API errors (see api-client.ts)
38+ const apiError = ( message : string , code = 4 , status = 400 ) =>
39+ new StreamAPIError ( message , {
40+ code,
41+ status,
42+ response : {
43+ data : { code, message, StatusCode : status } ,
44+ status,
45+ } as unknown as StreamAPIError [ 'response' ] ,
46+ } ) ;
47+
3648type TestChatClient = Omit <
3749 MockStreamChatClient ,
3850 'channel' | 'channelManager'
@@ -223,7 +235,7 @@ describe('ChannelService', () => {
223235 const notificationSpy = jasmine . createSpy ( ) ;
224236 notificationService . notifications$ . subscribe ( notificationSpy ) ;
225237 notificationSpy . calls . reset ( ) ;
226- const error = 'there was an error' ;
238+ const error = apiError ( 'QueryChannels failed with error: "error"' ) ;
227239
228240 await expectAsync (
229241 init ( undefined , undefined , undefined , ( ) =>
@@ -249,7 +261,9 @@ describe('ChannelService', () => {
249261
250262 await expectAsync (
251263 init ( undefined , undefined , undefined , ( ) =>
252- mockChatClient . queryChannels . and . rejectWith ( 'there was an error' )
264+ mockChatClient . queryChannels . and . rejectWith (
265+ apiError ( 'QueryChannels failed with error: "error"' )
266+ )
253267 )
254268 ) . toBeRejected ( ) ;
255269
@@ -283,20 +297,21 @@ describe('ChannelService', () => {
283297 expect ( calls [ 2 ] . args [ 0 ] . state ) . toBe ( 'success' ) ;
284298
285299 spy . calls . reset ( ) ;
300+ const error = apiError ( 'QueryChannels failed with error: "error"' ) ;
286301
287302 await expectAsync (
288303 init ( undefined , undefined , undefined , ( ) =>
289- mockChatClient . queryChannels . and . rejectWith ( 'there was an error' )
304+ mockChatClient . queryChannels . and . rejectWith ( error )
290305 )
291- ) . toBeRejected ( ) ;
306+ ) . toBeRejectedWith ( error ) ;
292307
293308 calls = spy . calls . all ( ) ;
294309
295310 expect ( calls [ 0 ] . args [ 0 ] ?. state ) . toBe ( 'in-progress' ) ;
296311
297312 expect ( calls [ 1 ] . args [ 0 ] ) . toEqual ( {
298313 state : 'error' ,
299- error : 'there was an error' ,
314+ error,
300315 } ) ;
301316
302317 /* eslint-enable @typescript-eslint/no-unsafe-member-access */
@@ -612,7 +627,7 @@ describe('ChannelService', () => {
612627 const channel = generateMockChannels ( 1 ) [ 0 ] ;
613628 channel . cid = 'not-initialized' ;
614629 ( channel as unknown as { initialized : boolean } ) . initialized = false ;
615- const error = new Error ( 'watch failed') ;
630+ const error = apiError ( 'GetOrCreateChannel failed with error: "error" ') ;
616631 spyOn ( channel , 'watch' ) . and . rejectWith ( error ) ;
617632 const notificationService = TestBed . inject ( NotificationService ) ;
618633 spyOn ( notificationService , 'addTemporaryNotification' ) ;
@@ -2121,16 +2136,12 @@ describe('ChannelService', () => {
21212136 service . activeChannelMessages$ . subscribe ( ( m ) => {
21222137 latestMessage = m [ m . length - 1 ] ;
21232138 } ) ;
2124- spyOn ( channel , 'sendMessage' ) . and . rejectWith ( {
2125- code : 4 ,
2126- status : 400 ,
2127- response : {
2128- data : {
2129- message :
2130- 'SendMessage failed with error: "a message with ID zitaszuperagetstreamio-9005f6d8-aeb7-42a1-984c-921f94567759 already exists"' ,
2131- } ,
2132- } ,
2133- } ) ;
2139+ spyOn ( channel , 'sendMessage' ) . and . rejectWith (
2140+ new StreamAPIError (
2141+ 'SendMessage failed with error: "a message with ID zitaszuperagetstreamio-9005f6d8-aeb7-42a1-984c-921f94567759 already exists"' ,
2142+ { code : 4 , status : 400 , response : undefined }
2143+ )
2144+ ) ;
21342145 spyOn ( channel . messagePaginator , 'ingestItem' ) . and . callThrough ( ) ;
21352146 await service . resendMessage ( latestMessage ) ;
21362147
@@ -2277,25 +2288,17 @@ describe('ChannelService', () => {
22772288 ( { file } : { file : File } ) => {
22782289 switch ( file . name ) {
22792290 case 'file_error.jpg' :
2280- return Promise . reject ( {
2281- response : {
2282- data : {
2283- code : 4 ,
2284- message :
2285- 'UploadImage failed with error: "File extension .jpg is not supported"' ,
2286- } ,
2287- } ,
2288- } ) ;
2291+ return Promise . reject (
2292+ apiError (
2293+ 'UploadImage failed with error: "File extension .jpg is not supported"'
2294+ )
2295+ ) ;
22892296 case 'file_too_big.jpg' :
2290- return Promise . reject ( {
2291- response : {
2292- data : {
2293- code : 4 ,
2294- message :
2295- 'UploadImage failed with error: "File size is above the size limit (1048576 bytes)"' ,
2296- } ,
2297- } ,
2298- } ) ;
2297+ return Promise . reject (
2298+ apiError (
2299+ 'UploadImage failed with error: "File size is above the size limit (1048576 bytes)"'
2300+ )
2301+ ) ;
22992302 default :
23002303 return Promise . resolve ( {
23012304 file : 'http://url/to/image' ,
@@ -2308,15 +2311,13 @@ describe('ChannelService', () => {
23082311 ( { file } : { file : File } ) => {
23092312 switch ( file . name ) {
23102313 case 'file_error.pdf' :
2311- return Promise . reject ( {
2312- response : {
2313- data : {
2314- code : 22 ,
2315- message :
2316- 'UploadFile failed with error: "max upload size of 50MB exceeded"' ,
2317- } ,
2318- } ,
2319- } ) ;
2314+ return Promise . reject (
2315+ apiError (
2316+ 'UploadFile failed with error: "max upload size of 50MB exceeded"' ,
2317+ 22 ,
2318+ 413
2319+ )
2320+ ) ;
23202321 default :
23212322 return Promise . resolve ( {
23222323 file : 'http://url/to/file' ,
@@ -2997,7 +2998,7 @@ describe('ChannelService', () => {
29972998 service . activeChannelMessages$ . subscribe ( messagesSpy ) ;
29982999 messagesSpy . calls . reset ( ) ;
29993000 const paginator = mockPaginator ( getActiveChannel ( ) ) ;
3000- const error = new Error ( ) ;
3001+ const error = apiError ( 'GetOrCreateChannel failed with error: "error"' ) ;
30013002 spyOn ( paginator , 'jumpToMessage' ) . and . rejectWith ( error ) ;
30023003 const notificationService = TestBed . inject ( NotificationService ) ;
30033004 spyOn ( notificationService , 'addTemporaryNotification' ) ;
@@ -3012,6 +3013,34 @@ describe('ChannelService', () => {
30123013 expect ( service . isMessageLoadingInProgress ) . toBeFalse ( ) ;
30133014 } ) ;
30143015
3016+ it ( `should display error notification if message couldn't be loaded - paginator doesn't throw API errors` , async ( ) => {
3017+ await init ( ) ;
3018+ const jumpToMessageIdSpy = jasmine . createSpy ( ) ;
3019+ service . jumpToMessage$ . subscribe ( jumpToMessageIdSpy ) ;
3020+ jumpToMessageIdSpy . calls . reset ( ) ;
3021+ const paginator = mockPaginator ( getActiveChannel ( ) ) ;
3022+ // By default (throwErrors: false) the stream-chat paginator records the error and resolves to false
3023+ spyOn ( paginator , 'jumpToMessage' ) . and . callFake ( ( ) => {
3024+ paginator . state . partialNext ( {
3025+ lastQueryError : apiError (
3026+ 'GetOrCreateChannel failed with error: "error"' ,
3027+ - 1 ,
3028+ 500
3029+ ) ,
3030+ } ) ;
3031+ return Promise . resolve ( false ) ;
3032+ } ) ;
3033+ const notificationService = TestBed . inject ( NotificationService ) ;
3034+ spyOn ( notificationService , 'addTemporaryNotification' ) ;
3035+ await expectAsync ( service . jumpToMessage ( '1232121123' ) ) . toBeRejected ( ) ;
3036+
3037+ expect ( jumpToMessageIdSpy ) . not . toHaveBeenCalled ( ) ;
3038+ expect ( notificationService . addTemporaryNotification ) . toHaveBeenCalledWith (
3039+ 'streamChat.Message not found'
3040+ ) ;
3041+ expect ( service . isMessageLoadingInProgress ) . toBeFalse ( ) ;
3042+ } ) ;
3043+
30153044 it ( `should display error notification if message wasn't found` , async ( ) => {
30163045 await init ( ) ;
30173046 const jumpToMessageIdSpy = jasmine . createSpy ( ) ;
@@ -3124,7 +3153,7 @@ describe('ChannelService', () => {
31243153 it ( 'should jump to first unread message - error' , async ( ) => {
31253154 await init ( ) ;
31263155 const paginator = mockPaginator ( getActiveChannel ( ) ) ;
3127- const error = new Error ( ' error') ;
3156+ const error = apiError ( 'GetOrCreateChannel failed with error: "error" ') ;
31283157 spyOn ( paginator , 'jumpToTheFirstUnreadMessage' ) . and . rejectWith ( error ) ;
31293158 const notificationService = TestBed . inject ( NotificationService ) ;
31303159 spyOn ( notificationService , 'addTemporaryNotification' ) ;
@@ -3139,6 +3168,47 @@ describe('ChannelService', () => {
31393168 expect ( service . isMessageLoadingInProgress ) . toBeFalse ( ) ;
31403169 } ) ;
31413170
3171+ it ( `should jump to first unread message - error, paginator doesn't throw API errors` , async ( ) => {
3172+ await init ( ) ;
3173+ const paginator = mockPaginator ( getActiveChannel ( ) ) ;
3174+ const error = apiError (
3175+ 'GetOrCreateChannel failed with error: "error"' ,
3176+ - 1 ,
3177+ 500
3178+ ) ;
3179+ spyOn ( paginator , 'jumpToTheFirstUnreadMessage' ) . and . callFake ( ( ) => {
3180+ paginator . state . partialNext ( { lastQueryError : error } ) ;
3181+ return Promise . resolve ( false ) ;
3182+ } ) ;
3183+ const notificationService = TestBed . inject ( NotificationService ) ;
3184+ spyOn ( notificationService , 'addTemporaryNotification' ) ;
3185+
3186+ await expectAsync ( service . jumpToFirstUnreadMessage ( ) ) . toBeRejectedWith (
3187+ error
3188+ ) ;
3189+
3190+ expect ( notificationService . addTemporaryNotification ) . toHaveBeenCalledWith (
3191+ 'streamChat.Message not found'
3192+ ) ;
3193+ expect ( service . isMessageLoadingInProgress ) . toBeFalse ( ) ;
3194+ } ) ;
3195+
3196+ it ( `shouldn't display error notification if there is no unread message to jump to` , async ( ) => {
3197+ await init ( ) ;
3198+ const paginator = mockPaginator ( getActiveChannel ( ) ) ;
3199+ // An error of an earlier query shouldn't be reported again
3200+ paginator . state . partialNext ( {
3201+ lastQueryError : apiError ( 'earlier error' , - 1 , 500 ) ,
3202+ } ) ;
3203+ spyOn ( paginator , 'jumpToTheFirstUnreadMessage' ) . and . resolveTo ( false ) ;
3204+ const notificationService = TestBed . inject ( NotificationService ) ;
3205+ spyOn ( notificationService , 'addTemporaryNotification' ) ;
3206+
3207+ expect ( await service . jumpToFirstUnreadMessage ( ) ) . toBeFalse ( ) ;
3208+ expect ( notificationService . addTemporaryNotification ) . not . toHaveBeenCalled ( ) ;
3209+ expect ( service . isMessageLoadingInProgress ) . toBeFalse ( ) ;
3210+ } ) ;
3211+
31423212 it ( 'should set active parent message' , async ( ) => {
31433213 await init ( ) ;
31443214 const channel = getActiveChannel ( ) ;
@@ -3252,7 +3322,7 @@ describe('ChannelService', () => {
32523322 parentMessage : channel . messagePaginator . getItem ( parentMessage . id ) ! ,
32533323 } ) ;
32543324 thread . hasStaleState = true ;
3255- const error = new Error ( 'reload failed') ;
3325+ const error = apiError ( 'GetThread failed with error: "error" ') ;
32563326 spyOn ( thread , 'reload' ) . and . rejectWith ( error ) ;
32573327 const notificationService = TestBed . inject ( NotificationService ) ;
32583328 spyOn ( notificationService , 'addTemporaryNotification' ) ;
@@ -3304,7 +3374,7 @@ describe('ChannelService', () => {
33043374 const message = mockMessage ( ) ;
33053375 const notificationService = TestBed . inject ( NotificationService ) ;
33063376 spyOn ( notificationService , 'addTemporaryNotification' ) . and . callThrough ( ) ;
3307- const error = new Error ( ' error') ;
3377+ const error = apiError ( 'UpdateMessagePartial failed with error: "error" ') ;
33083378 mockChatClient . pinMessage . and . rejectWith ( error ) ;
33093379
33103380 await expectAsync ( service . pinMessage ( message ) ) . toBeRejectedWith ( error ) ;
@@ -3329,7 +3399,7 @@ describe('ChannelService', () => {
33293399 const message = mockMessage ( ) ;
33303400 const notificationService = TestBed . inject ( NotificationService ) ;
33313401 spyOn ( notificationService , 'addTemporaryNotification' ) . and . callThrough ( ) ;
3332- const error = new Error ( ' error') ;
3402+ const error = apiError ( 'UpdateMessagePartial failed with error: "error" ') ;
33333403 mockChatClient . unpinMessage . and . rejectWith ( error ) ;
33343404
33353405 await expectAsync ( service . unpinMessage ( message ) ) . toBeRejectedWith ( error ) ;
@@ -3573,7 +3643,7 @@ describe('ChannelService', () => {
35733643 it ( 'should load message reactions - error' , async ( ) => {
35743644 await init ( ) ;
35753645 const message = service . activeChannelMessages [ 0 ] ;
3576- const error = new Error ( 'Reactions loading failed') ;
3646+ const error = apiError ( 'GetReactions failed with error: "error" ') ;
35773647 mockChatClient . getReactions . and . rejectWith ( error ) ;
35783648 const notificationService = TestBed . inject ( NotificationService ) ;
35793649 const notificationSpy = jasmine . createSpy ( ) ;
@@ -3607,12 +3677,10 @@ describe('ChannelService', () => {
36073677 const activeChannel = getActiveChannel ( ) ;
36083678 const message = service . activeChannelMessages [ 0 ] ;
36093679
3610- const error = {
3611- response : {
3612- data : { code : 4 , StatusCode : 400 , message : 'You made a mistake' } ,
3613- } ,
3614- } ;
3615- spyOn ( activeChannel , 'markUnread' ) . and . rejectWith ( error ) ;
3680+ let error = apiError ( 'You made a mistake' ) ;
3681+ spyOn ( activeChannel , 'markUnread' ) . and . callFake ( ( ) =>
3682+ Promise . reject ( error )
3683+ ) ;
36163684 const spy = jasmine . createSpy ( ) ;
36173685 const notificationService = TestBed . inject ( NotificationService ) ;
36183686 notificationService . notifications$ . subscribe ( spy ) ;
@@ -3627,8 +3695,9 @@ describe('ChannelService', () => {
36273695 } ) ,
36283696 ] ) ;
36293697
3630- error . response . data . message =
3631- 'MarkUnread failed with error: "Either the message with ID "zitaszuperagetstreamio-c710c117-5a8e-4769-a43d-f6add79d8520" does not exist, or it is older than last 100 channel messages."' ;
3698+ error = apiError (
3699+ 'MarkUnread failed with error: "Either the message with ID "zitaszuperagetstreamio-c710c117-5a8e-4769-a43d-f6add79d8520" does not exist, or it is older than last 100 channel messages."'
3700+ ) ;
36323701 spy . calls . reset ( ) ;
36333702 await expectAsync ( service . markMessageUnread ( message . id ) ) . toBeRejectedWith (
36343703 error
0 commit comments