Skip to content

Commit 2389ff4

Browse files
authored
fix: exclude other data from poll messages and refresh poll composer state on poll creation (#1528)
When message with poll is created we have to exclude any other message data that was typed / uploaded / mentioned etc. by the user previously. This data is kept in the message composer state whereas the poll composer state is reset upon poll message creation on the server-side.
1 parent 81d9bb1 commit 2389ff4

File tree

11 files changed

+174
-19
lines changed

11 files changed

+174
-19
lines changed

src/messageComposer/messageComposer.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,18 @@ const initState = (
8888
const quotedMessage = composition.quoted_message;
8989
let message;
9090
let draftId = null;
91+
let id = MessageComposer.generateId(); // do not use draft id for messsage id
9192
if (compositionIsDraftResponse(composition)) {
9293
message = composition.message;
9394
draftId = composition.message.id;
9495
} else {
9596
message = composition;
97+
id = composition.id;
9698
}
9799

98100
return {
99101
draftId,
100-
id: message.id,
102+
id,
101103
quotedMessage: quotedMessage
102104
? formatMessage(quotedMessage as MessageResponseBase)
103105
: null,
@@ -318,6 +320,7 @@ export class MessageComposer {
318320
this.attachmentManager.initState({ message });
319321
this.linkPreviewsManager.initState({ message });
320322
this.textComposer.initState({ message });
323+
this.pollComposer.initState();
321324
this.customDataManager.initState({ message });
322325
this.state.next(initState(composition));
323326
if (
@@ -543,11 +546,6 @@ export class MessageComposer {
543546
};
544547

545548
clear = () => {
546-
this.attachmentManager.initState();
547-
this.linkPreviewsManager.initState();
548-
this.textComposer.initState();
549-
this.pollComposer.initState();
550-
this.customDataManager.initState();
551549
this.initState();
552550
};
553551

@@ -630,6 +628,7 @@ export class MessageComposer {
630628
try {
631629
const { poll } = await this.client.createPoll(composition.data);
632630
this.state.partialNext({ pollId: poll.id });
631+
this.pollComposer.initState();
633632
} catch (error) {
634633
this.client.notifications.add({
635634
message: 'Failed to create the poll',

src/messageComposer/middleware/messageComposer/MessageComposerMiddlewareExecutor.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
createCustomDataCompositionMiddleware,
3131
createDraftCustomDataCompositionMiddleware,
3232
} from './customData';
33+
import { createPollOnlyCompositionMiddleware } from './pollOnly';
3334

3435
export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
3536
MessageComposerMiddlewareState,
@@ -40,6 +41,7 @@ export class MessageComposerMiddlewareExecutor extends MiddlewareExecutor<
4041
// todo: document how to add custom data to a composed message using middleware
4142
// or adding custom composer components (apart from AttachmentsManager, TextComposer etc.)
4243
this.use([
44+
createPollOnlyCompositionMiddleware(composer),
4345
createTextComposerCompositionMiddleware(composer),
4446
createAttachmentsCompositionMiddleware(composer),
4547
createLinkPreviewsCompositionMiddleware(composer),
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type {
2+
MessageComposerMiddlewareState,
3+
MessageCompositionMiddleware,
4+
} from './types';
5+
import type { MessageComposer } from '../../messageComposer';
6+
import type { MiddlewareHandlerParams } from '../../../middleware';
7+
import type { LocalMessage } from '../../../types';
8+
9+
const pollLocalMessageNullifiedFields: Pick<
10+
LocalMessage,
11+
'attachments' | 'mentioned_users' | 'parent_id' | 'quoted_message' | 'text'
12+
> = {
13+
attachments: [],
14+
mentioned_users: [],
15+
parent_id: undefined,
16+
quoted_message: undefined,
17+
text: '',
18+
};
19+
20+
export const createPollOnlyCompositionMiddleware = (
21+
composer: MessageComposer,
22+
): MessageCompositionMiddleware => ({
23+
id: 'stream-io/message-composer-middleware/poll-only',
24+
handlers: {
25+
compose: ({
26+
state,
27+
complete,
28+
forward,
29+
}: MiddlewareHandlerParams<MessageComposerMiddlewareState>) => {
30+
const pollId = composer.pollId;
31+
const isEditingMessage = !!composer.editedMessage;
32+
const isComposingThreadReply = !!composer.threadId;
33+
if (!pollId || isComposingThreadReply || isEditingMessage) return forward();
34+
35+
return complete({
36+
...state,
37+
localMessage: {
38+
...state.localMessage,
39+
...pollLocalMessageNullifiedFields,
40+
poll_id: pollId,
41+
},
42+
message: {
43+
id: state.localMessage.id,
44+
poll_id: pollId,
45+
},
46+
});
47+
},
48+
},
49+
});

test/unit/MessageComposer/messageComposer.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ describe('MessageComposer', () => {
192192

193193
const { messageComposer } = setup({ composition: draftMessage });
194194

195-
expect(messageComposer.draftId).toBe('test-draft-id');
195+
expect(messageComposer.draftId).toBe(draftMessage.message.id);
196+
expect(messageComposer.id).not.toBe(draftMessage.message.id);
196197
});
197198
});
198199

@@ -718,6 +719,7 @@ describe('MessageComposer', () => {
718719

719720
expect(spyCompose).toHaveBeenCalled();
720721
expect(spyCreatePoll).toHaveBeenCalledWith(mockPoll);
722+
expect(messageComposer.pollComposer.initState).toHaveBeenCalled();
721723
expect(messageComposer.state.getLatestValue().pollId).toBe('test-poll-id');
722724
});
723725

test/unit/MessageComposer/middleware/messageComposer/attachments.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ const setupDraft = (initialState: MessageDraftComposerMiddlewareValueState) => {
3838
};
3939
};
4040

41-
describe('AttachmentsMiddleware', () => {
41+
describe('stream-io/message-composer-middleware/attachments', () => {
4242
let channel: Channel;
4343
let client: StreamChat;
4444
let messageComposer: MessageComposer;
@@ -377,7 +377,7 @@ describe('AttachmentsMiddleware', () => {
377377
});
378378
});
379379

380-
describe('DraftAttachmentsMiddleware', () => {
380+
describe('stream-io/message-composer-middleware/draft-attachments', () => {
381381
let channel: Channel;
382382
let client: StreamChat;
383383
let messageComposer: MessageComposer;

test/unit/MessageComposer/middleware/messageComposer/compositionValidation.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const setupDraft = (initialState: MessageDraftComposerMiddlewareValueState) => {
4040
};
4141
};
4242

43-
describe('MessageComposerValidationMiddleware', () => {
43+
describe('stream-io/message-composer-middleware/data-validation', () => {
4444
let channel: Channel;
4545
let client: StreamChat;
4646
let messageComposer: MessageComposer;
@@ -348,7 +348,7 @@ describe('MessageComposerValidationMiddleware', () => {
348348
});
349349
});
350350

351-
describe('DraftCompositionValidationMiddleware', () => {
351+
describe('stream-io/message-composer-middleware/draft-data-validation', () => {
352352
let channel: Channel;
353353
let client: StreamChat;
354354
let messageComposer: MessageComposer;

test/unit/MessageComposer/middleware/messageComposer/customData.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
MessageComposerMiddlewareState,
1111
MessageDraftComposerMiddlewareValueState,
1212
} from '../../../../../src/messageComposer/middleware/messageComposer/types';
13+
import { MiddlewareStatus } from '../../../../../src';
1314

1415
const setup = (initialState: MessageComposerMiddlewareState) => {
1516
return {
@@ -52,7 +53,7 @@ describe('Custom Data Middleware', () => {
5253
});
5354
});
5455

55-
describe('createCustomDataCompositionMiddleware', () => {
56+
describe('stream-io/message-composer-middleware/custom-data', () => {
5657
it('should initialize with custom data', async () => {
5758
const data = { key: 'value' };
5859
composer.customDataManager.setMessageData(data);
@@ -108,7 +109,7 @@ describe('Custom Data Middleware', () => {
108109
});
109110
});
110111

111-
describe('createDraftCustomDataCompositionMiddleware', () => {
112+
describe('stream-io/message-composer-middleware/draft-custom-data', () => {
112113
it('should initialize with custom data', async () => {
113114
const data = { key: 'value' };
114115
composer.customDataManager.setMessageData(data);

test/unit/MessageComposer/middleware/messageComposer/linkPreviews.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const setup = ({
9090
return { linkPreviewsMiddleware, messageComposer };
9191
};
9292

93-
describe('LinkPreviewsMiddleware', () => {
93+
describe('stream-io/message-composer-middleware/link-previews', () => {
9494
it('should keep message attachments empty if not link previews are available', async () => {
9595
const { linkPreviewsMiddleware } = setup();
9696
const result = await linkPreviewsMiddleware.handlers.compose(
@@ -603,7 +603,7 @@ const setupForDraft = ({
603603

604604
return { linkPreviewsMiddleware, mockClient, mockChannel, messageComposer };
605605
};
606-
describe('DraftLinkPreviewsMiddleware', () => {
606+
describe('stream-io/message-composer-middleware/draft-link-previews', () => {
607607
it('should handle draft without link previews', async () => {
608608
const { linkPreviewsMiddleware } = setupForDraft();
609609
const result = await linkPreviewsMiddleware.handlers.compose(

test/unit/MessageComposer/middleware/messageComposer/messageComposerState.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const setupHandlerParamsDraft = (
3737
};
3838
};
3939

40-
describe('MessageComposerStateMiddleware', () => {
40+
describe('stream-io/message-composer-middleware/own-state', () => {
4141
let channel: Channel;
4242
let client: StreamChat;
4343
let messageComposer: MessageComposer;
@@ -307,7 +307,7 @@ describe('MessageComposerStateMiddleware', () => {
307307
});
308308
});
309309

310-
describe('DraftMessageComposerStateMiddleware', () => {
310+
describe('stream-io/message-composer-middleware/draft-own-state', () => {
311311
let channel: Channel;
312312
let client: StreamChat;
313313
let messageComposer: MessageComposer;
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { describe, expect, it, vi } from 'vitest';
2+
import { MessageComposerMiddlewareState } from '../../../../../src';
3+
import { createPollOnlyCompositionMiddleware } from '../../../../../src/messageComposer/middleware/messageComposer/pollOnly';
4+
5+
const setupMiddlewareApi = (initialState: MessageComposerMiddlewareState) => {
6+
return {
7+
state: initialState,
8+
next: vi.fn().mockReturnValue(null),
9+
complete: vi.fn().mockReturnValue(null),
10+
discard: vi.fn().mockReturnValue(null),
11+
forward: vi.fn().mockReturnValue(null),
12+
};
13+
};
14+
15+
const stateSeed: MessageComposerMiddlewareState = {
16+
message: {
17+
id: 'test-id',
18+
parent_id: undefined,
19+
type: 'regular',
20+
},
21+
localMessage: {
22+
attachments: [],
23+
created_at: new Date(),
24+
deleted_at: null,
25+
error: undefined,
26+
id: 'test-id',
27+
mentioned_users: [],
28+
parent_id: undefined,
29+
pinned_at: null,
30+
reaction_groups: null,
31+
status: 'sending',
32+
text: '',
33+
type: 'regular',
34+
updated_at: new Date(),
35+
},
36+
sendOptions: {},
37+
};
38+
39+
describe('stream-io/message-composer-middleware/poll-only', () => {
40+
it('should complete if poll id available and not editing a message or composing a thread reply', async () => {
41+
const messageComposer = {
42+
pollId: 'poll-id',
43+
editedMessage: false,
44+
threadId: false,
45+
} as any;
46+
47+
const middleware = createPollOnlyCompositionMiddleware(messageComposer);
48+
const middlewareApi = setupMiddlewareApi(stateSeed);
49+
await middleware.handlers.compose(middlewareApi);
50+
51+
expect(middlewareApi.complete).toHaveBeenCalledWith({
52+
localMessage: {
53+
...stateSeed.localMessage,
54+
poll_id: messageComposer.pollId,
55+
},
56+
message: {
57+
id: stateSeed.localMessage.id,
58+
poll_id: messageComposer.pollId,
59+
},
60+
sendOptions: {},
61+
});
62+
});
63+
it('should forward if poll id is undefined', async () => {
64+
const messageComposer = {
65+
pollId: false,
66+
editedMessage: false,
67+
threadId: false,
68+
} as any;
69+
70+
const middleware = createPollOnlyCompositionMiddleware(messageComposer);
71+
const middlewareApi = setupMiddlewareApi(stateSeed);
72+
await middleware.handlers.compose(middlewareApi);
73+
74+
expect(middlewareApi.forward).toHaveBeenCalled();
75+
});
76+
it('should forward if editing a message', async () => {
77+
const messageComposer = {
78+
pollId: true,
79+
editedMessage: true,
80+
threadId: false,
81+
} as any;
82+
83+
const middleware = createPollOnlyCompositionMiddleware(messageComposer);
84+
const middlewareApi = setupMiddlewareApi(stateSeed);
85+
await middleware.handlers.compose(middlewareApi);
86+
87+
expect(middlewareApi.forward).toHaveBeenCalled();
88+
});
89+
it('should forward if composing a thread reply', async () => {
90+
const messageComposer = {
91+
pollId: true,
92+
editedMessage: false,
93+
threadId: true,
94+
} as any;
95+
96+
const middleware = createPollOnlyCompositionMiddleware(messageComposer);
97+
const middlewareApi = setupMiddlewareApi(stateSeed);
98+
await middleware.handlers.compose(middlewareApi);
99+
100+
expect(middlewareApi.forward).toHaveBeenCalled();
101+
});
102+
});

test/unit/MessageComposer/middleware/messageComposer/textComposer.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const setupDraft = (initialState: MessageDraftComposerMiddlewareValueState) => {
3636
};
3737
};
3838

39-
describe('TextComposerMiddleware', () => {
39+
describe('stream-io/message-composer-middleware/text-composition', () => {
4040
let channel: Channel;
4141
let client: StreamChat;
4242
let messageComposer: MessageComposer;
@@ -350,7 +350,7 @@ describe('TextComposerMiddleware', () => {
350350
});
351351
});
352352

353-
describe('DraftTextComposerMiddleware', () => {
353+
describe('stream-io/message-composer-middleware/draft-text-composition', () => {
354354
let channel: Channel;
355355
let client: StreamChat;
356356
let messageComposer: MessageComposer;

0 commit comments

Comments
 (0)