import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:miaoji_zhang/shared/services/recognition_import_service.dart'; import 'package:miaoji_zhang/shared/services/recognition_diagnostic_formatter.dart'; import 'package:miaoji_zhang/shared/services/screenshot_channel.dart'; import 'package:miaoji_zhang/shared/services/shanghai_time.dart'; import 'package:miaoji_zhang/shared/theme/theme_store.dart'; import 'package:shared_preferences/shared_preferences.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); test('上海时区周期边界不受设备时区影响', () { final civil = ShanghaiTime.toCivil(DateTime.utc(2026, 6, 30, 16, 30)); expect(civil, DateTime(2026, 7, 1, 0, 30)); final month = ShanghaiTime.monthRangeUtc(2026, 7); expect(month.start, DateTime.utc(2026, 6, 30, 16)); expect(month.end, DateTime.utc(2026, 7, 31, 16)); final week = ShanghaiTime.weekRangeUtc(DateTime(2026, 7, 19)); expect(week.start, DateTime.utc(2026, 7, 12, 16)); expect(week.end, DateTime.utc(2026, 7, 19, 16)); }); test('无障碍运行时长不会被当作 Unix 时间戳', () { final now = DateTime.utc(2026, 7, 22, 8, 30); expect(RecognitionImportService.validOccurredAtUtc(1234, now: now), now); expect( RecognitionImportService.validOccurredAtUtc( DateTime.utc(2026, 7, 22, 8).millisecondsSinceEpoch, now: now, ), DateTime.utc(2026, 7, 22, 8), ); }); test('三态主题从本地恢复并持久化', () async { SharedPreferences.setMockInitialValues({'theme_preference': 'dark'}); await ThemeStore.instance.initialize(); expect(ThemeStore.instance.preference, JzThemePreference.dark); expect(ThemeStore.instance.themeMode, ThemeMode.dark); await ThemeStore.instance.setPreference(JzThemePreference.light); final preferences = await SharedPreferences.getInstance(); expect(preferences.getString('theme_preference'), 'light'); expect(ThemeStore.instance.themeMode, ThemeMode.light); }); test('旧版无时区接口时间按 UTC 解释后显示上海时间', () { expect( ShanghaiTime.parseUtcInstant('2026-07-22T14:35:00'), DateTime.utc(2026, 7, 22, 14, 35), ); expect( ShanghaiTime.parseCivil('2026-07-22T14:35:00'), DateTime(2026, 7, 22, 22, 35), ); expect( ShanghaiTime.parseCivil('2026-07-22T14:35:00Z'), DateTime(2026, 7, 22, 22, 35), ); }); test('原生识别候选保留红包分类与金额来源', () { final candidate = RecognitionCandidate.fromJson({ 'id': 'candidate-1', 'clientRequestId': 'recognition-candidate-1', 'state': 'auto_ready', 'confidence': 'auto', 'type': 'income', 'source': 'local_ocr', 'appName': '微信', 'amount': 8.88, 'occurredAtEpochMs': DateTime.utc( 2026, 7, 22, 14, 35, ).millisecondsSinceEpoch, 'recognitionKind': 'red_packet_receive', 'categoryHint': '红包', 'amountSource': 'result', 'resultFingerprint': 'abc123', }); expect(candidate.recognitionKind, 'red_packet_receive'); expect(candidate.categoryHint, '红包'); expect(candidate.amountSource, 'result'); expect(candidate.resultFingerprint, 'abc123'); }); test('AI 批次模型保留动作、原因与可恢复状态', () { final batch = RecognitionBatch.fromJson({ 'id': 'batch-1', 'state': 'ready', 'openedAt': DateTime.utc(2026, 7, 25, 8).millisecondsSinceEpoch, 'completedAt': DateTime.utc(2026, 7, 25, 8, 1).millisecondsSinceEpoch, 'summary': { 'kept': 1, 'updated': 1, 'created': 0, 'dropped': 1, 'fallback': false, }, 'items': [ { 'candidateId': 'candidate-1', 'action': 'drop', 'reason': '同一流程重复结果页', 'state': 'ai_dropped', 'type': 'expense', 'amount': 20, 'merchant': '张三', 'canRestore': true, }, ], }); expect(batch.completedAt.isUtc, isTrue); expect(batch.updated, 1); expect(batch.dropped, 1); expect(batch.items.single.action, 'drop'); expect(batch.items.single.reason, '同一流程重复结果页'); expect(batch.items.single.canRestore, isTrue); }); test('智能识别诊断摘要能区分关键失败类型', () { RecognitionDiagnostic diagnostic(String result, String reason) { return RecognitionDiagnostic( at: DateTime.utc(2026, 7, 22, 14, 35), appName: '微信', stage: 'ocr', result: result, reason: reason, nodeCount: 12, amountCandidates: 1, ); } expect( RecognitionDiagnosticDisplay.from( diagnostic('failed', 'capture_timeout'), ).summaryLabel, '截图失败', ); expect( RecognitionDiagnosticDisplay.from( diagnostic('failed', 'no_text'), ).summaryLabel, 'OCR 无结果', ); expect( RecognitionDiagnosticDisplay.from( diagnostic('rejected', 'payment_input_page'), ).summaryLabel, '规则拒绝', ); expect( RecognitionDiagnosticDisplay.from( diagnostic('ignored', 'duplicate_result_surface'), ).summaryLabel, '已合并', ); }); }