Flutter desktop app for building Marp presentations via structured slide editors, with live preview, fullscreen presenter, and PDF/PPTX export. Includes Makefile quality gate, CI workflow, and full test suite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
3 KiB
Dart
100 lines
3 KiB
Dart
import 'dart:io';
|
|
import 'dart:ui' as ui;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:ocideck/models/slide.dart';
|
|
import 'package:ocideck/widgets/slides/slide_preview.dart';
|
|
|
|
Future<File> _writeRedPng(String dir) async {
|
|
final recorder = ui.PictureRecorder();
|
|
final canvas = Canvas(recorder);
|
|
canvas.drawRect(
|
|
const Rect.fromLTWH(0, 0, 8, 8),
|
|
Paint()..color = const Color(0xFFFF0000),
|
|
);
|
|
final picture = recorder.endRecording();
|
|
final img = await picture.toImage(8, 8);
|
|
final data = await img.toByteData(format: ui.ImageByteFormat.png);
|
|
final file = File('$dir/red.png');
|
|
file.writeAsBytesSync(data!.buffer.asUint8List());
|
|
return file;
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('bulletsImage paints the image on the right panel', (
|
|
tester,
|
|
) async {
|
|
await tester.runAsync(() async {
|
|
final dir = Directory.systemTemp.createTempSync('ocideck_test');
|
|
final redPng = await _writeRedPng(dir.path);
|
|
|
|
final slide = Slide(
|
|
id: 'x',
|
|
type: SlideType.bulletsImage,
|
|
title: 'Een titel',
|
|
bullets: const [
|
|
'Eerste bullet met wat tekst',
|
|
'Tweede bullet',
|
|
'Derde bullet met veel tekst zodat de hoogte flink wordt gevuld',
|
|
'Vierde bullet',
|
|
'Vijfde bullet',
|
|
],
|
|
imagePath: redPng.path,
|
|
imageSize: 40,
|
|
);
|
|
|
|
final key = GlobalKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: RepaintBoundary(
|
|
key: key,
|
|
child: SizedBox(
|
|
width: 800,
|
|
height: 450,
|
|
child: SlidePreviewWidget(slide: slide),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Let the file image decode and paint.
|
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
|
await tester.pump();
|
|
|
|
final boundary =
|
|
key.currentContext!.findRenderObject() as RenderRepaintBoundary;
|
|
final image = await boundary.toImage(pixelRatio: 1.0);
|
|
final bytes = (await image.toByteData(
|
|
format: ui.ImageByteFormat.rawRgba,
|
|
))!.buffer.asUint8List();
|
|
|
|
({int r, int g, int b}) pixelAt(int x, int y) {
|
|
final i = (y * image.width + x) * 4;
|
|
return (r: bytes[i], g: bytes[i + 1], b: bytes[i + 2]);
|
|
}
|
|
|
|
// Right 40% panel must be the (red) image; left panel the white text bg.
|
|
final right = pixelAt((image.width * 0.95).round(), image.height ~/ 2);
|
|
final left = pixelAt((image.width * 0.05).round(), image.height ~/ 2);
|
|
|
|
expect(
|
|
right.r > 200 && right.g < 80 && right.b < 80,
|
|
isTrue,
|
|
reason: 'Image panel should be red but was $right',
|
|
);
|
|
expect(
|
|
left.r,
|
|
greaterThan(200),
|
|
reason: 'Text panel background should be white',
|
|
);
|
|
|
|
dir.deleteSync(recursive: true);
|
|
});
|
|
});
|
|
}
|