166 lines
8.1 KiB
Dart
166 lines
8.1 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/app/bookmarks_controller.dart';
|
|
import 'package:jj_mkvmaker/app/download_manager.dart';
|
|
import 'package:jj_mkvmaker/app/settings.dart';
|
|
import 'package:jj_mkvmaker/core/bookmarks.dart';
|
|
import 'package:jj_mkvmaker/core/download_detect.dart';
|
|
import 'package:jj_mkvmaker/core/web_address.dart';
|
|
import 'package:jj_mkvmaker/services/downloader.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import 'support/temp_dirs.dart';
|
|
import 'support/browser_doubles.dart';
|
|
|
|
void main() {
|
|
deleteTempDirsAtEnd();
|
|
group('즐겨찾기 트리', () {
|
|
test('추가 · 찾기 · 이동 · 삭제', () {
|
|
final t = BookmarkTree.defaults();
|
|
final f = t.add(BookmarkTree.barId, BookmarkNode.folder('f', '음악'));
|
|
t.add('f', BookmarkNode.link('a', 'A', 'https://a.com/'));
|
|
expect(t.findByUrl('https://a.com'), isNotNull); // 끝의 / 무시
|
|
expect(t.parentOf('a')!.id, 'f');
|
|
|
|
// 같은 폴더 안: 맨 앞 → 맨 뒤
|
|
final first = t.bar.children!.first.id;
|
|
expect(t.move(first, BookmarkTree.barId, t.bar.children!.length), isTrue);
|
|
expect(t.bar.children!.last.id, first);
|
|
|
|
// 폴더를 자기 안으로는 못 옮김
|
|
final sub = t.add('f', BookmarkNode.folder('s', '하위'));
|
|
expect(t.move('f', sub.id, 0), isFalse);
|
|
expect(t.move('a', BookmarkTree.otherId, 0), isTrue);
|
|
expect(t.other.children!.single.id, 'a');
|
|
|
|
final r = t.remove(f.id)!;
|
|
expect(r.$2, BookmarkTree.barId);
|
|
expect(t.find('s'), isNull);
|
|
expect(t.remove(BookmarkTree.barId), isNull); // 기본 폴더는 못 지움
|
|
});
|
|
|
|
test('JSON 저장 · 불러오기', () {
|
|
final t = BookmarkTree.defaults()..add(BookmarkTree.otherId, BookmarkNode.folder('x', 'X', [BookmarkNode.link('y', 'Y', 'https://y')]));
|
|
final back = BookmarkTree.fromJson(t.toJson());
|
|
expect(back.find('y')!.url, 'https://y');
|
|
expect(back.bar.children!.map((n) => n.title), t.bar.children!.map((n) => n.title));
|
|
});
|
|
|
|
test('Chrome 즐겨찾기 가져오기', () {
|
|
final t = BookmarkTree.defaults();
|
|
final n = t.importChromium({
|
|
'roots': {
|
|
'bookmark_bar': {
|
|
'type': 'folder', 'name': '북마크바',
|
|
'children': [
|
|
{'type': 'url', 'name': '네이버', 'url': 'https://naver.com'},
|
|
{'type': 'url', 'name': '설정', 'url': 'chrome://settings'}, // 제외
|
|
{'type': 'folder', 'name': '영상', 'children': [
|
|
{'type': 'url', 'name': 'YT', 'url': 'https://youtube.com'},
|
|
]},
|
|
],
|
|
},
|
|
'other': {'type': 'folder', 'name': '기타', 'children': []},
|
|
},
|
|
}, 'Chrome 에서 가져옴');
|
|
expect(n, 2);
|
|
final imported = t.other.children!.single;
|
|
expect(imported.title, 'Chrome 에서 가져옴');
|
|
expect(t.findByUrl('https://youtube.com')!.title, 'YT');
|
|
});
|
|
});
|
|
|
|
group('주소 · 동영상 판별 · 쿠키', () {
|
|
test('주소창 입력', () {
|
|
expect(normalizeAddress('youtube.com'), 'https://youtube.com');
|
|
expect(normalizeAddress('http://a.b/c'), 'http://a.b/c');
|
|
expect(normalizeAddress('localhost:8080/x'), 'https://localhost:8080/x');
|
|
expect(normalizeAddress('자막 만드는 법'), startsWith('https://www.google.com/search?q='));
|
|
});
|
|
test('동영상 페이지', () {
|
|
expect(looksLikeVideoPage('https://www.youtube.com/watch?v=abc'), isTrue);
|
|
expect(looksLikeVideoPage('https://vimeo.com/12345'), isTrue);
|
|
expect(looksLikeVideoPage('https://tv.naver.com/v/123'), isTrue);
|
|
expect(looksLikeVideoPage('https://www.google.com/'), isFalse);
|
|
});
|
|
test('137: 다운로드 버튼을 눈에 띄게 할지 - YouTube 첫 화면 · 구독 목록은 미리보기 <video> 가 있어도 아님', () {
|
|
expect(downloadLooksUseful('https://www.youtube.com/', pageHasVideo: true), isFalse);
|
|
expect(downloadLooksUseful('https://m.youtube.com/feed/subscriptions', pageHasVideo: true), isFalse);
|
|
expect(downloadLooksUseful('https://www.youtube.com/watch?v=abc', pageHasVideo: false), isTrue);
|
|
// 주소로 판단하지 않는 사이트는 페이지 안의 <video> 로
|
|
expect(downloadLooksUseful('https://example.com/post/1', pageHasVideo: true), isTrue);
|
|
expect(downloadLooksUseful('https://example.com/post/1', pageHasVideo: false), isFalse);
|
|
});
|
|
test('앱 안 브라우저 쿠키 → yt-dlp', () {
|
|
// 내보낸 cookies.txt 가 없으면 쿠키 없이 (프로필 폴더를 직접 읽지 않는다)
|
|
expect(ytDlpCookieArgs(browser: internalBrowserCookies, internalProfile: r'C:\w\EBWebView\Default'), isEmpty);
|
|
expect(isCookieReadError(r'ERROR: could not find edge cookies database in "C:\x"'), isTrue);
|
|
expect(isCookieReadError('ERROR: Failed to decrypt with DPAPI. See cookies issue'), isTrue);
|
|
expect(isCookieReadError('ERROR: Video unavailable'), isFalse);
|
|
expect(ytDlpCookieArgs(browser: internalBrowserCookies), isEmpty); // 아직 로그인 전
|
|
expect(AppSettings().download.ytCookiesBrowser, internalBrowserCookies); // 새 설치 기본값
|
|
});
|
|
test('cookies.txt 형식 (HttpOnly · 중복 제거 · 쿠키 파일 우선)', () {
|
|
final txt = toNetscapeCookies(const [
|
|
CookieRecord(name: 'SID', value: 'a\tb', domain: '.youtube.com', expires: 1800000000, secure: true, httpOnly: true),
|
|
CookieRecord(name: 'SID', value: 'dup', domain: '.youtube.com'),
|
|
CookieRecord(name: 'X', value: '1', domain: 'www.google.com'),
|
|
]);
|
|
final lines = txt.split('\n').where((l) => l.isNotEmpty && !l.startsWith('# ')).toList();
|
|
expect(lines, [
|
|
'#HttpOnly_.youtube.com\tTRUE\t/\tTRUE\t1800000000\tSID\tab',
|
|
'www.google.com\tFALSE\t/\tFALSE\t0\tX\t1',
|
|
]);
|
|
expect(txt, startsWith('# Netscape HTTP Cookie File'));
|
|
expect(
|
|
ytDlpCookieArgs(browser: internalBrowserCookies, internalProfile: r'C:\p', internalCookieFile: r'C:\c.txt'),
|
|
['--cookies', r'C:\c.txt']);
|
|
});
|
|
|
|
test('브라우저 페이지 받기 (YouTube 외 사이트도)', () {
|
|
final v = RecordingDownloadBackend(DownloadKind.video);
|
|
final d = DownloadManager(backends: [v], settings: () => AppSettings()..download.root = r'D:\dl');
|
|
expect(d.addPage('https://vimeo.com/12345')!.kind, DownloadKind.video);
|
|
expect(d.addPage('https://vimeo.com/12345'), isNull); // 이미 받는 중
|
|
expect(d.addPage('about:blank'), isNull);
|
|
expect(d.addPage('https://youtu.be/abc')!.source, 'https://youtu.be/abc');
|
|
d.dispose();
|
|
});
|
|
|
|
test('다 받은 영상 → 편집 목록 알림 (한 번만, 조각 파일 제외)', () async {
|
|
final v = RecordingDownloadBackend(DownloadKind.video);
|
|
final d = DownloadManager(backends: [v], settings: () => AppSettings()..download.root = r'D:\dl');
|
|
final got = <String>[];
|
|
d.onFinished.listen((t) => got.add(t.id));
|
|
final t = d.addPage('https://vimeo.com/1')!;
|
|
t.files.addAll([r'D:\dl\a.f137.mp4', r'D:\dl\a.mp4.part', r'D:\dl\a.mp4', r'D:\dl\a.en.vtt']);
|
|
expect(DownloadManager.videoFilesOf(t, exists: (_) => true), [r'D:\dl\a.mp4']);
|
|
t.state = DownloadState.done;
|
|
v.changed!(); // 백엔드가 "다 받음" 알림
|
|
v.changed!(); // 두 번 알려도 한 번만
|
|
await Future<void>.delayed(Duration.zero);
|
|
expect(got, [t.id]);
|
|
d.dispose();
|
|
});
|
|
});
|
|
|
|
test('즐겨찾기 파일 저장 · 다시 불러오기 · 순서 바꾸기', () async {
|
|
final dir = Directory.systemTemp.createTempSync('jj_bm2_');
|
|
addTearDown(() => dir.deleteSync(recursive: true));
|
|
final file = p.join(dir.path, 'bookmarks.json');
|
|
final a = BookmarksController(file);
|
|
await a.load();
|
|
a.addFolder('영상');
|
|
a.addLink('B', 'https://b.com');
|
|
final ids = a.tree.bar.children!.map((n) => n.title).toList();
|
|
a.reorder(BookmarkTree.barId, ids.length - 1, 0); // 맨 뒤 → 맨 앞
|
|
await Future<void>.delayed(const Duration(milliseconds: 300));
|
|
|
|
final b = BookmarksController(file);
|
|
await b.load();
|
|
expect(b.tree.bar.children!.first.title, 'B');
|
|
expect(b.tree.bar.children!.any((n) => n.isFolder && n.title == '영상'), isTrue);
|
|
});
|
|
}
|