From 9cc8112d72420f7c1ec84444227390c3795a619f Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Tue, 21 Jan 2025 16:39:00 -0500 Subject: [PATCH] compose: Handle hint texts when topic is empty Signed-off-by: Zixuan James Li --- lib/widgets/compose_box.dart | 13 +++++- test/widgets/compose_box_test.dart | 74 +++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/lib/widgets/compose_box.dart b/lib/widgets/compose_box.dart index 51123a49a3..afe2d2e6b2 100644 --- a/lib/widgets/compose_box.dart +++ b/lib/widgets/compose_box.dart @@ -586,12 +586,20 @@ class _StreamContentInputState extends State<_StreamContentInput> { final streamName = store.streams[widget.narrow.streamId]?.name ?? zulipLocalizations.unknownChannelName; final topic = TopicName(widget.controller.topic.textNormalized); + final String? topicDisplayName; + if (store.realmMandatoryTopics && widget.controller.topic.isTopicVacuous) { + topicDisplayName = null; + } else { + // ignore: dead_null_aware_expression // null topic names soon to be enabled + topicDisplayName = topic.displayName ?? store.realmEmptyTopicDisplayName; + } + return _ContentInput( narrow: widget.narrow, destination: TopicNarrow(widget.narrow.streamId, topic), controller: widget.controller, hintText: zulipLocalizations.composeBoxChannelContentHint( - '#$streamName > ${topic.displayName}')); + '#$streamName > $topicDisplayName')); } } @@ -650,7 +658,8 @@ class _FixedDestinationContentInput extends StatelessWidget { final streamName = store.streams[streamId]?.name ?? zulipLocalizations.unknownChannelName; return zulipLocalizations.composeBoxChannelContentHint( - '#$streamName > ${topic.displayName}'); + // ignore: dead_null_aware_expression // null topic names soon to be enabled + '#$streamName > ${topic.displayName ?? store.realmEmptyTopicDisplayName}'); case DmNarrow(otherRecipientIds: []): // The self-1:1 thread. return zulipLocalizations.composeBoxSelfDmContentHint; diff --git a/test/widgets/compose_box_test.dart b/test/widgets/compose_box_test.dart index 52d2d1c851..68aa8e009b 100644 --- a/test/widgets/compose_box_test.dart +++ b/test/widgets/compose_box_test.dart @@ -47,6 +47,7 @@ void main() { List otherUsers = const [], List streams = const [], bool? mandatoryTopics, + int? zulipFeatureLevel, }) async { if (narrow case ChannelNarrow(:var streamId) || TopicNarrow(: var streamId)) { assert(streams.any((stream) => stream.streamId == streamId), @@ -54,8 +55,10 @@ void main() { } addTearDown(testBinding.reset); selfUser ??= eg.selfUser; - final selfAccount = eg.account(user: selfUser); + zulipFeatureLevel ??= eg.futureZulipFeatureLevel; + final selfAccount = eg.account(user: selfUser, zulipFeatureLevel: zulipFeatureLevel); await testBinding.globalStore.add(selfAccount, eg.initialSnapshot( + zulipFeatureLevel: zulipFeatureLevel, realmMandatoryTopics: mandatoryTopics, )); @@ -326,11 +329,15 @@ void main() { Future prepare(WidgetTester tester, { required Narrow narrow, + bool? mandatoryTopics, + int? zulipFeatureLevel, }) async { await prepareComposeBox(tester, narrow: narrow, otherUsers: [eg.otherUser, eg.thirdUser], - streams: [channel]); + streams: [channel], + mandatoryTopics: mandatoryTopics, + zulipFeatureLevel: zulipFeatureLevel); } /// This checks the input's configured hint text without regard to whether @@ -351,9 +358,22 @@ void main() { .decoration.isNotNull().hintText.equals(contentHintText); } - group('to ChannelNarrow', () { + group('to ChannelNarrow, topics not mandatory', () { testWidgets('with empty topic', (tester) async { - await prepare(tester, narrow: ChannelNarrow(channel.streamId)); + await prepare(tester, narrow: ChannelNarrow(channel.streamId), + mandatoryTopics: false); + final narrow = ChannelNarrow(channel.streamId); + await prepare(tester, narrow: narrow, mandatoryTopics: false); + await tester.pump(); + checkComposeBoxHintTexts(tester, + topicHintText: 'Topic', + contentHintText: 'Message #${channel.name} > ${eg.defaultRealmEmptyTopicDisplayName}'); + }, skip: true); // null topic names soon to be enabled + + testWidgets('legacy: with empty topic', (tester) async { + await prepare(tester, narrow: ChannelNarrow(channel.streamId), + mandatoryTopics: false, + zulipFeatureLevel: 333); checkComposeBoxHintTexts(tester, topicHintText: 'Topic', contentHintText: 'Message #${channel.name} > (no topic)'); @@ -361,7 +381,8 @@ void main() { testWidgets('with non-empty topic', (tester) async { final narrow = ChannelNarrow(channel.streamId); - await prepare(tester, narrow: narrow); + await prepare(tester, narrow: narrow, + mandatoryTopics: false); await enterTopic(tester, narrow: narrow, topic: 'new topic'); await tester.pump(); checkComposeBoxHintTexts(tester, @@ -370,13 +391,52 @@ void main() { }); }); - testWidgets('to TopicNarrow', (tester) async { + group('to ChannelNarrow, mandatory topics', () { + testWidgets('with empty topic', (tester) async { + await prepare(tester, narrow: ChannelNarrow(channel.streamId), + mandatoryTopics: true); + checkComposeBoxHintTexts(tester, + topicHintText: 'Topic', + contentHintText: 'Message #${channel.name}'); + }, skip: true); // null topic names soon to be enabled + + testWidgets('legacy: with empty topic', (tester) async { + await prepare(tester, narrow: ChannelNarrow(channel.streamId), + mandatoryTopics: true, + zulipFeatureLevel: 333); + checkComposeBoxHintTexts(tester, + topicHintText: 'Topic', + contentHintText: 'Message #${channel.name}'); + }); + + testWidgets('with non-empty topic', (tester) async { + final narrow = ChannelNarrow(channel.streamId); + await prepare(tester, narrow: narrow, + mandatoryTopics: true); + await enterTopic(tester, narrow: narrow, topic: 'new topic'); + await tester.pump(); + checkComposeBoxHintTexts(tester, + topicHintText: 'Topic', + contentHintText: 'Message #${channel.name} > new topic'); + }); + }); + + testWidgets('to TopicNarrow with non-empty topic', (tester) async { await prepare(tester, - narrow: TopicNarrow(channel.streamId, TopicName('topic'))); + narrow: TopicNarrow(channel.streamId, TopicName('topic')), + mandatoryTopics: false); checkComposeBoxHintTexts(tester, contentHintText: 'Message #${channel.name} > topic'); }); + testWidgets('to TopicNarrow with empty topic', (tester) async { + await prepare(tester, + narrow: TopicNarrow(channel.streamId, TopicName('')), + mandatoryTopics: false); + checkComposeBoxHintTexts(tester, contentHintText: + 'Message #${channel.name} > ${eg.defaultRealmEmptyTopicDisplayName}'); + }, skip: true); // null topic names soon to be enabled + testWidgets('to DmNarrow with self', (tester) async { await prepare(tester, narrow: DmNarrow.withUser( eg.selfUser.userId, selfUserId: eg.selfUser.userId));