2026-06-08 21:48:06 +02:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
|
import 'package:ocideck/models/slide.dart';
|
2026-06-08 22:30:46 +02:00
|
|
|
import 'package:ocideck/widgets/slides/inline_markdown.dart';
|
2026-06-08 21:48:06 +02:00
|
|
|
import 'package:ocideck/widgets/slides/slide_preview.dart';
|
|
|
|
|
|
|
|
|
|
Widget _host(Slide slide) {
|
|
|
|
|
return MaterialApp(
|
|
|
|
|
home: Scaffold(
|
|
|
|
|
body: Center(
|
|
|
|
|
child: SizedBox(
|
|
|
|
|
width: 800,
|
|
|
|
|
height: 450,
|
|
|
|
|
child: SlidePreviewWidget(slide: slide),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
testWidgets('two-bullet columns render their optional headings', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
final slide = Slide.create(SlideType.twoBullets).copyWith(
|
|
|
|
|
title: 'Vergelijking',
|
|
|
|
|
columnTitle1: 'Voordelen',
|
|
|
|
|
columnTitle2: 'Nadelen',
|
|
|
|
|
bullets: const ['Snel', 'Goedkoop'],
|
|
|
|
|
bullets2: const ['Complex'],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await tester.pumpWidget(_host(slide));
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
|
|
|
|
expect(find.text('Voordelen'), findsOneWidget);
|
|
|
|
|
expect(find.text('Nadelen'), findsOneWidget);
|
|
|
|
|
// Heading sits above the bullets of its column.
|
|
|
|
|
final headingTop = tester.getTopLeft(find.text('Voordelen')).dy;
|
|
|
|
|
final bulletTop = tester.getTopLeft(find.text('Snel')).dy;
|
|
|
|
|
expect(headingTop, lessThan(bulletTop));
|
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
testWidgets('two-bullet columns without headings render no heading text', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
final slide = Slide.create(SlideType.twoBullets).copyWith(
|
|
|
|
|
title: 'Geen koppen',
|
|
|
|
|
bullets: const ['Links'],
|
|
|
|
|
bullets2: const ['Rechts'],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await tester.pumpWidget(_host(slide));
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
|
|
|
|
expect(find.text('Links'), findsOneWidget);
|
|
|
|
|
expect(find.text('Rechts'), findsOneWidget);
|
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-09 13:28:23 +02:00
|
|
|
testWidgets('two-bullet columns use the same font size', (tester) async {
|
2026-06-08 22:30:46 +02:00
|
|
|
final slide = Slide.create(SlideType.twoBullets).copyWith(
|
|
|
|
|
bullets: const ['Solo'],
|
|
|
|
|
bullets2: List.generate(14, (i) => 'Item $i'),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await tester.pumpWidget(_host(slide));
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
|
|
|
|
double sizeOf(String text) => tester
|
|
|
|
|
.widget<InlineMarkdownText>(
|
|
|
|
|
find.byWidgetPredicate(
|
|
|
|
|
(x) => x is InlineMarkdownText && x.text == text,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.style
|
|
|
|
|
.fontSize!;
|
|
|
|
|
|
2026-06-09 13:28:23 +02:00
|
|
|
expect(sizeOf('Solo'), sizeOf('Item 0'));
|
2026-06-08 22:30:46 +02:00
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
testWidgets('short bullet lists grow but respect the body-text maximum', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
final slide = Slide.create(
|
|
|
|
|
SlideType.bullets,
|
|
|
|
|
).copyWith(title: 'Kort', bullets: const ['Eén punt', 'Twee punten']);
|
|
|
|
|
|
|
|
|
|
await tester.pumpWidget(_host(slide));
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
|
|
|
|
final size = tester
|
|
|
|
|
.widget<InlineMarkdownText>(
|
|
|
|
|
find.byWidgetPredicate(
|
|
|
|
|
(x) => x is InlineMarkdownText && x.text == 'Eén punt',
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.style
|
|
|
|
|
.fontSize!;
|
|
|
|
|
// Auto-fit still grows the text beyond its design size (w * 0.026)…
|
|
|
|
|
expect(size, greaterThan(800 * 0.026));
|
|
|
|
|
// …but never past ≈32pt-on-a-16:9-deck (w * 0.0335), so the body text
|
|
|
|
|
// keeps a clear distance from the title.
|
|
|
|
|
expect(size, lessThanOrEqualTo(800 * 0.0335 + 0.1));
|
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-08 21:48:06 +02:00
|
|
|
testWidgets('bullets slide renders an optional subheading below the title', (
|
|
|
|
|
tester,
|
|
|
|
|
) async {
|
|
|
|
|
final slide = Slide.create(SlideType.bullets).copyWith(
|
|
|
|
|
title: 'Agenda',
|
|
|
|
|
subtitle: 'Vandaag',
|
|
|
|
|
bullets: const ['Punt een', 'Punt twee'],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await tester.pumpWidget(_host(slide));
|
|
|
|
|
await tester.pump();
|
|
|
|
|
|
|
|
|
|
expect(find.text('Agenda'), findsOneWidget);
|
|
|
|
|
expect(find.text('Vandaag'), findsOneWidget);
|
|
|
|
|
final titleTop = tester.getTopLeft(find.text('Agenda')).dy;
|
|
|
|
|
final subTop = tester.getTopLeft(find.text('Vandaag')).dy;
|
|
|
|
|
final bulletTop = tester.getTopLeft(find.text('Punt een')).dy;
|
|
|
|
|
expect(titleTop, lessThan(subTop));
|
|
|
|
|
expect(subTop, lessThan(bulletTop));
|
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
|
});
|
|
|
|
|
}
|