Skip to content

Commit

Permalink
content: Start rendering poll content
Browse files Browse the repository at this point in the history
Fixes: zulip#165

Signed-off-by: Zixuan James Li <[email protected]>
  • Loading branch information
PIG208 committed Sep 20, 2024
1 parent 65f4626 commit 598bb7d
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 0 deletions.
11 changes: 11 additions & 0 deletions lib/model/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart';

import '../api/model/submessage.dart';
import 'code_block.dart';

/// A node in a parse tree for Zulip message-style content.
Expand Down Expand Up @@ -75,6 +76,16 @@ mixin UnimplementedNode on ContentNode {
/// A parsed, ready-to-render representation of Zulip message content.
sealed class ZulipMessageContent {}

/// A wrapper around a mutable representation of a Zulip poll message.
///
/// Consumers are expected to listen for [Poll]'s changes to receive
/// live-updates.
class PollContent implements ZulipMessageContent {
const PollContent(this.poll);

final Poll poll;
}

/// A complete parse tree for a Zulip message's content,
/// or other complete piece of Zulip HTML content.
///
Expand Down
2 changes: 2 additions & 0 deletions lib/model/message_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ mixin _MessageSequence {
}

ZulipMessageContent _parseMessageContent(Message message) {
final poll = message.poll;
if (poll != null) return PollContent(poll);
return parseContent(message.content);
}

Expand Down
2 changes: 2 additions & 0 deletions lib/widgets/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'dialog.dart';
import 'icons.dart';
import 'lightbox.dart';
import 'message_list.dart';
import 'poll.dart';
import 'store.dart';
import 'text.dart';

Expand Down Expand Up @@ -263,6 +264,7 @@ class MessageContent extends StatelessWidget {
style: ContentTheme.of(context).textStylePlainParagraph,
child: switch (content) {
ZulipContent() => BlockContentList(nodes: content.nodes),
PollContent() => PollWidget(poll: content.poll),
}));
}
}
Expand Down
6 changes: 6 additions & 0 deletions test/model/content_checks.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:checks/checks.dart';
import 'package:checks/context.dart';
import 'package:flutter/foundation.dart';
import 'package:zulip/api/model/submessage.dart';
import 'package:zulip/model/content.dart';

extension ContentNodeChecks on Subject<ContentNode> {
Expand Down Expand Up @@ -115,3 +117,7 @@ Iterable<LinkNode> _findLinkNodes(Iterable<InlineContentNode> nodes) {
return _findLinkNodes(node.nodes);
});
}

extension PollContentChecks on Subject<PollContent> {
Subject<Poll> get poll => has((x) => x.poll, 'poll');
}
40 changes: 40 additions & 0 deletions test/model/message_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,41 @@ void main() {
checkNotNotified();
check(model).fetched.isFalse();
});

test('parse ZulipContent', () async {
final stream = eg.stream();
await prepare(narrow: ChannelNarrow(stream.streamId));
await prepareMessages(foundOldest: true, messages: []);

await store.handleEvent(MessageEvent(id: 0,
message: eg.streamMessage(stream: stream)));
// Each [checkNotifiedOnce] call ensures there's been a [checkInvariants]
// call, where the [ContentNode] gets checked. The additional checks to
// make this test explicit.
checkNotifiedOnce();
check(model).messages.single.poll.isNull();
check(model).contents.single.isA<ZulipContent>();
});

test('parse PollContent', () async {
final stream = eg.stream();
await prepare(narrow: ChannelNarrow(stream.streamId));
await prepareMessages(foundOldest: true, messages: []);

await store.handleEvent(MessageEvent(id: 0, message: eg.streamMessage(
stream: stream,
sender: eg.selfUser,
submessages: [
eg.submessage(senderId: eg.selfUser.userId,
content: eg.pollWidgetData(question: 'question', options: ['A'])),
])));
// Each [checkNotifiedOnce] call ensures there's been a [checkInvariants]
// call, where the value of the [Poll] gets checked. The additional
// checks make this test explicit.
checkNotifiedOnce();
check(model).messages.single.poll.isNotNull();
check(model).contents.single.isA<PollContent>();
});
});

group('messageContentChanged', () {
Expand Down Expand Up @@ -1744,6 +1779,11 @@ void checkInvariants(MessageListView model) {

check(model).contents.length.equals(model.messages.length);
for (int i = 0; i < model.contents.length; i++) {
final poll = model.messages[i].poll;
if (poll != null) {
check(model).contents[i].isA<PollContent>().poll.identicalTo(poll);
continue;
}
check(model.contents[i]).isA<ZulipContent>()
.equalsNode(parseContent(model.messages[i].content));
}
Expand Down

0 comments on commit 598bb7d

Please sign in to comment.