@@ -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
+ // [onNewStore] called initially
171
+ await pumpWithParams (light: true , accountId: accountId);
172
+ await tester.pump (); // global store
173
+ await tester.pump (); // per-account store
174
+ check (widgetWithMixinKey).currentState.isNotNull ()
175
+ ..anyDepChangeCounter.equals (1 )
176
+ ..storeChangeCounter.equals (1 );
177
+
178
+ // [onNewStore] not called on unrelated dependency change
179
+ await pumpWithParams (light: false , accountId: accountId);
180
+ await tester.pumpAndSettle (kThemeAnimationDuration);
181
+ check (widgetWithMixinKey).currentState.isNotNull ()
182
+ ..anyDepChangeCounter.equals (2 )
183
+ ..storeChangeCounter.equals (1 );
184
+
185
+ // [onNewStore] called when store changes
186
+ //
187
+ // TODO: Trigger `store` change by simulating an event-queue renewal,
188
+ // instead of with this hack where we change the UI's backing Account
189
+ // out from under it. (That account-swapping would be suspicious in
190
+ // production code, where we could reasonably add an assert against it.
191
+ // If forced, we could let this test code proceed despite such an assert…)
192
+ // hack; the snapshot probably corresponds to selfAccount, not otherAccount.
193
+ await TestZulipBinding .instance.globalStore.add (eg.otherAccount, eg.initialSnapshot ());
194
+ await pumpWithParams (light: false , accountId: eg.otherAccount.id);
195
+ // Nudge PerAccountStoreWidget to send its updated store to MyWidgetWithMixin.
196
+ //
197
+ // A change in PerAccountStoreWidget's [accountId] field doesn't by itself
198
+ // prompt dependent widgets (those using PerAccountStoreWidget.of) to update,
199
+ // even though it holds a new store. (See its state's didUpdateWidget,
200
+ // or lack thereof.) That's reasonable, since such a change is never expected;
201
+ // see TODO above.
202
+ //
203
+ // But when PerAccountStoreWidget gets a notification from the GlobalStore,
204
+ // it checks at that time whether it has a new PerAccountStore to distribute
205
+ // (as it will when widget.accountId has changed), and if so,
206
+ // it will notify dependent widgets. (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 (3 )
212
+ ..storeChangeCounter.equals (2 );
213
+ });
116
214
}
0 commit comments