only-bible-app v3.7.2+24

#kotlin#android#ios

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
// Integration-style tests: these mount the real App widget and drive it
// through actual taps (find + tap real keys/text), rather than dispatching
// actions programmatically against a bare store/router. That distinction
// matters: an earlier bug (App._syncCurrentChapter clobbering
// savedBook/savedChapter right after a push()) went unnoticed by unit tests
// that dispatched actions directly, because they never mounted the widget
// tree that wires the router's listener up in the first place. Driving the
// same flows through the UI is what actually would have caught it.
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "package:async_redux/async_redux.dart";
import "package:only_bible_app/app.dart";
import "package:only_bible_app/store/app_state.dart";
import "package:only_bible_app/widgets/book_select_sheet.dart";
import "package:only_bible_app/widgets/book_tile.dart";
import "app_logic_test.dart" show buildTestBible;

/// Taps the [label] tile inside whichever [BookSelectSheet] is currently on
/// screen - scoped there (rather than a bare find.text(label)) because plain
/// text like a chapter number ("1", "2") can otherwise collide with the
/// current-chapter number shown in the app bar behind the sheet.
Future<void> tapSheetTile(WidgetTester tester, String label) async {
  final sheet = find.byType(BookSelectSheet);
  await tester.tap(find.descendant(of: sheet, matching: find.text(label)));
  await tester.pumpAndSettle();
}

bool _isSelectedTile(Widget w, String label) =>
    w is BookTile && w.label == label && w.isSelected;

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  testWidgets(
    "navigating via the book+chapter selector updates the displayed chapter, and "
    "reopening the selector highlights that new book/chapter - not the one "
    "navigated away from (regression: App._syncCurrentChapter used to clobber "
    "savedBook/savedChapter right after the push, so the selector kept "
    "highlighting the pre-navigation position)",
    (tester) async {
      final bible = buildTestBible();
      final store = Store<AppState>(
        initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
      );
      await tester.pumpWidget(
        App(globalNavigatorKey: GlobalKey<NavigatorState>(), store: store),
      );
      await tester.pumpAndSettle();

      // Sanity check: Genesis 1 is on screen to start with. Verse text
      // renders as rich text with a "1 " verse-number span prefixed, so this
      // is a substring match rather than find.text's exact match.
      expect(find.textContaining("In the beginning..."), findsOneWidget);

      // Open the book selector via the app bar's book title.
      await tester.tap(find.byKey(const Key("bookTitle")));
      await tester.pumpAndSettle();

      // Pick Revelation (a multi-chapter book, so this animates to the
      // chapter grid rather than closing immediately).
      await tapSheetTile(tester, "Rev");

      // Pick chapter 2.
      await tapSheetTile(tester, "2");

      // The displayed chapter should now be Revelation 2.
      expect(find.textContaining("Rev 2:1"), findsOneWidget);
      expect(store.state.savedBook, 2);
      expect(store.state.savedChapter, 1);

      // Reopen the book selector - this is the exact regression.
      await tester.tap(find.byKey(const Key("bookTitle")));
      await tester.pumpAndSettle();

      final sheet = find.byType(BookSelectSheet);
      expect(
        find.descendant(
          of: sheet,
          matching: find.byWidgetPredicate((w) => _isSelectedTile(w, "Rev")),
        ),
        findsOneWidget,
      );
      expect(
        find.descendant(
          of: sheet,
          matching: find.byWidgetPredicate((w) => _isSelectedTile(w, "Gen")),
        ),
        findsNothing,
      );
    },
  );

  testWidgets(
    "swiping to the next chapter after using the picker keeps the app bar's chapter number in sync",
    (tester) async {
      final bible = buildTestBible();
      final store = Store<AppState>(
        initialState: AppState(bible: bible, savedBook: 0, savedChapter: 0),
      );
      await tester.pumpWidget(
        App(globalNavigatorKey: GlobalKey<NavigatorState>(), store: store),
      );
      await tester.pumpAndSettle();

      // Jump to Revelation 1 via the picker (push).
      await tester.tap(find.byKey(const Key("bookTitle")));
      await tester.pumpAndSettle();
      await tapSheetTile(tester, "Rev");
      await tapSheetTile(tester, "1");
      expect(find.textContaining("Rev 1:1"), findsOneWidget);

      // Swipe left to advance to chapter 2 (pushReplacement on top of the
      // still-2-entry stack the picker's push left behind).
      await tester.fling(
        find.byType(SingleChildScrollView),
        const Offset(-400, 0),
        800,
      );
      await tester.pumpAndSettle();

      expect(find.textContaining("Rev 2:1"), findsOneWidget);
      expect(store.state.savedBook, 2);
      expect(store.state.savedChapter, 1);
      // The app bar's chapter number (built from the same live state) must
      // agree with what's actually on screen.
      expect(
        find.descendant(
          of: find.byKey(const Key("chapterTitle")),
          matching: find.text("2"),
        ),
        findsOneWidget,
      );
    },
  );
}