101 lines
6.1 KiB
Dart
101 lines
6.1 KiB
Dart
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 --exclude=*.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 --delete --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);
|
||
});
|
||
}
|
||
});
|
||
}
|