import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ocideck/models/slide.dart'; import 'package:ocideck/state/deck_provider.dart'; import 'package:ocideck/state/editor_provider.dart'; import 'package:ocideck/theme/app_theme.dart'; import 'package:ocideck/widgets/panels/slide_list_panel.dart'; import 'package:ocideck/widgets/slides/slide_thumbnail.dart'; void main() { testWidgets('resizing the rail brings the edited slide back into view', ( tester, ) async { final container = ProviderContainer(); addTearDown(container.dispose); final deckNotifier = container.read(deckProvider.notifier); deckNotifier.newDeck('Test'); for (var i = 0; i < 19; i++) { deckNotifier.addSlide(SlideType.bullets); } container.read(editorProvider.notifier).select(12); final width = ValueNotifier(320); addTearDown(width.dispose); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: MaterialApp( theme: AppTheme.light, home: Scaffold( body: Align( alignment: Alignment.topLeft, child: ValueListenableBuilder( valueListenable: width, builder: (_, w, _) => SizedBox( width: w, height: 600, child: SlideListPanel(railWidth: w), ), ), ), ), ), ), ); await tester.pump(); // The selected slide (12) sits far below the fold and nothing scrolls it // into view on its own. bool slide12Visible() => find .byWidgetPredicate((w) => w is SlideThumbnail && w.index == 12) .evaluate() .isNotEmpty; expect(slide12Visible(), isFalse); // Drag the rail wider: thumbnails change height, and once the resize // settles the list scrolls the slide being edited back to the top. width.value = 240; await tester.pump(); await tester.pump(const Duration(milliseconds: 250)); // debounce fires await tester.pump(); // coarse jump near the unbuilt slide await tester.pump(); // precise reveal starts await tester.pump(const Duration(milliseconds: 200)); // animateTo settles expect(slide12Visible(), isTrue); final rect = tester.getRect( find.byWidgetPredicate((w) => w is SlideThumbnail && w.index == 12), ); // At the top of the list area (below the panel header). expect(rect.top, lessThan(120)); }); testWidgets('thumbnails expose one concise semantic label per slide', ( tester, ) async { final handle = tester.ensureSemantics(); final container = ProviderContainer(); addTearDown(container.dispose); final deckNotifier = container.read(deckProvider.notifier); deckNotifier.newDeck('Test'); deckNotifier.addSlide(SlideType.bullets); await tester.pumpWidget( UncontrolledProviderScope( container: container, child: MaterialApp( theme: AppTheme.light, home: const Scaffold( body: SizedBox(width: 320, child: SlideListPanel(railWidth: 320)), ), ), ), ); await tester.pump(); // Screen readers get "Slide n/m: title-or-type" per card, instead of the // full content of every mini preview. expect(find.bySemanticsLabel(RegExp(r'^Slide 1/2: ')), findsOneWidget); expect(find.bySemanticsLabel(RegExp(r'^Slide 2/2: ')), findsOneWidget); handle.dispose(); }); }