|
|
@ -1,6 +1,7 @@ |
|
|
import 'dart:convert'; |
|
|
import 'dart:convert'; |
|
|
|
|
|
|
|
|
import 'package:eaimar/data/models/assistant_directive.dart'; |
|
|
import 'package:eaimar/data/models/assistant_directive.dart'; |
|
|
|
|
|
import 'package:eaimar/data/models/memory_item.dart'; |
|
|
import 'package:flutter_test/flutter_test.dart'; |
|
|
import 'package:flutter_test/flutter_test.dart'; |
|
|
|
|
|
|
|
|
/// 用例里的 payload 全部是 2026-08-29 从真机上抓的原始帧(见 scratchpad 的 |
|
|
/// 用例里的 payload 全部是 2026-08-29 从真机上抓的原始帧(见 scratchpad 的 |
|
|
@ -28,7 +29,15 @@ void main() { |
|
|
|
|
|
|
|
|
final r = AssistantReminder.fromSetClock(ds.first)!; |
|
|
final r = AssistantReminder.fromSetClock(ds.first)!; |
|
|
expect(r.time, '17:00'); |
|
|
expect(r.time, '17:00'); |
|
|
expect(r.date, '2026-08-29'); |
|
|
// ⚠️ 这里**不能**断言 '2026-08-29':帧是那天抓的,重放时早已是过去, |
|
|
|
|
|
// 会被 rollForwardPastDate 挪到就近的未来(见下面那个 group)。 |
|
|
|
|
|
// 原始值仍由上面的 args['date'] 守着。 |
|
|
|
|
|
final today = DateTime.now(); |
|
|
|
|
|
expect( |
|
|
|
|
|
DateTime.parse(r.date!).isBefore(DateTime(today.year, today.month, today.day)), |
|
|
|
|
|
isFalse, |
|
|
|
|
|
reason: '一次性闹钟不该落在过去', |
|
|
|
|
|
); |
|
|
expect(r.repeat, RepeatRule.once); |
|
|
expect(r.repeat, RepeatRule.once); |
|
|
expect(r.content, isEmpty); // 这一轮模型没给 content |
|
|
expect(r.content, isEmpty); // 这一轮模型没给 content |
|
|
}); |
|
|
}); |
|
|
@ -98,6 +107,125 @@ void main() { |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
/// 真机实测(2026-09-19 09:25 说「下午2点去恒玄公司」):模型下发的 date 是 |
|
|
|
|
|
/// 2026-09-18,比当天早一天,闹钟落到昨天那页、且永远不会响。 |
|
|
|
|
|
/// 见 AssistantReminder.rollForwardPastDate 的注释。 |
|
|
|
|
|
group('模型给了过去的日期', () { |
|
|
|
|
|
// 2026-09-19 09:25,正是那次真机复现的时刻 |
|
|
|
|
|
final now = DateTime(2026, 9, 19, 9, 25); |
|
|
|
|
|
|
|
|
|
|
|
test('时间点还没到 → 挪到今天', () { |
|
|
|
|
|
expect( |
|
|
|
|
|
AssistantReminder.rollForwardPastDate( |
|
|
|
|
|
'2026-09-18', '14:00', now, RepeatRule.once), |
|
|
|
|
|
'2026-09-19', |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('时间点已经过了 → 挪到明天', () { |
|
|
|
|
|
expect( |
|
|
|
|
|
AssistantReminder.rollForwardPastDate( |
|
|
|
|
|
'2026-09-18', '08:00', now, RepeatRule.once), |
|
|
|
|
|
'2026-09-20', |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('今天或以后的日期原样不动', () { |
|
|
|
|
|
for (final d in ['2026-09-19', '2026-09-20', '2026-12-31']) { |
|
|
|
|
|
expect( |
|
|
|
|
|
AssistantReminder.rollForwardPastDate(d, '08:00', now, RepeatRule.once), |
|
|
|
|
|
d, |
|
|
|
|
|
reason: '$d 不在过去,不该被改', |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('重复闹钟的锚点即使在过去也不动', () { |
|
|
|
|
|
// 「每周一」的锚点本来就可能是上周一,挪了会把第一次触发推迟一周 |
|
|
|
|
|
for (final rule in [ |
|
|
|
|
|
RepeatRule.daily, |
|
|
|
|
|
RepeatRule.weekly, |
|
|
|
|
|
RepeatRule.weekdays, |
|
|
|
|
|
RepeatRule.weekend, |
|
|
|
|
|
]) { |
|
|
|
|
|
expect( |
|
|
|
|
|
AssistantReminder.rollForwardPastDate('2026-09-01', '08:00', now, rule), |
|
|
|
|
|
'2026-09-01', |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('没有日期就还是没有日期', () { |
|
|
|
|
|
expect( |
|
|
|
|
|
AssistantReminder.rollForwardPastDate(null, '08:00', now, RepeatRule.once), |
|
|
|
|
|
isNull, |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
/// 拾忆页的「本地兜底」投影要不要出这一条。 |
|
|
|
|
|
/// |
|
|
|
|
|
/// 真机实测(2026-09-19):同一个闹钟出现两张卡,一张能编辑(服务端那份), |
|
|
|
|
|
/// 点另一张提示「没有同步到服务器」(本地那份)。服务端 memory_item |
|
|
|
|
|
/// id=36/37 各只有一行,重复纯在客户端投影层。 |
|
|
|
|
|
group('本地提醒去重(pendingLocalReminders)', () { |
|
|
|
|
|
AssistantReminder mk(String id, {bool synced = false}) => AssistantReminder( |
|
|
|
|
|
id: id, |
|
|
|
|
|
content: '送员工到恒玄公司', |
|
|
|
|
|
time: '17:00', |
|
|
|
|
|
date: '2026-09-19', |
|
|
|
|
|
repeat: RepeatRule.once, |
|
|
|
|
|
repeatRaw: '', |
|
|
|
|
|
weekday: null, |
|
|
|
|
|
createdAt: DateTime(2026, 9, 19, 9, 50), |
|
|
|
|
|
directiveId: id, |
|
|
|
|
|
roundId: 'r', |
|
|
|
|
|
synced: synced, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 真机那条:client_key 就是指令 id |
|
|
|
|
|
MemoryItem server(String key, {bool byClientKey = true}) => MemoryItem( |
|
|
|
|
|
id: 37, |
|
|
|
|
|
title: '送员工到恒玄公司', |
|
|
|
|
|
happenDate: '2026-09-19', |
|
|
|
|
|
happenTime: '17:00', |
|
|
|
|
|
clientKey: byClientKey ? key : '', |
|
|
|
|
|
sourceId: byClientKey ? '' : key, |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const k = '79a34c5913ad4fddba0c91e8bcb031b6#0'; |
|
|
|
|
|
|
|
|
|
|
|
test('服务端已有(client_key 对上)→ 本地那份不投影', () { |
|
|
|
|
|
expect(pendingLocalReminders([mk(k)], [server(k)]), isEmpty); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('只写了 source_id 的历史数据也认', () { |
|
|
|
|
|
expect( |
|
|
|
|
|
pendingLocalReminders([mk(k)], [server(k, byClientKey: false)]), |
|
|
|
|
|
isEmpty, |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('⚠️ synced 标志丢了也不该重复 —— 这正是真机上出现两张卡的原因', () { |
|
|
|
|
|
// 上传成功后进程被杀 / 重装 / 清缓存,本地 synced 会退回 false, |
|
|
|
|
|
// 但服务端那行还在。只看 synced 就会又投影一条出来。 |
|
|
|
|
|
final r = mk(k, synced: false); |
|
|
|
|
|
expect(pendingLocalReminders([r], [server(k)]), isEmpty); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('服务端确实没有 → 必须投影,否则本地闹钟没有任何展示出口', () { |
|
|
|
|
|
expect(pendingLocalReminders([mk(k)], const []), hasLength(1)); |
|
|
|
|
|
expect( |
|
|
|
|
|
pendingLocalReminders([mk(k)], [server('别的指令#0')]), |
|
|
|
|
|
hasLength(1), |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
test('本地已标记 synced 的不投影', () { |
|
|
|
|
|
expect(pendingLocalReminders([mk(k, synced: true)], const []), isEmpty); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
group('中文重复规则', () { |
|
|
group('中文重复规则', () { |
|
|
test('每天 / 每日', () { |
|
|
test('每天 / 每日', () { |
|
|
expect(AssistantReminder.parseRepeat('每天').$1, RepeatRule.daily); |
|
|
expect(AssistantReminder.parseRepeat('每天').$1, RepeatRule.daily); |
|
|
@ -157,7 +285,14 @@ void main() { |
|
|
AssistantDirective.parseFromOutput(output, source: 'emai').single)!; |
|
|
AssistantDirective.parseFromOutput(output, source: 'emai').single)!; |
|
|
expect(r.time, '15:30'); |
|
|
expect(r.time, '15:30'); |
|
|
expect(r.content, '项目 review'); |
|
|
expect(r.content, '项目 review'); |
|
|
expect(r.date, '2026-08-30'); |
|
|
// 同上:帧里的 2026-08-30 重放时已是过去,会被挪到就近的未来。 |
|
|
|
|
|
// 这条用例守的是 15:30 那个校正,日期由专门的 group 守。 |
|
|
|
|
|
final today = DateTime.now(); |
|
|
|
|
|
expect( |
|
|
|
|
|
DateTime.parse(r.date!) |
|
|
|
|
|
.isBefore(DateTime(today.year, today.month, today.day)), |
|
|
|
|
|
isFalse, |
|
|
|
|
|
); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|