diff --git a/lib/model/channel.dart b/lib/model/channel.dart index 353acde8e4..81dc4123cb 100644 --- a/lib/model/channel.dart +++ b/lib/model/channel.dart @@ -25,7 +25,21 @@ mixin ChannelStore { /// For UI contexts that are not specific to a particular stream, see /// [isTopicVisible]. bool isTopicVisibleInStream(int streamId, String topic) { - switch (topicVisibilityPolicy(streamId, topic)) { + return _isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic)); + } + + /// Whether the given event will change the result of [isTopicVisibleInStream] + /// for its stream and topic, compared to the current state. + VisibilityEffect willChangeIfTopicVisibleInStream(UserTopicEvent event) { + final streamId = event.streamId; + final topic = event.topicName; + return VisibilityEffect._fromBeforeAfter( + _isTopicVisibleInStream(topicVisibilityPolicy(streamId, topic)), + _isTopicVisibleInStream(event.visibilityPolicy)); + } + + static bool _isTopicVisibleInStream(UserTopicVisibilityPolicy policy) { + switch (policy) { case UserTopicVisibilityPolicy.none: return true; case UserTopicVisibilityPolicy.muted: @@ -48,7 +62,21 @@ mixin ChannelStore { /// For UI contexts that are specific to a particular stream, see /// [isTopicVisibleInStream]. bool isTopicVisible(int streamId, String topic) { - switch (topicVisibilityPolicy(streamId, topic)) { + return _isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic)); + } + + /// Whether the given event will change the result of [isTopicVisible] + /// for its stream and topic, compared to the current state. + VisibilityEffect willChangeIfTopicVisible(UserTopicEvent event) { + final streamId = event.streamId; + final topic = event.topicName; + return VisibilityEffect._fromBeforeAfter( + _isTopicVisible(streamId, topicVisibilityPolicy(streamId, topic)), + _isTopicVisible(streamId, event.visibilityPolicy)); + } + + bool _isTopicVisible(int streamId, UserTopicVisibilityPolicy policy) { + switch (policy) { case UserTopicVisibilityPolicy.none: switch (subscriptions[streamId]?.isMuted) { case false: return true; @@ -67,6 +95,28 @@ mixin ChannelStore { } } +/// Whether and how a given [UserTopicEvent] will affect the results +/// that [ChannelStore.isTopicVisible] or [ChannelStore.isTopicVisibleInStream] +/// would give for some messages. +enum VisibilityEffect { + /// The event will have no effect on the visibility results. + none, + + /// The event will change some visibility results from true to false. + muted, + + /// The event will change some visibility results from false to true. + unmuted; + + factory VisibilityEffect._fromBeforeAfter(bool before, bool after) { + return switch ((before, after)) { + (false, true) => VisibilityEffect.unmuted, + (true, false) => VisibilityEffect.muted, + _ => VisibilityEffect.none, + }; + } +} + /// The implementation of [ChannelStore] that does the work. /// /// Generally the only code that should need this class is [PerAccountStore] @@ -156,10 +206,10 @@ class ChannelStoreImpl with ChannelStore { switch (event) { case SubscriptionAddEvent(): for (final subscription in event.subscriptions) { - assert(streams.containsKey(subscription.streamId) - && streams[subscription.streamId] is! Subscription); - assert(streamsByName.containsKey(subscription.name) - && streamsByName[subscription.name] is! Subscription); + assert(streams.containsKey(subscription.streamId)); + assert(streams[subscription.streamId] is! Subscription); + assert(streamsByName.containsKey(subscription.name)); + assert(streamsByName[subscription.name] is! Subscription); assert(!subscriptions.containsKey(subscription.streamId)); streams[subscription.streamId] = subscription; streamsByName[subscription.name] = subscription; diff --git a/lib/model/message.dart b/lib/model/message.dart index 1e3abbbcdb..a3094941f0 100644 --- a/lib/model/message.dart +++ b/lib/model/message.dart @@ -82,6 +82,12 @@ class MessageStoreImpl with MessageStore { } } + void handleUserTopicEvent(UserTopicEvent event) { + for (final view in _messageListViews) { + view.handleUserTopicEvent(event); + } + } + void handleMessageEvent(MessageEvent event) { // If the message is one we already know about (from a fetch), // clobber it with the one from the event system. diff --git a/lib/model/message_list.dart b/lib/model/message_list.dart index f20960e221..95d660d4f5 100644 --- a/lib/model/message_list.dart +++ b/lib/model/message_list.dart @@ -5,6 +5,7 @@ import '../api/model/events.dart'; import '../api/model/model.dart'; import '../api/route/messages.dart'; import 'algorithms.dart'; +import 'channel.dart'; import 'content.dart'; import 'narrow.dart'; import 'store.dart'; @@ -158,6 +159,38 @@ mixin _MessageSequence { _processMessage(messages.length - 1); } + /// Removes all messages from the list that satisfy [test]. + /// + /// Returns true if any messages were removed, false otherwise. + bool _removeMessagesWhere(bool Function(Message) test) { + // Before we find a message to remove, there's no need to copy elements. + // This is like the loop below, but simplified for `target == candidate`. + int candidate = 0; + while (true) { + if (candidate == messages.length) return false; + if (test(messages[candidate])) break; + candidate++; + } + + int target = candidate; + candidate++; + assert(contents.length == messages.length); + while (candidate < messages.length) { + if (test(messages[candidate])) { + candidate++; + continue; + } + messages[target] = messages[candidate]; + contents[target] = contents[candidate]; + target++; candidate++; + } + messages.length = target; + contents.length = target; + assert(contents.length == messages.length); + _reprocessAll(); + return true; + } + /// Removes the given messages, if present. /// /// Returns true if at least one message was present, false otherwise. @@ -389,6 +422,24 @@ class MessageListView with ChangeNotifier, _MessageSequence { } } + /// Whether this event could affect the result that [_messageVisible] + /// would ever have returned for any possible message in this message list. + VisibilityEffect _canAffectVisibility(UserTopicEvent event) { + switch (narrow) { + case CombinedFeedNarrow(): + return store.willChangeIfTopicVisible(event); + + case ChannelNarrow(:final streamId): + if (event.streamId != streamId) return VisibilityEffect.none; + return store.willChangeIfTopicVisibleInStream(event); + + case TopicNarrow(): + case DmNarrow(): + case MentionsNarrow(): + return VisibilityEffect.none; + } + } + /// Whether [_messageVisible] is true for all possible messages. /// /// This is useful for an optimization. @@ -477,6 +528,31 @@ class MessageListView with ChangeNotifier, _MessageSequence { } } + void handleUserTopicEvent(UserTopicEvent event) { + switch (_canAffectVisibility(event)) { + case VisibilityEffect.none: + return; + + case VisibilityEffect.muted: + if (_removeMessagesWhere((message) => + (message is StreamMessage + && message.streamId == event.streamId + && message.topic == event.topicName))) { + notifyListeners(); + } + + case VisibilityEffect.unmuted: + // TODO get the newly-unmuted messages from the message store + // For now, we simplify the task by just refetching this message list + // from scratch. + if (fetched) { + _reset(); + notifyListeners(); + fetchInitial(); + } + } + } + void handleDeleteMessageEvent(DeleteMessageEvent event) { if (_removeMessagesById(event.messageIds)) { notifyListeners(); diff --git a/lib/model/store.dart b/lib/model/store.dart index 228ff2b37e..bdd6c0d9a5 100644 --- a/lib/model/store.dart +++ b/lib/model/store.dart @@ -503,6 +503,8 @@ class PerAccountStore extends ChangeNotifier with ChannelStore, MessageStore { case UserTopicEvent(): assert(debugLog("server event: user_topic")); + _messages.handleUserTopicEvent(event); + // Update _channels last, so other handlers can compare to the old value. _channels.handleUserTopicEvent(event); notifyListeners(); diff --git a/test/example_data.dart b/test/example_data.dart index bd7d214a88..aa4f1931e4 100644 --- a/test/example_data.dart +++ b/test/example_data.dart @@ -183,11 +183,14 @@ ZulipStream stream({ }) { _checkPositive(streamId, 'stream ID'); _checkPositive(firstMessageId, 'message ID'); + var effectiveStreamId = streamId ?? _nextStreamId(); + var effectiveName = name ?? 'stream $effectiveStreamId'; + var effectiveDescription = description ?? 'Description of $effectiveName'; return ZulipStream( - streamId: streamId ?? _nextStreamId(), - name: name ?? 'A stream', // TODO generate example names - description: description ?? 'A description', // TODO generate example descriptions - renderedDescription: renderedDescription ?? '<p>A description</p>', // TODO generate random + streamId: effectiveStreamId, + name: effectiveName, + description: effectiveDescription, + renderedDescription: renderedDescription ?? '<p>$effectiveDescription</p>', dateCreated: dateCreated ?? 1686774898, firstMessageId: firstMessageId, inviteOnly: inviteOnly ?? false, @@ -426,6 +429,17 @@ const _unreadMsgs = unreadMsgs; // Events. // +UserTopicEvent userTopicEvent( + int streamId, String topic, UserTopicVisibilityPolicy visibilityPolicy) { + return UserTopicEvent( + id: 1, + streamId: streamId, + topicName: topic, + lastUpdated: 1234567890, + visibilityPolicy: visibilityPolicy, + ); +} + DeleteMessageEvent deleteMessageEvent(List<StreamMessage> messages) { assert(messages.isNotEmpty); final streamId = messages.first.streamId; diff --git a/test/model/channel_test.dart b/test/model/channel_test.dart index d8dc3c07f4..219529e2da 100644 --- a/test/model/channel_test.dart +++ b/test/model/channel_test.dart @@ -34,8 +34,8 @@ void main() { } test('initial', () { - final stream1 = eg.stream(streamId: 1, name: 'stream 1'); - final stream2 = eg.stream(streamId: 2, name: 'stream 2'); + final stream1 = eg.stream(); + final stream2 = eg.stream(); checkUnified(eg.store(initialSnapshot: eg.initialSnapshot( streams: [stream1, stream2], subscriptions: [eg.subscription(stream1)], @@ -43,8 +43,8 @@ void main() { }); test('added by events', () async { - final stream1 = eg.stream(streamId: 1, name: 'stream 1'); - final stream2 = eg.stream(streamId: 2, name: 'stream 2'); + final stream1 = eg.stream(); + final stream2 = eg.stream(); final store = eg.store(); checkUnified(store); @@ -106,8 +106,8 @@ void main() { }); group('topic visibility', () { - final stream1 = eg.stream(streamId: 1, name: 'stream 1'); - final stream2 = eg.stream(streamId: 2, name: 'stream 2'); + final stream1 = eg.stream(); + final stream2 = eg.stream(); group('getter topicVisibilityPolicy', () { test('with nothing for stream', () { @@ -189,6 +189,97 @@ void main() { }); }); + group('willChangeIfTopicVisible/InStream', () { + UserTopicEvent mkEvent(UserTopicVisibilityPolicy policy) => + eg.userTopicEvent(stream1.streamId, 'topic', policy); + + void checkChanges(PerAccountStore store, + UserTopicVisibilityPolicy newPolicy, + VisibilityEffect expectedInStream, VisibilityEffect expectedOverall) { + final event = mkEvent(newPolicy); + check(store.willChangeIfTopicVisibleInStream(event)).equals(expectedInStream); + check(store.willChangeIfTopicVisible (event)).equals(expectedOverall); + } + + test('stream not muted, policy none -> followed, no change', () async { + final store = eg.store(); + await store.addStream(stream1); + await store.addSubscription(eg.subscription(stream1)); + checkChanges(store, UserTopicVisibilityPolicy.followed, + VisibilityEffect.none, VisibilityEffect.none); + }); + + test('stream not muted, policy none -> muted, means muted', () async { + final store = eg.store(); + await store.addStream(stream1); + await store.addSubscription(eg.subscription(stream1)); + checkChanges(store, UserTopicVisibilityPolicy.muted, + VisibilityEffect.muted, VisibilityEffect.muted); + }); + + test('stream muted, policy none -> followed, means none/unmuted', () async { + final store = eg.store(); + await store.addStream(stream1); + await store.addSubscription(eg.subscription(stream1, isMuted: true)); + checkChanges(store, UserTopicVisibilityPolicy.followed, + VisibilityEffect.none, VisibilityEffect.unmuted); + }); + + test('stream muted, policy none -> muted, means muted/none', () async { + final store = eg.store(); + await store.addStream(stream1); + await store.addSubscription(eg.subscription(stream1, isMuted: true)); + checkChanges(store, UserTopicVisibilityPolicy.muted, + VisibilityEffect.muted, VisibilityEffect.none); + }); + + final policies = [ + UserTopicVisibilityPolicy.muted, + UserTopicVisibilityPolicy.none, + UserTopicVisibilityPolicy.unmuted, + ]; + for (final streamMuted in [null, false, true]) { + for (final oldPolicy in policies) { + for (final newPolicy in policies) { + final streamDesc = switch (streamMuted) { + false => "stream not muted", + true => "stream muted", + null => "stream unsubscribed", + }; + test('$streamDesc, topic ${oldPolicy.name} -> ${newPolicy.name}', () async { + final store = eg.store(); + await store.addStream(stream1); + if (streamMuted != null) { + await store.addSubscription( + eg.subscription(stream1, isMuted: streamMuted)); + } + store.handleEvent(mkEvent(oldPolicy)); + final oldVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic'); + final oldVisible = store.isTopicVisible(stream1.streamId, 'topic'); + + final event = mkEvent(newPolicy); + final willChangeInStream = store.willChangeIfTopicVisibleInStream(event); + final willChange = store.willChangeIfTopicVisible(event); + + store.handleEvent(event); + final newVisibleInStream = store.isTopicVisibleInStream(stream1.streamId, 'topic'); + final newVisible = store.isTopicVisible(stream1.streamId, 'topic'); + + VisibilityEffect fromOldNew(bool oldVisible, bool newVisible) { + if (newVisible == oldVisible) return VisibilityEffect.none; + if (newVisible) return VisibilityEffect.unmuted; + return VisibilityEffect.muted; + } + check(willChangeInStream) + .equals(fromOldNew(oldVisibleInStream, newVisibleInStream)); + check(willChange) + .equals(fromOldNew(oldVisible, newVisible)); + }); + } + } + } + }); + void compareTopicVisibility(PerAccountStore store, List<UserTopicItem> expected) { final expectedStore = eg.store(initialSnapshot: eg.initialSnapshot( userTopics: expected, diff --git a/test/model/message_list_test.dart b/test/model/message_list_test.dart index ce1f582b1e..ccb006e78a 100644 --- a/test/model/message_list_test.dart +++ b/test/model/message_list_test.dart @@ -311,6 +311,179 @@ void main() { check(model).fetched.isFalse(); }); + group('UserTopicEvent', () { + // The ChannelStore.willChangeIfTopicVisible/InStream methods have their own + // thorough unit tests. So these tests focus on the rest of the logic. + + final stream = eg.stream(); + const String topic = 'foo'; + + Future<void> setVisibility(UserTopicVisibilityPolicy policy) async { + await store.handleEvent(eg.userTopicEvent(stream.streamId, topic, policy)); + } + + /// (Should run after `prepare`.) + Future<void> prepareMutes([ + bool streamMuted = false, + UserTopicVisibilityPolicy policy = UserTopicVisibilityPolicy.none, + ]) async { + await store.addStream(stream); + await store.addSubscription(eg.subscription(stream, isMuted: streamMuted)); + await setVisibility(policy); + } + + void checkHasMessageIds(Iterable<int> messageIds) { + check(model.messages.map((m) => m.id)).deepEquals(messageIds); + } + + test('mute a visible topic', () async { + await prepare(narrow: const CombinedFeedNarrow()); + await prepareMutes(); + final otherStream = eg.stream(); + await store.addStream(otherStream); + await store.addSubscription(eg.subscription(otherStream)); + await prepareMessages(foundOldest: true, messages: [ + eg.streamMessage(id: 1, stream: stream, topic: 'bar'), + eg.streamMessage(id: 2, stream: stream, topic: topic), + eg.streamMessage(id: 3, stream: otherStream, topic: 'elsewhere'), + eg.dmMessage( id: 4, from: eg.otherUser, to: [eg.selfUser]), + ]); + checkHasMessageIds([1, 2, 3, 4]); + + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotifiedOnce(); + checkHasMessageIds([1, 3, 4]); + }); + + test('in CombinedFeedNarrow, use combined-feed visibility', () async { + // Compare the parallel ChannelNarrow test below. + await prepare(narrow: const CombinedFeedNarrow()); + // Mute the stream, so that combined-feed vs. stream visibility differ. + await prepareMutes(true, UserTopicVisibilityPolicy.followed); + await prepareMessages(foundOldest: true, messages: [ + eg.streamMessage(id: 1, stream: stream, topic: topic), + ]); + checkHasMessageIds([1]); + + // Dropping from followed to none hides the message + // (whereas it'd have no effect in a stream narrow). + await setVisibility(UserTopicVisibilityPolicy.none); + checkNotifiedOnce(); + checkHasMessageIds([]); + + // Dropping from none to muted has no further effect + // (whereas it'd hide the message in a stream narrow). + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotNotified(); + checkHasMessageIds([]); + }); + + test('in ChannelNarrow, use stream visibility', () async { + // Compare the parallel CombinedFeedNarrow test above. + await prepare(narrow: ChannelNarrow(stream.streamId)); + // Mute the stream, so that combined-feed vs. stream visibility differ. + await prepareMutes(true, UserTopicVisibilityPolicy.followed); + await prepareMessages(foundOldest: true, messages: [ + eg.streamMessage(id: 1, stream: stream, topic: topic), + ]); + checkHasMessageIds([1]); + + // Dropping from followed to none has no effect + // (whereas it'd hide the message in the combined feed). + await setVisibility(UserTopicVisibilityPolicy.none); + checkNotNotified(); + checkHasMessageIds([1]); + + // Dropping from none to muted hides the message + // (whereas it'd have no effect in a stream narrow). + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotifiedOnce(); + checkHasMessageIds([]); + }); + + test('in TopicNarrow, stay visible', () async { + await prepare(narrow: TopicNarrow(stream.streamId, topic)); + await prepareMutes(); + await prepareMessages(foundOldest: true, messages: [ + eg.streamMessage(id: 1, stream: stream, topic: topic), + ]); + checkHasMessageIds([1]); + + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotNotified(); + checkHasMessageIds([1]); + }); + + test('in DmNarrow, do nothing (smoke test)', () async { + await prepare(narrow: + DmNarrow.withUser(eg.otherUser.userId, selfUserId: eg.selfUser.userId)); + await prepareMutes(); + await prepareMessages(foundOldest: true, messages: [ + eg.dmMessage(id: 1, from: eg.otherUser, to: [eg.selfUser]), + ]); + checkHasMessageIds([1]); + + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotNotified(); + checkHasMessageIds([1]); + }); + + test('no affected messages -> no notification', () async { + await prepare(narrow: const CombinedFeedNarrow()); + await prepareMutes(); + await prepareMessages(foundOldest: true, messages: [ + eg.streamMessage(id: 1, stream: stream, topic: 'bar'), + ]); + checkHasMessageIds([1]); + + await setVisibility(UserTopicVisibilityPolicy.muted); + checkNotNotified(); + checkHasMessageIds([1]); + }); + + test('unmute a topic -> refetch from scratch', () => awaitFakeAsync((async) async { + await prepare(narrow: const CombinedFeedNarrow()); + await prepareMutes(true); + final messages = [ + eg.dmMessage(id: 1, from: eg.otherUser, to: [eg.selfUser]), + eg.streamMessage(id: 2, stream: stream, topic: topic), + ]; + await prepareMessages(foundOldest: true, messages: messages); + checkHasMessageIds([1]); + + connection.prepare( + json: newestResult(foundOldest: true, messages: messages).toJson()); + await setVisibility(UserTopicVisibilityPolicy.unmuted); + checkNotifiedOnce(); + check(model).fetched.isFalse(); + checkHasMessageIds([]); + + async.elapse(Duration.zero); + checkNotifiedOnce(); + checkHasMessageIds([1, 2]); + })); + + test('unmute a topic before initial fetch completes -> do nothing', () => awaitFakeAsync((async) async { + await prepare(narrow: const CombinedFeedNarrow()); + await prepareMutes(true); + final messages = [ + eg.streamMessage(id: 1, stream: stream, topic: topic), + ]; + + connection.prepare( + json: newestResult(foundOldest: true, messages: messages).toJson()); + final fetchFuture = model.fetchInitial(); + + await setVisibility(UserTopicVisibilityPolicy.unmuted); + checkNotNotified(); + + // The new policy does get applied when the fetch eventually completes. + await fetchFuture; + checkNotifiedOnce(); + checkHasMessageIds([1]); + })); + }); + group('DeleteMessageEvent', () { final stream = eg.stream(); final messages = List.generate(30, (i) => eg.streamMessage(stream: stream)); @@ -467,8 +640,8 @@ void main() { }); group('messagesMoved', () { - final stream = eg.stream(streamId: 1, name: 'test stream'); - final otherStream = eg.stream(streamId: 2, name: 'other stream'); + final stream = eg.stream(); + final otherStream = eg.stream(); void checkHasMessages(Iterable<Message> messages) { check(model.messages.map((e) => e.id)).deepEquals(messages.map((e) => e.id)); @@ -1094,8 +1267,8 @@ void main() { group('stream/topic muting', () { test('in CombinedFeedNarrow', () async { - final stream1 = eg.stream(streamId: 1, name: 'stream 1'); - final stream2 = eg.stream(streamId: 2, name: 'stream 2'); + final stream1 = eg.stream(); + final stream2 = eg.stream(); await prepare(narrow: const CombinedFeedNarrow()); await store.addStreams([stream1, stream2]); await store.addSubscription(eg.subscription(stream1)); @@ -1157,7 +1330,7 @@ void main() { }); test('in ChannelNarrow', () async { - final stream = eg.stream(streamId: 1, name: 'stream 1'); + final stream = eg.stream(); await prepare(narrow: ChannelNarrow(stream.streamId)); await store.addStream(stream); await store.addSubscription(eg.subscription(stream, isMuted: true)); @@ -1204,7 +1377,7 @@ void main() { }); test('in TopicNarrow', () async { - final stream = eg.stream(streamId: 1, name: 'stream 1'); + final stream = eg.stream(); await prepare(narrow: TopicNarrow(stream.streamId, 'A')); await store.addStream(stream); await store.addSubscription(eg.subscription(stream, isMuted: true)); @@ -1236,7 +1409,7 @@ void main() { }); test('in MentionsNarrow', () async { - final stream = eg.stream(streamId: 1, name: 'muted stream'); + final stream = eg.stream(); const mutedTopic = 'muted'; await prepare(narrow: const MentionsNarrow()); await store.addStream(stream); diff --git a/test/model/test_store.dart b/test/model/test_store.dart index 32aeed5216..e3a7a5cae9 100644 --- a/test/model/test_store.dart +++ b/test/model/test_store.dart @@ -5,6 +5,7 @@ import 'package:zulip/model/store.dart'; import 'package:zulip/widgets/store.dart'; import '../api/fake_api.dart'; +import '../example_data.dart' as eg; /// A [GlobalStore] containing data provided by callers, /// and that causes no database queries or network requests. @@ -146,13 +147,7 @@ extension PerAccountStoreTestExtension on PerAccountStore { } Future<void> addUserTopic(ZulipStream stream, String topic, UserTopicVisibilityPolicy visibilityPolicy) async { - await handleEvent(UserTopicEvent( - id: 1, - streamId: stream.streamId, - topicName: topic, - lastUpdated: 1234567890, - visibilityPolicy: visibilityPolicy, - )); + await handleEvent(eg.userTopicEvent(stream.streamId, topic, visibilityPolicy)); } Future<void> addMessage(Message message) async { diff --git a/test/model/unreads_test.dart b/test/model/unreads_test.dart index b35f5c770e..1f66b2ad25 100644 --- a/test/model/unreads_test.dart +++ b/test/model/unreads_test.dart @@ -153,9 +153,9 @@ void main() { group('count helpers', () { test('countInCombinedFeedNarrow', () async { - final stream1 = eg.stream(streamId: 1, name: 'stream 1'); - final stream2 = eg.stream(streamId: 2, name: 'stream 2'); - final stream3 = eg.stream(streamId: 3, name: 'stream 3'); + final stream1 = eg.stream(); + final stream2 = eg.stream(); + final stream3 = eg.stream(); prepare(); await channelStore.addStreams([stream1, stream2, stream3]); await channelStore.addSubscription(eg.subscription(stream1)); diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index 23a0be361a..741dbdeec1 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -618,8 +618,8 @@ void main() { group('Update Narrow on message move', () { const topic = 'foo'; - final channel = eg.stream(name: 'move test stream'); - final otherChannel = eg.stream(name: 'other move test stream'); + final channel = eg.stream(); + final otherChannel = eg.stream(); final narrow = TopicNarrow(channel.streamId, topic); void prepareGetMessageResponse(List<Message> messages) { diff --git a/test/widgets/subscription_list_test.dart b/test/widgets/subscription_list_test.dart index 9909dc09e9..b1c7b89137 100644 --- a/test/widgets/subscription_list_test.dart +++ b/test/widgets/subscription_list_test.dart @@ -284,10 +284,10 @@ void main() { check(wght).equals(expectedWght); } - final unmutedStreamWithUnmutedUnreads = eg.stream(name: 'Unmuted stream with unmuted unreads'); - final unmutedStreamWithNoUnmutedUnreads = eg.stream(name: 'Unmuted stream with no unmuted unreads'); - final mutedStreamWithUnmutedUnreads = eg.stream(name: 'Muted stream with unmuted unreads'); - final mutedStreamWithNoUnmutedUnreads = eg.stream(name: 'Muted stream with no unmuted unreads'); + final unmutedStreamWithUnmutedUnreads = eg.stream(); + final unmutedStreamWithNoUnmutedUnreads = eg.stream(); + final mutedStreamWithUnmutedUnreads = eg.stream(); + final mutedStreamWithNoUnmutedUnreads = eg.stream(); await setupStreamListPage(tester, subscriptions: [