JJ_MKVMaker/test/option_paste_test.dart

101 lines
6.1 KiB
Dart
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/core/rsync_filter.dart';
import 'package:jj_mkvmaker/core/sync_preview.dart' show optionsDelete;
import 'package:jj_mkvmaker/core/sync_tools.dart';
import 'package:jj_mkvmaker/ui/sync_preview_view.dart';
import 'package:path/path.dart' as p;
import 'support/rsync_scenes.dart';
/// 관리자 지시 (10/20, 28 확인): 웹 · 문서에서 옵션 칸에 붙여 넣은 글 (굽은 따옴표 · 대시 · 보이지 않는 글자) 을
/// 곧은 글자로 쓴 것과 똑같이 읽는다. 예전에는 --exclude=‘*.log’ 가 아무것도 거르지 못해 받는 쪽 keep.log 가 지워졌고,
/// –delete 는 원본 경로로 넘어가 --delete 가 몰래 빠졌다. 보이지 않는 글자는 \u 표기로 쓴다
void main() {
// 28 의 13가지: (이름, 붙여 넣은 글, 곧은 글자로 쓴 것)
const cases = <(String, String, String)>[
('줄바꿈 없는 빈칸 (U+00A0)', '-a --delete --exclude=*.log', '-a --delete --exclude=*.log'),
('화면 글의 U+2060', '-a --delete --exclude=⁠*.log', '-a --delete --exclude=*.log'),
('U+200B', '-a --delete --excl​ude=*.log', '-a --delete --exclude=*.log'),
('줄바꿈 · 탭', '-a\t--delete\n--exclude=*.log\r\n', '-a --delete --exclude=*.log'),
('전각 빈칸 (U+3000)', '-a --delete --exclude=*.log', '-a --delete --exclude=*.log'),
('en dash (–delete)', '-a –delete --exclude=*.log', '-a --delete --exclude=*.log'),
('em dash (—delete)', '-a —delete --exclude=*.log', '-a --delete --exclude=*.log'),
('굽은 큰따옴표', '-a --delete --filter=“- *.log”', '-a --delete --filter="- *.log"'),
('굽은 작은따옴표', '-a --delete --exclude=‘*.log’', "-a --delete --exclude='*.log'"),
('소프트 하이픈 (U+00AD)', '-a --del­ete --exclude=*.log', '-a --delete --exclude=*.log'),
('줄바꿈 없는 하이픈 (U+2011)', '-a ‑‑delete --exclude=*.log', '-a --delete --exclude=*.log'),
('전각 하이픈 (U+FF0D)', '-a --delete --exclude=*.log', '-a --delete --exclude=*.log'),
('BOM 앞', '-a --delete --exclude=*.log', '-a --delete --exclude=*.log'),
];
group('붙여 넣은 옵션 = 곧은 글자로 쓴 것', () {
for (final (name, pasted, straight) in cases) {
test(name, () {
// 옵션 칸 (OptionTextFormatter) 을 거친 글도, 거치지 않은 글 (예전에 저장된 값) 도
final typed = const OptionTextFormatter()
.formatEditUpdate(TextEditingValue.empty, TextEditingValue(text: pasted, selection: TextSelection.collapsed(offset: pasted.length)))
.text;
for (final s in [pasted, typed]) {
expect(splitOptions(s), splitOptions(straight));
expect(unreadableOptions(s), isEmpty);
expect(optionsDelete(s), optionsDelete(straight));
final f = RsyncFilter.parse(s), g = RsyncFilter.parse(straight);
expect(f.excluded('keep.log', isDir: false), g.excluded('keep.log', isDir: false));
expect(f.excluded('a.txt', isDir: false), isFalse);
}
});
}
});
test('낱말 가운데 · 이름 안은 그대로 (파일 이름일 수 있음)', () {
// 이름 안의 굽은 작은따옴표 (it’s) · 대시 (a–b) 는 따옴표 · 옵션으로 읽지 않는다
expect(splitOptions('--exclude=it’s.txt'), ['--exclude=it’s.txt']);
expect(splitOptions('--exclude=a–b.txt'), ['--exclude=a–b.txt']);
// 굽은 따옴표 안의 굽은 따옴표 (뒤가 빈칸 · 끝이 아니면 닫지 않음)
expect(splitOptions('--exclude=‘it’s a.txt’ -a'), ['--exclude=it’s a.txt', '-a']);
// 짧은 옵션 뒤 (-f“- *.log”)
expect(splitOptions('-f“- *.log”'), ['-f- *.log']);
// 대시 하나 + 짧은 글자는 - (–av → -av), 소문자 낱말이면 -- (–delete → --delete, –bwlimit=100 → --bwlimit=100)
expect(splitOptions('–av –delete –bwlimit=100'), ['-av', '--delete', '--bwlimit=100']);
});
test('고쳐 읽어도 알아볼 수 없는 옵션 (ASCII 밖 글자로 시작) 은 알린다 - 옵션의 값 자리는 세지 않음', () {
expect(unreadableOptions('-a «delete»'), ['«delete»']);
expect(unreadableOptions('-a …delete'), ['…delete']);
expect(unreadableOptions('--exclude 한글.txt -a'), isEmpty, reason: '--exclude 의 값');
expect(unreadableOptions('-f 한글.txt'), isEmpty, reason: '-f 의 값');
expect(unreadableOptions('-a 한글'), ['한글'], reason: '옵션 자리 - rsync 는 원본 경로로 읽는다');
expect(unreadableOptions('-avPog --delete'), isEmpty);
});
testWidgets('알아볼 수 없는 옵션 알림 (빨간 글, 옵션 글은 줄바꿈 없이)', (tester) async {
await tester.pumpWidget(const MaterialApp(home: Scaffold(body: OptionProblemNote(['«delete»']))));
expect(find.textContaining('옵션을 알아볼 수 없습니다'), findsOneWidget);
});
// 실제 rsync (Windows, 앱에 든 것): 붙여 넣은 글로 맞춘 결과 = 곧은 글자로 맞춘 결과 (28 의 장면: 받는 쪽 keep.log · old.txt)
group('실제 rsync 결과가 같다', () {
for (final (name, pasted, straight) in cases) {
test(name, () async {
final rsync = sceneRsync;
if (rsync == null) return markTestSkipped('앱에 든 rsync 없음 (Windows · third_party)');
final tmp = Directory.systemTemp.createTempSync('jj_optpaste_');
addTearDown(() => tmp.deleteSync(recursive: true));
Future<Map<String, String>> run(String opts, String tag) async {
final s = p.join(tmp.path, '${tag}s'), d = p.join(tmp.path, '${tag}d');
writeScene(s, d, srcFiles: const ['a.txt', 'b.log'], dstFiles: const ['old.txt', 'keep.log']);
return realRsyncTree(rsync, s, d, opts);
}
final want = await run(straight, 'w');
expect(want.containsKey('keep.log'), isTrue, reason: '곧은 글자: *.log 는 거르므로 받는 쪽 keep.log 를 지키고');
expect(want.containsKey('old.txt'), isFalse, reason: '--delete 로 old.txt 는 지움');
expect(await run(pasted, 'g'), want);
});
}
});
}