diff --git a/app/components/draft/__snapshots__/draft.test.tsx.snap b/app/components/draft/__snapshots__/draft.test.tsx.snap
new file mode 100644
index 0000000000..46ae0cc841
--- /dev/null
+++ b/app/components/draft/__snapshots__/draft.test.tsx.snap
@@ -0,0 +1,1102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Draft should match the file count 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file1.txt
+
+
+
+
+ 64 B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ file2.pdf
+
+
+
+
+ 64 B
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Draft should render the draft with channel info and draft message 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Draft should render the draft with channel info and draft message for a thread 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Draft should render the draft with post priority 1`] = `
+
+
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+
+
+
+
+ IMPORTANT
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/draft/draft.test.tsx b/app/components/draft/draft.test.tsx
new file mode 100644
index 0000000000..42b579d883
--- /dev/null
+++ b/app/components/draft/draft.test.tsx
@@ -0,0 +1,190 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import CompassIcon from '@components/compass_icon';
+import FormattedText from '@components/formatted_text';
+import {General} from '@constants';
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import Draft from './draft';
+
+import type {Database} from '@nozbe/watermelondb';
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type DraftModel from '@typings/database/models/servers/draft';
+
+jest.mock('@components/formatted_text', () => jest.fn(() => null));
+jest.mock('@components/formatted_time', () => jest.fn(() => null));
+jest.mock('@components/compass_icon', () => jest.fn(() => null));
+
+describe('Draft', () => {
+ let database: Database;
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+ });
+ it('should render the draft with channel info and draft message', () => {
+ const props = {
+ channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ location: 'channel',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ isPostPriorityEnabled: false,
+ };
+ const wrapper = renderWithEverything(
+
+ , {database},
+ );
+
+ const {getByText} = wrapper;
+
+ expect(FormattedText).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: 'channel_info.draft_in_channel',
+ defaultMessage: 'In:',
+ }),
+ expect.anything(),
+ );
+ expect(CompassIcon).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'globe',
+ }),
+ expect.anything(),
+ );
+ expect(getByText('Hello, World!')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should match the file count', () => {
+ const props = {
+ channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ location: 'channel',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [{
+ has_preview_image: false,
+ height: 0,
+ name: 'file1.txt',
+ extension: 'txt',
+ size: 64,
+ }, {
+ has_preview_image: false,
+ height: 0,
+ name: 'file2.pdf',
+ extension: 'txt',
+ size: 64,
+ }],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ isPostPriorityEnabled: false,
+ };
+ const wrapper = renderWithEverything(
+
+ , {database},
+ );
+ const {getAllByTestId} = wrapper;
+ expect(getAllByTestId('file_attachment')).toHaveLength(2);
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render the draft with channel info and draft message for a thread', () => {
+ const props = {
+ channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ location: 'thread',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: 'root_id',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ isPostPriorityEnabled: false,
+ };
+ const wrapper = renderWithEverything(
+
+ , {database},
+ );
+
+ const {getByText} = wrapper;
+ expect(FormattedText).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: 'channel_info.thread_in',
+ defaultMessage: 'Thread in:',
+ }),
+ expect.anything(),
+ );
+
+ expect(CompassIcon).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'globe',
+ }),
+ expect.anything(),
+ );
+ expect(getByText('Hello, World!')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render the draft with post priority', () => {
+ const props = {
+ channel: {type: General.OPEN_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ location: 'thread',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: 'root_id',
+ files: [],
+ metadata: {priority: {priority: 'important', requested_ack: false}},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ isPostPriorityEnabled: true,
+ };
+ const wrapper = renderWithEverything(
+
+ , {database},
+ );
+ const {getByText} = wrapper;
+ expect(getByText('IMPORTANT')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap b/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap
new file mode 100644
index 0000000000..bb98c07a3d
--- /dev/null
+++ b/app/components/draft/draft_post/__snapshots__/draft_message.test.tsx.snap
@@ -0,0 +1,63 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Draft Message should match snapshot 1`] = `
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+`;
diff --git a/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap b/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap
new file mode 100644
index 0000000000..6b951f3879
--- /dev/null
+++ b/app/components/draft/draft_post/__snapshots__/draft_post.test.tsx.snap
@@ -0,0 +1,72 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Draft Post should match the snapshot 1`] = `
+
+
+
+
+
+
+
+
+ Hello, World!
+
+
+
+
+
+
+
+
+`;
diff --git a/app/components/draft/draft_post/draft_message.test.tsx b/app/components/draft/draft_post/draft_message.test.tsx
new file mode 100644
index 0000000000..445d2c13e4
--- /dev/null
+++ b/app/components/draft/draft_post/draft_message.test.tsx
@@ -0,0 +1,59 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import DraftMessage from './draft_message';
+
+import type {Database} from '@nozbe/watermelondb';
+import type DraftModel from '@typings/database/models/servers/draft';
+
+describe('Draft Message', () => {
+ let database: Database;
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+ });
+ it('should render the message', () => {
+ const props = {
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ location: 'draft',
+ };
+ const {getByText} = renderWithEverything(
+ , {database},
+ );
+ expect(getByText('Hello, World!')).toBeTruthy();
+ });
+
+ it('should match snapshot', () => {
+ const props = {
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ location: 'draft',
+ };
+ const wrapper = renderWithEverything(
+ , {database},
+ );
+
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/draft/draft_post/draft_post.test.tsx b/app/components/draft/draft_post/draft_post.test.tsx
new file mode 100644
index 0000000000..0151e62912
--- /dev/null
+++ b/app/components/draft/draft_post/draft_post.test.tsx
@@ -0,0 +1,40 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import DraftPost from '.';
+
+import type {Database} from '@nozbe/watermelondb';
+import type DraftModel from '@typings/database/models/servers/draft';
+
+describe('Draft Post', () => {
+ let database: Database;
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+ });
+ it('should match the snapshot', () => {
+ const props = {
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ layoutWidth: 100,
+ location: 'draft',
+ };
+ const wrapper = renderWithEverything(
+ , {database},
+ );
+
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap b/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap
new file mode 100644
index 0000000000..d7e92523bf
--- /dev/null
+++ b/app/components/draft_post_header/__snapshots__/channel_info.test.tsx.snap
@@ -0,0 +1,324 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`DraftPostHeader Component renders CompassIcon when draftReceiverUser is not provided 1`] = `
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+`;
+
+exports[`DraftPostHeader Component renders correctly for a DM channel 1`] = `
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+`;
+
+exports[`DraftPostHeader Component renders correctly for a public channel 1`] = `
+
+
+
+
+
+
+
+
+ Public Channel
+
+
+
+`;
+
+exports[`DraftPostHeader Component renders correctly for a thread 1`] = `
+
+
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+`;
+
+exports[`DraftPostHeader Component renders the Avatar when draftReceiverUser is provided 1`] = `
+
+
+
+
+
+
+ Direct Message Channel
+
+
+
+`;
diff --git a/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap b/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap
new file mode 100644
index 0000000000..706c7472ec
--- /dev/null
+++ b/app/components/draft_post_header/__snapshots__/profile_avatar.test.tsx.snap
@@ -0,0 +1,102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`ProfileAvatar Component renders the avatar image when URI is available 1`] = `
+
+
+
+`;
+
+exports[`ProfileAvatar Component renders the fallback icon when URI is not available 1`] = `
+
+
+
+`;
+
+exports[`ProfileAvatar Component renders the fallback icon when author is not provided 1`] = `
+
+
+
+`;
diff --git a/app/components/draft_post_header/channel_info.test.tsx b/app/components/draft_post_header/channel_info.test.tsx
new file mode 100644
index 0000000000..2c1dab5d57
--- /dev/null
+++ b/app/components/draft_post_header/channel_info.test.tsx
@@ -0,0 +1,164 @@
+// 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 CompassIcon from '@components/compass_icon';
+import FormattedText from '@components/formatted_text';
+import FormattedTime from '@components/formatted_time';
+import {General} from '@constants';
+import {useTheme} from '@context/theme';
+import {getUserTimezone} from '@utils/user';
+
+import DraftPostHeader from './draft_post_header';
+import ProfileAvatar from './profile_avatar';
+
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type UserModel from '@typings/database/models/servers/user';
+
+jest.mock('@context/theme', () => ({
+ useTheme: jest.fn(),
+}));
+
+jest.mock('@components/formatted_text', () => jest.fn(() => null));
+jest.mock('@components/formatted_time', () => jest.fn(() => null));
+jest.mock('./profile_avatar', () => jest.fn(() => null));
+jest.mock('@components/compass_icon', () => jest.fn(() => null));
+jest.mock('@utils/user', () => ({
+ getUserTimezone: jest.fn(),
+}));
+
+describe('DraftPostHeader Component', () => {
+ const mockTheme = {
+ centerChannelColor: '#000000',
+ };
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ (useTheme as jest.Mock).mockReturnValue(mockTheme);
+ (getUserTimezone as jest.Mock).mockReturnValue('UTC');
+ });
+
+ it('renders correctly for a DM channel', () => {
+ const baseProps = {
+ channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ draftReceiverUser: undefined,
+ updateAt: 1633024800000,
+ rootId: undefined,
+ testID: 'channel-info',
+ currentUser: {timezone: 'UTC'} as unknown as UserModel,
+ isMilitaryTime: true,
+ };
+
+ const wrapper = render();
+ expect(CompassIcon).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'globe',
+ }),
+ expect.anything(),
+ );
+ expect(FormattedTime).toHaveBeenCalledWith(
+ expect.objectContaining({
+ timezone: 'UTC',
+ isMilitaryTime: true,
+ value: 1633024800000,
+ }),
+ expect.anything(),
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders correctly for a public channel', () => {
+ const baseProps = {
+ channel: {type: General.OPEN_CHANNEL, displayName: 'Public Channel', createAt: 0, creatorId: '', deleteAt: 0, updateAt: 0} as ChannelModel,
+ draftReceiverUser: undefined,
+ updateAt: 1633024800000,
+ rootId: undefined,
+ testID: 'channel-info',
+ currentUser: {timezone: 'UTC'} as unknown as UserModel,
+ isMilitaryTime: true,
+ };
+ const wrapper = render();
+ expect(FormattedText).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: 'channel_info.draft_in_channel',
+ defaultMessage: 'In:',
+ }),
+ expect.anything(),
+ );
+ expect(CompassIcon).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'globe',
+ }),
+ expect.anything(),
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders correctly for a thread', () => {
+ const baseProps = {
+ channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ draftReceiverUser: undefined,
+ updateAt: 1633024800000,
+ rootId: 'root-post-id',
+ testID: 'channel-info',
+ currentUser: {timezone: 'UTC'} as unknown as UserModel,
+ isMilitaryTime: true,
+ };
+ const wrapper = render();
+ const {getByTestId} = wrapper;
+
+ expect(useTheme).toHaveBeenCalled();
+ expect(getByTestId('channel-info')).toBeTruthy();
+ expect(FormattedText).toHaveBeenCalledWith(
+ expect.objectContaining({
+ id: 'channel_info.thread_in',
+ defaultMessage: 'Thread in:',
+ }),
+ expect.anything(),
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders the Avatar when draftReceiverUser is provided', () => {
+ const baseProps = {
+ channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ draftReceiverUser: {id: 'user-id', username: 'JohnDoe'} as UserModel,
+ updateAt: 1633024800000,
+ rootId: undefined,
+ testID: 'channel-info',
+ currentUser: {timezone: 'UTC'} as unknown as UserModel,
+ isMilitaryTime: true,
+ };
+ const wrapper = render();
+
+ expect(ProfileAvatar).toHaveBeenCalledWith(
+ expect.objectContaining({
+ author: baseProps.draftReceiverUser,
+ }),
+ expect.anything(),
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders CompassIcon when draftReceiverUser is not provided', () => {
+ const baseProps = {
+ channel: {type: General.DM_CHANNEL, displayName: 'Direct Message Channel'} as ChannelModel,
+ draftReceiverUser: undefined,
+ updateAt: 1633024800000,
+ rootId: undefined,
+ testID: 'channel-info',
+ currentUser: {timezone: 'UTC'} as unknown as UserModel,
+ isMilitaryTime: true,
+ };
+ const wrapper = render();
+ expect(CompassIcon).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'globe',
+ }),
+ expect.anything(),
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/draft_post_header/profile_avatar.test.tsx b/app/components/draft_post_header/profile_avatar.test.tsx
new file mode 100644
index 0000000000..d33e3d6e5d
--- /dev/null
+++ b/app/components/draft_post_header/profile_avatar.test.tsx
@@ -0,0 +1,86 @@
+// 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 ProfileAvatar from './profile_avatar';
+
+import type UserModel from '@typings/database/models/servers/user';
+
+jest.mock('@actions/remote/user', () => ({
+ buildProfileImageUrlFromUser: jest.fn(),
+}));
+
+jest.mock('@actions/remote/file', () => ({
+ buildAbsoluteUrl: jest.fn(),
+}));
+
+jest.mock('@context/server', () => ({
+ useServerUrl: jest.fn(),
+}));
+
+const mockBuildAbsoluteUrl = require('@actions/remote/file').buildAbsoluteUrl;
+const mockBuildProfileImageUrlFromUser = require('@actions/remote/user').buildProfileImageUrlFromUser;
+const mockUseServerUrl = require('@context/server').useServerUrl;
+
+describe('ProfileAvatar Component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders the avatar image when URI is available', () => {
+ const mockServerUrl = 'base.url.com';
+ const mockUri = '/api/v4/users/mock-user-id/image';
+ const mockAuthor = {id: 'mock-user-id'} as UserModel;
+
+ mockUseServerUrl.mockReturnValue(mockServerUrl);
+ mockBuildProfileImageUrlFromUser.mockImplementation((_: string, author: UserModel) => {
+ return author ? mockUri : '';
+ });
+ mockBuildAbsoluteUrl.mockImplementation((serverUrl: string, uri: string) => `${serverUrl}${uri}`);
+
+ const wrapper = render();
+ const {queryByTestId} = wrapper;
+
+ expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor);
+ expect(mockBuildAbsoluteUrl).toHaveBeenCalledWith(mockServerUrl, mockUri);
+
+ expect(queryByTestId('avatar-icon')).toBeNull();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders the fallback icon when URI is not available', () => {
+ const mockServerUrl = 'base.url.com';
+ const mockAuthor = {id: 'mock-user-id'} as UserModel;
+
+ mockUseServerUrl.mockReturnValue(mockServerUrl);
+ mockBuildProfileImageUrlFromUser.mockReturnValue('');
+ mockBuildAbsoluteUrl.mockReturnValue('');
+ const wrapper = render();
+ const {getByTestId, queryByTestId} = wrapper;
+
+ expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalledWith(mockServerUrl, mockAuthor);
+ expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled();
+
+ const icon = getByTestId('avatar-icon');
+ expect(icon.props.name).toBe('account-outline');
+ expect(queryByTestId('avatar-image')).toBeNull();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders the fallback icon when author is not provided', () => {
+ const mockServerUrl = 'base.url.com';
+
+ mockUseServerUrl.mockReturnValue(mockServerUrl);
+ const wrapper = render();
+ const {getByTestId, queryByTestId} = wrapper;
+
+ expect(mockBuildProfileImageUrlFromUser).toHaveBeenCalled();
+ expect(mockBuildAbsoluteUrl).not.toHaveBeenCalled();
+
+ const icon = getByTestId('avatar-icon');
+ expect(icon.props.name).toBe('account-outline');
+ expect(queryByTestId('avatar-image')).toBeNull();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/draft_post_header/profile_avatar.tsx b/app/components/draft_post_header/profile_avatar.tsx
index db36bad66b..f1513ac1fc 100644
--- a/app/components/draft_post_header/profile_avatar.tsx
+++ b/app/components/draft_post_header/profile_avatar.tsx
@@ -43,6 +43,7 @@ const ProfileAvatar = ({
if (uri) {
picture = (
@@ -50,6 +51,7 @@ const ProfileAvatar = ({
} else {
picture = (
+
+
+
+ Drafts
+
+
+
+
+ 5
+
+
+
+
+`;
+
+exports[`Drafts Button should render the drafts button component 1`] = `
+
+
+
+
+ Drafts
+
+
+
+
+ 1
+
+
+
+
+`;
+
+exports[`Drafts Button should render the drafts button component with drafts count 1`] = `
+
+
+
+
+ Drafts
+
+
+
+
+ 23
+
+
+
+
+`;
diff --git a/app/components/drafts_buttton/drafts_button.test.tsx b/app/components/drafts_buttton/drafts_button.test.tsx
new file mode 100644
index 0000000000..e6c38f9bdd
--- /dev/null
+++ b/app/components/drafts_buttton/drafts_button.test.tsx
@@ -0,0 +1,52 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import {fireEvent} from '@testing-library/react-native';
+import React from 'react';
+
+import {switchToGlobalDrafts} from '@actions/local/draft';
+import {renderWithIntl} from '@test/intl-test-helper';
+
+import DraftsButton from './drafts_button';
+
+jest.mock('@actions/local/draft', () => ({
+ switchToGlobalDrafts: jest.fn(),
+}));
+describe('Drafts Button', () => {
+ it('should render the drafts button component', () => {
+ const wrapper = renderWithIntl(
+ ,
+ );
+
+ const {getByText} = wrapper;
+ expect(getByText('Drafts')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render the drafts button component with drafts count', () => {
+ const wrapper = renderWithIntl(
+ ,
+ );
+
+ const {getByText} = wrapper;
+ expect(getByText('Drafts')).toBeTruthy();
+ expect(getByText('23')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('calls switchToGlobalDrafts when pressed', () => {
+ const wrapper = renderWithIntl(
+ ,
+ );
+ const {getByTestId} = wrapper;
+ fireEvent.press(getByTestId('channel_list.drafts.button'));
+ expect(switchToGlobalDrafts).toHaveBeenCalledTimes(1);
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/components/files/files.tsx b/app/components/files/files.tsx
index 1006393637..ffdece6f1e 100644
--- a/app/components/files/files.tsx
+++ b/app/components/files/files.tsx
@@ -92,6 +92,7 @@ const Files = ({canDownloadFiles, failed, filesInfo, isReplyPost, layoutWidth, l
+
+
+ Delete draft
+
+
+`;
+
+exports[`DeleteDraft should render the component 1`] = `
+
+
+
+ Delete draft
+
+
+`;
diff --git a/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap b/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap
new file mode 100644
index 0000000000..090ab7cb76
--- /dev/null
+++ b/app/screens/draft_options/__snapshots__/draft_options.test.tsx.snap
@@ -0,0 +1,793 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Draft Options should render the draft options 1`] = `
+[
+ ,
+
+
+
+
+
+
+
+
+ Draft actions
+
+
+
+
+ Edit draft
+
+
+
+
+
+ Send draft
+
+
+
+
+
+ Delete draft
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+]
+`;
+
+exports[`Draft Options should render the draft options component 1`] = `
+[
+ ,
+
+
+
+
+
+
+
+
+ Draft actions
+
+
+
+
+ Edit draft
+
+
+
+
+
+ Send draft
+
+
+
+
+
+ Delete draft
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+]
+`;
diff --git a/app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap
new file mode 100644
index 0000000000..1fb97ef14c
--- /dev/null
+++ b/app/screens/draft_options/__snapshots__/edit_draft.test.tsx.snap
@@ -0,0 +1,127 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Edit Draft Should call editHandler when pressed 1`] = `
+
+
+
+ Edit draft
+
+
+`;
+
+exports[`Edit Draft Should render the Edit draft component 1`] = `
+
+
+
+ Edit draft
+
+
+`;
diff --git a/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap
new file mode 100644
index 0000000000..ca3528993a
--- /dev/null
+++ b/app/screens/draft_options/__snapshots__/send_draft.test.tsx.snap
@@ -0,0 +1,125 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Send Draft should call dismissBottmSheet after sending the draft 1`] = `
+
+
+
+ Send draft
+
+
+`;
+
+exports[`Send Draft should render the component 1`] = `
+
+
+
+ Send draft
+
+
+`;
diff --git a/app/screens/draft_options/delete_draft.test.tsx b/app/screens/draft_options/delete_draft.test.tsx
new file mode 100644
index 0000000000..d892a99070
--- /dev/null
+++ b/app/screens/draft_options/delete_draft.test.tsx
@@ -0,0 +1,44 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {Screens} from '@constants';
+import {dismissBottomSheet} from '@screens/navigation';
+import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper';
+
+import DeleteDraft from './delete_draft';
+
+jest.mock('@screens/navigation', () => ({
+ dismissBottomSheet: jest.fn(),
+}));
+
+describe('DeleteDraft', () => {
+ it('should render the component', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByText, getByTestId} = wrapper;
+ expect(getByText('Delete draft')).toBeTruthy();
+ expect(getByTestId('trash-can-outline-icon')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('calls draftDeleteHandler when pressed', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByTestId} = wrapper;
+ fireEvent.press(getByTestId('trash-can-outline-icon'));
+ expect(dismissBottomSheet).toHaveBeenCalledTimes(1);
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/screens/draft_options/delete_draft.tsx b/app/screens/draft_options/delete_draft.tsx
index 3b1bdfd0a7..f7a4ca27ad 100644
--- a/app/screens/draft_options/delete_draft.tsx
+++ b/app/screens/draft_options/delete_draft.tsx
@@ -66,6 +66,7 @@ const DeleteDraft: React.FC = ({
>
diff --git a/app/screens/draft_options/draft_options.test.tsx b/app/screens/draft_options/draft_options.test.tsx
new file mode 100644
index 0000000000..2f7a43bdc8
--- /dev/null
+++ b/app/screens/draft_options/draft_options.test.tsx
@@ -0,0 +1,71 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {renderWithEverything} from '@test/intl-test-helper';
+import TestHelper from '@test/test_helper';
+
+import DraftOptions from '.';
+
+import type {Database} from '@nozbe/watermelondb';
+import type ChannelModel from '@typings/database/models/servers/channel';
+import type DraftModel from '@typings/database/models/servers/draft';
+describe('Draft Options', () => {
+ let database: Database;
+
+ beforeAll(async () => {
+ const server = await TestHelper.setupServerDatabase();
+ database = server.database;
+ });
+ it('should render the draft options component', () => {
+ const props = {
+ channel: {
+ id: 'channel_id',
+ teamId: 'team_id',
+ } as unknown as ChannelModel,
+ rootId: '',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ draftReceiverUserName: undefined,
+ };
+ const wrapper = renderWithEverything(
+ , {database},
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render the draft options', () => {
+ const props = {
+ channel: {
+ id: 'channel_id',
+ teamId: 'team_id',
+ } as unknown as ChannelModel,
+ rootId: '',
+ draft: {
+ updateAt: 1633024800000,
+ message: 'Hello, World!',
+ channelId: 'channel_id',
+ rootId: '',
+ files: [],
+ metadata: {},
+ } as unknown as DraftModel,
+ draftReceiverUserName: 'username',
+ };
+ const wrapper = renderWithEverything(
+ , {database},
+ );
+ const {getByText} = wrapper;
+ expect(getByText('Draft actions')).toBeTruthy();
+ expect(getByText('Edit draft')).toBeTruthy();
+ expect(getByText('Send draft')).toBeTruthy();
+ expect(getByText('Delete draft')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/screens/draft_options/edit_draft.test.tsx b/app/screens/draft_options/edit_draft.test.tsx
new file mode 100644
index 0000000000..15d4d09ae9
--- /dev/null
+++ b/app/screens/draft_options/edit_draft.test.tsx
@@ -0,0 +1,54 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {Screens} from '@constants';
+import {dismissBottomSheet} from '@screens/navigation';
+import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper';
+
+import EditDraft from './edit_draft';
+
+import type ChannelModel from '@typings/database/models/servers/channel';
+
+jest.mock('@screens/navigation', () => ({
+ dismissBottomSheet: jest.fn(),
+}));
+
+describe('Edit Draft', () => {
+ it('Should render the Edit draft component', () => {
+ const props = {
+ bottomSheetId: Screens.DRAFT_OPTIONS,
+ channel: {
+ id: 'channel_id',
+ teamId: 'team_id',
+ } as ChannelModel,
+ rootId: 'root_id',
+ };
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByTestId, getByText} = wrapper;
+ expect(getByTestId('pencil-outline-icon')).toBeTruthy();
+ expect(getByText('Edit draft')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('Should call editHandler when pressed', () => {
+ const props = {
+ bottomSheetId: Screens.DRAFT_OPTIONS,
+ channel: {
+ id: 'channel_id',
+ teamId: 'team_id',
+ } as ChannelModel,
+ rootId: 'root_id',
+ };
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByTestId} = wrapper;
+ fireEvent.press(getByTestId('pencil-outline-icon'));
+ expect(dismissBottomSheet).toHaveBeenCalledTimes(1);
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/screens/draft_options/edit_draft.tsx b/app/screens/draft_options/edit_draft.tsx
index 7062b85fa5..0ead831a23 100644
--- a/app/screens/draft_options/edit_draft.tsx
+++ b/app/screens/draft_options/edit_draft.tsx
@@ -65,6 +65,7 @@ const EditDraft: React.FC = ({
>
diff --git a/app/screens/draft_options/send_draft.test.tsx b/app/screens/draft_options/send_draft.test.tsx
new file mode 100644
index 0000000000..ccf94cba55
--- /dev/null
+++ b/app/screens/draft_options/send_draft.test.tsx
@@ -0,0 +1,75 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {General, Screens} from '@constants';
+import {dismissBottomSheet} from '@screens/navigation';
+import {fireEvent, renderWithIntlAndTheme} from '@test/intl-test-helper';
+
+import SendDraft from './send_draft';
+
+jest.mock('@screens/navigation', () => ({
+ dismissBottomSheet: jest.fn(),
+}));
+
+describe('Send Draft', () => {
+ it('should render the component', () => {
+ const props = {
+ channelId: 'channel_id',
+ channelName: 'channel_name',
+ rootId: '',
+ channelType: General.OPEN_CHANNEL,
+ bottomSheetId: Screens.DRAFT_OPTIONS,
+ currentUserId: 'current_user_id',
+ maxMessageLength: 4000,
+ useChannelMentions: true,
+ userIsOutOfOffice: false,
+ customEmojis: [],
+ value: 'value',
+ files: [],
+ postPriority: '' as unknown as PostPriority,
+ persistentNotificationInterval: 0,
+ persistentNotificationMaxRecipients: 0,
+ draftReceiverUserName: undefined,
+ };
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByText} = wrapper;
+ expect(getByText('Send draft')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should call dismissBottmSheet after sending the draft', () => {
+ const props = {
+ channelId: 'channel_id',
+ channelName: 'channel_name',
+ rootId: '',
+ channelType: General.OPEN_CHANNEL,
+ bottomSheetId: Screens.DRAFT_OPTIONS,
+ currentUserId: 'current_user_id',
+ maxMessageLength: 4000,
+ useChannelMentions: true,
+ userIsOutOfOffice: false,
+ customEmojis: [],
+ value: 'value',
+ files: [],
+ postPriority: '' as unknown as PostPriority,
+ persistentNotificationInterval: 0,
+ persistentNotificationMaxRecipients: 0,
+ draftReceiverUserName: undefined,
+ };
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ const {getByTestId} = wrapper;
+ fireEvent.press(getByTestId('send_draft_button'));
+ expect(dismissBottomSheet).toHaveBeenCalledTimes(1);
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});
diff --git a/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap
new file mode 100644
index 0000000000..7f816ebca4
--- /dev/null
+++ b/app/screens/global_drafts/components/__snapshots__/draft_empty_component.test.tsx.snap
@@ -0,0 +1,143 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Draft Empty Component should match the snapshot 1`] = `
+
+
+
+ No drafts at the moment
+
+
+ Any message you have started will show here.
+
+
+`;
+
+exports[`Draft Empty Component should render empty draft message 1`] = `
+
+
+
+ No drafts at the moment
+
+
+ Any message you have started will show here.
+
+
+`;
diff --git a/app/screens/global_drafts/components/draft_empty_component.test.tsx b/app/screens/global_drafts/components/draft_empty_component.test.tsx
new file mode 100644
index 0000000000..6f0c1b600d
--- /dev/null
+++ b/app/screens/global_drafts/components/draft_empty_component.test.tsx
@@ -0,0 +1,26 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+
+import {renderWithIntlAndTheme} from '@test/intl-test-helper';
+
+import DraftEmptyComponent from './draft_empty_component';
+
+describe('Draft Empty Component', () => {
+ it('should match the snapshot', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+
+ it('should render empty draft message', () => {
+ const wrapper = renderWithIntlAndTheme(
+ ,
+ );
+ expect(wrapper.getByText('No drafts at the moment')).toBeTruthy();
+ expect(wrapper.getByText('Any message you have started will show here.')).toBeTruthy();
+ expect(wrapper.toJSON()).toMatchSnapshot();
+ });
+});