import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/settings.dart'; import 'package:path/path.dart' as p; /// 설정 A 약속 시험: 설정 파일 (settings.json) 을 읽고 다시 쓴 결과가 나누기 전 판과 같다 (키 순서를 뺀 바이트 - 키를 정렬해 견줌). /// - 합성: 모든 키의 값을 바꾼 입력 (test/fixtures/settings_golden_in.json) → 나누기 전 판의 출력 (…_out.json) /// - 사용자 파일: JJ_USER_SETTINGS_EXPECT 가 있으면 사용자 설정 파일 (com.jj\jj_mkvmaker\settings.json) 을 시험 폴더에 복사해 읽기만 하고, /// 나누기 전 판이 같은 파일로 낸 출력 (scratch, 커밋하지 않음) 과 견준다. 사용자 파일에는 쓰지 않는다 /// JJ_SETTINGS_GOLDEN_WRITE=1 이면 견주지 않고 기준을 만든다 (나누기 전 판에서 한 번) void main() { final write = Platform.environment['JJ_SETTINGS_GOLDEN_WRITE'] == '1'; const fixtures = 'test/fixtures'; /// 키를 정렬한 JSON (키 순서는 분야를 옮기면 바뀐다 - 값 · 키 집합만 견줌) String canonical(Object? v) { Object? sort(Object? x) => x is Map ? {for (final k in (x.keys.map((e) => '$e').toList()..sort())) k: sort(x[k])} : x is List ? [for (final e in x) sort(e)] : x; return const JsonEncoder.withIndent(' ').convert(sort(v)); } /// 읽고 다시 쓴 것 + 읽을 때 정한 "바뀐 기본값 알림" 목록 (파일에는 없지만 읽기의 결과) String roundTrip(String text) { final s = AppSettings.fromJson(jsonDecode(text) as Map); return canonical({...s.toJson(), '__migrated': s.migrated}); } /// 모든 키의 값을 바꾼 입력 (bool 은 반대, 수는 +7, 글은 앞에 x_, 안쪽 묶음도) - 읽기의 검사 · 고침도 함께 지나게 Object? change(Object? v) => switch (v) { final bool b => !b, final int n => n + 7, final double d => d + 0.5, final String s => 'x_$s', final Map m => {for (final e in m.entries) '${e.key}': change(e.value)}, _ => v, }; Map mutated() => {for (final e in AppSettings().toJson(plainSecrets: true).entries) e.key: change(e.value)}; test('합성: 모든 키를 바꾼 설정을 읽고 쓰면 나누기 전 판과 같다 · 처음 값도', () { final inFile = File(p.join(fixtures, 'settings_golden_in.json')); final outFile = File(p.join(fixtures, 'settings_golden_out.json')); final defFile = File(p.join(fixtures, 'settings_golden_default.json')); if (write) { Directory(fixtures).createSync(recursive: true); inFile.writeAsStringSync(const JsonEncoder.withIndent(' ').convert(mutated())); outFile.writeAsStringSync(roundTrip(inFile.readAsStringSync())); defFile.writeAsStringSync(canonical(AppSettings().toJson())); return; } // git 이 줄 끝을 CRLF 로 바꿔 꺼낼 수 있다 - LF 로 맞춰 견줌 String read(File f) => f.readAsStringSync().replaceAll('\r\n', '\n'); expect(roundTrip(read(inFile)), read(outFile)); expect(canonical(AppSettings().toJson()), read(defFile)); }); test('옛 판 설정 (옮김 표시 없음 · 예전 처음 값): 읽은 값과 바뀐 기본값 알림이 나누기 전 판과 같다', () { const legacy = {'explorerLayout': 'auto', 'explorerClick': 'open', 'explorerOrientation': 'auto', 'screenOrientation': 'landscape'}; final outFile = File(p.join(fixtures, 'settings_golden_legacy_out.json')); if (write) { outFile.writeAsStringSync(roundTrip(jsonEncode(legacy))); return; } expect(roundTrip(jsonEncode(legacy)), outFile.readAsStringSync().replaceAll('\r\n', '\n')); }); final expectPath = Platform.environment['JJ_USER_SETTINGS_EXPECT']; test('사용자 설정 파일 (복사본): 읽고 쓰면 나누기 전 판과 같다', () { final user = File(p.join(Platform.environment['APPDATA'] ?? '', 'com.jj', 'jj_mkvmaker', 'settings.json')); final tmp = Directory.systemTemp.createTempSync('jj_settings_user_'); addTearDown(() => tmp.deleteSync(recursive: true)); final copy = user.copySync(p.join(tmp.path, 'settings.json')); final got = roundTrip(copy.readAsStringSync()); if (write) { File(expectPath!).writeAsStringSync(got); return; } expect(got, File(expectPath!).readAsStringSync().replaceAll('\r\n', '\n')); }, skip: expectPath == null ? '사용자 파일 기준 (JJ_USER_SETTINGS_EXPECT) 이 없으면 건너뜀 - 이 PC 에서만' : null); }