Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
111 lines
4.3 KiB
TypeScript
111 lines
4.3 KiB
TypeScript
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
|
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
|
|
import MessageBox from './MessageBox';
|
|
import type { createComposerAPI } from './createComposerAPI';
|
|
import type { ChatAPI } from '../../../../lib/chats/ChatAPI';
|
|
import { useFileUpload } from '../../body/hooks/useFileUpload';
|
|
import { useChat } from '../../contexts/ChatContext';
|
|
import { useComposerPopupOptions } from '../../contexts/ComposerPopupContext';
|
|
import { useRoom, useRoomSubscription } from '../../contexts/RoomContext';
|
|
|
|
jest.mock('../../../../lib/rooms/roomCoordinator', () => ({
|
|
roomCoordinator: { getRoomDirectives: () => ({ canSendMessage: () => true }) },
|
|
}));
|
|
jest.mock('../../../../lib/getURL', () => ({ getURL: (url: string) => url }));
|
|
jest.mock('../../hooks/useE2EERoomState', () => ({ useE2EERoomState: jest.fn() }));
|
|
jest.mock('../../contexts/ChatContext', () => ({ useChat: jest.fn() }));
|
|
jest.mock('../../contexts/RoomContext', () => ({ useRoom: jest.fn(), useRoomSubscription: jest.fn() }));
|
|
jest.mock('../../contexts/ComposerPopupContext', () => ({ useComposerPopupOptions: jest.fn() }));
|
|
jest.mock('../../body/hooks/useFileUpload', () => ({ useFileUpload: jest.fn() }));
|
|
|
|
const createChatStub = () => {
|
|
const chat = {
|
|
composer: undefined as ReturnType<typeof createComposerAPI> | undefined,
|
|
setComposerAPI: (composer?: ReturnType<typeof createComposerAPI>) => {
|
|
chat.composer?.release();
|
|
chat.composer = composer;
|
|
},
|
|
currentEditingMessage: { getMID: () => undefined },
|
|
action: { start: jest.fn(), stop: jest.fn(), performContinuously: jest.fn() },
|
|
emojiPicker: { open: jest.fn(), close: jest.fn() },
|
|
flows: {},
|
|
};
|
|
|
|
return chat as unknown as ChatAPI & { composer?: ReturnType<typeof createComposerAPI> };
|
|
};
|
|
|
|
const renderMessageBox = (onSend: jest.Mock, endpointHandler: jest.Mock) => {
|
|
const chat = createChatStub();
|
|
|
|
(useChat as jest.Mock).mockReturnValue(chat);
|
|
|
|
const view = render(<MessageBox showFormattingTips={false} onSend={onSend} />, {
|
|
wrapper: mockAppRoot().withUserPreference('sendOnEnter', 'normal').withEndpoint('POST', '/v1/rooms.saveDraft', endpointHandler).build(),
|
|
});
|
|
|
|
return { chat, ...view };
|
|
};
|
|
|
|
beforeEach(() => {
|
|
(useRoom as jest.Mock).mockReturnValue({ _id: 'rid', t: 'c', name: 'general' });
|
|
(useRoomSubscription as jest.Mock).mockReturnValue({ rid: 'rid' });
|
|
(useComposerPopupOptions as jest.Mock).mockReturnValue([]);
|
|
(useFileUpload as jest.Mock).mockReturnValue({
|
|
hasUploads: false,
|
|
handleUploadFiles: jest.fn(),
|
|
isUploading: false,
|
|
isProcessingUploads: false,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
localStorage.clear();
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('MessageBox drafts', () => {
|
|
it('should not discard a newer draft when a pending send resolves after the room changed', async () => {
|
|
const user = userEvent.setup();
|
|
const endpointHandler = jest.fn(() => null);
|
|
|
|
let resolveSend: (() => void) | undefined;
|
|
// Mirrors the real send flow: it clears the composer, then awaits the server response.
|
|
const onSend = jest.fn(() => {
|
|
chat.composer?.clear();
|
|
|
|
return new Promise<void>((resolve) => {
|
|
resolveSend = () => resolve();
|
|
});
|
|
});
|
|
|
|
const { chat, unmount } = renderMessageBox(onSend, endpointHandler);
|
|
|
|
const composer = screen.getByRole('textbox', { name: 'Message #general' });
|
|
|
|
// Send a message and keep its response pending.
|
|
await user.type(composer, 'message A');
|
|
// The composer listens for `which`, which userEvent does not set.
|
|
// eslint-disable-next-line testing-library/prefer-user-event
|
|
fireEvent.keyDown(composer, { key: 'Enter', which: 13, keyCode: 13 });
|
|
await waitFor(() => expect(onSend).toHaveBeenCalledWith(expect.objectContaining({ value: 'message A' })));
|
|
|
|
// Type a new draft while the send is still in flight.
|
|
await user.type(composer, 'draft B');
|
|
await waitFor(() => expect(localStorage.getItem('messagebox_rid')).toBe('draft B'));
|
|
|
|
// Leave the room: the draft is flushed to the server on unmount.
|
|
unmount();
|
|
await waitFor(() => expect(endpointHandler).toHaveBeenCalledWith({ rid: 'rid', draft: 'draft B' }));
|
|
|
|
// The delayed send finally resolves.
|
|
await act(async () => {
|
|
resolveSend?.();
|
|
});
|
|
|
|
expect(endpointHandler).toHaveBeenCalledTimes(1);
|
|
expect(endpointHandler).not.toHaveBeenCalledWith(expect.objectContaining({ draft: '' }));
|
|
});
|
|
});
|