diff --git a/app/components/post_list/post/body/content/message_attachments/message_attachment.test.tsx b/app/components/post_list/post/body/content/message_attachments/message_attachment.test.tsx
new file mode 100644
index 000000000..b6f5a1963
--- /dev/null
+++ b/app/components/post_list/post/body/content/message_attachments/message_attachment.test.tsx
@@ -0,0 +1,324 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {render} from '@testing-library/react-native';
+import React from 'react';
+
+import Preferences from '@constants/preferences';
+
+// Mock the child components to focus on testing Boolean checks
+const mockAttachmentThumbnail = jest.fn().mockReturnValue(null);
+jest.mock('./attachment_thumbnail', () => {
+ return function MockAttachmentThumbnail(props: {uri: string}) {
+ mockAttachmentThumbnail(props);
+ return null;
+ };
+});
+
+const mockAttachmentFields = jest.fn().mockReturnValue(null);
+jest.mock('./attachment_fields', () => {
+ return function MockAttachmentFields(props: {fields: MessageAttachmentField[]}) {
+ mockAttachmentFields(props);
+ return null;
+ };
+});
+
+const mockAttachmentFooter = jest.fn().mockReturnValue(null);
+jest.mock('./attachment_footer', () => {
+ return function MockAttachmentFooter(props: {text: string; icon?: string; theme: Theme}) {
+ mockAttachmentFooter(props);
+ return null;
+ };
+});
+
+const mockAttachmentActions = jest.fn().mockReturnValue(null);
+jest.mock('./attachment_actions', () => {
+ return function MockAttachmentActions(props: {actions: PostAction[]; postId: string; theme: Theme}) {
+ mockAttachmentActions(props);
+ return null;
+ };
+});
+
+jest.mock('./attachment_author', () => {
+ return function MockAttachmentAuthor() {
+ return null;
+ };
+});
+
+jest.mock('./attachment_image', () => {
+ return function MockAttachmentImage() {
+ return null;
+ };
+});
+
+jest.mock('./attachment_pretext', () => {
+ return function MockAttachmentPretext() {
+ return null;
+ };
+});
+
+jest.mock('./attachment_text', () => {
+ return function MockAttachmentText() {
+ return null;
+ };
+});
+
+jest.mock('./attachment_title', () => {
+ return function MockAttachmentTitle() {
+ return null;
+ };
+});
+
+// Mock the URL validation function
+jest.mock('@utils/url', () => ({
+ isValidUrl: jest.fn((url) => url === 'https://example.com/image.png'),
+}));
+
+import MessageAttachment from './message_attachment';
+
+describe('MessageAttachment', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const baseProps = {
+ attachment: {
+ id: 1,
+ text: 'This is the text of an attachment',
+ thumb_url: 'https://example.com/image.png',
+ author_name: 'Author Name',
+ author_icon: 'https://example.com/author.png',
+ title: 'Attachment Title',
+ title_link: 'https://example.com',
+ fields: [
+ {
+ title: 'Field Title',
+ value: 'Field Value',
+ short: true,
+ },
+ ],
+ actions: [
+ {
+ id: 'action1',
+ name: 'Action 1',
+ type: 'button',
+ integration: {
+ url: 'https://example.com/action',
+ },
+ },
+ ],
+ footer: 'Attachment Footer',
+ footer_icon: 'https://example.com/footer.png',
+ image_url: 'https://example.com/image.jpg',
+ },
+ channelId: 'channel-id',
+ postId: 'post-id',
+ location: 'CENTER',
+ theme: Preferences.THEMES.denim,
+ metadata: {
+ images: {
+ 'https://example.com/image.jpg': {
+ width: 200,
+ height: 200,
+ format: 'jpg',
+ frame_count: 0,
+ },
+ },
+ },
+ };
+
+ test('it renders without crashing with full attachment data', () => {
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ });
+
+ test('it handles undefined thumb_url without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ thumb_url: undefined,
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
+ });
+
+ test('it handles falsy thumb_url without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ thumb_url: '',
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
+ });
+
+ test('it correctly passes non-null thumb_url to AttachmentThumbnail', () => {
+ render();
+ expect(mockAttachmentThumbnail).toHaveBeenCalledWith(
+ expect.objectContaining({
+ uri: 'https://example.com/image.png',
+ }),
+ );
+ });
+
+ test('it handles undefined fields without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ fields: undefined,
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentFields).not.toHaveBeenCalled();
+ });
+
+ test('it handles empty fields array without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ fields: [],
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentFields).not.toHaveBeenCalled();
+ });
+
+ test('it correctly passes non-null fields to AttachmentFields', () => {
+ render();
+ expect(mockAttachmentFields).toHaveBeenCalledWith(
+ expect.objectContaining({
+ fields: baseProps.attachment.fields,
+ }),
+ );
+ });
+
+ test('it handles undefined footer without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ footer: undefined,
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentFooter).not.toHaveBeenCalled();
+ });
+
+ test('it handles falsy footer without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ footer: '',
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentFooter).not.toHaveBeenCalled();
+ });
+
+ test('it correctly passes non-null footer to AttachmentFooter', () => {
+ render();
+ expect(mockAttachmentFooter).toHaveBeenCalledWith(
+ expect.objectContaining({
+ text: 'Attachment Footer',
+ }),
+ );
+ });
+
+ test('it handles undefined actions without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ actions: undefined,
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentActions).not.toHaveBeenCalled();
+ });
+
+ test('it handles empty actions array without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ ...baseProps.attachment,
+ actions: [],
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentActions).not.toHaveBeenCalled();
+ });
+
+ test('it correctly passes non-null actions to AttachmentActions', () => {
+ render();
+ expect(mockAttachmentActions).toHaveBeenCalledWith(
+ expect.objectContaining({
+ actions: baseProps.attachment.actions,
+ }),
+ );
+ });
+
+ test('it handles multiple undefined properties without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ id: 1,
+ text: 'This is the text of an attachment with multiple undefined properties',
+ thumb_url: undefined,
+ fields: undefined,
+ actions: undefined,
+ footer: undefined,
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
+ expect(mockAttachmentFields).not.toHaveBeenCalled();
+ expect(mockAttachmentFooter).not.toHaveBeenCalled();
+ expect(mockAttachmentActions).not.toHaveBeenCalled();
+ });
+
+ test('it handles mixed falsy properties without crashing', () => {
+ const props = {
+ ...baseProps,
+ attachment: {
+ id: 1,
+ text: 'This is the text of an attachment with mixed falsy properties',
+ thumb_url: '',
+ fields: undefined,
+ actions: [],
+ footer: '',
+ },
+ };
+
+ const wrapper = render();
+ expect(wrapper.toJSON()).toBeTruthy();
+ expect(mockAttachmentThumbnail).not.toHaveBeenCalled();
+ expect(mockAttachmentFields).not.toHaveBeenCalled();
+ expect(mockAttachmentFooter).not.toHaveBeenCalled();
+ expect(mockAttachmentActions).not.toHaveBeenCalled();
+ });
+});
diff --git a/app/components/post_list/post/body/content/message_attachments/message_attachment.tsx b/app/components/post_list/post/body/content/message_attachments/message_attachment.tsx
index a035a2bfe..0f894fe32 100644
--- a/app/components/post_list/post/body/content/message_attachments/message_attachment.tsx
+++ b/app/components/post_list/post/body/content/message_attachments/message_attachment.tsx
@@ -98,8 +98,8 @@ export default function MessageAttachment({attachment, channelId, layoutWidth, l
value={attachment.title}
/>
}
- {attachment.thumb_url && isValidUrl(attachment.thumb_url) &&
-
+ {Boolean(attachment.thumb_url) && isValidUrl(attachment.thumb_url) &&
+
}
{Boolean(attachment.text) &&
}
- {attachment.fields && attachment.fields?.length &&
+ {Boolean(attachment.fields?.length) &&
}
- {attachment.footer &&
+ {Boolean(attachment.footer) &&
}
- {attachment.actions && attachment.actions.length &&
+ {Boolean(attachment.actions && attachment.actions.length) &&