Skip to content

Commit fbbbbeb

Browse files
committed
msglist test: Overhaul setup of tests, with shared helpers
All the tests in this file are going to have a common structure to the objects they operate on: chiefly, a MessageListView. So we can hoist those up to the level of `main`, which enables the tests to share helper functions that act on those objects. In principle this means a state leak between tests. We mitigate that by resetting all the shared variables at the same time, in the `prepare` function that each test calls at the outset. We could get a similar effect by putting these shared variables and helpers as fields and methods on a class, and having each test case make its own instance of the class -- its own "tester" object, akin to the WidgetTester object that `testWidgets` provides to its test body. That'd provide more encapsulation, and would be a good strategy if for example this logic were to be shared across several test files. But I think this test file can get away without it. The immediate payoff of the shared helpers is that they let us centralize the logic for checking that the view-model has, or hasn't, notified its listeners. That simplifies each test case, plus it lets us make that logic slightly more sophisticated (at O(1) rather than O(n) cost in the source code): we now detect duplicate calls too. Coming up, we'll add further centralized useful logic to these helpers, and add some tests that benefit from the split between `prepare` and `prepareMessages`.
1 parent dbf86dd commit fbbbbeb

File tree

1 file changed

+64
-57
lines changed

1 file changed

+64
-57
lines changed

test/model/message_list_test.dart

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,50 @@ import '../api/fake_api.dart';
1212
import '../api/model/model_checks.dart';
1313
import '../example_data.dart' as eg;
1414

