Skip to content

Commit 6886bc5

Browse files
committed
compose: Support sending to empty topic
This behavior is designed to replace how sending to an empty topic is effectively sending to "(no topic)". The key difference being that the `TopicName.apiValue` is actually empty, instead of being converted to "(no topic)" with `_computeTextNormalized`. Signed-off-by: Zixuan James Li <[email protected]>
1 parent 7be2ac4 commit 6886bc5

File tree

2 files changed

+51
-4
lines changed

2 files changed

+51
-4
lines changed

lib/widgets/compose_box.dart

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,34 @@ class ComposeTopicController extends ComposeController<TopicValidationError> {
157157
@override
158158
String _computeTextNormalized() {
159159
String trimmed = text.trim();
160-
return trimmed.isEmpty ? kNoTopicTopic : trimmed;
160+
// TODO(server-10): simplify
161+
if (store.connection.zulipFeatureLevel! < 334) {
162+
return trimmed.isEmpty ? kNoTopicTopic : trimmed;
163+
}
164+
165+
return trimmed;
161166
}
162167

163168
/// Whether [textNormalized] would fail a mandatory-topics check
164169
/// (see [mandatory]).
165170
///
166171
/// The term "Vacuous" draws distinction from [String.isEmpty], in the sense
167172
/// that certain strings are empty but also indicate the absence of a topic.
168-
bool get isTopicVacuous => textNormalized == kNoTopicTopic;
173+
bool get isTopicVacuous {
174+
bool result = textNormalized.isEmpty
175+
// We keep checking for '(no topic)' regardless of the feature level
176+
// because it remains equivalent to an empty topic even when FL >= 334.
177+
// This can change in the future:
178+
// https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/.28realm_.29mandatory_topics.20behavior/near/2062391
179+
|| textNormalized == kNoTopicTopic;
180+
181+
// TODO(server-10): simplify
182+
if (store.connection.zulipFeatureLevel! >= 334) {
183+
result |= textNormalized == store.realmEmptyTopicDisplayName;
184+
}
185+
186+
return result;
187+
}
169188

170189
@override
171190
List<TopicValidationError> _computeValidationErrors() {

test/widgets/compose_box_test.dart

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ void main() {
716716
Future<void> setupAndTapSend(WidgetTester tester, {
717717
required String topicInputText,
718718
required bool mandatoryTopics,
719+
int? zulipFeatureLevel,
719720
}) async {
720721
TypingNotifier.debugEnable = false;
721722
addTearDown(TypingNotifier.debugReset);
@@ -724,7 +725,8 @@ void main() {
724725
final narrow = ChannelNarrow(channel.streamId);
725726
await prepareComposeBox(tester,
726727
narrow: narrow, streams: [channel],
727-
mandatoryTopics: mandatoryTopics);
728+
mandatoryTopics: mandatoryTopics,
729+
zulipFeatureLevel: zulipFeatureLevel);
728730

729731
await enterTopic(tester, narrow: narrow, topic: topicInputText);
730732
await tester.enterText(contentInputFinder, 'test content');
@@ -739,10 +741,21 @@ void main() {
739741
expectedMessage: 'Topics are required in this organization.');
740742
}
741743

742-
testWidgets('empty topic -> "(no topic)"', (tester) async {
744+
testWidgets('empty topic -> empty topic', (tester) async {
743745
await setupAndTapSend(tester,
744746
topicInputText: '',
745747
mandatoryTopics: false);
748+
check(connection.lastRequest).isA<http.Request>()
749+
..method.equals('POST')
750+
..url.path.equals('/api/v1/messages')
751+
..bodyFields['topic'].equals('');
752+
}, skip: true); // null topic names soon to be enabled
753+
754+
testWidgets('legacy: empty topic -> "(no topic)"', (tester) async {
755+
await setupAndTapSend(tester,
756+
topicInputText: '',
757+
mandatoryTopics: false,
758+
zulipFeatureLevel: 333);
746759
check(connection.lastRequest).isA<http.Request>()
747760
..method.equals('POST')
748761
..url.path.equals('/api/v1/messages')
@@ -756,12 +769,27 @@ void main() {
756769
checkMessageNotSent(tester);
757770
});
758771

772+
testWidgets('if topics are mandatory, reject `realmEmptyTopicDisplayName`', (tester) async {
773+
await setupAndTapSend(tester,
774+
topicInputText: eg.defaultRealmEmptyTopicDisplayName,
775+
mandatoryTopics: true);
776+
checkMessageNotSent(tester);
777+
}, skip: true); // null topic names soon to be enabled
778+
759779
testWidgets('if topics are mandatory, reject "(no topic)"', (tester) async {
760780
await setupAndTapSend(tester,
761781
topicInputText: '(no topic)',
762782
mandatoryTopics: true);
763783
checkMessageNotSent(tester);
764784
});
785+
786+
testWidgets('legacy: if topics are mandatory, reject "(no topic)"', (tester) async {
787+
await setupAndTapSend(tester,
788+
topicInputText: '(no topic)',
789+
mandatoryTopics: true,
790+
zulipFeatureLevel: 333);
791+
checkMessageNotSent(tester);
792+
});
765793
});
766794

767795
group('uploads', () {

0 commit comments

Comments
 (0)