feat: 重构 Reminder Notification 系统并更新应用包名

This commit is contained in:
qzl
2026-03-30 18:36:57 +08:00
parent 9fb2a6857b
commit 91bf3c3f96
90 changed files with 5133 additions and 3017 deletions
@@ -73,6 +73,7 @@ class _FakeAgUiService extends AgUiService {
loadHistoryHandler;
int loadHistoryCalls = 0;
final List<String?> setUserContextCalls = <String?>[];
void emitEventForTest(AgUiEvent event) {
onEvent(event);
@@ -104,7 +105,9 @@ class _FakeAgUiService extends AgUiService {
}
@override
Future<void> setUserContext(String? userId) async {}
Future<void> setUserContext(String? userId) async {
setUserContextCalls.add(userId);
}
}
HistorySnapshot _snapshot(
@@ -146,9 +149,14 @@ void main() {
() async {
final service = _FakeAgUiService();
final completer = Completer<HistorySnapshot>();
var loadCall = 0;
service.loadHistoryHandler =
({DateTime? beforeDate, bool forceRefresh = false}) {
return completer.future;
loadCall += 1;
if (loadCall == 1) {
return completer.future;
}
return Future<HistorySnapshot>.value(_snapshot(const []));
};
final bloc = ChatBloc(
@@ -178,6 +186,82 @@ void main() {
},
);
test('switchUser loads history after resetting state', () async {
final service = _FakeAgUiService();
service.loadHistoryHandler =
({DateTime? beforeDate, bool forceRefresh = false}) async {
final now = DateTime.now();
return _snapshot([
_historyMessage(
id: 'history-1',
seq: 1,
role: 'assistant',
content: 'welcome back',
timestamp: now,
),
]);
};
final bloc = ChatBloc(service: service, chatApi: _NoopChatApi());
await bloc.switchUser('user-a');
expect(service.setUserContextCalls, ['user-a']);
expect(service.loadHistoryCalls, 1);
expect(bloc.state.items, hasLength(1));
expect(
bloc.state.items.single,
isA<TextMessageItem>().having(
(item) => item.content,
'content',
'welcome back',
),
);
});
test('switchUser keeps flow when history load fails', () async {
final service = _FakeAgUiService();
service.loadHistoryHandler =
({DateTime? beforeDate, bool forceRefresh = false}) {
throw StateError('history unavailable');
};
final bloc = ChatBloc(service: service, chatApi: _NoopChatApi());
await bloc.switchUser('user-a');
expect(service.setUserContextCalls, ['user-a']);
expect(service.loadHistoryCalls, 1);
expect(bloc.state.error, contains('history unavailable'));
});
test(
'tool calendar_write success triggers calendar refresh callback',
() async {
final service = _FakeAgUiService();
var refreshCalls = 0;
final bloc = ChatBloc(
service: service,
chatApi: _NoopChatApi(),
onCalendarMutated: () async {
refreshCalls += 1;
},
);
service.emitEventForTest(
ToolCallResultEvent(
messageId: 'msg-1',
toolCallId: 'call-1',
toolName: 'calendar_write',
resultSummary: 'ok',
status: 'success',
),
);
await Future<void>.delayed(Duration.zero);
expect(refreshCalls, 1);
expect(bloc.state.isLoading, isFalse);
},
);
test(
'sendMessage recovers from premature SSE close with polled history',
() async {