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