Ocideck/test/slide_list_panel_test.dart
Brenno de Winter 68725341a7 Add image-library dedupe and untagged filter, UI text scaling, table paste
Image library:
- "Clean up duplicates" finds byte-identical images by md5, keeps one
  file per group (preferring the most-used, then the oldest), merges
  the tags/descriptions and captions of the copies, repoints slides in
  open decks and in .md presentations on disk, and deletes the copies
  after a confirmation that lists every group.
- A header toggle filters to images without tags/description, so it is
  easy to see which ones still need attention.
- The delete warning now also lists presentations on disk that still
  reference the image (marked "not open"), next to the open decks.

Editor and accessibility (already in tree):
- Interface text scaling up to 200%, keyboard-operable panel divider,
  keyboard-first add-slide dialog, and screen-reader improvements.
- Paste a spreadsheet/CSV/markdown selection into a table cell to fill
  the whole grid.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:36:44 +02:00

103 lines
3.5 KiB
Dart

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<double>(320);
addTearDown(width.dispose);
await tester.pumpWidget(
UncontrolledProviderScope(
container: container,
child: MaterialApp(
theme: AppTheme.light,
home: Scaffold(
body: Align(
alignment: Alignment.topLeft,
child: ValueListenableBuilder<double>(
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();
});
}