Skip to content

Commit 03f6dc3

Browse files
committed
compose: Respect realm setting for mandatory topics
Signed-off-by: Zixuan James Li <[email protected]>
1 parent b977a1b commit 03f6dc3

File tree

3 files changed

+87
-14
lines changed

3 files changed

+87
-14
lines changed

lib/widgets/compose_box.dart

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ enum TopicValidationError {
6666
}
6767

6868
class ComposeTopicController extends ComposeController<TopicValidationError> {
69-
ComposeTopicController() {
69+
ComposeTopicController({required this.store}) {
7070
_update();
7171
}
7272

73+
PerAccountStore store;
74+
7375
// TODO: subscribe to this value:
7476
// https://zulip.com/help/require-topics
75-
final mandatory = true;
77+
bool get mandatory => store.realmMandatoryTopics;
7678

7779
@override
7880
String _computeTextNormalized() {
@@ -1194,7 +1196,10 @@ sealed class ComposeBoxController {
11941196
}
11951197

11961198
class StreamComposeBoxController extends ComposeBoxController {
1197-
final topic = ComposeTopicController();
1199+
StreamComposeBoxController({required PerAccountStore store})
1200+
: topic = ComposeTopicController(store: store);
1201+
1202+
final ComposeTopicController topic;
11981203
final topicFocusNode = FocusNode();
11991204

12001205
@override
@@ -1271,16 +1276,17 @@ abstract class ComposeBoxState extends State<ComposeBox> {
12711276
ComposeBoxController get controller;
12721277
}
12731278

1274-
class _ComposeBoxState extends State<ComposeBox> implements ComposeBoxState {
1275-
@override ComposeBoxController get controller => _controller;
1276-
late final ComposeBoxController _controller;
1279+
class _ComposeBoxState extends State<ComposeBox> with PerAccountStoreAwareStateMixin<ComposeBox> implements ComposeBoxState {
1280+
@override ComposeBoxController get controller => _controller!;
1281+
ComposeBoxController? _controller;
12771282

12781283
@override
1279-
void initState() {
1280-
super.initState();
1284+
void onNewStore() {
12811285
switch (widget.narrow) {
12821286
case ChannelNarrow():
1283-
_controller = StreamComposeBoxController();
1287+
final store = PerAccountStoreWidget.of(context);
1288+
_controller ??= StreamComposeBoxController(store: store);
1289+
(controller as StreamComposeBoxController).topic.store = store;
12841290
case TopicNarrow():
12851291
case DmNarrow():
12861292
_controller = FixedDestinationComposeBoxController();
@@ -1293,7 +1299,7 @@ class _ComposeBoxState extends State<ComposeBox> implements ComposeBoxState {
12931299

12941300
@override
12951301
void dispose() {
1296-
_controller.dispose();
1302+
_controller!.dispose();
12971303
super.dispose();
12981304
}
12991305

@@ -1334,14 +1340,15 @@ class _ComposeBoxState extends State<ComposeBox> implements ComposeBoxState {
13341340
}
13351341

13361342
final narrow = widget.narrow;
1337-
switch (_controller) {
1343+
final controller = _controller!;
1344+
switch (controller) {
13381345
case StreamComposeBoxController(): {
13391346
narrow as ChannelNarrow;
1340-
body = _StreamComposeBoxBody(controller: _controller, narrow: narrow);
1347+
body = _StreamComposeBoxBody(controller: controller, narrow: narrow);
13411348
}
13421349
case FixedDestinationComposeBoxController(): {
13431350
narrow as SendableNarrow;
1344-
body = _FixedDestinationComposeBoxBody(controller: _controller, narrow: narrow);
1351+
body = _FixedDestinationComposeBoxBody(controller: controller, narrow: narrow);
13451352
}
13461353
}
13471354

test/model/autocomplete_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,8 @@ void main() {
835835

836836
final description = 'topic-input with text: $markedText produces: ${expectedQuery?.raw ?? 'No Query!'}';
837837
test(description, () {
838-
final controller = ComposeTopicController();
838+
final store = eg.store();
839+
final controller = ComposeTopicController(store: store);
839840
controller.value = parsed.value;
840841
if (expectedQuery == null) {
841842
check(controller).autocompleteIntent.isNull();

test/widgets/compose_box_test.dart

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,71 @@ void main() {
456456
});
457457
});
458458

459+
group('sending to empty topic', () {
460+
late ZulipStream channel;
461+
462+
Future<void> setupAndTapSend(WidgetTester tester, {
463+
bool? mandatoryTopics,
464+
required String topicInputText,
465+
}) async {
466+
TypingNotifier.debugEnable = false;
467+
addTearDown(TypingNotifier.debugReset);
468+
addTearDown(testBinding.reset);
469+
470+
channel = eg.stream();
471+
final account = eg.account(user: eg.selfUser);
472+
final initialSnapshot = eg.initialSnapshot(
473+
realmMandatoryTopics: mandatoryTopics);
474+
await testBinding.globalStore.add(account, initialSnapshot);
475+
476+
store = await testBinding.globalStore.perAccount(account.id);
477+
await store.addStream(channel);
478+
await store.addUser(eg.selfUser);
479+
connection = store.connection as FakeApiConnection;
480+
481+
final narrow = ChannelNarrow(channel.streamId);
482+
await prepareComposeBox(tester,
483+
narrow: narrow, account: account);
484+
await enterTopic(tester, narrow: narrow, topic: topicInputText);
485+
await tester.enterText(contentInputFinder, 'test content');
486+
await tester.tap(find.byIcon(ZulipIcons.send));
487+
await tester.pump();
488+
}
489+
490+
void checkMessageNotSent(WidgetTester tester) {
491+
check(connection.takeRequests()).isEmpty();
492+
checkErrorDialog(tester,
493+
expectedTitle: 'Message not sent',
494+
expectedMessage: 'Topics are required in this organization.');
495+
}
496+
497+
testWidgets('empty topic -> (no topic)', (tester) async {
498+
await setupAndTapSend(tester, topicInputText: '');
499+
check(connection.lastRequest).isA<http.Request>()
500+
..method.equals('POST')
501+
..url.path.equals('/api/v1/messages')
502+
..bodyFields.deepEquals({
503+
'type': 'stream',
504+
'to': channel.streamId.toString(),
505+
'topic': '(no topic)',
506+
'content': 'test content',
507+
'read_by_sender': 'true',
508+
});
509+
});
510+
511+
testWidgets('if topics are mandatory, reject empty topic', (tester) async {
512+
await setupAndTapSend(tester, topicInputText: '',
513+
mandatoryTopics: true);
514+
checkMessageNotSent(tester);
515+
});
516+
517+
testWidgets('if topics are mandatory, reject (no topic)', (tester) async {
518+
await setupAndTapSend(tester, topicInputText: '(no topic)',
519+
mandatoryTopics: true);
520+
checkMessageNotSent(tester);
521+
});
522+
});
523+
459524
group('uploads', () {
460525
void checkAppearsLoading(WidgetTester tester, bool expected) {
461526
final sendButtonElement = tester.element(find.ancestor(

0 commit comments

Comments
 (0)