15-
Future<MessageListView> messageListViewWithMessages(List<Message> messages, [Narrow narrow = const AllMessagesNarrow()]) async {
16-
final store = eg.store();
17-
final model = MessageListView.init(store: store, narrow: narrow);
18-
19-
final connection = store.connection as FakeApiConnection;
20-
connection.prepare(json:
21-
newestResult(foundOldest: true, messages: messages).toJson());
22-
await model.fetch();
23-
24-
return model;
25-
}
26-
2715
void main() async {
16+
// These variables are the common state operated on by each test.
17+
// Each test case calls [prepare] to initialize them.
18+
late PerAccountStore store;
19+
late FakeApiConnection connection;
20+
late MessageListView model;
21+
late int notifiedCount;
22+
23+
void checkNotNotified() {
24+
check(notifiedCount).equals(0);
25+
}
26+
27+
void checkNotifiedOnce() {
28+
check(notifiedCount).equals(1);
29+
notifiedCount = 0;
30+
}
31+
32+
/// Initialize [model] and the rest of the test state.
33+
void prepare({Narrow narrow = const AllMessagesNarrow()}) {
34+
store = eg.store();
35+
connection = store.connection as FakeApiConnection;
36+
notifiedCount = 0;
37+
model = MessageListView.init(store: store, narrow: narrow)
38+
..addListener(() { notifiedCount++; });
39+
check(model).fetched.isFalse();
40+
checkNotNotified();
41+
}
42+
43+
/// Perform the initial message fetch for [model].
44+
///
45+
/// The test case must have already called [prepare] to initialize the state.
46+
Future<void> prepareMessages({
47+
required bool foundOldest,
48+
required List<Message> messages,
49+
}) async {
50+
connection.prepare(json:
51+
newestResult(foundOldest: foundOldest, messages: messages).toJson());
52+
await model.fetch();
53+
checkNotifiedOnce();
54+
}
55+
2856
test('findMessageWithId', () async {
29-
final model = await messageListViewWithMessages([
57+
prepare();
58+
await prepareMessages(foundOldest: true, messages: [
3059
eg.streamMessage(id: 2),
3160
eg.streamMessage(id: 4),
3261
eg.streamMessage(id: 6),
@@ -57,10 +86,8 @@ void main() async {
5786
userId: 1,
5887
renderingOnly: false,
5988
);
60-
61-
final model = await messageListViewWithMessages([originalMessage]);
62-
bool listenersNotified = false;
63-
model.addListener(() { listenersNotified = true; });
89+
prepare();
90+
await prepareMessages(foundOldest: true, messages: [originalMessage]);
6491

6592
final message = model.messages.single;
6693
check(message)
@@ -70,7 +97,7 @@ void main() async {
7097
..isMeMessage.not(it()..equals(updateEvent.isMeMessage!));
7198

7299
model.maybeUpdateMessage(updateEvent);
73-
check(listenersNotified).isTrue();
100+
checkNotifiedOnce();
74101
check(model).messages.single
75102
..identicalTo(message)
76103
..content.equals(updateEvent.renderedContent!)
@@ -92,13 +119,11 @@ void main() async {
92119
userId: 1,
93120
renderingOnly: false,
94121
);
95-
96-
final model = await messageListViewWithMessages([originalMessage]);
97-
bool listenersNotified = false;
98-
model.addListener(() { listenersNotified = true; });
122+
prepare();
123+
await prepareMessages(foundOldest: true, messages: [originalMessage]);
99124

100125
model.maybeUpdateMessage(updateEvent);
101-
check(listenersNotified).isFalse();
126+
checkNotNotified();
102127
check(model).messages.single
103128
..content.equals(originalMessage.content)
104129
..content.not(it()..equals(updateEvent.renderedContent!));
@@ -119,14 +144,12 @@ void main() async {
119144
renderingOnly: legacy ? null : true,
120145
userId: null,
121146
);
122-
123-
final model = await messageListViewWithMessages([originalMessage]);
124-
bool listenersNotified = false;
125-
model.addListener(() { listenersNotified = true; });
126-
147+
prepare();
148+
await prepareMessages(foundOldest: true, messages: [originalMessage]);
127149
final message = model.messages.single;
150+
128151
model.maybeUpdateMessage(updateEvent);
129-
check(listenersNotified).isTrue();
152+
checkNotifiedOnce();
130153
check(model).messages.single
131154
..identicalTo(message)
132155
// Content is updated...
@@ -159,33 +182,25 @@ void main() async {
159182

160183
test('add reaction', () async {
161184
final originalMessage = eg.streamMessage(reactions: []);
162-
final model = await messageListViewWithMessages([originalMessage]);
163-
185+
prepare();
186+
await prepareMessages(foundOldest: true, messages: [originalMessage]);
164187
final message = model.messages.single;
165188

166-
bool listenersNotified = false;
167-
model.addListener(() { listenersNotified = true; });
168-
169189
model.maybeUpdateMessageReactions(
170190
mkEvent(eg.unicodeEmojiReaction, ReactionOp.add, originalMessage.id));
171-
172-
check(listenersNotified).isTrue();
191+
checkNotifiedOnce();
173192
check(model).messages.single
174193
..identicalTo(message)
175194
..reactions.jsonEquals([eg.unicodeEmojiReaction]);
176195
});
177196

178197
test('add reaction; message is not in list', () async {
179198
final someMessage = eg.streamMessage(id: 1, reactions: []);
180-
final model = await messageListViewWithMessages([someMessage]);
181-
182-
bool listenersNotified = false;
183-
model.addListener(() { listenersNotified = true; });
184-
199+
prepare();
200+
await prepareMessages(foundOldest: true, messages: [someMessage]);
185201
model.maybeUpdateMessageReactions(
186202
mkEvent(eg.unicodeEmojiReaction, ReactionOp.add, 1000));
187-
188-
check(listenersNotified).isFalse();
203+
checkNotNotified();
189204
check(model).messages.single.reactions.jsonEquals([]);
190205
});
191206

@@ -211,33 +226,25 @@ void main() async {
211226

212227
final originalMessage = eg.streamMessage(
213228
reactions: [reaction2, reaction3, reaction4]);
214-
final model = await messageListViewWithMessages([originalMessage]);
215-
229+
prepare();
230+
await prepareMessages(foundOldest: true, messages: [originalMessage]);
216231
final message = model.messages.single;
217232

218-
bool listenersNotified = false;
219-
model.addListener(() { listenersNotified = true; });
220-
221233
model.maybeUpdateMessageReactions(
222234
mkEvent(eventReaction, ReactionOp.remove, originalMessage.id));
223-
224-
check(listenersNotified).isTrue();
235+
checkNotifiedOnce();
225236
check(model).messages.single
226237
..identicalTo(message)
227238
..reactions.jsonEquals([reaction2, reaction3]);
228239
});
229240

230241
test('remove reaction; message is not in list', () async {
231242
final someMessage = eg.streamMessage(id: 1, reactions: [eg.unicodeEmojiReaction]);
232-
final model = await messageListViewWithMessages([someMessage]);
233-
234-
bool listenersNotified = false;
235-
model.addListener(() { listenersNotified = true; });
236-
243+
prepare();
244+
await prepareMessages(foundOldest: true, messages: [someMessage]);
237245
model.maybeUpdateMessageReactions(
238246
mkEvent(eg.unicodeEmojiReaction, ReactionOp.remove, 1000));
239-
240-
check(listenersNotified).isFalse();
247+
checkNotNotified();
241248
check(model).messages.single.reactions.jsonEquals([eg.unicodeEmojiReaction]);
242249
});
243250
});

0 commit comments

Comments
 (0)