133 lines
6.2 KiB
Dart
133 lines
6.2 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/core/explorer/dir_loader.dart';
|
|
import 'package:jj_mkvmaker/core/explorer/pane_state.dart';
|
|
import 'package:jj_mkvmaker/core/explorer/tree_rows.dart';
|
|
import 'package:jj_mkvmaker/core/file_ops.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
/// E5a 약속 시험: lib/core/explorer/pane_state.dart · dir_loader.dart · tree_rows.dart 맨 위 약속.
|
|
/// 실제 시험 폴더를 읽는다 (가짜 파일 시스템 없음).
|
|
void main() {
|
|
late Directory tmp;
|
|
late String root, show, movies, ep1, notes;
|
|
setUp(() {
|
|
tmp = Directory.systemTemp.createTempSync('jj_rows_');
|
|
root = tmp.path;
|
|
show = p.join(root, 'Show');
|
|
movies = p.join(root, 'Movies');
|
|
Directory(p.join(show, 'Extras')).createSync(recursive: true);
|
|
Directory(movies).createSync();
|
|
ep1 = p.join(show, 'ep01.mkv');
|
|
File(ep1).writeAsStringSync('1');
|
|
File(p.join(show, 'ep02.mkv')).writeAsStringSync('22');
|
|
notes = p.join(root, 'notes.txt');
|
|
File(notes).writeAsStringSync('n');
|
|
});
|
|
tearDown(() => tmp.deleteSync(recursive: true));
|
|
|
|
List<FileEntry> byName(List<FileEntry> l, String _) => sortEntries(l, SortBy.name);
|
|
Future<void> open(PaneState pane, String dir) async {
|
|
pane.expanded.add(dir);
|
|
await loadDir(pane, dir, sort: byName);
|
|
}
|
|
|
|
List<String> names(List<TreeRow> rows) =>
|
|
[for (final r in rows) r.kind == TreeRow.hiddenToggle ? 'hidden' : '${' ' * r.depth}${p.basename(r.entry.path)}'];
|
|
|
|
test('창 상태: 지금 폴더를 바꾸면 내역에 · 닫은 뒤 알리지 않음', () {
|
|
final pane = PaneState(root);
|
|
var n = 0;
|
|
pane.addListener(() => n++);
|
|
pane.current = show;
|
|
pane.current = movies;
|
|
expect(pane.nav.back, [root, show]);
|
|
pane.changed();
|
|
expect(n, 1);
|
|
pane.dispose();
|
|
pane.changed();
|
|
expect(n, 1);
|
|
});
|
|
|
|
test('폴더 읽기: 읽음 → 정렬된 목록 · 두 번째는 읽어 둔 것 · 새로 읽기 · 읽는 동안 loading', () async {
|
|
final pane = PaneState(root);
|
|
final seen = <bool>[];
|
|
pane.addListener(() => seen.add(pane.loading.contains(root)));
|
|
expect((await loadDir(pane, root, sort: byName)).kind, 'loaded');
|
|
expect(seen.first, isTrue, reason: '읽는 동안 loading');
|
|
expect(pane.loading, isEmpty);
|
|
expect([for (final e in pane.cache[root]!) p.basename(e.path)], ['Movies', 'Show', 'notes.txt']);
|
|
expect((await loadDir(pane, root, sort: byName)).kind, 'cached');
|
|
File(p.join(root, 'new.txt')).writeAsStringSync('x');
|
|
var fresh = '';
|
|
expect((await loadDir(pane, root, sort: byName, force: true, onFresh: (d) => fresh = d)).kind, 'loaded');
|
|
expect(fresh, root);
|
|
expect(pane.cache[root]!.length, 4);
|
|
});
|
|
|
|
test('폴더 읽기 오류: 이유를 남기고 빈 목록 · 보던 폴더가 지워지면 남은 상위 폴더 · OS 가 막은 폴더 · 압축 안', () async {
|
|
final pane = PaneState(root);
|
|
final gone = p.join(show, 'Extras');
|
|
pane.current = gone;
|
|
Directory(gone).deleteSync();
|
|
final r = await loadDir(pane, gone, sort: byName);
|
|
expect([r.kind, r.vanishedUpTo], ['vanished', show]);
|
|
expect(pane.cache.containsKey(gone), isFalse);
|
|
final other = p.join(root, 'nope');
|
|
expect((await loadDir(pane, other, sort: byName)).kind, 'failed');
|
|
expect(pane.cache[other], isEmpty);
|
|
expect(pane.errors[other], isNotEmpty, reason: '빈 목록으로 숨기지 않음');
|
|
expect((await loadDir(pane, movies, sort: byName, osBlocked: (_) => 'BLOCKED')).kind, 'loaded');
|
|
expect(pane.errors[movies], 'BLOCKED', reason: '비어 보이는 막힌 폴더');
|
|
final zip = p.join(root, 'a.zip');
|
|
await loadDir(pane, zip, sort: byName, inside: (d, f) async => [FileEntry(p.join(zip, 'in.txt'), isDir: false, modified: DateTime(0))]);
|
|
expect(pane.cache[zip]!.single.path, endsWith('in.txt'));
|
|
});
|
|
|
|
test('줄 목록: 펼친 것만 · 깊이 · 빠른 필터 (안에 맞는 것이 있으면 폴더는 남김) · 숨김 · 폴더만', () async {
|
|
final pane = PaneState(root);
|
|
await open(pane, root);
|
|
await open(pane, show);
|
|
List<TreeRow> rows({bool showHidden = false, bool foldersOnly = false}) => buildTreeRows(pane,
|
|
foldersOnly: foldersOnly, isHidden: (x) => samePath(x, notes), showHidden: showHidden, browsable: (_) => false);
|
|
expect(names(rows()), [p.basename(root), ' Movies', ' Show', ' Extras', ' ep01.mkv', ' ep02.mkv', 'hidden']);
|
|
expect(rows().last.count, 1, reason: '숨긴 수');
|
|
expect(names(rows(showHidden: true)), contains(' notes.txt'));
|
|
// 폴더만 보기는 파일을 먼저 건너뛰어 숨긴 파일도 세지 않는다 (숨김 줄 없음)
|
|
expect(names(rows(foldersOnly: true)), [p.basename(root), ' Movies', ' Show', ' Extras']);
|
|
pane.current = show;
|
|
final filtered = _Filtered(pane, 'ep02');
|
|
expect(names(buildTreeRows(filtered, isHidden: (_) => false, browsable: (_) => false)),
|
|
[p.basename(root), ' Movies', ' Show', ' ep02.mkv', ' notes.txt'],
|
|
reason: '지금 폴더 (Show) 안만 거르고, 밖 (Movies) 은 그대로');
|
|
});
|
|
|
|
test('줄 목록: 맨 위 다른 장치 · 즐겨찾기 묶음 (펼치면 안의 것)', () async {
|
|
final pane = PaneState(root);
|
|
await open(pane, root);
|
|
final rows = buildTreeRows(pane,
|
|
roots: [r'A:\', root, r'Z:\'], favorites: [show], isHidden: (_) => false, browsable: (_) => false);
|
|
expect([for (final r in rows) r.kind].where((k) => k == TreeRow.otherRoot).length, 2);
|
|
expect(rows.first.entry.path, r'A:\');
|
|
expect(rows.last.kind, TreeRow.favorites);
|
|
pane.favoritesOpen = true;
|
|
final open2 = buildTreeRows(pane, roots: [root], favorites: [show], isHidden: (_) => false, browsable: (_) => false);
|
|
expect(open2.last.kind, TreeRow.favorite);
|
|
expect(quickFilterMatch('Show.S01E02.mkv', '*.mkv'), isTrue);
|
|
expect(globToRegExp('*.srt; ep??.mkv').hasMatch('EP01.MKV'), isTrue);
|
|
});
|
|
}
|
|
|
|
/// 빠른 필터 글을 가진 창 (화면이 칸의 글로 덮어쓰는 것과 같게)
|
|
class _Filtered extends PaneState {
|
|
_Filtered(PaneState from, this._f) : super(from.root) {
|
|
expanded.addAll(from.expanded);
|
|
cache.addAll(from.cache);
|
|
current = from.current;
|
|
}
|
|
final String _f;
|
|
@override
|
|
String get filter => _f;
|
|
}
|