133 lines
7.4 KiB
Dart
133 lines
7.4 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/core/batch_rename.dart';
|
|
import 'package:jj_mkvmaker/core/file_ops.dart';
|
|
import 'package:jj_mkvmaker/ui/batch_rename_dialog.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// 78: 일괄 이름 바꾸기 - 번호 [C] · 찾아 바꾸기 · 대소문자, 미리 보기 (겹침 · 이미 있음), 맞바꾸기, 실패하면 되돌림, 되돌리기
|
|
void main() {
|
|
group('규칙', () {
|
|
test('드라마 회차: "Show S01E[C]" - 확장자는 그대로, 번호는 자릿수만큼', () {
|
|
const r = RenameRule(template: 'Show S01E[C]');
|
|
expect(r.apply('아무거나.mkv', isDir: false, index: 0), 'Show S01E01.mkv');
|
|
expect(r.apply('x.srt', isDir: false, index: 9), 'Show S01E10.srt');
|
|
expect(const RenameRule(template: '[C] - [N]', start: 5, step: 5, digits: 3).apply('a.mp4', isDir: false, index: 2), '015 - a.mp4');
|
|
});
|
|
|
|
test('찾아 바꾸기 (대소문자 무시 · 가리기) · 대소문자 바꾸기 · 폴더는 점 뒤도 이름', () {
|
|
expect(const RenameRule(find: 'x264', replace: 'H264').apply('Movie.X264.mkv', isDir: false, index: 0), 'Movie.H264.mkv');
|
|
expect(const RenameRule(find: 'x264', replace: '', ignoreCase: false).apply('Movie.X264.mkv', isDir: false, index: 0),
|
|
'Movie.X264.mkv');
|
|
expect(const RenameRule(caseMode: 'lower').apply('ABC.MKV', isDir: false, index: 0), 'abc.mkv');
|
|
expect(const RenameRule(caseMode: 'upper').apply('abc.mkv', isDir: false, index: 0), 'ABC.MKV');
|
|
expect(const RenameRule(caseMode: 'title').apply('the big show.mkv', isDir: false, index: 0), 'The Big Show.mkv');
|
|
expect(const RenameRule(template: '[N]_v2').apply('Season.1', isDir: true, index: 0), 'Season.1_v2');
|
|
expect(const RenameRule().isIdentity, isTrue);
|
|
});
|
|
});
|
|
|
|
group('미리 보기', () {
|
|
final dir = p.join('C:', 'v');
|
|
String at(String n) => p.join(dir, n);
|
|
|
|
test('같은 이름 겹침 · 고르지 않은 것과 겹침 · 쓸 수 없는 글자 · 맞바꾸기는 괜찮음', () {
|
|
final rows = planRename([at('a.mkv'), at('b.mkv')], const RenameRule(template: 'same'),
|
|
isDir: (_) => false, siblings: {dir: {'a.mkv', 'b.mkv'}});
|
|
expect(rows.every((r) => r.problem == '다른 항목과 같은 이름'), isTrue);
|
|
|
|
final taken = planRename([at('a.mkv')], const RenameRule(template: 'c'), isDir: (_) => false, siblings: {dir: {'a.mkv', 'C.MKV'}});
|
|
expect(taken.single.problem, '같은 이름이 이미 있음', reason: '대소문자만 다른 것도 겹친다');
|
|
|
|
final bad = planRename([at('a.mkv')], const RenameRule(template: 'a:b'), isDir: (_) => false, siblings: {dir: {'a.mkv'}});
|
|
expect(bad.single.problem, '쓸 수 없는 이름');
|
|
|
|
// a.mkv → b.mkv, b.mkv → a.mkv (찾아 바꾸기로 맞바꾸지는 못하니 틀로 흉내: 번호 차례를 거꾸로)
|
|
final swap = planRename([at('b.mkv'), at('a.mkv')], const RenameRule(find: 'b', replace: 'a'),
|
|
isDir: (_) => false, siblings: {dir: {'a.mkv', 'b.mkv'}});
|
|
expect(swap.first.newName, 'a.mkv');
|
|
expect(swap.first.problem, '다른 항목과 같은 이름', reason: 'a.mkv 는 그대로 a.mkv 라 겹친다');
|
|
});
|
|
|
|
test('대소문자만 바꾸는 것은 자기 자신과 겹치지 않는다', () {
|
|
final rows = planRename([at('abc.mkv')], const RenameRule(caseMode: 'upper'), isDir: (_) => false, siblings: {dir: {'abc.mkv'}});
|
|
expect(rows.single.problem, isNull);
|
|
expect(rows.single.newName, 'ABC.MKV');
|
|
});
|
|
});
|
|
|
|
group('바꾸기 (실제 파일)', () {
|
|
late Directory tmp;
|
|
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_ren78_'));
|
|
tearDown(() => tmp.deleteSync(recursive: true));
|
|
String mk(String n, [String body = '']) => (File(p.join(tmp.path, n))..writeAsStringSync(body.isEmpty ? n : body)).path;
|
|
List<String> names() => (tmp.listSync().map((e) => p.basename(e.path)).toList()..sort());
|
|
|
|
test('여러 개 · 맞바꾸기 (a↔b) · 되돌리기', () async {
|
|
final a = mk('a.txt'), b = mk('b.txt'), c = mk('c.txt');
|
|
final done = await runBatchRename([(a, 'b.txt'), (b, 'a.txt'), (c, 'Show 01.txt')]);
|
|
expect(names(), ['Show 01.txt', 'a.txt', 'b.txt']);
|
|
expect(File(p.join(tmp.path, 'b.txt')).readAsStringSync(), 'a.txt', reason: '내용이 바뀐 이름을 따라감');
|
|
expect(File(p.join(tmp.path, 'a.txt')).readAsStringSync(), 'b.txt');
|
|
// 되돌리기
|
|
await runBatchRename([for (final (orig, now) in done) (now, p.basename(orig))]);
|
|
expect(names(), ['a.txt', 'b.txt', 'c.txt']);
|
|
expect(File(p.join(tmp.path, 'a.txt')).readAsStringSync(), 'a.txt');
|
|
});
|
|
|
|
test('도중에 실패하면 바꾼 것을 모두 원래대로 (임시 이름이 남지 않음)', () async {
|
|
final a = mk('a.txt'), b = mk('b.txt'), c = mk('c.txt');
|
|
var calls = 0;
|
|
Future<String> flaky(String path, String name) {
|
|
// 2단계 (새 이름으로) 의 세 번째에서 실패
|
|
if (++calls == 6) throw const FileSystemException('잠김');
|
|
return FileOps.rename(path, name);
|
|
}
|
|
|
|
await expectLater(
|
|
runBatchRename([(a, 'x.txt'), (b, 'y.txt'), (c, 'z.txt')], renamer: flaky),
|
|
throwsA(isA<BatchRenameException>().having((e) => e.stuck, '되돌리지 못한 것', isEmpty)),
|
|
);
|
|
expect(names(), ['a.txt', 'b.txt', 'c.txt']);
|
|
});
|
|
|
|
testWidgets('창: 규칙을 넣으면 미리 보기, 문제가 있으면 [바꾸기] 꺼짐, 바꾸면 결과를 돌려준다', (tester) async {
|
|
tester.view.physicalSize = const Size(1000, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.reset);
|
|
final paths = [mk('ep_b.mkv'), mk('ep_a.mkv')];
|
|
mk('Show 01.mkv', 'other');
|
|
List<(String, String)>? result;
|
|
final nav = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(MaterialApp(navigatorKey: nav, home: const Scaffold()));
|
|
showBatchRename(nav.currentContext!, paths, isDir: (_) => false).then((r) => result = r);
|
|
for (var i = 0; i < 20; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(find.text('이름 일괄 바꾸기 · 2개'), findsOneWidget);
|
|
expect(find.text('바뀌는 것 0개'), findsOneWidget);
|
|
|
|
await tester.enterText(find.widgetWithText(TextField, '이름 틀 (확장자는 그대로)'), 'Show [C]');
|
|
await tester.pump();
|
|
// 첫 번째가 Show 01.mkv - 고르지 않은 파일과 겹친다
|
|
expect(find.textContaining('같은 이름이 이미 있음'), findsOneWidget);
|
|
expect(tester.widget<FilledButton>(find.widgetWithText(FilledButton, '바꾸기 (2개)')).onPressed, isNull);
|
|
|
|
await tester.enterText(find.widgetWithText(TextField, '번호 시작'), '2');
|
|
await tester.pump();
|
|
expect(find.text('바뀌는 것 2개'), findsOneWidget);
|
|
await tester.tap(find.widgetWithText(FilledButton, '바꾸기 (2개)'));
|
|
for (var i = 0; i < 20 && result == null; i++) {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
|
|
await tester.pump();
|
|
}
|
|
expect(result, hasLength(2));
|
|
expect(names(), ['Show 01.mkv', 'Show 02.mkv', 'Show 03.mkv']);
|
|
expect(File(p.join(tmp.path, 'Show 02.mkv')).readAsStringSync(), 'ep_b.mkv', reason: '받은 차례대로 번호');
|
|
});
|
|
});
|
|
}
|