JJ_MKVMaker/test/reader_test.dart

115 lines
5.1 KiB
Dart

import 'dart:io';
import 'dart:typed_data';
import 'package:archive/archive_io.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/settings.dart';
import 'package:jj_mkvmaker/core/reader_sources.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/reader_page.dart';
import 'package:path/path.dart' as p;
Uint8List _png(int r, int g, int b, {int w = 40, int h = 60}) {
final im = img.Image(width: w, height: h);
img.fill(im, color: img.ColorRgb8(r, g, b));
return Uint8List.fromList(img.encodePng(im));
}
void main() {
late Directory tmp;
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_reader_'));
tearDown(() => tmp.deleteSync(recursive: true));
test('ZIP: 목록 · 그림만 자연 순서 · 골라서 / 모두 풀기 (바깥 경로 막기)', () async {
final zip = p.join(tmp.path, 'comic.zip');
final a = Archive()
..add(ArchiveFile.bytes('ch1/10.png', _png(1, 2, 3)))
..add(ArchiveFile.bytes('ch1/2.png', _png(4, 5, 6)))
..add(ArchiveFile.bytes('ch1/1.jpg', img.encodeJpg(img.Image(width: 8, height: 8))))
..add(ArchiveFile.bytes('readme.txt', 'hi'.codeUnits))
..add(ArchiveFile.bytes('../evil.txt', 'x'.codeUnits));
File(zip).writeAsBytesSync(ZipEncoder().encode(a));
final entries = await zipEntries(zip, tempDir: tmp.path);
expect(entries.where((f) => f.isFile).length, 5);
final src = await ZipImagesSource.open(zip, tempDir: tmp.path, imageExts: defaultImageExtensions);
expect([for (var i = 0; i < src.length; i++) src.pageName(i)], ['1.jpg', '2.png', '10.png']);
final page = await src.load(1, maxWidth: 800);
expect(page.provider, isNotNull);
await src.dispose();
final out1 = p.join(tmp.path, 'out1');
expect(await extractZip(zip, out1, tempDir: tmp.path, names: ['readme.txt']), 1);
expect(File(p.join(out1, 'readme.txt')).readAsStringSync(), 'hi');
final out2 = p.join(tmp.path, 'out2');
expect(await extractZip(zip, out2, tempDir: tmp.path, names: ['ch1']), 3); // 폴더째
expect(File(p.join(out2, 'ch1', '10.png')).existsSync(), isTrue);
final out3 = p.join(tmp.path, 'out3');
await extractZip(zip, out3, tempDir: tmp.path);
expect(File(p.join(out3, 'evil.txt')).existsSync(), isTrue); // ../ 는 떼고 안에
expect(File(p.join(tmp.path, 'evil.txt')).existsSync(), isFalse);
});
test('보기 설정 저장 · 읽기', () {
final s = AppSettings()
..imageExts = ['png', 'webp']
..readerFit = 'width'
..readerRtl = true
..readerBrightness = 0.3
..zipComic = false;
final b = AppSettings.fromJson(s.toJson());
expect([b.imageExts, b.readerFit, b.readerRtl, b.readerBrightness, b.zipComic], [
['png', 'webp'], 'width', true, 0.3, false,
]);
expect(AppSettings.fromJson({}).imageExts, defaultImageExtensions);
});
testWidgets('그림 보기: 오른쪽을 누르면 다음, 왼쪽을 누르면 이전, 가운데는 막대 숨기기 · 오른쪽→왼쪽 넘기기', (tester) async {
tester.view.physicalSize = const Size(900, 1200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final files = [
for (final (i, n) in ['a.png', 'b.png', 'c.png'].indexed)
(File(p.join(tmp.path, n))..writeAsBytesSync(_png(i * 80, 0, 0))).path,
];
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
final src = ImageFilesSource(files, tempDir: tmp.path, title: 'imgs');
await tester.pumpWidget(MaterialApp(home: ReaderPage(c: c, source: src)));
await tester.pump();
expect(find.text('1 / 3'), findsOneWidget);
expect(find.text('a.png'), findsOneWidget);
Future<void> tapAt(double x) async {
await tester.tapAt(Offset(x, 600));
await tester.pumpAndSettle();
}
await tapAt(850); // 오른쪽 → 다음
expect(find.text('2 / 3'), findsOneWidget);
await tapAt(850);
expect(find.text('3 / 3'), findsOneWidget);
await tapAt(50); // 왼쪽 → 이전
expect(find.text('2 / 3'), findsOneWidget);
await tapAt(450); // 가운데 → 막대 숨김
expect(find.text('2 / 3'), findsNothing);
await tapAt(450);
expect(find.text('2 / 3'), findsOneWidget);
// 만화 (오른쪽 → 왼쪽): 왼쪽을 누르면 다음
await tester.tap(find.byTooltip('넘기는 방향: 왼쪽 → 오른쪽'));
await tester.pumpAndSettle();
expect(c.settings.readerRtl, isTrue);
await tapAt(50);
expect(find.text('3 / 3'), findsOneWidget);
// 맞추기 · 밝기
await tester.tap(find.byTooltip('좌우 맞추기 (세로로 밀어 봄)'));
await tester.tap(find.byTooltip('밝게'));
await tester.pump();
expect(c.settings.readerFit, 'width');
expect(c.settings.readerBrightness, closeTo(0.1, 1e-9));
});
}