Skip to content

Commit 096de30

Browse files
authored
fix: recognize async moderated shadow blocked messages as blocked (#3132)
https://linear.app/stream/issue/REACT-945/handle-shadow-blocked-images-in-react-chat-sdk `isMessageBlocked` method decides if we should display the `MessageBlocked` placeholder for a message. This method didn't categorize `shadowed` messages as blocked (probably because these messages are not displayed in message list, except when they change from `shadowed: false` to `shadowed: true`) _Provide a description of the implementation_ _Add relevant screenshots_ ### 🎯 Goal _Describe why we are making this change_ ### 🛠 Implementation details _Provide a description of the implementation_ ### 🎨 UI Changes _Add relevant screenshots_
1 parent 01d92a3 commit 096de30

5 files changed

Lines changed: 63 additions & 45 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@
234234
"react": "^19.0.0",
235235
"react-dom": "^19.0.0",
236236
"semantic-release": "^25.0.2",
237-
"stream-chat": "^9.38.0",
237+
"stream-chat": "^9.41.1",
238238
"ts-jest": "^29.2.5",
239239
"typescript": "^5.4.5",
240240
"typescript-eslint": "^8.17.0"

src/components/Message/utils.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -497,11 +497,12 @@ export const isMessageBounced = (
497497
message.moderation?.action === 'bounce');
498498

499499
export const isMessageBlocked = (
500-
message: Pick<LocalMessage, 'type' | 'moderation' | 'moderation_details'>,
500+
message: Pick<LocalMessage, 'type' | 'moderation' | 'moderation_details' | 'shadowed'>,
501501
) =>
502-
message.type === 'error' &&
503-
(message.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_REMOVE' ||
504-
message.moderation?.action === 'remove');
502+
message.shadowed ||
503+
(message.type === 'error' &&
504+
(message.moderation_details?.action === 'MESSAGE_RESPONSE_ACTION_REMOVE' ||
505+
message.moderation?.action === 'remove'));
505506

506507
export const isMessageEdited = (message: Pick<LocalMessage, 'message_text_updated_at'>) =>
507508
!!message.message_text_updated_at;

src/components/MessageInput/__tests__/EditMessageForm.test.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,27 @@ const fileUploadUrl = 'http://www.getstream.io'; // real url, because ImageAttac
9191
const getImage = () => new File(['content'], filename, { type: 'image/png' });
9292
const getFile = (name = filename) => new File(['content'], name, { type: 'text/plain' });
9393

94+
/** Matches Channel.sendFile / sendImage (uri, name?, contentType?, user?, axiosRequestConfig?). */
95+
const expectChannelUploadSendFile = (spy, file) => {
96+
expect(spy).toHaveBeenCalledWith(
97+
file,
98+
undefined,
99+
undefined,
100+
undefined,
101+
expect.objectContaining({ onUploadProgress: expect.any(Function) }),
102+
);
103+
};
104+
105+
const expectChannelUploadSendImage = (spy, image) => {
106+
expect(spy).toHaveBeenCalledWith(
107+
image,
108+
undefined,
109+
undefined,
110+
undefined,
111+
expect.objectContaining({ onUploadProgress: expect.any(Function) }),
112+
);
113+
};
114+
94115
const sendMessageMock = jest.fn();
95116
const editMock = jest.fn();
96117
const mockAddNotification = jest.fn();
@@ -440,8 +461,8 @@ describe(`EditMessageForm`, () => {
440461
});
441462
const filenameTexts = await screen.findAllByTitle(filename);
442463
await waitFor(() => {
443-
expect(sendFileSpy).toHaveBeenCalledWith(file);
444-
expect(sendImageSpy).toHaveBeenCalledWith(image);
464+
expectChannelUploadSendFile(sendFileSpy, file);
465+
expectChannelUploadSendImage(sendImageSpy, image);
445466
expect(screen.getByTestId(IMAGE_PREVIEW_TEST_ID)).toBeInTheDocument();
446467
expect(screen.getByTestId(FILE_PREVIEW_TEST_ID)).toBeInTheDocument();
447468
filenameTexts.forEach((filenameText) => expect(filenameText).toBeInTheDocument());
@@ -512,7 +533,7 @@ describe(`EditMessageForm`, () => {
512533
dropFile(file, formElement);
513534
});
514535
await waitFor(() => {
515-
expect(sendImageSpy).toHaveBeenCalledWith(file);
536+
expectChannelUploadSendImage(sendImageSpy, file);
516537
});
517538
const results = await axe(container);
518539
expect(results).toHaveNoViolations();
@@ -579,7 +600,7 @@ describe(`EditMessageForm`, () => {
579600
act(() => dropFile(file, formElement));
580601

581602
await waitFor(() => {
582-
expect(sendFileSpy).toHaveBeenCalledWith(file);
603+
expectChannelUploadSendFile(sendFileSpy, file);
583604
});
584605

585606
sendFileSpy.mockImplementationOnce(() => Promise.resolve({ file }));
@@ -590,7 +611,7 @@ describe(`EditMessageForm`, () => {
590611

591612
await waitFor(() => {
592613
expect(sendFileSpy).toHaveBeenCalledTimes(2);
593-
expect(sendFileSpy).toHaveBeenCalledWith(file);
614+
expectChannelUploadSendFile(sendFileSpy, file);
594615
});
595616
await axeNoViolations(container);
596617
});

src/components/MessageInput/__tests__/MessageInput.test.js

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ const fileUploadUrl = 'http://www.getstream.io'; // real url, because ImageAttac
7979
const getImage = () => new File(['content'], filename, { type: 'image/png' });
8080
const getFile = (name = filename) => new File(['content'], name, { type: 'text/plain' });
8181

82+
/** Matches Channel.sendFile / sendImage (uri, name?, contentType?, user?, axiosRequestConfig?). */
83+
const expectChannelUploadSendFile = (spy, file) => {
84+
expect(spy).toHaveBeenCalledWith(
85+
file,
86+
undefined,
87+
undefined,
88+
undefined,
89+
expect.objectContaining({ onUploadProgress: expect.any(Function) }),
90+
);
91+
};
92+
93+
const expectChannelUploadSendImage = (spy, image) => {
94+
expect(spy).toHaveBeenCalledWith(
95+
image,
96+
undefined,
97+
undefined,
98+
undefined,
99+
expect.objectContaining({ onUploadProgress: expect.any(Function) }),
100+
);
101+
};
102+
82103
const sendMessageMock = jest.fn();
83104
const mockAddNotification = jest.fn();
84105

@@ -411,8 +432,8 @@ describe(`MessageInputFlat`, () => {
411432
});
412433
const filenameTexts = await screen.findAllByTitle(filename);
413434
await waitFor(() => {
414-
expect(sendFileSpy).toHaveBeenCalledWith(file);
415-
expect(sendImageSpy).toHaveBeenCalledWith(image);
435+
expectChannelUploadSendFile(sendFileSpy, file);
436+
expectChannelUploadSendImage(sendImageSpy, image);
416437
expect(screen.getByTestId(IMAGE_PREVIEW_TEST_ID)).toBeInTheDocument();
417438
expect(screen.getByTestId(FILE_PREVIEW_TEST_ID)).toBeInTheDocument();
418439
filenameTexts.forEach((filenameText) => expect(filenameText).toBeInTheDocument());
@@ -483,7 +504,7 @@ describe(`MessageInputFlat`, () => {
483504
dropFile(file, formElement);
484505
});
485506
await waitFor(() => {
486-
expect(sendImageSpy).toHaveBeenCalledWith(file);
507+
expectChannelUploadSendImage(sendImageSpy, file);
487508
});
488509
const results = await axe(container);
489510
expect(results).toHaveNoViolations();

yarn.lock

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -11936,10 +11936,10 @@ stdin-discarder@^0.2.2:
1193611936
resolved "https://registry.yarnpkg.com/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be"
1193711937
integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==
1193811938

11939-
stream-chat@^9.38.0:
11940-
version "9.38.0"
11941-
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.38.0.tgz#5c13eb8bbc2fa4adb774687b0c9c51f129d1b458"
11942-
integrity sha512-nyTFKHnhGfk1Op/xuZzPKzM9uNTy4TBma69+ApwGj/UtrK2pT6rSaU0Qy/oAqub+Bh7jR2/5vlV/8FWJ2BObFg==
11939+
stream-chat@^9.41.1:
11940+
version "9.41.1"
11941+
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.41.1.tgz#a877c8aa800d78b497eec2fad636345d4422309c"
11942+
integrity sha512-W8zjfINYol2UtdRMz2t/NN2GyjDrvb4pJgKmhtuRYzCY1u0Cjezcsu5OCNgyAM0QsenlY6tRqnvAU8Qam5R49Q==
1194311943
dependencies:
1194411944
"@types/jsonwebtoken" "^9.0.8"
1194511945
"@types/ws" "^8.5.14"
@@ -11984,16 +11984,7 @@ string-length@^4.0.1:
1198411984
char-regex "^1.0.2"
1198511985
strip-ansi "^6.0.0"
1198611986

11987-
"string-width-cjs@npm:string-width@^4.2.0":
11988-
version "4.2.3"
11989-
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
11990-
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
11991-
dependencies:
11992-
emoji-regex "^8.0.0"
11993-
is-fullwidth-code-point "^3.0.0"
11994-
strip-ansi "^6.0.1"
11995-
11996-
string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
11987+
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
1199711988
version "4.2.3"
1199811989
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
1199911990
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -12118,7 +12109,7 @@ stringify-entities@^4.0.0:
1211812109
character-entities-html4 "^2.0.0"
1211912110
character-entities-legacy "^3.0.0"
1212012111

12121-
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
12112+
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
1212212113
version "6.0.1"
1212312114
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
1212412115
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -12132,13 +12123,6 @@ strip-ansi@^3.0.0:
1213212123
dependencies:
1213312124
ansi-regex "^2.0.0"
1213412125

12135-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
12136-
version "6.0.1"
12137-
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
12138-
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
12139-
dependencies:
12140-
ansi-regex "^5.0.1"
12141-
1214212126
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
1214312127
version "7.1.0"
1214412128
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -13227,7 +13211,7 @@ wordwrap@^1.0.0:
1322713211
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
1322813212
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
1322913213

13230-
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
13214+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
1323113215
version "7.0.0"
1323213216
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
1323313217
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -13245,15 +13229,6 @@ wrap-ansi@^6.2.0:
1324513229
string-width "^4.1.0"
1324613230
strip-ansi "^6.0.0"
1324713231

13248-
wrap-ansi@^7.0.0:
13249-
version "7.0.0"
13250-
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
13251-
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
13252-
dependencies:
13253-
ansi-styles "^4.0.0"
13254-
string-width "^4.1.0"
13255-
strip-ansi "^6.0.0"
13256-
1325713232
wrap-ansi@^8.1.0:
1325813233
version "8.1.0"
1325913234
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

0 commit comments

Comments
 (0)