From 6ec1defc947984310782406ad49b25cb0d1f0fa5 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 6 Aug 2025 18:05:13 -0700 Subject: [PATCH 1/4] msglist [nfc]: Add MessageListPage.maybeAncestorOf, alongside .ancestorOf --- lib/widgets/message_list.dart | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index fa6c8b9229..61dbbe1d4c 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -197,14 +197,30 @@ class MessageListPage extends StatefulWidget { /// /// Uses the inefficient [BuildContext.findAncestorStateOfType]; /// don't call this in a build method. - // If we do find ourselves wanting this in a build method, it won't be hard - // to enable that: we'd just need to add an [InheritedWidget] here. + /// + /// See also: + /// * [maybeAncestorOf], which returns null instead of throwing + /// when an ancestor [MessageListPageState] is not found. static MessageListPageState ancestorOf(BuildContext context) { - final state = context.findAncestorStateOfType<_MessageListPageState>(); + final state = maybeAncestorOf(context); assert(state != null, 'No MessageListPage ancestor'); return state!; } + /// The [MessageListPageState] above this context in the tree, if any. + /// + /// Uses the inefficient [BuildContext.findAncestorStateOfType]; + /// don't call this in a build method. + /// + /// See also: + /// * [ancestorOf], which throws instead of returning null + /// when an ancestor [MessageListPageState] is not found. + // If we do find ourselves wanting this in a build method, it won't be hard + // to enable that: we'd just need to add an [InheritedWidget] here. + static MessageListPageState? maybeAncestorOf(BuildContext context) { + return context.findAncestorStateOfType<_MessageListPageState>(); + } + final Narrow initNarrow; final int? initAnchorMessageId; // TODO(#1564) highlight target upon load From d0db6d4c37d68b228c2fcf8b5f0f9e03db4648a7 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 6 Aug 2025 19:00:00 -0700 Subject: [PATCH 2/4] action_sheet test: Remove `messages` in showFromAppBar channel-sheet setup It looks like this param was only being used to avoid the topic-autocomplete behavior mentioned in the code comment. For invoking the channel action sheet by long-pressing the app bar, it shouldn't matter if the message list isn't showing any messages. --- test/widgets/action_sheet_test.dart | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 41e0462233..226ad54018 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -199,14 +199,17 @@ void main() { Future showFromAppBar(WidgetTester tester, { ZulipStream? channel, - List? messages, required Narrow narrow, }) async { channel ??= someChannel; - messages ??= [someMessage]; connection.prepare(json: eg.newestGetMessagesResult( - foundOldest: true, messages: messages).toJson()); + foundOldest: true, messages: []).toJson()); + if (narrow case ChannelNarrow()) { + // We auto-focus the topic input when there are no messages; + // this is for topic autocomplete. + connection.prepare(json: GetStreamTopicsResult(topics: []).toJson()); + } await tester.pumpWidget(TestZulipApp( accountId: eg.selfAccount.id, child: MessageListPage( @@ -446,13 +449,11 @@ void main() { testWidgets('smoke, public channel', (tester) async { final channel = eg.stream(inviteOnly: false); - final message = eg.streamMessage(stream: channel); await prepare(); await store.addStream(channel); await store.addSubscription(eg.subscription(channel)); final narrow = ChannelNarrow(channel.streamId); - await showFromAppBar(tester, - channel: channel, narrow: narrow, messages: [message]); + await showFromAppBar(tester, channel: channel, narrow: narrow); connection.prepare(json: {}); await tapButton(tester); @@ -470,13 +471,11 @@ void main() { testWidgets('smoke, private channel', (tester) async { final channel = eg.stream(inviteOnly: true); - final message = eg.streamMessage(stream: channel); await prepare(); await store.addStream(channel); await store.addSubscription(eg.subscription(channel)); final narrow = ChannelNarrow(channel.streamId); - await showFromAppBar(tester, - channel: channel, narrow: narrow, messages: [message]); + await showFromAppBar(tester, channel: channel, narrow: narrow); connection.takeRequests(); connection.prepare(json: {}); From 296e600d1164692eb2d9bd7ab59f8a3adb92cf3c Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 6 Aug 2025 19:06:43 -0700 Subject: [PATCH 3/4] action_sheet test: Also test showing channel sheet from topic list app bar --- test/widgets/action_sheet_test.dart | 51 +++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index 226ad54018..f955e2ff7b 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -33,6 +33,7 @@ import 'package:zulip/widgets/inbox.dart'; import 'package:zulip/widgets/message_list.dart'; import 'package:share_plus_platform_interface/method_channel/method_channel_share.dart'; import 'package:zulip/widgets/subscription_list.dart'; +import 'package:zulip/widgets/topic_list.dart'; import 'package:zulip/widgets/user.dart'; import '../api/fake_api.dart'; @@ -197,7 +198,7 @@ void main() { await tester.pump(const Duration(milliseconds: 250)); } - Future showFromAppBar(WidgetTester tester, { + Future showFromMsglistAppBar(WidgetTester tester, { ZulipStream? channel, required Narrow narrow, }) async { @@ -239,6 +240,22 @@ void main() { await tester.pump(const Duration(milliseconds: 250)); } + Future showFromTopicListAppBar(WidgetTester tester) async { + final transitionDurationObserver = TransitionDurationObserver(); + + connection.prepare(json: GetStreamTopicsResult(topics: []).toJson()); + await tester.pumpWidget(TestZulipApp( + navigatorObservers: [transitionDurationObserver], + accountId: eg.selfAccount.id, + child: TopicListPage(streamId: someChannel.streamId))); + await tester.pump(); + + await tester.longPress(find.descendant( + of: find.byType(ZulipAppBar), + matching: find.text(someChannel.name))); + await transitionDurationObserver.pumpPastTransition(tester); + } + final actionSheetFinder = find.byType(BottomSheet); Finder findButtonForLabel(String label) => find.descendant(of: actionSheetFinder, matching: find.text(label)); @@ -277,17 +294,17 @@ void main() { check(findButtonForLabel('Mark channel as read')).findsNothing(); }); - testWidgets('show from app bar in channel narrow', (tester) async { + testWidgets('show from message-list app bar in channel narrow', (tester) async { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkButtons(); }); - testWidgets('show from app bar in topic narrow', (tester) async { + testWidgets('show from message-list app bar in topic narrow', (tester) async { await prepare(); final narrow = eg.topicNarrow(someChannel.streamId, someTopic); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkButtons(); }); @@ -296,6 +313,12 @@ void main() { await showFromRecipientHeader(tester, message: someMessage); checkButtons(); }); + + testWidgets('show from topic-list app bar', (tester) async { + await prepare(); + await showFromTopicListAppBar(tester); + checkButtons(); + }); }); group('SubscribeButton', () { @@ -308,7 +331,7 @@ void main() { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); await store.removeSubscription(narrow.streamId); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkButton('Subscribe'); }); @@ -316,7 +339,7 @@ void main() { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); check(store.subscriptions[narrow.streamId]).isNotNull(); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkNoButton('Subscribe'); }); @@ -324,7 +347,7 @@ void main() { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); await store.removeSubscription(narrow.streamId); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); connection.prepare(json: {}); await tapButton(tester); @@ -387,7 +410,7 @@ void main() { testWidgets('TopicListButton', (tester) async { await prepare(); - await showFromAppBar(tester, + await showFromMsglistAppBar(tester, narrow: ChannelNarrow(someChannel.streamId)); connection.prepare(json: GetStreamTopicsResult(topics: [ @@ -415,7 +438,7 @@ void main() { testWidgets('copies channel link to clipboard', (tester) async { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); await tapCopyChannelLinkButton(tester); await tester.pump(Duration.zero); @@ -435,7 +458,7 @@ void main() { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); check(store.subscriptions[narrow.streamId]).isNotNull(); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkButton('Unsubscribe'); }); @@ -443,7 +466,7 @@ void main() { await prepare(); final narrow = ChannelNarrow(someChannel.streamId); await store.removeSubscription(narrow.streamId); - await showFromAppBar(tester, narrow: narrow); + await showFromMsglistAppBar(tester, narrow: narrow); checkNoButton('Unsubscribe'); }); @@ -453,7 +476,7 @@ void main() { await store.addStream(channel); await store.addSubscription(eg.subscription(channel)); final narrow = ChannelNarrow(channel.streamId); - await showFromAppBar(tester, channel: channel, narrow: narrow); + await showFromMsglistAppBar(tester, channel: channel, narrow: narrow); connection.prepare(json: {}); await tapButton(tester); @@ -475,7 +498,7 @@ void main() { await store.addStream(channel); await store.addSubscription(eg.subscription(channel)); final narrow = ChannelNarrow(channel.streamId); - await showFromAppBar(tester, channel: channel, narrow: narrow); + await showFromMsglistAppBar(tester, channel: channel, narrow: narrow); connection.takeRequests(); connection.prepare(json: {}); From 1d8961138da7e4bc1fa75dda2de59f81de0ed1a7 Mon Sep 17 00:00:00 2001 From: Chris Bobbe Date: Wed, 6 Aug 2025 18:00:46 -0700 Subject: [PATCH 4/4] action_sheet: Add 'Channel feed' button to channel action sheet Fixes #1705. --- assets/l10n/app_en.arb | 4 ++ lib/generated/l10n/zulip_localizations.dart | 10 +++- .../l10n/zulip_localizations_ar.dart | 3 + .../l10n/zulip_localizations_de.dart | 3 + .../l10n/zulip_localizations_en.dart | 3 + .../l10n/zulip_localizations_fr.dart | 3 + .../l10n/zulip_localizations_it.dart | 3 + .../l10n/zulip_localizations_ja.dart | 3 + .../l10n/zulip_localizations_nb.dart | 3 + .../l10n/zulip_localizations_pl.dart | 3 + .../l10n/zulip_localizations_ru.dart | 3 + .../l10n/zulip_localizations_sk.dart | 3 + .../l10n/zulip_localizations_sl.dart | 3 + .../l10n/zulip_localizations_uk.dart | 3 + .../l10n/zulip_localizations_zh.dart | 3 + lib/widgets/action_sheet.dart | 33 +++++++++++ test/widgets/action_sheet_test.dart | 57 +++++++++++++++++++ 17 files changed, 141 insertions(+), 2 deletions(-) diff --git a/assets/l10n/app_en.arb b/assets/l10n/app_en.arb index 77200c01eb..a51ed6b186 100644 --- a/assets/l10n/app_en.arb +++ b/assets/l10n/app_en.arb @@ -116,6 +116,10 @@ "@actionSheetOptionListOfTopics": { "description": "Label for navigating to a channel's topic-list page." }, + "actionSheetOptionChannelFeed": "Channel feed", + "@actionSheetOptionChannelFeed": { + "description": "Label for navigating to a channel's channel-feed page." + }, "actionSheetOptionUnsubscribe": "Unsubscribe", "@actionSheetOptionUnsubscribe": { "description": "Label in the channel action sheet for unsubscribing from the channel." diff --git a/lib/generated/l10n/zulip_localizations.dart b/lib/generated/l10n/zulip_localizations.dart index d7e9c25f8f..bda5addcd7 100644 --- a/lib/generated/l10n/zulip_localizations.dart +++ b/lib/generated/l10n/zulip_localizations.dart @@ -275,7 +275,7 @@ abstract class ZulipLocalizations { /// **'To upload files, please grant Zulip additional permissions in Settings.'** String get permissionsDeniedReadExternalStorage; - /// Label in the channel context menu for subscribing to the channel. + /// Label in the channel action sheet for subscribing to the channel. /// /// In en, this message translates to: /// **'Subscribe'** @@ -305,7 +305,13 @@ abstract class ZulipLocalizations { /// **'List of topics'** String get actionSheetOptionListOfTopics; - /// Label in the channel context menu for unsubscribing from the channel. + /// Label for navigating to a channel's channel-feed page. + /// + /// In en, this message translates to: + /// **'Channel feed'** + String get actionSheetOptionChannelFeed; + + /// Label in the channel action sheet for unsubscribing from the channel. /// /// In en, this message translates to: /// **'Unsubscribe'** diff --git a/lib/generated/l10n/zulip_localizations_ar.dart b/lib/generated/l10n/zulip_localizations_ar.dart index 5ff9981001..6d0bbf8a86 100644 --- a/lib/generated/l10n/zulip_localizations_ar.dart +++ b/lib/generated/l10n/zulip_localizations_ar.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsAr extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_de.dart b/lib/generated/l10n/zulip_localizations_de.dart index 8d0826bac9..1f56cc575e 100644 --- a/lib/generated/l10n/zulip_localizations_de.dart +++ b/lib/generated/l10n/zulip_localizations_de.dart @@ -104,6 +104,9 @@ class ZulipLocalizationsDe extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Themenliste'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_en.dart b/lib/generated/l10n/zulip_localizations_en.dart index bffdb9f9a9..ff1087d2f4 100644 --- a/lib/generated/l10n/zulip_localizations_en.dart +++ b/lib/generated/l10n/zulip_localizations_en.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsEn extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_fr.dart b/lib/generated/l10n/zulip_localizations_fr.dart index 5001c79eed..17fa68ce9c 100644 --- a/lib/generated/l10n/zulip_localizations_fr.dart +++ b/lib/generated/l10n/zulip_localizations_fr.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsFr extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_it.dart b/lib/generated/l10n/zulip_localizations_it.dart index 25a5c4e999..4cf5f471e3 100644 --- a/lib/generated/l10n/zulip_localizations_it.dart +++ b/lib/generated/l10n/zulip_localizations_it.dart @@ -103,6 +103,9 @@ class ZulipLocalizationsIt extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Elenco degli argomenti'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_ja.dart b/lib/generated/l10n/zulip_localizations_ja.dart index e39ebe4377..cac6a6b087 100644 --- a/lib/generated/l10n/zulip_localizations_ja.dart +++ b/lib/generated/l10n/zulip_localizations_ja.dart @@ -101,6 +101,9 @@ class ZulipLocalizationsJa extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'トピック一覧'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_nb.dart b/lib/generated/l10n/zulip_localizations_nb.dart index d08ca0eaf0..50f4fcde2d 100644 --- a/lib/generated/l10n/zulip_localizations_nb.dart +++ b/lib/generated/l10n/zulip_localizations_nb.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsNb extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_pl.dart b/lib/generated/l10n/zulip_localizations_pl.dart index 9b856a6aae..eea24acaf5 100644 --- a/lib/generated/l10n/zulip_localizations_pl.dart +++ b/lib/generated/l10n/zulip_localizations_pl.dart @@ -104,6 +104,9 @@ class ZulipLocalizationsPl extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Lista wątków'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_ru.dart b/lib/generated/l10n/zulip_localizations_ru.dart index 848b02eefb..8ee4350cc8 100644 --- a/lib/generated/l10n/zulip_localizations_ru.dart +++ b/lib/generated/l10n/zulip_localizations_ru.dart @@ -104,6 +104,9 @@ class ZulipLocalizationsRu extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Список тем'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_sk.dart b/lib/generated/l10n/zulip_localizations_sk.dart index 96e9e0c542..d4b6538447 100644 --- a/lib/generated/l10n/zulip_localizations_sk.dart +++ b/lib/generated/l10n/zulip_localizations_sk.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsSk extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_sl.dart b/lib/generated/l10n/zulip_localizations_sl.dart index bdb56cf44d..f045eb5f21 100644 --- a/lib/generated/l10n/zulip_localizations_sl.dart +++ b/lib/generated/l10n/zulip_localizations_sl.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsSl extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Seznam tem'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_uk.dart b/lib/generated/l10n/zulip_localizations_uk.dart index 0c2f49363e..79929630e9 100644 --- a/lib/generated/l10n/zulip_localizations_uk.dart +++ b/lib/generated/l10n/zulip_localizations_uk.dart @@ -105,6 +105,9 @@ class ZulipLocalizationsUk extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'Список тем'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/generated/l10n/zulip_localizations_zh.dart b/lib/generated/l10n/zulip_localizations_zh.dart index fdfd2966b5..ad0f0b5949 100644 --- a/lib/generated/l10n/zulip_localizations_zh.dart +++ b/lib/generated/l10n/zulip_localizations_zh.dart @@ -102,6 +102,9 @@ class ZulipLocalizationsZh extends ZulipLocalizations { @override String get actionSheetOptionListOfTopics => 'List of topics'; + @override + String get actionSheetOptionChannelFeed => 'Channel feed'; + @override String get actionSheetOptionUnsubscribe => 'Unsubscribe'; diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart index 7adfb2ba24..b7f1e2b3cb 100644 --- a/lib/widgets/action_sheet.dart +++ b/lib/widgets/action_sheet.dart @@ -239,11 +239,18 @@ enum BottomSheetDismissButtonStyle { /// Show a sheet of actions you can take on a channel. /// /// Needs a [PageRoot] ancestor. +/// May or may not have a [MessageListPage] ancestor; +/// some callers are on that page and some aren't. void showChannelActionSheet(BuildContext context, { required int channelId, }) { final pageContext = PageRoot.contextOf(context); final store = PerAccountStoreWidget.of(pageContext); + final messageListPageState = MessageListPage.maybeAncestorOf(pageContext); + + final messageListPageNarrow = messageListPageState?.narrow; + final isOnChannelFeed = messageListPageNarrow is ChannelNarrow + && messageListPageNarrow.streamId == channelId; final unreadCount = store.unreads.countInChannelNarrow(channelId); final isSubscribed = store.subscriptions[channelId] != null; @@ -255,6 +262,8 @@ void showChannelActionSheet(BuildContext context, { if (unreadCount > 0) MarkChannelAsReadButton(pageContext: pageContext, channelId: channelId), TopicListButton(pageContext: pageContext, channelId: channelId), + if (!isOnChannelFeed) + ChannelFeedButton(pageContext: pageContext, channelId: channelId), CopyChannelLinkButton(channelId: channelId, pageContext: pageContext) ], if (isSubscribed) @@ -355,6 +364,30 @@ class TopicListButton extends ActionSheetMenuItemButton { } } +class ChannelFeedButton extends ActionSheetMenuItemButton { + const ChannelFeedButton({ + super.key, + required this.channelId, + required super.pageContext, + }); + + final int channelId; + + @override + IconData get icon => ZulipIcons.message_feed; + + @override + String label(ZulipLocalizations zulipLocalizations) { + return zulipLocalizations.actionSheetOptionChannelFeed; + } + + @override + void onPressed() { + Navigator.push(pageContext, + MessageListPage.buildRoute(context: pageContext, narrow: ChannelNarrow(channelId))); + } +} + class CopyChannelLinkButton extends ActionSheetMenuItemButton { const CopyChannelLinkButton({ super.key, diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart index f955e2ff7b..36e1f1ef78 100644 --- a/test/widgets/action_sheet_test.dart +++ b/test/widgets/action_sheet_test.dart @@ -177,7 +177,9 @@ void main() { } Future showFromInbox(WidgetTester tester) async { + transitionDurationObserver = TransitionDurationObserver(); await tester.pumpWidget(TestZulipApp(accountId: eg.selfAccount.id, + navigatorObservers: [transitionDurationObserver], child: const HomePage())); await tester.pump(); check(find.byType(InboxPageBody)).findsOne(); @@ -421,6 +423,61 @@ void main() { check(find.text('some topic foo')).findsOne(); }); + group('ChannelFeedButton', () { + Future tapButtonAndPump(WidgetTester tester) async { + await tester.tap(findButtonForLabel('Channel feed')); + await tester.pump(); // [MenuItemButton.onPressed] called in a post-frame callback: flutter/flutter@e4a39fa2e + } + + testWidgets('from inbox: visible ', (tester) async { + await prepare(); + await showFromInbox(tester); + checkButton('Channel feed'); + }); + + testWidgets('from subscription list: visible ', (tester) async { + await prepare(); + await showFromInbox(tester); + checkButton('Channel feed'); + }); + + testWidgets('from recipient header in combined feed: visible ', (tester) async { + await prepare(); + await showFromRecipientHeader(tester); + checkButton('Channel feed'); + }); + + testWidgets('from app bar on topic list: visible ', (tester) async { + await prepare(); + await showFromTopicListAppBar(tester); + checkButton('Channel feed'); + }); + + testWidgets('from msglist app bar on channel feed: not visible ', (tester) async { + await prepare(); + await showFromMsglistAppBar(tester, narrow: ChannelNarrow(someChannel.streamId)); + checkNoButton('Channel feed'); + }); + + // (The channel action sheet isn't reached from a recipient header + // in the channel feed.) + + testWidgets('navigates to channel feed', (tester) async { + await prepare(); + await showFromInbox(tester); + + connection.prepare(json: eg.newestGetMessagesResult( + foundOldest: true, messages: []).toJson()); + // for topic autocomplete + connection.prepare(json: GetStreamTopicsResult(topics: []).toJson()); + await tapButtonAndPump(tester); + await transitionDurationObserver.pumpPastTransition(tester); + + final appBar = tester.widget(find.byType(MessageListAppBarTitle)) as MessageListAppBarTitle; + check(appBar.narrow).equals(ChannelNarrow(someChannel.streamId)); + }); + }); + group('CopyChannelLinkButton', () { setUp(() async { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(