192 lines
6.1 KiB
Dart
192 lines
6.1 KiB
Dart
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('waiting', 'payment_input_page'),
|
|
).summaryLabel,
|
|
'等待结果页',
|
|
);
|
|
expect(
|
|
RecognitionDiagnosticDisplay.from(
|
|
diagnostic('waiting', 'payment_input_page'),
|
|
).reasonLabel,
|
|
'当前仍是付款输入或确认页面,等待结果页,不会提前入账',
|
|
);
|
|
expect(
|
|
RecognitionDiagnosticDisplay.from(
|
|
diagnostic('rejected', 'result_page_timeout'),
|
|
).reasonLabel,
|
|
'90 秒内未等到明确结果页,本次流程已停止追踪',
|
|
);
|
|
expect(
|
|
RecognitionDiagnosticDisplay.from(
|
|
diagnostic('ignored', 'duplicate_result_surface'),
|
|
).summaryLabel,
|
|
'已合并',
|
|
);
|
|
expect(
|
|
RecognitionDiagnosticDisplay.from(
|
|
diagnostic('confirm', 'expected_amount_fallback'),
|
|
).reasonLabel,
|
|
'结果页未读到金额,已使用本次付款流程确认的金额',
|
|
);
|
|
expect(
|
|
RecognitionDiagnosticDisplay.from(
|
|
diagnostic('confirm', 'result_amount_conflict'),
|
|
).reasonLabel,
|
|
'结果页金额与本次付款金额不一致,请确认后入账',
|
|
);
|
|
});
|
|
}
|