Skip to content

Commit f8a1078

Browse files
committed
actions test: Port most MarkAsReadWidget tests to markNarrowAsRead
This sets us up to be able to refactor this function for other use cases that aren't exercised by MarkAsReadWidget, and have good tests for those cases too. There are a couple of pieces of functionality of the widget's _handlePress method that probably should be within the function markNarrowAsRead instead, so that they can be reused together. For the tests for those, we port them too but mark them as skipped, with TODO comments. After refactoring to move that functionality inside, we can enable the tests. The new markNarrowAsRead unit tests do most of the work of tests for MarkAsReadWidget. We leave behind a couple of smoke tests that mostly just show it is indeed using markNarrowAsRead, plus the tests whose ported versions are skipped.
1 parent a483e1a commit f8a1078

File tree

2 files changed

+291
-169
lines changed

2 files changed

+291
-169
lines changed

test/widgets/actions_test.dart

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
import 'dart:convert';
2+
3+
import 'package:checks/checks.dart';
4+
import 'package:flutter/material.dart';
5+
import 'package:flutter_gen/gen_l10n/zulip_localizations.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:http/http.dart' as http;
8+
import 'package:zulip/api/model/initial_snapshot.dart';
9+
import 'package:zulip/api/model/narrow.dart';
10+
import 'package:zulip/api/route/messages.dart';
11+
import 'package:zulip/model/localizations.dart';
12+
import 'package:zulip/model/narrow.dart';
13+
import 'package:zulip/model/store.dart';
14+
import 'package:zulip/widgets/actions.dart';
15+
import 'package:zulip/widgets/store.dart';
16+
import 'package:zulip/widgets/theme.dart';
17+
18+
import '../api/fake_api.dart';
19+
import '../example_data.dart' as eg;
20+
import '../model/binding.dart';
21+
import '../model/unreads_checks.dart';
22+
import '../stdlib_checks.dart';
23+
import 'dialog_checks.dart';
24+
25+
void main() {
26+
TestZulipBinding.ensureInitialized();
27+
28+
group('markNarrowAsRead', () {
29+
late PerAccountStore store;
30+
late FakeApiConnection connection;
31+
late BuildContext context;
32+
33+
Future<void> prepare(WidgetTester tester, {
34+
UnreadMessagesSnapshot? unreadMsgs,
35+
}) async {
36+
addTearDown(testBinding.reset);
37+
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot(
38+
unreadMsgs: unreadMsgs));
39+
store = await testBinding.globalStore.perAccount(eg.selfAccount.id);
40+
connection = store.connection as FakeApiConnection;
41+
42+
await tester.pumpWidget(Builder(builder: (context) =>
43+
MaterialApp(
44+
theme: zulipThemeData(context),
45+
localizationsDelegates: ZulipLocalizations.localizationsDelegates,
46+
supportedLocales: ZulipLocalizations.supportedLocales,
47+
home: GlobalStoreWidget(
48+
child: PerAccountStoreWidget(
49+
accountId: eg.selfAccount.id,
50+
child: const Scaffold(
51+
body: Placeholder()))))));
52+
await tester.pumpAndSettle();
53+
context = tester.element(find.byType(Placeholder));
54+
}
55+
56+
testWidgets('smoke test on modern server', (tester) async {
57+
final narrow = TopicNarrow.ofMessage(eg.streamMessage());
58+
await prepare(tester);
59+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
60+
processedCount: 11, updatedCount: 3,
61+
firstProcessedId: null, lastProcessedId: null,
62+
foundOldest: true, foundNewest: true).toJson());
63+
markNarrowAsRead(context, narrow, false);
64+
await tester.pump(Duration.zero);
65+
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
66+
check(connection.lastRequest).isA<http.Request>()
67+
..method.equals('POST')
68+
..url.path.equals('/api/v1/messages/flags/narrow')
69+
..bodyFields.deepEquals({
70+
'anchor': 'oldest',
71+
'include_anchor': 'false',
72+
'num_before': '0',
73+
'num_after': '1000',
74+
'narrow': jsonEncode(apiNarrow),
75+
'op': 'add',
76+
'flag': 'read',
77+
});
78+
});
79+
80+
81+
testWidgets('use is:unread optimization', (WidgetTester tester) async {
82+
const narrow = CombinedFeedNarrow();
83+
await prepare(tester);
84+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
85+
processedCount: 11, updatedCount: 3,
86+
firstProcessedId: null, lastProcessedId: null,
87+
foundOldest: true, foundNewest: true).toJson());
88+
markNarrowAsRead(context, narrow, false);
89+
await tester.pump(Duration.zero);
90+
check(connection.lastRequest).isA<http.Request>()
91+
..method.equals('POST')
92+
..url.path.equals('/api/v1/messages/flags/narrow')
93+
..bodyFields.deepEquals({
94+
'anchor': 'oldest',
95+
'include_anchor': 'false',
96+
'num_before': '0',
97+
'num_after': '1000',
98+
'narrow': json.encode([{'operator': 'is', 'operand': 'unread'}]),
99+
'op': 'add',
100+
'flag': 'read',
101+
});
102+
});
103+
104+
testWidgets('pagination', (WidgetTester tester) async {
105+
// Check that `lastProcessedId` returned from an initial
106+
// response is used as `anchorId` for the subsequent request.
107+
final narrow = TopicNarrow.ofMessage(eg.streamMessage());
108+
await prepare(tester);
109+
110+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
111+
processedCount: 1000, updatedCount: 890,
112+
firstProcessedId: 1, lastProcessedId: 1989,
113+
foundOldest: true, foundNewest: false).toJson());
114+
markNarrowAsRead(context, narrow, false);
115+
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
116+
check(connection.lastRequest).isA<http.Request>()
117+
..method.equals('POST')
118+
..url.path.equals('/api/v1/messages/flags/narrow')
119+
..bodyFields.deepEquals({
120+
'anchor': 'oldest',
121+
'include_anchor': 'false',
122+
'num_before': '0',
123+
'num_after': '1000',
124+
'narrow': jsonEncode(apiNarrow),
125+
'op': 'add',
126+
'flag': 'read',
127+
});
128+
129+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
130+
processedCount: 20, updatedCount: 10,
131+
firstProcessedId: 2000, lastProcessedId: 2023,
132+
foundOldest: false, foundNewest: true).toJson());
133+
await tester.pumpAndSettle();
134+
check(find.bySubtype<SnackBar>().evaluate()).length.equals(1);
135+
check(connection.lastRequest).isA<http.Request>()
136+
..method.equals('POST')
137+
..url.path.equals('/api/v1/messages/flags/narrow')
138+
..bodyFields.deepEquals({
139+
'anchor': '1989',
140+
'include_anchor': 'false',
141+
'num_before': '0',
142+
'num_after': '1000',
143+
'narrow': jsonEncode(apiNarrow),
144+
'op': 'add',
145+
'flag': 'read',
146+
});
147+
});
148+
149+
testWidgets('on mark-all-as-read when Unreads.oldUnreadsMissing: true', (tester) async {
150+
const narrow = CombinedFeedNarrow();
151+
await prepare(tester);
152+
store.unreads.oldUnreadsMissing = true;
153+
154+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
155+
processedCount: 11, updatedCount: 3,
156+
firstProcessedId: null, lastProcessedId: null,
157+
foundOldest: true, foundNewest: true).toJson());
158+
markNarrowAsRead(context, narrow, false);
159+
await tester.pump(Duration.zero);
160+
await tester.pumpAndSettle();
161+
check(store.unreads.oldUnreadsMissing).isFalse();
162+
}, skip: true, // TODO move this functionality inside markNarrowAsRead
163+
);
164+
165+
testWidgets('on invalid response', (WidgetTester tester) async {
166+
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
167+
final narrow = TopicNarrow.ofMessage(eg.streamMessage());
168+
await prepare(tester);
169+
connection.prepare(json: UpdateMessageFlagsForNarrowResult(
170+
processedCount: 1000, updatedCount: 0,
171+
firstProcessedId: null, lastProcessedId: null,
172+
foundOldest: true, foundNewest: false).toJson());
173+
markNarrowAsRead(context, narrow, false);
174+
await tester.pump(Duration.zero);
175+
final apiNarrow = narrow.apiEncode()..add(ApiNarrowIsUnread());
176+
check(connection.lastRequest).isA<http.Request>()
177+
..method.equals('POST')
178+
..url.path.equals('/api/v1/messages/flags/narrow')
179+
..bodyFields.deepEquals({
180+
'anchor': 'oldest',
181+
'include_anchor': 'false',
182+
'num_before': '0',
183+
'num_after': '1000',
184+
'narrow': jsonEncode(apiNarrow),
185+
'op': 'add',
186+
'flag': 'read',
187+
});
188+
189+
await tester.pumpAndSettle();
190+
checkErrorDialog(tester,
191+
expectedTitle: zulipLocalizations.errorMarkAsReadFailedTitle,
192+
expectedMessage: zulipLocalizations.errorInvalidResponse);
193+
});
194+
195+
testWidgets('CombinedFeedNarrow on legacy server', (WidgetTester tester) async {
196+
const narrow = CombinedFeedNarrow();
197+
await prepare(tester);
198+
// Might as well test with oldUnreadsMissing: true.
199+
store.unreads.oldUnreadsMissing = true;
200+
201+
connection.zulipFeatureLevel = 154;
202+
connection.prepare(json: {});
203+
markNarrowAsRead(context, narrow, true); // TODO move legacy-server check inside markNarrowAsRead
204+
await tester.pump(Duration.zero);
205+
check(connection.lastRequest).isA<http.Request>()
206+
..method.equals('POST')
207+
..url.path.equals('/api/v1/mark_all_as_read')
208+
..bodyFields.deepEquals({});
209+
210+
// Check that [Unreads.handleAllMessagesReadSuccess] wasn't called;
211+
// in the legacy protocol, that'd be redundant with the mark-read event.
212+
check(store.unreads).oldUnreadsMissing.isTrue();
213+
});
214+
215+
testWidgets('StreamNarrow on legacy server', (WidgetTester tester) async {
216+
final stream = eg.stream();
217+
final narrow = StreamNarrow(stream.streamId);
218+
await prepare(tester);
219+
connection.zulipFeatureLevel = 154;
220+
connection.prepare(json: {});
221+
markNarrowAsRead(context, narrow, true); // TODO move legacy-server check inside markNarrowAsRead
222+
await tester.pump(Duration.zero);
223+
check(connection.lastRequest).isA<http.Request>()
224+
..method.equals('POST')
225+
..url.path.equals('/api/v1/mark_stream_as_read')
226+
..bodyFields.deepEquals({
227+
'stream_id': stream.streamId.toString(),
228+
});
229+
});
230+
231+
testWidgets('TopicNarrow on legacy server', (WidgetTester tester) async {
232+
final narrow = TopicNarrow.ofMessage(eg.streamMessage());
233+
await prepare(tester);
234+
connection.zulipFeatureLevel = 154;
235+
connection.prepare(json: {});
236+
markNarrowAsRead(context, narrow, true); // TODO move legacy-server check inside markNarrowAsRead
237+
await tester.pump(Duration.zero);
238+
check(connection.lastRequest).isA<http.Request>()
239+
..method.equals('POST')
240+
..url.path.equals('/api/v1/mark_topic_as_read')
241+
..bodyFields.deepEquals({
242+
'stream_id': narrow.streamId.toString(),
243+
'topic_name': narrow.topic,
244+
});
245+
});
246+
247+
testWidgets('DmNarrow on legacy server', (WidgetTester tester) async {
248+
final message = eg.dmMessage(from: eg.otherUser, to: [eg.selfUser]);
249+
final narrow = DmNarrow.ofMessage(message, selfUserId: eg.selfUser.userId);
250+
final unreadMsgs = eg.unreadMsgs(dms: [
251+
UnreadDmSnapshot(otherUserId: eg.otherUser.userId,
252+
unreadMessageIds: [message.id]),
253+
]);
254+
await prepare(tester, unreadMsgs: unreadMsgs);
255+
connection.zulipFeatureLevel = 154;
256+
connection.prepare(json:
257+
UpdateMessageFlagsResult(messages: [message.id]).toJson());
258+
markNarrowAsRead(context, narrow, true); // TODO move legacy-server check inside markNarrowAsRead
259+
await tester.pump(Duration.zero);
260+
check(connection.lastRequest).isA<http.Request>()
261+
..method.equals('POST')
262+
..url.path.equals('/api/v1/messages/flags')
263+
..bodyFields.deepEquals({
264+
'messages': jsonEncode([message.id]),
265+
'op': 'add',
266+
'flag': 'read',
267+
});
268+
});
269+
270+
testWidgets('catch-all api errors', (WidgetTester tester) async {
271+
final zulipLocalizations = GlobalLocalizations.zulipLocalizations;
272+
const narrow = CombinedFeedNarrow();
273+
await prepare(tester);
274+
connection.prepare(exception: http.ClientException('Oops'));
275+
markNarrowAsRead(context, narrow, false);
276+
await tester.pump(Duration.zero);
277+
await tester.pumpAndSettle();
278+
checkErrorDialog(tester,
279+
expectedTitle: zulipLocalizations.errorMarkAsReadFailedTitle,
280+
expectedMessage: 'NetworkException: Oops (ClientException: Oops)');
281+
}, skip: true, // TODO move this functionality inside markNarrowAsRead
282+
);
283+
});
284+
}

0 commit comments

Comments
 (0)