135 lines
5.7 KiB
Dart
135 lines
5.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:image/image.dart' as img;
|
|
import 'package:integration_test/integration_test.dart';
|
|
import 'package:jj_mkvmaker/core/hwp_source.dart';
|
|
import 'package:jj_mkvmaker/core/reader_sources.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:pdfrx/pdfrx.dart';
|
|
|
|
import 'support/temp_dir.dart';
|
|
|
|
/// 쪽마다 그림 한 장인 진짜 PDF 를 만드는 가짜 엔진 (rhwp 대신) - 부른 범위를 적어 둔다
|
|
class _FakeEngine implements HwpPdfEngine {
|
|
final int pages;
|
|
final String tempDir;
|
|
final calls = <(int, int)>[];
|
|
var failNext = 0;
|
|
Completer<void>? hold;
|
|
_FakeEngine(this.pages, this.tempDir);
|
|
|
|
@override
|
|
Future<HwpPdfDoc> open(String path, {String? password}) async {
|
|
if (password == 'bad') throw const HwpPdfException('암호가 틀림');
|
|
return _FakeDoc(this);
|
|
}
|
|
}
|
|
|
|
class _FakeDoc implements HwpPdfDoc {
|
|
final _FakeEngine e;
|
|
var closed = false;
|
|
_FakeDoc(this.e);
|
|
@override
|
|
int get pageCount => e.pages;
|
|
|
|
@override
|
|
Future<void> exportPdf(int from, int to, String out, {List<String> fontDirs = const []}) async {
|
|
e.calls.add((from, to));
|
|
// 끝나지 않는 변환 (시간 제한 시험): 풀려도 아무것도 쓰지 않는다 (시험이 끝난 뒤 폴더에 쓰지 않게)
|
|
if (e.hold != null) {
|
|
await e.hold!.future;
|
|
throw const HwpPdfException('늦게 끝남');
|
|
}
|
|
if (e.failNext > 0) {
|
|
e.failNext--;
|
|
throw const HwpPdfException('렌더 실패');
|
|
}
|
|
final docs = <PdfDocument>[];
|
|
for (var i = from; i < to; i++) {
|
|
final png = p.join(e.tempDir, 'pg$i.png');
|
|
// 쪽마다 폭이 달라 (100 + 쪽 번호) 어느 쪽이 그려졌는지 크기로 안다
|
|
File(png).writeAsBytesSync(img.encodePng(img.Image(width: 100 + i, height: 140)));
|
|
docs.add(await imagePageDocument(png, tempDir: e.tempDir, sourceName: 'jj_fake_$i'));
|
|
}
|
|
final outDoc = await PdfDocument.createNew(sourceName: 'jj_fake_out_$from');
|
|
outDoc.pages = [for (final d in docs) d.pages.first];
|
|
File(out).writeAsBytesSync(await outDoc.encodePdf());
|
|
await outDoc.dispose();
|
|
for (final d in docs) {
|
|
await d.dispose();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> close() async => closed = true;
|
|
}
|
|
|
|
/// 관리자 지시 (10/20): HWP · HWPX 보기 - 보는 쪽의 묶음만 PDF 로 바꾸고 다음 묶음을 미리 만든다 · 시간 제한 · 실패하면 다시 · 닫기.
|
|
/// (rhwp 대신 가짜 엔진, PDF 를 그리는 PDFium 이 필요해 Windows 통합 시험)
|
|
void main() {
|
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
testWidgets('보는 쪽의 묶음 (5쪽) 만 바꾸고 다음 묶음을 미리 · 끝 묶음은 남은 쪽만', (tester) async {
|
|
final dir = testTempDir('jj_hwpsrc_');
|
|
final doc = File(p.join(dir.path, 'a.hwp'))..writeAsStringSync('x');
|
|
final engine = _FakeEngine(12, dir.path);
|
|
final src = await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, chunk: 5));
|
|
expect(src!.length, 12);
|
|
expect(src.pageName(0), '1 / 12');
|
|
final first = await tester.runAsync(() => src.load(0, maxWidth: 200));
|
|
expect(first!.image, isNotNull);
|
|
first.dispose();
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(seconds: 2)));
|
|
expect(engine.calls, [(0, 5), (5, 10)], reason: '첫 묶음 + 다음 묶음 미리');
|
|
// 끝 쪽: 남은 2쪽만 (11번째 = 폭 110)
|
|
final last = await tester.runAsync(() => src.load(11, maxWidth: 200));
|
|
expect(last!.image, isNotNull);
|
|
last.dispose();
|
|
expect(engine.calls.last, (10, 12));
|
|
// 이미 만든 묶음은 다시 만들지 않는다
|
|
final again = await tester.runAsync(() => src.load(3, maxWidth: 200));
|
|
again!.dispose();
|
|
expect(engine.calls.where((c) => c == (0, 5)), hasLength(1));
|
|
await tester.runAsync(src.dispose);
|
|
expect(Directory(src.work.path).existsSync(), isFalse, reason: '닫으면 임시 PDF 를 지움');
|
|
});
|
|
|
|
testWidgets('묶음이 실패하면 그 쪽에 이유, 다시 보면 다시 만든다 · 시간 제한 · 열기 실패', (tester) async {
|
|
final dir = testTempDir('jj_hwpsrc_');
|
|
final doc = File(p.join(dir.path, 'b.hwpx'))..writeAsStringSync('x');
|
|
final engine = _FakeEngine(3, dir.path)..failNext = 1;
|
|
final src = await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, chunk: 5,
|
|
timeout: const Duration(seconds: 2)));
|
|
Object? err;
|
|
await tester.runAsync(() => src!.load(0, maxWidth: 200).then((r) => r.dispose(), onError: (Object e) {
|
|
err = e;
|
|
}));
|
|
expect('$err', contains('렌더 실패'));
|
|
final ok = await tester.runAsync(() => src!.load(0, maxWidth: 200));
|
|
expect(ok!.image, isNotNull, reason: '실패한 묶음은 다시');
|
|
ok.dispose();
|
|
await tester.runAsync(src!.dispose);
|
|
|
|
// 시간 제한: 끝나지 않는 변환
|
|
final slow = _FakeEngine(3, dir.path)..hold = Completer<void>();
|
|
final s2 = await tester.runAsync(() => HwpSource.open(doc.path, engine: slow, tempDir: dir.path, timeout: const Duration(seconds: 1)));
|
|
Object? err2;
|
|
await tester.runAsync(() => s2!.load(1, maxWidth: 200).then((r) => r.dispose(), onError: (Object e) {
|
|
err2 = e;
|
|
}));
|
|
expect(err2, isA<HwpPdfException>());
|
|
expect('$err2', contains('1~3'));
|
|
slow.hold!.complete();
|
|
await tester.runAsync(s2!.dispose);
|
|
|
|
// 열기 실패 (암호 등) 는 그대로 알린다 - 파일 탐색기는 다른 앱으로 연다
|
|
Object? err3;
|
|
await tester.runAsync(() => HwpSource.open(doc.path, engine: engine, tempDir: dir.path, password: 'bad').then((_) {}, onError: (Object e) {
|
|
err3 = e;
|
|
}));
|
|
expect('$err3', contains('암호'));
|
|
});
|
|
}
|