mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
MM-60541 Don't save an unmodified draft when changing channels (#28620)
* MM-60541 Don't save an unmodified draft when changing channels * Address feedback
This commit is contained in:
parent
bafe37d4b2
commit
3811dccd79
@ -7,19 +7,32 @@ import type {Channel} from '@mattermost/types/channels';
|
|||||||
|
|
||||||
import Permissions from 'mattermost-redux/constants/permissions';
|
import Permissions from 'mattermost-redux/constants/permissions';
|
||||||
|
|
||||||
|
import {removeDraft, updateDraft} from 'actions/views/drafts';
|
||||||
|
|
||||||
import type {FileUpload} from 'components/file_upload/file_upload';
|
import type {FileUpload} from 'components/file_upload/file_upload';
|
||||||
import type Textbox from 'components/textbox/textbox';
|
import type Textbox from 'components/textbox/textbox';
|
||||||
|
|
||||||
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||||
import {renderWithContext, userEvent, screen} from 'tests/react_testing_utils';
|
import {renderWithContext, userEvent, screen} from 'tests/react_testing_utils';
|
||||||
|
import {StoragePrefixes} from 'utils/constants';
|
||||||
import {TestHelper} from 'utils/test_helper';
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
import type {PostDraft} from 'types/store/draft';
|
import type {PostDraft} from 'types/store/draft';
|
||||||
|
|
||||||
import AdavancedTextEditor from './advanced_text_editor';
|
import AdvancedTextEditor from './advanced_text_editor';
|
||||||
|
|
||||||
|
jest.mock('actions/views/drafts', () => ({
|
||||||
|
...jest.requireActual('actions/views/drafts'),
|
||||||
|
updateDraft: jest.fn((...args) => ({type: 'MOCK_UPDATE_DRAFT', args})),
|
||||||
|
removeDraft: jest.fn((...args) => ({type: 'MOCK_REMOVE_DRAFT', args})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const mockedRemoveDraft = jest.mocked(removeDraft);
|
||||||
|
const mockedUpdateDraft = jest.mocked(updateDraft);
|
||||||
|
|
||||||
const currentUserId = 'current_user_id';
|
const currentUserId = 'current_user_id';
|
||||||
const channelId = 'current_channel_id';
|
const channelId = 'current_channel_id';
|
||||||
|
const otherChannelId = 'other_channel_id';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
entities: {
|
entities: {
|
||||||
@ -39,15 +52,33 @@ const initialState = {
|
|||||||
},
|
},
|
||||||
channels: {
|
channels: {
|
||||||
channels: {
|
channels: {
|
||||||
current_channel_id: TestHelper.getChannelMock({id: 'current_channel_id', team_id: 'current_team_id'}),
|
current_channel_id: TestHelper.getChannelMock({
|
||||||
|
id: 'current_channel_id',
|
||||||
|
team_id: 'current_team_id',
|
||||||
|
display_name: 'Test Channel',
|
||||||
|
}),
|
||||||
|
other_channel_id: TestHelper.getChannelMock({
|
||||||
|
id: 'other_channel_id',
|
||||||
|
team_id: 'current_team_id',
|
||||||
|
display_name: 'Other Channel',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
current_channel_id: {
|
current_channel_id: {
|
||||||
member_count: 1,
|
member_count: 1,
|
||||||
},
|
},
|
||||||
|
other_channel_id: {
|
||||||
|
member_count: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
roles: {
|
roles: {
|
||||||
current_channel_id: new Set(['channel_roles']),
|
current_channel_id: new Set(['channel_roles']),
|
||||||
|
other_channel_id: new Set(['channel_roles']),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
roles: {
|
||||||
|
roles: {
|
||||||
|
user_roles: {permissions: [Permissions.CREATE_POST]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teams: {
|
teams: {
|
||||||
@ -147,7 +178,7 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||||||
describe('keyDown behavior', () => {
|
describe('keyDown behavior', () => {
|
||||||
it('ESC should blur the input', () => {
|
it('ESC should blur the input', () => {
|
||||||
renderWithContext(
|
renderWithContext(
|
||||||
<AdavancedTextEditor
|
<AdvancedTextEditor
|
||||||
{...baseProps}
|
{...baseProps}
|
||||||
/>,
|
/>,
|
||||||
mergeObjects(initialState, {
|
mergeObjects(initialState, {
|
||||||
@ -165,4 +196,186 @@ describe('components/avanced_text_editor/advanced_text_editor', () => {
|
|||||||
expect(textbox).not.toHaveFocus();
|
expect(textbox).not.toHaveFocus();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the textbox value to an existing draft on mount and when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
mergeObjects(initialState, {
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
[StoragePrefixes.DRAFT + channelId]: {
|
||||||
|
value: TestHelper.getPostDraftMock({
|
||||||
|
message: 'original draft',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
[StoragePrefixes.DRAFT + otherChannelId]: {
|
||||||
|
value: TestHelper.getPostDraftMock({
|
||||||
|
message: 'a different draft',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('write to test channel')).toHaveValue('original draft');
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(screen.getByLabelText('write to other channel')).toHaveValue('a different draft');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save a new draft when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.type(screen.getByLabelText('write to test channel'), 'some text');
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft.mock.calls[0][1]).toMatchObject({
|
||||||
|
message: 'some text',
|
||||||
|
show: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-60541 should not save an unmodified draft when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
mergeObjects(initialState, {
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
[StoragePrefixes.DRAFT + channelId]: {
|
||||||
|
value: TestHelper.getPostDraftMock({
|
||||||
|
message: 'original draft',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should save an updated draft when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
mergeObjects(initialState, {
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
[StoragePrefixes.DRAFT + channelId]: {
|
||||||
|
value: TestHelper.getPostDraftMock({
|
||||||
|
message: 'original draft',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.type(screen.getByLabelText('write to test channel'), ' plus some new text');
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedUpdateDraft).toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft.mock.calls[0][1]).toMatchObject({
|
||||||
|
message: 'original draft plus some new text',
|
||||||
|
show: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deleted a deleted draft when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
mergeObjects(initialState, {
|
||||||
|
storage: {
|
||||||
|
storage: {
|
||||||
|
[StoragePrefixes.DRAFT + channelId]: {
|
||||||
|
value: TestHelper.getPostDraftMock({
|
||||||
|
message: 'original draft',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
userEvent.clear(screen.getByLabelText('write to test channel'));
|
||||||
|
|
||||||
|
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedRemoveDraft).toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('MM-60541 should not attempt to delete a non-existent draft when changing channels', () => {
|
||||||
|
const {rerender} = renderWithContext(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<AdvancedTextEditor
|
||||||
|
{...baseProps}
|
||||||
|
channelId={otherChannelId}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockedRemoveDraft).not.toHaveBeenCalled();
|
||||||
|
expect(mockedUpdateDraft).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -150,7 +150,7 @@ const AdvancedTextEditor = ({
|
|||||||
const textboxRef = useRef<TextboxClass>(null);
|
const textboxRef = useRef<TextboxClass>(null);
|
||||||
const loggedInAriaLabelTimeout = useRef<NodeJS.Timeout>();
|
const loggedInAriaLabelTimeout = useRef<NodeJS.Timeout>();
|
||||||
const saveDraftFrame = useRef<NodeJS.Timeout>();
|
const saveDraftFrame = useRef<NodeJS.Timeout>();
|
||||||
const previousDraft = useRef(draftFromStore);
|
const draftRef = useRef(draftFromStore);
|
||||||
const storedDrafts = useRef<Record<string, PostDraft | undefined>>({});
|
const storedDrafts = useRef<Record<string, PostDraft | undefined>>({});
|
||||||
const lastBlurAt = useRef(0);
|
const lastBlurAt = useRef(0);
|
||||||
|
|
||||||
@ -427,17 +427,25 @@ const AdvancedTextEditor = ({
|
|||||||
};
|
};
|
||||||
}, [handleDraftChange, draft]);
|
}, [handleDraftChange, draft]);
|
||||||
|
|
||||||
// Set the draft from store when changing post or channels, and store the previus one
|
// Keep track of the draft as a ref so that we can save it when changing channels
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDraft(draftFromStore);
|
draftRef.current = draft;
|
||||||
return () => handleDraftChange(previousDraft.current, {instant: true, show: true});
|
|
||||||
}, [channelId, postId]);
|
|
||||||
|
|
||||||
// Keep track of the previous draft
|
|
||||||
useEffect(() => {
|
|
||||||
previousDraft.current = draft;
|
|
||||||
}, [draft]);
|
}, [draft]);
|
||||||
|
|
||||||
|
// Set the draft from store when changing post or channels, and store the previous one
|
||||||
|
useEffect(() => {
|
||||||
|
// Store the draft that existed when we opened the channel to know if it should be saved
|
||||||
|
const draftOnOpen = draftFromStore;
|
||||||
|
|
||||||
|
setDraft(draftOnOpen);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (draftOnOpen !== draftRef.current) {
|
||||||
|
handleDraftChange(draftRef.current, {instant: true, show: true});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [channelId, postId]);
|
||||||
|
|
||||||
const disableSendButton = Boolean(readOnlyChannel || (!draft.message.trim().length && !draft.fileInfos.length)) || !isValidPersistentNotifications;
|
const disableSendButton = Boolean(readOnlyChannel || (!draft.message.trim().length && !draft.fileInfos.length)) || !isValidPersistentNotifications;
|
||||||
const sendButton = readOnlyChannel ? null : (
|
const sendButton = readOnlyChannel ? null : (
|
||||||
<SendButton
|
<SendButton
|
||||||
|
Loading…
Reference in New Issue
Block a user