only-bible-app v3.7.2+24
git clone https://git.pyrossh.dev/only-bible-app
The only bible app you will ever need. No ads. No in-app purchases. No distractions.
test/navigation_integration_test.dart
| 64b2ff0 | 1 | // Integration-style tests: these mount the real App widget and drive it |
| 64b2ff0 | 2 | // through actual taps (find + tap real keys/text), rather than dispatching |
| 64b2ff0 | 3 | // actions programmatically against a bare store/router. That distinction |
| 64b2ff0 | 4 | // matters: an earlier bug (App._syncCurrentChapter clobbering |
| 64b2ff0 | 5 | // savedBook/savedChapter right after a push()) went unnoticed by unit tests |
| 64b2ff0 | 6 | // that dispatched actions directly, because they never mounted the widget |
| 64b2ff0 | 7 | // tree that wires the router's listener up in the first place. Driving the |
| 64b2ff0 | 8 | // same flows through the UI is what actually would have caught it. |
| 64b2ff0 | 9 | import "package:flutter/material.dart"; |
| 64b2ff0 | 10 | import "package:flutter_test/flutter_test.dart"; |
| 64b2ff0 | 11 | import "package:async_redux/async_redux.dart"; |
| 64b2ff0 | 12 | import "package:only_bible_app/app.dart"; |
| 64b2ff0 | 13 | import "package:only_bible_app/store/app_state.dart"; |
| 64b2ff0 | 14 | import "package:only_bible_app/widgets/book_select_sheet.dart"; |
| 64b2ff0 | 15 | import "package:only_bible_app/widgets/book_tile.dart"; |
| 64b2ff0 | 16 | import "app_logic_test.dart" show buildTestBible; |
| 64b2ff0 | 17 | |
| 64b2ff0 | 18 | /// Taps the [label] tile inside whichever [BookSelectSheet] is currently on |
| 64b2ff0 | 19 | /// screen - scoped there (rather than a bare find.text(label)) because plain |
| 64b2ff0 | 20 | /// text like a chapter number ("1", "2") can otherwise collide with the |
| 64b2ff0 | 21 | /// current-chapter number shown in the app bar behind the sheet. |
| 64b2ff0 | 22 | Future<void> tapSheetTile(WidgetTester tester, String label) async { |
| 64b2ff0 | 23 | final sheet = find.byType(BookSelectSheet); |
| 64b2ff0 | 24 | await tester.tap(find.descendant(of: sheet, matching: find.text(label))); |
| 64b2ff0 | 25 | await tester.pumpAndSettle(); |
| 64b2ff0 | 26 | } |
| 64b2ff0 | 27 | |
| 64b2ff0 | 28 | bool _isSelectedTile(Widget w, String label) => |
| 64b2ff0 | 29 | w is BookTile && w.label == label && w.isSelected; |
| 64b2ff0 | 30 | |
| 64b2ff0 | 31 | void main() { |
| 64b2ff0 | 32 | TestWidgetsFlutterBinding.ensureInitialized(); |
| 64b2ff0 | 33 | |
| 64b2ff0 | 34 | testWidgets( |
| 64b2ff0 | 35 | "navigating via the book+chapter selector updates the displayed chapter, and " |
| 64b2ff0 | 36 | "reopening the selector highlights that new book/chapter - not the one " |
| 64b2ff0 | 37 | "navigated away from (regression: App._syncCurrentChapter used to clobber " |
| 64b2ff0 | 38 | "savedBook/savedChapter right after the push, so the selector kept " |
| 64b2ff0 | 39 | "highlighting the pre-navigation position)", |
| 64b2ff0 | 40 | (tester) async { |
| 64b2ff0 | 41 | final bible = buildTestBible(); |
| 64b2ff0 | 42 | final store = Store<AppState>( |
| 64b2ff0 | 43 | initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0), |
| 64b2ff0 | 44 | ); |
| 64b2ff0 | 45 | await tester.pumpWidget( |
| 64b2ff0 | 46 | App(globalNavigatorKey: GlobalKey<NavigatorState>(), store: store), |
| 64b2ff0 | 47 | ); |
| 64b2ff0 | 48 | await tester.pumpAndSettle(); |
| 64b2ff0 | 49 | |
| 64b2ff0 | 50 | // Sanity check: Genesis 1 is on screen to start with. Verse text |
| 64b2ff0 | 51 | // renders as rich text with a "1 " verse-number span prefixed, so this |
| 64b2ff0 | 52 | // is a substring match rather than find.text's exact match. |
| 64b2ff0 | 53 | expect(find.textContaining("In the beginning..."), findsOneWidget); |
| 64b2ff0 | 54 | |
| 64b2ff0 | 55 | // Open the book selector via the app bar's book title. |
| 64b2ff0 | 56 | await tester.tap(find.byKey(const Key("bookTitle"))); |
| 64b2ff0 | 57 | await tester.pumpAndSettle(); |
| 64b2ff0 | 58 | |
| 64b2ff0 | 59 | // Pick Revelation (a multi-chapter book, so this animates to the |
| 64b2ff0 | 60 | // chapter grid rather than closing immediately). |
| 64b2ff0 | 61 | await tapSheetTile(tester, "Rev"); |
| 64b2ff0 | 62 | |
| 64b2ff0 | 63 | // Pick chapter 2. |
| 64b2ff0 | 64 | await tapSheetTile(tester, "2"); |
| 64b2ff0 | 65 | |
| 64b2ff0 | 66 | // The displayed chapter should now be Revelation 2. |
| 64b2ff0 | 67 | expect(find.textContaining("Rev 2:1"), findsOneWidget); |
| 64b2ff0 | 68 | expect(store.state.savedBook, 2); |
| 64b2ff0 | 69 | expect(store.state.savedChapter, 1); |
| 64b2ff0 | 70 | |
| 64b2ff0 | 71 | // Reopen the book selector - this is the exact regression. |
| 64b2ff0 | 72 | await tester.tap(find.byKey(const Key("bookTitle"))); |
| 64b2ff0 | 73 | await tester.pumpAndSettle(); |
| 64b2ff0 | 74 | |
| 64b2ff0 | 75 | final sheet = find.byType(BookSelectSheet); |
| 64b2ff0 | 76 | expect( |
| 64b2ff0 | 77 | find.descendant( |
| 64b2ff0 | 78 | of: sheet, |
| 64b2ff0 | 79 | matching: find.byWidgetPredicate((w) => _isSelectedTile(w, "Rev")), |
| 64b2ff0 | 80 | ), |
| 64b2ff0 | 81 | findsOneWidget, |
| 64b2ff0 | 82 | ); |
| 64b2ff0 | 83 | expect( |
| 64b2ff0 | 84 | find.descendant( |
| 64b2ff0 | 85 | of: sheet, |
| 64b2ff0 | 86 | matching: find.byWidgetPredicate((w) => _isSelectedTile(w, "Gen")), |
| 64b2ff0 | 87 | ), |
| 64b2ff0 | 88 | findsNothing, |
| 64b2ff0 | 89 | ); |
| 64b2ff0 | 90 | }, |
| 64b2ff0 | 91 | ); |
| 64b2ff0 | 92 | |
| 64b2ff0 | 93 | testWidgets( |
| 64b2ff0 | 94 | "swiping to the next chapter after using the picker keeps the app bar's chapter number in sync", |
| 64b2ff0 | 95 | (tester) async { |
| 64b2ff0 | 96 | final bible = buildTestBible(); |
| 64b2ff0 | 97 | final store = Store<AppState>( |
| 64b2ff0 | 98 | initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0), |
| 64b2ff0 | 99 | ); |
| 64b2ff0 | 100 | await tester.pumpWidget( |
| 64b2ff0 | 101 | App(globalNavigatorKey: GlobalKey<NavigatorState>(), store: store), |
| 64b2ff0 | 102 | ); |
| 64b2ff0 | 103 | await tester.pumpAndSettle(); |
| 64b2ff0 | 104 | |
| 64b2ff0 | 105 | // Jump to Revelation 1 via the picker (push). |
| 64b2ff0 | 106 | await tester.tap(find.byKey(const Key("bookTitle"))); |
| 64b2ff0 | 107 | await tester.pumpAndSettle(); |
| 64b2ff0 | 108 | await tapSheetTile(tester, "Rev"); |
| 64b2ff0 | 109 | await tapSheetTile(tester, "1"); |
| 64b2ff0 | 110 | expect(find.textContaining("Rev 1:1"), findsOneWidget); |
| 64b2ff0 | 111 | |
| 64b2ff0 | 112 | // Swipe left to advance to chapter 2 (pushReplacement on top of the |
| 64b2ff0 | 113 | // still-2-entry stack the picker's push left behind). |
| 64b2ff0 | 114 | await tester.fling( |
| 64b2ff0 | 115 | find.byType(SingleChildScrollView), |
| 64b2ff0 | 116 | const Offset(-400, 0), |
| 64b2ff0 | 117 | 800, |
| 64b2ff0 | 118 | ); |
| 64b2ff0 | 119 | await tester.pumpAndSettle(); |
| 64b2ff0 | 120 | |
| 64b2ff0 | 121 | expect(find.textContaining("Rev 2:1"), findsOneWidget); |
| 64b2ff0 | 122 | expect(store.state.savedBook, 2); |
| 64b2ff0 | 123 | expect(store.state.savedChapter, 1); |
| 64b2ff0 | 124 | // The app bar's chapter number (built from the same live state) must |
| 64b2ff0 | 125 | // agree with what's actually on screen. |
| 64b2ff0 | 126 | expect( |
| 64b2ff0 | 127 | find.descendant( |
| 64b2ff0 | 128 | of: find.byKey(const Key("chapterTitle")), |
| 64b2ff0 | 129 | matching: find.text("2"), |
| 64b2ff0 | 130 | ), |
| 64b2ff0 | 131 | findsOneWidget, |
| 64b2ff0 | 132 | ); |
| 64b2ff0 | 133 | }, |
| 64b2ff0 | 134 | ); |
| 64b2ff0 | 135 | } |