1
+ import 'dart:convert' ;
2
+
1
3
import 'package:checks/checks.dart' ;
4
+ import 'package:flutter/material.dart' ;
2
5
import 'package:flutter_test/flutter_test.dart' ;
6
+ import 'package:http/http.dart' as http;
7
+ import 'package:zulip/api/model/events.dart' ;
3
8
import 'package:zulip/api/model/model.dart' ;
9
+ import 'package:zulip/api/route/channels.dart' ;
10
+ import 'package:zulip/model/localizations.dart' ;
11
+ import 'package:zulip/model/store.dart' ;
4
12
import 'package:zulip/widgets/channel_list.dart' ;
13
+ import 'package:zulip/widgets/icons.dart' ;
5
14
15
+ import '../api/fake_api.dart' ;
6
16
import '../model/binding.dart' ;
7
17
import '../example_data.dart' as eg;
18
+ import '../stdlib_checks.dart' ;
19
+ import 'dialog_checks.dart' ;
8
20
import 'test_app.dart' ;
9
21
10
22
void main () {
11
23
TestZulipBinding .ensureInitialized ();
24
+ late FakeApiConnection connection;
25
+ late PerAccountStore store;
12
26
13
27
Future <void > setupChannelListPage (WidgetTester tester, {
14
28
required List <ZulipStream > streams,
@@ -18,8 +32,10 @@ void main() {
18
32
final initialSnapshot = eg.initialSnapshot (
19
33
subscriptions: subscriptions,
20
34
streams: streams,
21
- );
35
+ realmUsers : [eg.selfUser] );
22
36
await testBinding.globalStore.add (eg.selfAccount, initialSnapshot);
37
+ store = await testBinding.globalStore.perAccount (eg.selfAccount.id);
38
+ connection = store.connection as FakeApiConnection ;
23
39
24
40
await tester.pumpWidget (TestZulipApp (accountId: eg.selfAccount.id, child: const ChannelListPage ()));
25
41
@@ -67,4 +83,160 @@ void main() {
67
83
check (listedStreamNames (tester)).deepEquals (['a' , 'b' , 'c' ]);
68
84
});
69
85
});
86
+
87
+ group ('subscription toggle' , () {
88
+ final zulipLocalizations = GlobalLocalizations .zulipLocalizations;
89
+
90
+ Future <ZulipStream > prepareSingleStream (WidgetTester tester) async {
91
+ final stream = eg.stream ();
92
+ await setupChannelListPage (tester, streams: [stream], subscriptions: []);
93
+ return stream;
94
+ }
95
+
96
+ Future <void > tapSubscribeButton (WidgetTester tester) async {
97
+ await tester.tap (find.byIcon (Icons .add));
98
+ }
99
+
100
+ Future <void > waitAndCheckSnackbarIsShown (WidgetTester tester, String message, String channelName) async {
101
+ await tester.pump (Duration .zero);
102
+ await tester.pumpAndSettle ();
103
+ final richTextFinder = find.byWidgetPredicate (
104
+ (widget) => widget is RichText && widget.text.toPlainText ().contains (message));
105
+ check (richTextFinder.evaluate ()).single;
106
+ final richTextWidget = tester.widget <RichText >(richTextFinder);
107
+ check (richTextWidget.text.toPlainText ()).contains (message);
108
+ check (richTextWidget.text.toPlainText ()).contains (channelName);
109
+ check (find.descendant (
110
+ of: richTextFinder,
111
+ matching: find.byIcon (ZulipIcons .hash_sign),
112
+ ).evaluate ()).single;
113
+ }
114
+
115
+ testWidgets ('is affected by subscription events' , (WidgetTester tester) async {
116
+ final stream = await prepareSingleStream (tester);
117
+ connection.prepare (json: SubscribeToChannelsResult (
118
+ subscribed: {eg.selfUser.email: [stream.name]},
119
+ alreadySubscribed: {}).toJson ());
120
+
121
+ check (find.byIcon (Icons .add).evaluate ()).isNotEmpty ();
122
+
123
+ await store.handleEvent (SubscriptionAddEvent (id: 1 ,
124
+ subscriptions: [eg.subscription (stream)]));
125
+ await tester.pumpAndSettle ();
126
+
127
+ check (find.byIcon (Icons .add).evaluate ()).isEmpty ();
128
+
129
+ await store.handleEvent (SubscriptionRemoveEvent (id: 2 , streamIds: [stream.streamId]));
130
+ await tester.pumpAndSettle ();
131
+
132
+ check (find.byIcon (Icons .add).evaluate ()).isNotEmpty ();
133
+ });
134
+
135
+ testWidgets ('is disabled while loading' , (WidgetTester tester) async {
136
+ final stream = eg.stream ();
137
+ await setupChannelListPage (tester, streams: [stream], subscriptions: []);
138
+ connection.prepare (json: SubscribeToChannelsResult (
139
+ subscribed: {eg.selfUser.email: [stream.name]},
140
+ alreadySubscribed: {}).toJson ());
141
+ await tapSubscribeButton (tester);
142
+ await tester.pump ();
143
+
144
+ check (tester.widget <IconButton >(
145
+ find.byType (IconButton )).onPressed).isNull ();
146
+
147
+ await tester.pump (const Duration (seconds: 2 ));
148
+
149
+ check (tester.widget <IconButton >(
150
+ find.byType (IconButton )).onPressed).isNotNull ();
151
+ });
152
+
153
+ testWidgets ('is disabled while loading and enabled back when loading fails' , (WidgetTester tester) async {
154
+ final stream = eg.stream ();
155
+ await setupChannelListPage (tester, streams: [stream], subscriptions: []);
156
+ connection.prepare (exception: http.ClientException ('Oops' ), delay: const Duration (seconds: 2 ));
157
+ await tapSubscribeButton (tester);
158
+ await tester.pump ();
159
+
160
+ check (tester.widget <IconButton >(
161
+ find.byType (IconButton )).onPressed).isNull ();
162
+
163
+ await tester.pump (const Duration (seconds: 2 ));
164
+
165
+ check (tester.widget <IconButton >(
166
+ find.byType (IconButton )).onPressed).isNotNull ();
167
+ });
168
+
169
+ group ('subscribe' , () {
170
+ testWidgets ('is shown only for streams that user is not subscribed to' , (tester) async {
171
+ final streams = [eg.stream (), eg.stream (), eg.subscription (eg.stream ())];
172
+ final subscriptions = [streams[2 ] as Subscription ];
173
+ await setupChannelListPage (tester, streams: streams, subscriptions: subscriptions);
174
+
175
+ check (find.byIcon (Icons .add).evaluate ().length).equals (2 );
176
+ });
177
+
178
+ testWidgets ('smoke api' , (tester) async {
179
+ final stream = await prepareSingleStream (tester);
180
+ connection.prepare (json: SubscribeToChannelsResult (
181
+ subscribed: {eg.selfUser.email: [stream.name]},
182
+ alreadySubscribed: {}).toJson ());
183
+ await tapSubscribeButton (tester);
184
+
185
+ await tester.pump (Duration .zero);
186
+ await tester.pumpAndSettle ();
187
+ check (connection.lastRequest).isA< http.Request > ()
188
+ ..method.equals ('POST' )
189
+ ..url.path.equals ('/api/v1/users/me/subscriptions' )
190
+ ..bodyFields.deepEquals ({
191
+ 'subscriptions' : jsonEncode ([{'name' : stream.name}])
192
+ });
193
+ });
194
+
195
+ testWidgets ('shows a snackbar when subscription passes' , (WidgetTester tester) async {
196
+ final stream = await prepareSingleStream (tester);
197
+ connection.prepare (json: SubscribeToChannelsResult (
198
+ subscribed: {eg.selfUser.email: [stream.name]},
199
+ alreadySubscribed: {}).toJson ());
200
+ await tapSubscribeButton (tester);
201
+
202
+ await waitAndCheckSnackbarIsShown (tester,
203
+ zulipLocalizations.messageSubscribedToChannel, stream.name);
204
+ });
205
+
206
+ testWidgets ('shows a snackbar when already subscribed' , (WidgetTester tester) async {
207
+ final stream = await prepareSingleStream (tester);
208
+ connection.prepare (json: SubscribeToChannelsResult (
209
+ subscribed: {},
210
+ alreadySubscribed: {eg.selfUser.email: [stream.name]}).toJson ());
211
+ await tapSubscribeButton (tester);
212
+
213
+ await waitAndCheckSnackbarIsShown (tester,
214
+ zulipLocalizations.messageAlreadySubscribedToChannel, stream.name);
215
+ });
216
+
217
+ testWidgets ('shows a snackbar when subscription fails' , (WidgetTester tester) async {
218
+ final stream = await prepareSingleStream (tester);
219
+ connection.prepare (json: SubscribeToChannelsResult (
220
+ subscribed: {},
221
+ alreadySubscribed: {},
222
+ unauthorized: [stream.name]).toJson ());
223
+ await tapSubscribeButton (tester);
224
+
225
+ await waitAndCheckSnackbarIsShown (tester,
226
+ zulipLocalizations.errorFailedToSubscribedToChannel, stream.name);
227
+ });
228
+
229
+ testWidgets ('catch-all api errors' , (WidgetTester tester) async {
230
+ await prepareSingleStream (tester);
231
+ connection.prepare (exception: http.ClientException ('Oops' ));
232
+ await tapSubscribeButton (tester);
233
+ await tester.pump (Duration .zero);
234
+ await tester.pumpAndSettle ();
235
+
236
+ checkErrorDialog (tester,
237
+ expectedTitle: zulipLocalizations.errorFailedToSubscribedToChannel,
238
+ expectedMessage: 'NetworkException: Oops (ClientException: Oops)' );
239
+ });
240
+ });
241
+ });
70
242
}
0 commit comments