import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; void main() { test('登录强制刷新远端资料并按后端隔离本地账号数据', () { final auth = File('lib/shared/api/auth_api.dart').readAsStringSync(); final session = File( 'lib/shared/services/session_store.dart', ).readAsStringSync(); expect(auth, contains('me(forceRemote: true)')); expect(auth, contains('if (!forceRemote &&')); expect(session, contains('BackendIdentity.accountNamespace(userId)')); expect(session, contains('pending_account_archive_namespace')); expect(session, contains('last_account_namespace')); final client = File('lib/shared/api/api_client.dart').readAsStringSync(); expect(client, contains("'auth_token_\${BackendIdentity.scope}'")); }); test('启动页只读取本地缓存,不等待远程接口', () { final source = File( 'lib/features/auth/pages/splash_page.dart', ).readAsStringSync(); expect(source, contains('loadCached')); expect(source, isNot(contains('PublicConfigApi'))); expect(source, isNot(contains('AuthApi.me'))); expect(source, isNot(contains('ensureLoaded'))); }); test('业务页面不再直接使用系统感强的控件或旧青绿色板', () { final banned = [ 'AlertDialog(', 'DropdownButtonFormField', 'showDatePicker(', 'showTimePicker(', 'PopupMenuButton', 'LinearGradient(', 'RadialGradient(', 'SweepGradient(', 'BackdropFilter(', '0xFF087F6D', '0xFF066B5C', '0xFFE3F2EF', ]; final violations = []; for (final file in Directory('lib/features') .listSync(recursive: true) .whereType() .where((file) => file.path.endsWith('.dart'))) { final source = file.readAsStringSync(); for (final token in banned) { if (source.contains(token)) { violations.add('${file.path}: $token'); } } } expect(violations, isEmpty, reason: violations.join('\n')); }); test('深色模式的选中与未选中状态使用语义色板', () { final controls = File( 'lib/shared/widgets/app_controls.dart', ).readAsStringSync(); final addPage = File('lib/features/add/add_page.dart').readAsStringSync(); final chat = File('lib/features/chat/chat_page.dart').readAsStringSync(); final companion = File( 'lib/features/settings/companion_page.dart', ).readAsStringSync(); final me = File('lib/features/settings/me_page.dart').readAsStringSync(); final report = File( 'lib/features/stats/report_page.dart', ).readAsStringSync(); expect(controls, contains(': context.jz.card')); expect(addPage, isNot(contains('selected ? _activeColor : Colors.white'))); expect(chat, isNot(contains('isMe ? AppTheme.primary : Colors.white'))); expect( companion, isNot(contains('context.jz.aiBackground : Colors.white')), ); expect(me, isNot(contains('context.jz.primaryBackground : Colors.white'))); expect(report, isNot(contains('selected ? Colors.white'))); }); test('聊天附件只保留拍照和相册导入', () { final source = File('lib/features/chat/chat_page.dart').readAsStringSync(); expect(source, contains('ImageSource.camera')); expect(source, contains('ImageSource.gallery')); expect(source, isNot(contains('截屏记账'))); expect(source, isNot(contains('手动输入文字'))); expect(source, isNot(contains('ScreenshotChannel.capture'))); }); test('表情包具备离线缓存、内置兜底和展开刷新', () { final api = File('lib/shared/api/business_api.dart').readAsStringSync(); final chat = File('lib/features/chat/chat_page.dart').readAsStringSync(); expect(api, contains('global_stickers_v1')); expect(api, contains('_fallback')); expect(api, contains('SharedPreferences.getInstance')); expect( chat, contains('setState(() => _composerPanel = _ComposerPanel.stickers)'), ); expect(chat, contains('_stkLoad();')); }); test('无障碍只解析明确成功页且使用墙钟时间', () { final parser = File( 'android/app/src/main/kotlin/com/nx/miaoji/PaymentParser.kt', ).readAsStringSync(); expect(parser, contains('occurredAt = System.currentTimeMillis()')); expect(parser, contains('historyTitles')); expect(parser, contains('"扫码支付成功"')); expect(parser, contains('"转账已完成"')); expect(parser, contains('STANDALONE_AMOUNT')); expect(parser, contains('MAX_PAYMENT_SCAN_LINES = 48')); expect(parser, isNot(contains('listOf("收款成功", "已收款", "到账"'))); expect(parser, isNot(contains('"支出", "消费成功"'))); expect(parser, isNot(contains('listOf("微信支付", "支付宝", "交易成功")'))); final service = File( 'android/app/src/main/kotlin/com/nx/miaoji/ScreenshotAccessibilityService.kt', ).readAsStringSync(); expect(service, contains('AccessibilityEvent.TYPE_WINDOWS_CHANGED')); expect(service, contains('AccessibilityEvent.TYPE_VIEW_CLICKED')); expect(service, contains('PaymentFlow(')); expect(service, contains('PAYMENT_FLOW_TTL_MS = 90_000L')); expect(service, contains('VISUAL_STABILITY_DELAY_MS = 700L')); expect(service, contains('takeScreenshotOfWindow')); expect(service, contains('bitmap.recycle()')); expect(service, contains('CAPTURE_CALLBACK_TIMEOUT_MS = 6_000L')); expect(service, contains('OCR_CALLBACK_TIMEOUT_MS = 12_000L')); final localOcr = File( 'android/app/src/main/kotlin/com/nx/miaoji/LocalPaymentOcr.kt', ).readAsStringSync(); expect(localOcr, contains('ChineseTextRecognizerOptions')); expect(localOcr, contains('InputImage.fromBitmap')); expect(localOcr, contains('distinctCents.size == 1')); expect(localOcr, contains('evidenceConfidence = if (highConfidence)')); final store = File( 'android/app/src/main/kotlin/com/nx/miaoji/RecognitionStore.kt', ).readAsStringSync(); expect(store, contains('"local_ocr" -> 8')); expect(store, contains('flowSessionId')); expect(store, contains('ACCESSIBILITY_NOTIFICATION_MASK')); final gradle = File('android/app/build.gradle.kts').readAsStringSync(); expect( gradle, contains('com.google.mlkit:text-recognition-chinese:16.0.1'), ); }); test('本地 OCR 需要一次性用途确认并提供无隐私诊断', () { final settings = File( 'lib/features/settings/screenshot_settings_page.dart', ).readAsStringSync(); final legal = File( 'lib/features/settings/legal_document_page.dart', ).readAsStringSync(); final diagnostics = File( 'android/app/src/main/kotlin/com/nx/miaoji/RecognitionDiagnostics.kt', ).readAsStringSync(); final formatter = File( 'lib/shared/services/recognition_diagnostic_formatter.dart', ).readAsStringSync(); expect(settings, contains('local_ocr_consent_v1')); expect(settings, contains('图片只在内存中由本地 OCR 处理')); expect(settings, contains('_RecognitionDiagnosticCard')); expect(settings, contains('Timer.periodic')); expect(formatter, contains('operation_interrupted')); expect(formatter, contains('截图失败')); expect(formatter, contains('OCR 无结果')); expect(formatter, contains('规则拒绝')); expect(formatter, contains('已合并')); expect(legal, contains('ML Kit 中文文字识别(Google)')); expect(diagnostics, isNot(contains('sourceText'))); expect(diagnostics, isNot(contains('image'))); expect(diagnostics, contains('STALE_OPERATION_MS = 20_000L')); final service = File( 'android/app/src/main/kotlin/com/nx/miaoji/ScreenshotAccessibilityService.kt', ).readAsStringSync(); expect(service, contains('resolveAccessibilityWindowId')); final localOcr = File( 'android/app/src/main/kotlin/com/nx/miaoji/LocalPaymentOcr.kt', ).readAsStringSync(); expect(localOcr, contains('MlKitContext.initializeIfNeeded')); expect(localOcr, contains('ocr_model_unavailable')); expect(localOcr, contains('OcrDiagnosticRedactor')); expect(localOcr, contains('qualifiesWeakAuto')); expect(settings, contains('OCR 诊断预览')); expect(settings, contains('10 分钟后自动关闭')); expect(diagnostics, contains('statusStrength')); expect(diagnostics, contains('expectedAmountMatched')); expect(diagnostics, contains('resultTransitionObserved')); expect(diagnostics, contains('ocrPreview')); expect(service, contains('shouldFallbackToDisplay')); expect(service, contains('ERROR_TAKE_SCREENSHOT_INVALID_WINDOW')); expect(service, contains('WINDOW_CAPTURE_FALLBACK_DELAY_MS = 450L')); }); test('智能识别授权未完成时不会提前打开开关', () { final source = File( 'lib/features/settings/screenshot_settings_page.dart', ).readAsStringSync(); expect(source, contains('_pendingAuthorizationKey')); expect(source, contains('未完成系统授权,识别开关仍保持关闭')); expect(source, contains('setRecognitionToggle(key, false)')); expect(source, contains('_BackgroundKeepAliveCard')); }); }