220 lines
9.3 KiB
Dart
220 lines
9.3 KiB
Dart
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 = <String>[
|
||
'AlertDialog(',
|
||
'DropdownButtonFormField',
|
||
'showDatePicker(',
|
||
'showTimePicker(',
|
||
'PopupMenuButton',
|
||
'LinearGradient(',
|
||
'RadialGradient(',
|
||
'SweepGradient(',
|
||
'BackdropFilter(',
|
||
'0xFF087F6D',
|
||
'0xFF066B5C',
|
||
'0xFFE3F2EF',
|
||
];
|
||
final violations = <String>[];
|
||
for (final file
|
||
in Directory('lib/features')
|
||
.listSync(recursive: true)
|
||
.whereType<File>()
|
||
.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'));
|
||
});
|
||
}
|