Skip to content

Commit 2e82af2

Browse files
committed
msglist: In unsubscribed channel, clear outbox message on send success
This fixes half of the "third buggy behavior" described in #1798: the send-message case. We'll fix it for the edit-message case too, coming up. Fixes-partly: #1798
1 parent 7d73858 commit 2e82af2

File tree

3 files changed

+85
-7
lines changed

3 files changed

+85
-7
lines changed

lib/model/message.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,13 +889,18 @@ const kSendMessageOfferRestoreWaitPeriod = Duration(seconds: 10); // TODO(#1441
889889
/// timed out. not finished when
890890
/// wait period timed out.
891891
///
892-
/// Event received.
893-
/// (any state) ─────────────────► (delete)
892+
/// Event received, or [sendMessage]
893+
/// request succeeds and we're sending to
894+
/// an unsubscribed channel.
895+
/// (any state) ───────────────────────────────────────► (delete)
894896
/// ```
895897
///
896898
/// During its lifecycle, it is guaranteed that the outbox message is deleted
897899
/// as soon a message event with a matching [MessageEvent.localMessageId]
898900
/// arrives.
901+
/// If we're sending to an unsubscribed channel, we don't expect an event
902+
/// (see "third buggy behavior" in #1798) so in that case
903+
/// the outbox message is deleted when the [sendMessage] request succeeds.
899904
enum OutboxMessageState {
900905
/// The [sendMessage] HTTP request has started but the resulting
901906
/// [MessageEvent] hasn't arrived, and nor has the request failed. In this
@@ -1148,6 +1153,19 @@ mixin _OutboxMessageStore on HasChannelStore {
11481153
// The message event already arrived; nothing to do.
11491154
return;
11501155
}
1156+
1157+
if (destination is StreamDestination && subscriptions[destination.streamId] == null) {
1158+
// We don't expect an event (we're sending to an unsubscribed channel);
1159+
// clear the loading spinner.
1160+
_outboxMessages.remove(localMessageId);
1161+
_outboxMessageDebounceTimers.remove(localMessageId)?.cancel();
1162+
_outboxMessageWaitPeriodTimers.remove(localMessageId)?.cancel();
1163+
for (final view in _messageListViews) {
1164+
view.notifyListenersIfOutboxMessagePresent(localMessageId);
1165+
}
1166+
return;
1167+
}
1168+
11511169
// The send request succeeded, so the message was definitely sent.
11521170
// Cancel the timer that would have had us start presuming that the
11531171
// send might have failed.

test/model/message_test.dart

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void main() {
5353

5454
/// Initialize [store] and the rest of the test state.
5555
Future<void> prepare({
56+
Narrow narrow = const CombinedFeedNarrow(),
5657
ZulipStream? stream,
5758
bool isChannelSubscribed = true,
5859
int? zulipFeatureLevel,
@@ -71,7 +72,7 @@ void main() {
7172
connection = store.connection as FakeApiConnection;
7273
notifiedCount = 0;
7374
messageList = MessageListView.init(store: store,
74-
narrow: const CombinedFeedNarrow(),
75+
narrow: narrow,
7576
anchor: AnchorCode.newest)
7677
..addListener(() {
7778
notifiedCount++;
@@ -172,11 +173,17 @@ void main() {
172173
check(store.outboxMessages).values.single.state;
173174

174175
Future<void> prepareOutboxMessage({
176+
Narrow narrow = const CombinedFeedNarrow(),
175177
MessageDestination? destination,
178+
bool isChannelSubscribed = true,
176179
int? zulipFeatureLevel,
177180
}) async {
178181
message = eg.streamMessage(stream: stream);
179-
await prepare(stream: stream, zulipFeatureLevel: zulipFeatureLevel);
182+
await prepare(
183+
narrow: narrow,
184+
stream: stream,
185+
isChannelSubscribed: isChannelSubscribed,
186+
zulipFeatureLevel: zulipFeatureLevel);
180187
await prepareMessages([eg.streamMessage(stream: stream)]);
181188
connection.prepare(json: SendMessageResult(id: 1).toJson());
182189
await store.sendMessage(
@@ -368,6 +375,16 @@ void main() {
368375
checkNotNotified();
369376
}));
370377

378+
test('hidden -> (delete) because send succeeded in unsubscribed channel', () => awaitFakeAsync((async) async {
379+
await prepareOutboxMessage(
380+
// Not CombinedFeedNarrow,
381+
// which always hides outbox messages in unsubscribed channels.
382+
narrow: ChannelNarrow(stream.streamId),
383+
isChannelSubscribed: false);
384+
check(store.outboxMessages).isEmpty();
385+
checkNotNotified();
386+
}));
387+
371388
test('waiting -> (delete) because event received', () => awaitFakeAsync((async) async {
372389
await prepareOutboxMessage();
373390
async.elapse(kLocalEchoDebounceDuration);
@@ -400,6 +417,28 @@ void main() {
400417
checkNotNotified();
401418
}));
402419

420+
test('waiting -> (delete) because send succeeded in unsubscribed channel', () => awaitFakeAsync((async) async {
421+
await prepare(
422+
// Not CombinedFeedNarrow,
423+
// which always hides outbox messages in unsubscribed channels.
424+
narrow: ChannelNarrow(stream.streamId),
425+
stream: stream,
426+
isChannelSubscribed: false);
427+
await prepareMessages([eg.streamMessage(stream: stream)]);
428+
connection.prepare(json: SendMessageResult(id: 1).toJson(),
429+
delay: kLocalEchoDebounceDuration + Duration(seconds: 1));
430+
final future = store.sendMessage(
431+
destination: streamDestination, content: 'content');
432+
async.elapse(kLocalEchoDebounceDuration);
433+
checkState().equals(OutboxMessageState.waiting);
434+
checkNotifiedOnce();
435+
436+
async.elapse(Duration(seconds: 1));
437+
await check(future).completes();
438+
check(store.outboxMessages).isEmpty();
439+
checkNotifiedOnce();
440+
}));
441+
403442
test('waitPeriodExpired -> (delete) when event arrives before send request fails', () => awaitFakeAsync((async) async {
404443
// Set up an error to fail `sendMessage` with a delay, leaving time for
405444
// the message event to arrive.
@@ -435,6 +474,27 @@ void main() {
435474
checkNotifiedOnce();
436475
}));
437476

477+
test('waitPeriodExpired -> (delete) because send succeeded in unsubscribed channel', () => awaitFakeAsync((async) async {
478+
await prepare(
479+
// Not CombinedFeedNarrow,
480+
// which always hides outbox messages in unsubscribed channels.
481+
narrow: ChannelNarrow(stream.streamId),
482+
isChannelSubscribed: false);
483+
await prepareMessages([eg.streamMessage(stream: stream)]);
484+
connection.prepare(json: SendMessageResult(id: 1).toJson(),
485+
delay: kSendMessageOfferRestoreWaitPeriod + Duration(seconds: 1));
486+
final future = store.sendMessage(
487+
destination: streamDestination, content: 'content');
488+
async.elapse(kSendMessageOfferRestoreWaitPeriod);
489+
checkState().equals(OutboxMessageState.waitPeriodExpired);
490+
checkNotified(count: 2);
491+
492+
async.elapse(Duration(seconds: 1));
493+
await check(future).completes();
494+
check(store.outboxMessages).isEmpty();
495+
checkNotifiedOnce();
496+
}));
497+
438498
test('failed -> (delete) because event received', () => awaitFakeAsync((async) async {
439499
await prepareOutboxMessageToFailAfterDelay(Duration.zero);
440500
await check(outboxMessageFailFuture).throws();

test/widgets/compose_box_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,9 +1022,9 @@ void main() {
10221022
of: find.byType(MessageWithPossibleSender),
10231023
matching: find.text('hello world'))
10241024
).findsOne();
1025-
// TODO(#1798) There's actually a lingering OutboxMessageWithPossibleSender,
1026-
// too (the "third buggy behavior" in #1798). We'll fix that soon,
1027-
// and it'll be convenient to test that here too.
1025+
// Regression coverage for the "third buggy behavior"
1026+
// in https://github.com/zulip/zulip-flutter/issues/1798 .
1027+
check(find.byType(OutboxMessageWithPossibleSender)).findsNothing();
10281028
});
10291029
});
10301030

0 commit comments

Comments
 (0)