192 lines
9.8 KiB
Dart
192 lines
9.8 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/core/selection.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// E1 약속 시험 (모듈 하나에 한 장): lib/core/selection.dart 맨 위 약속을 그대로 본다.
|
|
/// 기준은 wiki "파일 탐색기 선택 규칙 (사용자 지시 모음)" 규칙 2~6 · 8 과 관리자 결정 (10/22).
|
|
/// 폴더 안 목록은 실제 파일 시스템 (dart:io) 에서 읽는다.
|
|
void main() {
|
|
group('ClickPolicy (규칙 2 · 3 · 5 · 6)', () {
|
|
// (누르기, 종류, 선택 모드, 펼침, 목록, 다시, 기대) - 규칙 표 그대로
|
|
const table = [
|
|
(Press.tap, ItemKind.file, false, false, false, false, ClickAction.focus), // 규칙 2 한 번 = 선택
|
|
(Press.doubleTap, ItemKind.file, false, false, false, false, ClickAction.open), // 규칙 2 두 번 = 실행
|
|
(Press.longPress, ItemKind.file, false, false, false, false, ClickAction.menu), // 규칙 2 길게 = 메뉴
|
|
(Press.secondary, ItemKind.folder, false, false, false, false, ClickAction.menu),
|
|
(Press.longPress, ItemKind.root, false, false, false, false, ClickAction.none), // 장치 · .. 는 메뉴 없음
|
|
(Press.tap, ItemKind.folder, false, false, false, false, ClickAction.expand), // 규칙 3 펼치기
|
|
(Press.tap, ItemKind.folder, false, true, false, false, ClickAction.collapse), // 규칙 3 접기
|
|
(Press.tap, ItemKind.folder, false, true, false, true, ClickAction.none), // 두 번 누르기의 두 번째는 접지 않음
|
|
(Press.tap, ItemKind.root, false, true, false, false, ClickAction.collapse),
|
|
(Press.tap, ItemKind.folder, false, false, true, false, ClickAction.open), // 목록의 폴더 = 들어가기
|
|
(Press.tap, ItemKind.up, false, false, true, false, ClickAction.open),
|
|
(Press.tap, ItemKind.file, true, false, false, false, ClickAction.toggleMark), // 규칙 5
|
|
(Press.tap, ItemKind.folder, true, false, false, false, ClickAction.toggleMark), // 규칙 6
|
|
(Press.tap, ItemKind.root, true, false, false, false, ClickAction.expand), // 장치는 고르지 않음
|
|
(Press.doubleTap, ItemKind.file, true, false, false, false, ClickAction.none),
|
|
(Press.longPress, ItemKind.file, true, false, false, false, ClickAction.menu),
|
|
];
|
|
for (final (press, kind, sel, exp, list, rep, want) in table) {
|
|
test('$press $kind 선택=$sel 펼침=$exp 목록=$list 다시=$rep → $want', () {
|
|
expect(ClickPolicy.decide(press, kind, selectMode: sel, expanded: exp, list: list, repeat: rep), want);
|
|
});
|
|
}
|
|
test('사용자 설정: 바로 열기 · 다시 눌러 접지 않기', () {
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.open);
|
|
// TC 모양 (doubleOpens, 관리자 10/23): 폴더 · ".." 도 한 번 = 고르기, 두 번 = 열기 (선택 모드는 그대로 표시)
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.focus);
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.focus);
|
|
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true, doubleOpens: true), ClickAction.open);
|
|
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.up, selectMode: false, list: true, doubleOpens: true), ClickAction.open);
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.toggleMark);
|
|
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: true, list: true, doubleOpens: true), ClickAction.none);
|
|
// 그 밖의 목록 (폴더 + 파일 목록) 은 한 번 = 열기 그대로
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, list: true), ClickAction.open);
|
|
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.folder, selectMode: false, list: true), ClickAction.none);
|
|
expect(ClickPolicy.decide(Press.doubleTap, ItemKind.file, selectMode: false, tapOpens: true), ClickAction.none);
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.file, selectMode: true, tapOpens: true), ClickAction.toggleMark);
|
|
expect(ClickPolicy.decide(Press.tap, ItemKind.folder, selectMode: false, expanded: true, retapCollapses: false),
|
|
ClickAction.expand);
|
|
});
|
|
});
|
|
|
|
group('Selection (규칙 5 · 6 · 8)', () {
|
|
late Directory tmp;
|
|
late String show, e1, e2, e3, sub, s1, notes;
|
|
var reads = 0;
|
|
Future<List<String>> lister(String dir) async {
|
|
reads++;
|
|
return [for (final e in Directory(dir).listSync()) e.path]..sort();
|
|
}
|
|
|
|
setUp(() {
|
|
tmp = Directory.systemTemp.createTempSync('jj_sel_');
|
|
show = p.join(tmp.path, 'Show');
|
|
sub = p.join(show, 'Extras');
|
|
Directory(sub).createSync(recursive: true);
|
|
e1 = p.join(show, 'ep01.mkv');
|
|
e2 = p.join(show, 'ep02.mkv');
|
|
e3 = p.join(show, 'ep03.mkv');
|
|
s1 = p.join(sub, 'bonus.mkv');
|
|
notes = p.join(tmp.path, 'notes.txt');
|
|
for (final f in [e1, e2, e3, s1, notes]) {
|
|
File(f).writeAsStringSync('x');
|
|
}
|
|
reads = 0;
|
|
});
|
|
tearDown(() => tmp.deleteSync(recursive: true));
|
|
|
|
test('파일: 누르면 고름 · 다시 누르면 취소 (규칙 5)', () async {
|
|
final s = Selection();
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
expect(s.marked, [e1]);
|
|
await s.toggle(e2, isDir: false, list: lister);
|
|
expect(s.marked, [e1, e2]);
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
expect(s.marked, [e2]);
|
|
expect(reads, 0); // 파일은 목록을 읽지 않음
|
|
});
|
|
|
|
test('폴더: 자기 + 안 전체 · 다시 누르면 전체 취소 (규칙 6)', () async {
|
|
final s = Selection();
|
|
await s.toggle(e1, isDir: false, list: lister); // 안에 따로 고른 것은 폴더 하나로 합쳐짐
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
expect(s.marked, [show]);
|
|
for (final x in [show, e1, e2, e3, sub, s1]) {
|
|
expect(s.isMarked(x), isTrue, reason: x);
|
|
}
|
|
expect(s.isMarked(notes), isFalse);
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
expect(s.isEmpty, isTrue);
|
|
});
|
|
|
|
test('고른 폴더 안의 하나를 누르면 그것만 빠짐 (깊은 곳도)', () async {
|
|
final s = Selection();
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
expect(s.isMarked(e1), isFalse);
|
|
for (final x in [e2, e3, sub, s1]) {
|
|
expect(s.isMarked(x), isTrue, reason: x);
|
|
}
|
|
expect(s.isMarked(show), isFalse);
|
|
// 두 단계 아래
|
|
final t = Selection();
|
|
await t.toggle(show, isDir: true, list: lister);
|
|
await t.toggle(s1, isDir: false, list: lister);
|
|
expect(t.isMarked(s1), isFalse);
|
|
expect([e1, e2, e3].every(t.isMarked), isTrue);
|
|
});
|
|
|
|
test('하나를 뺀 폴더를 다시 누르면 그 폴더와 안 전체 취소 (관리자 결정 10/22)', () async {
|
|
final s = Selection();
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
await s.toggle(notes, isDir: false, list: lister); // 바깥 것은 남음
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
expect(s.marked, [notes]);
|
|
// 그 뒤 다시 누르면 처음처럼 전체 고름
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
expect(s.isMarked(e1), isTrue);
|
|
});
|
|
|
|
test('목록을 읽지 못하면 표시를 바꾸지 않고 던짐', () async {
|
|
final s = Selection();
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
Future<List<String>> broken(String dir) async => throw FileSystemException('읽지 못함', dir);
|
|
await expectLater(s.toggle(e1, isDir: false, list: broken), throwsA(isA<FileSystemException>()));
|
|
expect(s.marked, [show]);
|
|
});
|
|
|
|
test('범위 · 모두 · 뒤집기 · 지운 것 빼기 · 대상', () async {
|
|
final s = Selection();
|
|
final visible = [e1, e2, e3];
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
s.range(visible, e3);
|
|
expect(s.marked, [e1, e2, e3]);
|
|
s.invert([e2]);
|
|
expect(s.marked, [e1, e3]);
|
|
s.forget(e1);
|
|
expect(s.marked, [e3]);
|
|
s.addAll([show]);
|
|
expect(s.marked, [show]);
|
|
expect(s.targets(notes), [show]);
|
|
s.clear();
|
|
expect(s.targets(notes), [notes]);
|
|
expect(s.targets(null), isEmpty);
|
|
});
|
|
|
|
test('이름 바꾸기 · 없어진 것 따라가기', () async {
|
|
final s = Selection();
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
await s.toggle(e1, isDir: false, list: lister); // Show 를 나눔: e2 · e3 · Extras 표시
|
|
await s.toggle(notes, isDir: false, list: lister);
|
|
final moved = p.join(tmp.path, 'Season');
|
|
s.rename(show, moved);
|
|
expect(s.has(p.join(moved, 'ep02.mkv')), isTrue);
|
|
expect(s.isMarked(p.join(moved, 'Extras', 'bonus.mkv')), isTrue);
|
|
expect(s.has(e2), isFalse);
|
|
s.rename(notes, p.join(tmp.path, 'memo.txt'));
|
|
expect(s.has(p.join(tmp.path, 'memo.txt')), isTrue);
|
|
s.removeWhere((x) => x.endsWith('.txt'));
|
|
expect(s.marked.any((x) => x.endsWith('.txt')), isFalse);
|
|
// 나눈 폴더도 새 이름으로 - 다시 누르면 전체 취소
|
|
await s.toggle(moved, isDir: true, list: lister);
|
|
expect(s.isEmpty, isTrue);
|
|
});
|
|
|
|
test('Rsync: 폴더만 · 하나만 · 다시 누르면 취소 (규칙 8)', () async {
|
|
final s = Selection(rsync: true);
|
|
await s.toggle(e1, isDir: false, list: lister);
|
|
expect(s.isEmpty, isTrue);
|
|
await s.toggle(show, isDir: true, list: lister);
|
|
await s.toggle(sub, isDir: true, list: lister);
|
|
expect(s.marked, [sub]);
|
|
expect(s.isMarked(s1), isFalse); // Rsync 는 고른 그 폴더만
|
|
await s.toggle(sub, isDir: true, list: lister);
|
|
expect(s.isEmpty, isTrue);
|
|
});
|
|
});
|
|
}
|