Skip to content

Commit 80e7e06

Browse files
committed
fix: unnecesary draft emission when channel is changed
1 parent 4e8c1de commit 80e7e06

File tree

4 files changed

+49
-8
lines changed

4 files changed

+49
-8
lines changed

projects/stream-chat-angular/src/lib/channel.service.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ describe('ChannelService', () => {
359359
pinnedMessagesSpy.calls.reset();
360360
typingUsersSpy.calls.reset();
361361
typingUsersInThreadSpy.calls.reset();
362+
const channelSwitchStateSpy = jasmine.createSpy();
363+
service.channelSwitchState$.subscribe(channelSwitchStateSpy);
364+
channelSwitchStateSpy.calls.reset();
362365
service.deselectActiveChannel();
363366

364367
expect(messagesSpy).toHaveBeenCalledWith([]);
@@ -381,6 +384,10 @@ describe('ChannelService', () => {
381384

382385
expect(messagesSpy).not.toHaveBeenCalled();
383386
expect(service.isMessageLoadingInProgress).toBeFalse();
387+
388+
expect(channelSwitchStateSpy.calls.count()).toBe(2);
389+
expect(channelSwitchStateSpy.calls.first().args[0]).toBe('start');
390+
expect(channelSwitchStateSpy.calls.mostRecent().args[0]).toBe('end');
384391
});
385392

386393
it('should tell if user #hasMoreChannels$', async () => {
@@ -457,6 +464,9 @@ describe('ChannelService', () => {
457464
spyOn(newActiveChannel, 'markRead');
458465
const pinnedMessages = generateMockMessages();
459466
newActiveChannel.state.pinnedMessages = pinnedMessages;
467+
const channelSwitchStateSpy = jasmine.createSpy();
468+
service.channelSwitchState$.subscribe(channelSwitchStateSpy);
469+
channelSwitchStateSpy.calls.reset();
460470
service.setAsActiveChannel(newActiveChannel);
461471
result = spy.calls.mostRecent().args[0] as Channel;
462472

@@ -467,6 +477,9 @@ describe('ChannelService', () => {
467477
expect(pinnedMessagesSpy).toHaveBeenCalledWith(pinnedMessages);
468478
expect(typingUsersSpy).toHaveBeenCalledWith([]);
469479
expect(typingUsersInThreadSpy).toHaveBeenCalledWith([]);
480+
expect(channelSwitchStateSpy.calls.count()).toBe(2);
481+
expect(channelSwitchStateSpy.calls.first().args[0]).toBe('start');
482+
expect(channelSwitchStateSpy.calls.mostRecent().args[0]).toBe('end');
470483
});
471484

472485
it('should emit #activeChannelMessages$', async () => {

projects/stream-chat-angular/src/lib/channel.service.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export class ChannelService {
307307
beforeUpdateMessage?: (
308308
message: StreamMessage
309309
) => StreamMessage | Promise<StreamMessage>;
310+
/**
311+
* Since switching channels changes the state of multiple obserables, this observable can be used to check if all observables are updated.
312+
* - `end` means all observables are in stable state
313+
* - `start` means all observables are in unstable state
314+
*/
315+
channelSwitchState$: Observable<'start' | 'end'>;
310316
/**
311317
* @internal
312318
*/
@@ -357,6 +363,9 @@ export class ChannelService {
357363
private channelQueryStateSubject = new BehaviorSubject<
358364
ChannelQueryState | undefined
359365
>(undefined);
366+
private channelSwitchStateSubject = new BehaviorSubject<'start' | 'end'>(
367+
'end'
368+
);
360369
private channelQuery?:
361370
| ChannelQuery
362371
| ((queryType: ChannelQueryType) => Promise<ChannelQueryResult>);
@@ -511,6 +520,9 @@ export class ChannelService {
511520
this.channelQueryState$ = this.channelQueryStateSubject
512521
.asObservable()
513522
.pipe(shareReplay(1));
523+
this.channelSwitchState$ = this.channelSwitchStateSubject
524+
.asObservable()
525+
.pipe(shareReplay(1));
514526
}
515527

516528
/**
@@ -557,6 +569,7 @@ export class ChannelService {
557569
* @param channel
558570
*/
559571
setAsActiveChannel(channel: Channel) {
572+
this.channelSwitchStateSubject.next('start');
560573
const prevActiveChannel = this.activeChannelSubject.getValue();
561574
if (prevActiveChannel?.cid === channel.cid) {
562575
return;
@@ -585,12 +598,14 @@ export class ChannelService {
585598
);
586599
}
587600
this.setChannelState(channel);
601+
this.channelSwitchStateSubject.next('end');
588602
}
589603

590604
/**
591605
* Deselects the currently active (if any) channel
592606
*/
593607
deselectActiveChannel() {
608+
this.channelSwitchStateSubject.next('start');
594609
const activeChannel = this.activeChannelSubject.getValue();
595610
if (!activeChannel) {
596611
return;
@@ -611,6 +626,7 @@ export class ChannelService {
611626
this.activeChannelUnreadCount = undefined;
612627
this.areReadEventsPaused = false;
613628
this.isMessageLoadingInProgress = false;
629+
this.channelSwitchStateSubject.next('end');
614630
}
615631

616632
/**

projects/stream-chat-angular/src/lib/message-input/message-input.component.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import { CustomTemplatesService } from '../custom-templates.service';
4343
import { MessageInputConfigService } from './message-input-config.service';
4444
import { MessageTextComponent } from '../message-text/message-text.component';
4545

46-
fdescribe('MessageInputComponent', () => {
46+
describe('MessageInputComponent', () => {
4747
let nativeElement: HTMLElement;
4848
let component: MessageInputComponent;
4949
let fixture: ComponentFixture<MessageInputComponent>;
@@ -58,6 +58,7 @@ fdescribe('MessageInputComponent', () => {
5858
let mockActiveParentMessageId$: BehaviorSubject<string | undefined>;
5959
let sendMessageSpy: jasmine.Spy;
6060
let updateMessageSpy: jasmine.Spy;
61+
let channelSwitchState$: BehaviorSubject<'start' | 'end'>;
6162
let channel: Channel;
6263
let user: UserResponse;
6364
let attachmentService: {
@@ -127,6 +128,7 @@ fdescribe('MessageInputComponent', () => {
127128
],
128129
},
129130
});
131+
channelSwitchState$ = new BehaviorSubject<'start' | 'end'>('end');
130132
TestBed.configureTestingModule({
131133
imports: [TranslateModule.forRoot(), StreamAvatarModule],
132134
declarations: [
@@ -151,6 +153,7 @@ fdescribe('MessageInputComponent', () => {
151153
typingStarted: typingStartedSpy,
152154
typingStopped: typingStoppedSpy,
153155
latestMessageDateByUserByChannels$,
156+
channelSwitchState$: channelSwitchState$,
154157
},
155158
},
156159
{
@@ -1152,7 +1155,7 @@ fdescribe('MessageInputComponent', () => {
11521155
expect(queryVoiceRecorderButton()?.disabled).toBe(true);
11531156
});
11541157

1155-
describe('message draft output', () => {
1158+
describe('message draft change', () => {
11561159
it('should emit undefined when all message fields are cleared', () => {
11571160
// Parent id doesn't count here
11581161
component.mode = 'thread';
@@ -1325,14 +1328,21 @@ fdescribe('MessageInputComponent', () => {
13251328
});
13261329

13271330
it('should not emit if active channel changes', () => {
1331+
mockMessageToQuote$.next(mockMessage());
1332+
13281333
const messageDraftSpy = jasmine.createSpy();
13291334
component.messageDraftChange.subscribe(messageDraftSpy);
13301335
queryTextarea()?.valueChange.next('Hello');
1336+
fixture.detectChanges();
1337+
13311338
messageDraftSpy.calls.reset();
1339+
channelSwitchState$.next('start');
1340+
mockMessageToQuote$.next(undefined);
13321341
mockActiveChannel$.next({
13331342
...mockActiveChannel$.getValue(),
13341343
id: 'new-channel',
13351344
} as any as Channel);
1345+
channelSwitchState$.next('end');
13361346
fixture.detectChanges();
13371347

13381348
expect(messageDraftSpy).not.toHaveBeenCalled();

projects/stream-chat-angular/src/lib/message-input/message-input.component.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,12 @@ export class MessageInputComponent
228228
this.subscriptions.push(
229229
this.channelService.activeChannel$.subscribe((channel) => {
230230
if (channel && this.channel && channel.id !== this.channel.id) {
231-
this.isChannelChangeResetInProgress = true;
232231
this.textareaValue = '';
233232
this.attachmentService.resetAttachmentUploads();
234233
this.pollId = undefined;
235234
this.voiceRecorderService.isRecorderVisible$.next(false);
236235
// Preemptively deselect quoted message, to avoid unwanted draft emission
237236
this.channelService.selectMessageToQuote(undefined);
238-
this.isChannelChangeResetInProgress = false;
239237
}
240238
const capabilities = channel?.data?.own_capabilities as string[];
241239
if (capabilities) {
@@ -248,14 +246,18 @@ export class MessageInputComponent
248246
}
249247
})
250248
);
249+
this.subscriptions.push(
250+
this.channelService.channelSwitchState$.subscribe((state) => {
251+
this.isChannelChangeResetInProgress = state === 'start';
252+
})
253+
);
251254
this.subscriptions.push(
252255
this.channelService.messageToQuote$.subscribe((m) => {
253256
const isThreadReply = m && m.parent_id;
254257
if (
255-
((this.mode === 'thread' && isThreadReply) ||
256-
(this.mode === 'thread' && this.quotedMessage && !m) ||
257-
(this.mode === 'main' && !isThreadReply)) &&
258-
(!!m || !!this.quotedMessage)
258+
(this.mode === 'thread' && isThreadReply) ||
259+
(this.mode === 'thread' && this.quotedMessage && !m) ||
260+
(this.mode === 'main' && !isThreadReply)
259261
) {
260262
this.quotedMessage = m;
261263
this.updateMessageDraft();

0 commit comments

Comments
 (0)