diff --git a/lib/ui/vault/vault_session.dart b/lib/ui/vault/vault_session.dart index 69829cd..dc27f86 100644 --- a/lib/ui/vault/vault_session.dart +++ b/lib/ui/vault/vault_session.dart @@ -213,7 +213,7 @@ Future<({bool move, bool permanent})?> askVaultAdd(BuildContext context, List bad})> putVaultAdd(VaultUiState s, List paths, VaultAddPlan plan, {required Future Function() thumbTemp}) async { s.setBusy(true); @@ -226,7 +226,13 @@ Future<({int done, Set bad})> putVaultAdd(VaultUiState s, List p } for (var i = 0; i < plan.files.length; i++) { final x = plan.files[i]; - if (s.vault == null) break; // 넣는 동안 잠갔으면 멈춤 + if (s.vault == null) { + // 넣는 동안 잠갔으면 멈춤 - 넣지 못한 남은 것은 bad 로 (그 원본은 [원본 지우기 (이동)] 로 지우지 않게) + for (final rest in plan.files.skip(i)) { + bad.add(rest.top); + } + break; + } s.setProgress('${i + 1} / ${plan.files.length} · ${vBasename(x.src)}'); String? fetched; try { diff --git a/test/vault_modules_test.dart b/test/vault_modules_test.dart index 3e72980..ba5420e 100644 --- a/test/vault_modules_test.dart +++ b/test/vault_modules_test.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; @@ -158,4 +159,61 @@ void main() { await t.tap(find.text('나')); expect(got, hasLength(2), reason: '바쁜 동안은 누르기가 꺼짐'); }); + + testWidgets('vault_session (넣기): [원본 지우기 (이동)] 로 넣다가 중간에 잠그면 넣은 것만 원본을 지우고, 넣지 못한 원본은 바이트까지 그대로', (t) async { + final src = Directory(p.join(tmp.path, 'src')); + final names = ['1.bin', '2.bin', '3.bin', '4.bin']; + final bytes = {for (final n in names) n: List.generate(4096, (i) => (i * 7 + n.codeUnitAt(0)) % 256)}; + final vault = await t.runAsync(() async { + src.createSync(); + for (final n in names) { + File(p.join(src.path, n)).writeAsBytesSync(bytes[n]!); + } + Directory(p.join(tmp.path, 'thumbs')).createSync(); + return Vault.create(Directory(p.join(tmp.path, 'vault')), 'pw-for-test', iterations: 1000); + }); + final paths = [for (final n in names) p.join(src.path, n)]; + late VaultUiState s; + // 화면의 잠그기와 같게: 열쇠를 버린다 (vault = null). "3 / 4" 를 보이는 때 = 1 · 2 를 넣은 뒤 + s = VaultUiState( + changed: () { + if (s.progress?.startsWith('3 / ') ?? false) s.vault = null; + }, + snack: (_) {}) + ..vault = vault; + final deleted = []; + late BuildContext ctx; + await t.pumpWidget(MaterialApp(home: Builder(builder: (c) { + ctx = c; + return const Scaffold(); + }))); + var finished = false; + unawaited(vaultAddFlow(ctx, s, paths, thumbTemp: () async => p.join(tmp.path, 'thumbs'), deleteOriginals: (ok, {required permanent}) async { + deleted.addAll(ok); + for (final x in ok) { + File(x).deleteSync(); // 실제로 지운다 (탐색기의 지우기 대신) + } + }).whenComplete(() => finished = true)); + Future until(bool Function() ok) async { + for (var i = 0; i < 300 && !ok(); i++) { + await t.runAsync(() => Future.delayed(const Duration(milliseconds: 20))); + await t.pump(const Duration(milliseconds: 20)); + } + } + + await until(() => find.text('원본 지우기 (이동)').evaluate().isNotEmpty); + await t.tap(find.text('원본 지우기 (이동)')); + await t.pump(); + await t.tap(find.widgetWithText(FilledButton, '넣기')); + await until(() => finished); + expect(finished, isTrue); + expect(s.vault, isNull, reason: '중간에 잠금'); + expect([for (final e in vault!.entries) e.name], unorderedEquals(['1.bin', '2.bin']), reason: '잠그기 전 둘만 들어감'); + expect(deleted, unorderedEquals([paths[0], paths[1]]), reason: '넣은 것만 원본 지우기로'); + for (final n in ['3.bin', '4.bin']) { + final f = File(p.join(src.path, n)); + expect(f.existsSync(), isTrue, reason: '넣지 못한 원본 $n 은 남음'); + expect(f.readAsBytesSync(), bytes[n], reason: '$n 바이트 그대로'); + } + }); }