135 lines
4.5 KiB
Dart
135 lines
4.5 KiB
Dart
|
|
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<void> 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 = <int>[];
|
||
|
|
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()));
|
||
|
|
}
|