import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/app_controller.dart'; import 'package:jj_mkvmaker/app/bookmarks_controller.dart'; import 'package:jj_mkvmaker/app/download_manager.dart'; import 'package:jj_mkvmaker/app/net_guard.dart'; import 'package:jj_mkvmaker/app/settings.dart'; import 'package:jj_mkvmaker/core/bookmarks.dart'; import 'package:jj_mkvmaker/core/dns_resolver.dart'; import 'package:jj_mkvmaker/core/download_detect.dart'; import 'package:jj_mkvmaker/core/web_address.dart'; import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart'; import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart'; import 'package:jj_mkvmaker/services/downloader.dart'; import 'package:jj_mkvmaker/services/platform_services.dart'; import 'package:jj_mkvmaker/ui/browser_page.dart'; import 'package:jj_mkvmaker/ui/downloads_page.dart'; import 'package:path/path.dart' as p; import 'support/temp_dirs.dart'; import 'package:jj_mkvmaker/ui/notice.dart'; class _Backend implements DownloadBackend { @override final DownloadKind kind; final started = []; void Function()? changed; _Backend(this.kind); @override Future<(String, List)?> expandPlaylist(String url) async => null; @override Future start(DownloadTask t, void Function() changed) async { this.changed = changed; started.add(t.source); t.state = DownloadState.downloading; } @override Future pause(DownloadTask t) async {} @override Future cancel(DownloadTask t) async {} @override Future shutdown() async {} } class _Nav implements WebNav { int paused = 0; @override Future pauseMedia() async => paused++; final scripts = []; @override Future runScript(String js) async => scripts.add(js); final loads = []; @override Future load(String url) async => loads.add(url); int backs = 0; @override Future back() async => backs++; @override Future forward() async {} int reloads = 0; @override Future reload() async => reloads++; @override Future stop() async {} var exported = 0; @override Future exportCookies() async => exported++; @override Future clearData() async {} @override Future evaluate(String js) async { scripts.add(js); return null; } final javaScript = []; @override Future setJavaScript(bool on) async => javaScript.add(on); } /// 웹 뷰 대신: 몇 번 새로 만들어졌는지 센다 (250 - 덮개를 띄워도 웹 뷰를 다시 만들지 않아야 함) class _Probe extends StatefulWidget { static int created = 0; const _Probe(); @override State<_Probe> createState() => _ProbeState(); } class _ProbeState extends State<_Probe> { @override void initState() { super.initState(); _Probe.created++; } @override Widget build(BuildContext context) => const ColoredBox(color: Colors.white); } 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 첫 화면 · 구독 목록은 미리보기