import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter_test/flutter_test.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdf_engine.dart'; import 'package:halkyon_learn_flutter/src/feature/pdf_source/data/pdfrx_engine.dart'; import 'package:integration_test/integration_test.dart'; import 'package:material_ui/material_ui.dart'; // The app's PDF engine for real, PDFium included, which `flutter test` // can't load: on a device or the desktop, // // flutter test integration_test/pdf_engine_test.dart -d linux // // on a PDF of three pages, "Hello page N" on each, with an outline of two // chapters. void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(PdfrxEngine.initialize); testWidgets('opens a PDF, reads its chapters and draws a region', ( tester, ) async { final document = await tester.runAsync( () => PdfrxEngine().open(_threePages()), ); addTearDown(() => tester.runAsync(document!.dispose)); expect(document!.pageCount, 3); expect(await tester.runAsync(document.chapters), [ (title: 'One', page: 1), (title: 'Two', page: 3), ]); final png = await tester.runAsync( () => document.renderRegion(1, const Rect.fromLTWH(0, 0, 0.5, 0.5)), ); expect(png!.sublist(0, 4), [0x89, 0x50, 0x4e, 0x47]); }); testWidgets('refuses a file that is no PDF', (tester) async { // runAsync reports what is thrown inside instead of passing it on. final refused = await tester.runAsync(() async { try { await PdfrxEngine().open(Uint8List.fromList(utf8.encode('text'))); return false; } on FormatException { return true; } }); expect(refused, isTrue); }); testWidgets('its viewer turns pages and finds text', (tester) async { final document = await tester.runAsync( () => PdfrxEngine().open(_threePages()), ); final controller = PdfReaderController(); addTearDown(controller.dispose); await tester.pumpWidget( MaterialApp( home: Scaffold( body: SizedBox( width: 600, height: 800, child: document!.viewer(controller), ), ), ), ); Future until(bool Function() test) async { final waited = Stopwatch()..start(); while (!test() && waited.elapsed < const Duration(seconds: 20)) { await tester.pump(const Duration(milliseconds: 100)); } } await until(() => controller.isReady); expect(controller.isReady, isTrue); // The page turns over an animation, whose frames the test drives: it // is started, then pumped until it arrives. unawaited(controller.goToPage(3)); await until(() => controller.page == 3); expect(controller.page, 3); controller.search('hello'); await until(() => controller.matchCount == 3); expect(controller.matchCount, 3); expect(controller.pageRect(3), isNotNull); }); } /// A PDF of three pages, each saying "Hello page N", with an outline of /// chapter "One" at page 1 and "Two" at page 3. Uint8List _threePages() { String text(int page) { final stream = 'BT /F1 24 Tf 40 700 Td (Hello page $page) Tj ET'; return '<< /Length ${stream.length} >>\nstream\n$stream\nendstream'; } String pageOf(int page) => '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] ' '/Contents ${5 + page} 0 R /Resources << /Font << /F1 9 0 R >> >> >>'; final objects = [ '<< /Type /Catalog /Pages 2 0 R /Outlines 10 0 R >>', '<< /Type /Pages /Kids [3 0 R 4 0 R 5 0 R] /Count 3 >>', for (final page in [1, 2, 3]) pageOf(page), for (final page in [1, 2, 3]) text(page), '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>', '<< /Type /Outlines /First 11 0 R /Last 12 0 R /Count 2 >>', '<< /Title (One) /Parent 10 0 R /Next 12 0 R /Dest [3 0 R /Fit] >>', '<< /Title (Two) /Parent 10 0 R /Prev 11 0 R /Dest [5 0 R /Fit] >>', ]; final out = StringBuffer('%PDF-1.7\n'); final offsets = []; for (final (index, object) in objects.indexed) { offsets.add(out.length); out.write('${index + 1} 0 obj\n$object\nendobj\n'); } final xref = out.length; out ..write('xref\n0 ${objects.length + 1}\n0000000000 65535 f \n') ..writeAll([ for (final offset in offsets) '${offset.toString().padLeft(10, '0')} 00000 n \n', ]) ..write( 'trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\n' 'startxref\n$xref\n%%EOF\n', ); return Uint8List.fromList(latin1.encode(out.toString())); }