Skip to content

Commit

Permalink
store: Add details to error on no PerAccountStoreWidget
Browse files Browse the repository at this point in the history
This draws from MediaQuery.of and debugCheckHasMediaQuery.

Prompted by this question:
  https://chat.zulip.org/#narrow/channel/516-mobile-dev-help/topic/No.20PerAccountStoreWidget.20ancestor/near/2008484
  • Loading branch information
gnprice authored and chrisbobbe committed Dec 20, 2024
1 parent f2458b1 commit d9f88f3
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 2 deletions.
21 changes: 19 additions & 2 deletions lib/widgets/store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class PerAccountStoreWidget extends StatefulWidget {
/// * [InheritedNotifier], which provides the "dependency" mechanism.
static PerAccountStore of(BuildContext context) {
final widget = context.dependOnInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>();
assert(widget != null, 'No PerAccountStoreWidget ancestor');
assert(_debugCheckFound(context, widget));
return widget!.store;
}

Expand All @@ -183,12 +183,29 @@ class PerAccountStoreWidget extends StatefulWidget {
/// Like [of], the cost of this method is O(1) with a small constant factor.
static int accountIdOf(BuildContext context) {
final element = context.getElementForInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>();
assert(element != null, 'No PerAccountStoreWidget ancestor');
assert(_debugCheckFound(context, element));
final widget = element!.findAncestorWidgetOfExactType<PerAccountStoreWidget>();
assert(widget != null);
return widget!.accountId;
}

static bool _debugCheckFound(BuildContext context, Object? ancestor) {
// Compare [debugCheckHasMediaQuery], and its caller [MediaQuery.of].
assert(() {
if (ancestor != null) return true;
throw FlutterError.fromParts([
ErrorSummary('No PerAccountStoreWidget ancestor found.'),
ErrorDescription('${context.widget.runtimeType} widgets require a PerAccountStoreWidget ancestor.'),
context.describeWidget('The specific widget that could not find a PerAccountStoreWidget ancestor was'),
context.describeOwnershipChain('The ownership chain for the affected widget is'),
ErrorHint('For a new page in the app, consider MaterialAccountWidgetRoute '
'or AccountPageRouteBuilder.'),
ErrorHint('In tests, consider TestZulipApp with its accountId field.'),
]);
}());
return true;
}

/// Whether there is a relevant account specified for this widget.
static bool debugExistsOf(BuildContext context) {
return context.getElementForInheritedWidgetOfExactType<_PerAccountStoreInheritedWidget>() != null;
Expand Down
18 changes: 18 additions & 0 deletions test/widgets/store_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ void main() {
tester.widget(find.text('found store, account: ${eg.selfAccount.id}'));
});

testWidgets('PerAccountStoreWidget.of detailed error', (tester) async {
addTearDown(testBinding.reset);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: GlobalStoreWidget(
// no PerAccountStoreWidget
child: Builder(
builder: (context) {
final store = PerAccountStoreWidget.of(context);
return Text('found store, account: ${store.accountId}');
}))));
await tester.pump();
check(tester.takeException())
.has((x) => x.toString(), 'toString') // TODO(checks): what's a good convention for this?
.contains('consider MaterialAccountWidgetRoute');
});

testWidgets('PerAccountStoreWidget immediate data after first loaded', (tester) async {
await testBinding.globalStore.add(eg.selfAccount, eg.initialSnapshot());
addTearDown(testBinding.reset);
Expand Down

0 comments on commit d9f88f3

Please sign in to comment.