@@ -4,10 +4,47 @@ import 'package:flutter_test/flutter_test.dart';
4
4
import 'package:zulip/model/store.dart' ;
5
5
import 'package:zulip/widgets/store.dart' ;
6
6
7
+ import '../flutter_checks.dart' ;
7
8
import '../model/binding.dart' ;
8
9
import '../example_data.dart' as eg;
9
10
import '../model/store_checks.dart' ;
10
11
12
+ /// A widget whose state uses [PerAccountStoreAwareStateMixin] .
13
+ class MyWidgetWithMixin extends StatefulWidget {
14
+ const MyWidgetWithMixin ({super .key});
15
+
16
+ @override
17
+ State <MyWidgetWithMixin > createState () => MyWidgetWithMixinState ();
18
+ }
19
+
20
+ class MyWidgetWithMixinState extends State <MyWidgetWithMixin > with PerAccountStoreAwareStateMixin <MyWidgetWithMixin > {
21
+ int anyDepChangeCounter = 0 ;
22
+ int storeChangeCounter = 0 ;
23
+
24
+ @override
25
+ void onNewStore () {
26
+ storeChangeCounter++ ;
27
+ }
28
+
29
+ @override
30
+ void didChangeDependencies () {
31
+ super .didChangeDependencies ();
32
+ anyDepChangeCounter++ ;
33
+ }
34
+
35
+ @override
36
+ Widget build (BuildContext context) {
37
+ final brightness = Theme .of (context).brightness;
38
+ final accountId = PerAccountStoreWidget .of (context).account.id;
39
+ return Text ('brightness: $brightness ; accountId: $accountId ' );
40
+ }
41
+ }
42
+
43
+ extension MyWidgetWithMixinStateChecks on Subject <MyWidgetWithMixinState > {
44
+ Subject <int > get anyDepChangeCounter => has ((w) => w.anyDepChangeCounter, 'anyDepChangeCounter' );
45
+ Subject <int > get storeChangeCounter => has ((w) => w.storeChangeCounter, 'storeChangeCounter' );
46
+ }
47
+
11
48
void main () {
12
49
TestZulipBinding .ensureInitialized ();
13
50
@@ -113,4 +150,65 @@ void main() {
113
150
check (tester.any (find.textContaining ('found store' ))).isTrue ();
114
151
tester.widget (find.text ('found store, account: ${eg .selfAccount .id }' ));
115
152
});
153
+
154
+ testWidgets ('PerAccountStoreAwareStateMixin' , (tester) async {
155
+ final widgetWithMixinKey = GlobalKey <MyWidgetWithMixinState >();
156
+ final accountId = eg.selfAccount.id;
157
+
158
+ await TestZulipBinding .instance.globalStore.add (eg.selfAccount, eg.initialSnapshot ());
159
+
160
+ Future <void > pumpWithParams ({required bool light, required int accountId}) async {
161
+ await tester.pumpWidget (
162
+ MaterialApp (
163
+ theme: light ? ThemeData .light () : ThemeData .dark (),
164
+ home: GlobalStoreWidget (
165
+ child: PerAccountStoreWidget (
166
+ accountId: accountId,
167
+ child: MyWidgetWithMixin (key: widgetWithMixinKey))),
168
+ ));
169
+ }
170
+
171
+ // [onNewStore] called initially
172
+ await pumpWithParams (light: true , accountId: accountId);
173
+ await tester.pump (); // global store
174
+ await tester.pump (); // per-account store
175
+ check (widgetWithMixinKey).currentState.isNotNull ().storeChangeCounter.equals (1 );
176
+ check (widgetWithMixinKey).currentState.isNotNull ()
177
+ ..anyDepChangeCounter.equals (1 )
178
+ ..storeChangeCounter.equals (1 );
179
+
180
+ // [onNewStore] not called on unrelated dependency change
181
+ await pumpWithParams (light: false , accountId: accountId);
182
+ await tester.pumpAndSettle ();
183
+ check (widgetWithMixinKey).currentState.isNotNull ()
184
+ ..anyDepChangeCounter.equals (3 ) // (Dunno why 3 instead of 2. Anyway, more than 1…)
185
+ ..storeChangeCounter.equals (1 );
186
+
187
+ // [onNewStore] called when store changes
188
+ //
189
+ // TODO: Trigger `store` change by simulating an event-queue renewal,
190
+ // instead of with this hack where we change the UI's backing Account
191
+ // out from under it. (That account-swapping would be suspicious in
192
+ // production code, where we could reasonably add an assert against it.
193
+ // If forced, we could let this test code proceed despite such an assert…)
194
+ // hack; the snapshot probably corresponds to selfAccount, not otherAccount.
195
+ await TestZulipBinding .instance.globalStore.add (eg.otherAccount, eg.initialSnapshot ());
196
+ await pumpWithParams (light: false , accountId: eg.otherAccount.id);
197
+ // Nudge PerAccountStoreWidget to send its updated store to MyWidgetWithMixin.
198
+ //
199
+ // A change in PerAccountStoreWidget's [accountId] field doesn't by itself
200
+ // prompt dependant widgets (those using PerAccountStoreWidget.of) to update,
201
+ // even though it holds a new store. (See its state's didUpdateWidget,
202
+ // or lack thereof.) That's reasonable, since such a change is never expected;
203
+ // see TODO above.
204
+ //
205
+ // But it will prompt widgets to update when it receives a notification
206
+ // from the GlobalStore; see its state's didChangeDependencies.
207
+ // So, take advantage of that.
208
+ TestZulipBinding .instance.globalStore.notifyListeners ();
209
+ await tester.pumpAndSettle ();
210
+ check (widgetWithMixinKey).currentState.isNotNull ()
211
+ ..anyDepChangeCounter.equals (4 )
212
+ ..storeChangeCounter.equals (2 );
213
+ });
116
214
}
0 commit comments