forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathconversation_detail_provider_selection_test.dart
More file actions
139 lines (116 loc) · 6.21 KB
/
Copy pathconversation_detail_provider_selection_test.dart
File metadata and controls
139 lines (116 loc) · 6.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:omi/backend/preferences.dart';
import 'package:omi/backend/schema/conversation.dart';
import 'package:omi/backend/schema/structured.dart';
import 'package:omi/pages/conversation_detail/conversation_detail_provider.dart';
import 'package:omi/providers/conversation_provider.dart';
void main() {
setUp(() async {
SharedPreferences.setMockInitialValues({});
await SharedPreferencesUtil.init();
});
test('conversation getter resolves at every hour of the day, whatever the viewer timezone', () {
// Regression for "Bad state: No conversation available": conversations are
// grouped by the *local* calendar day of their effective date
// (`conversationLocalDayKey` over startedAt ?? createdAt), and tapping a
// list item selects that group's key. The getter used to validate the raw
// UTC year/month/day instead, so it rejected a conversation that is present
// in the selected group whenever the local day and the UTC day disagree —
// an evening conversation for a UTC+ viewer, a post-UTC-midnight one for a
// UTC- viewer — blanking the detail page (#10976).
//
// Sweeping every hour rather than pinning one timestamp keeps this honest
// in whatever timezone the suite runs in; a single fixed hour only trips
// the bug at some UTC offsets, which is how it reached main green.
for (var hour = 0; hour < 24; hour++) {
final startedAt = DateTime.utc(2026, 7, 18, hour, 30);
final convo = ServerConversation(
id: 'c1',
startedAt: startedAt,
// Later than startedAt, so the last hour still spans UTC midnight.
createdAt: startedAt.add(const Duration(minutes: 45)),
structured: Structured('Title', 'Overview'),
status: ConversationStatus.completed,
);
final conversationProvider = ConversationProvider(
conversationListFetcher: () async => (items: <ServerConversation>[], ok: true),
isSignedIn: () => true,
);
addTearDown(conversationProvider.dispose);
conversationProvider.conversations = [convo];
conversationProvider.groupConversationsByDate();
// The date key the list item passes on tap is the group key.
final groupDate = conversationProvider.groupedConversations.keys.single;
final detailProvider = ConversationDetailProvider();
addTearDown(detailProvider.dispose);
detailProvider.conversationProvider = conversationProvider;
detailProvider.updateConversation(convo.id, groupDate);
expect(detailProvider.conversationOrNull, isNotNull, reason: 'startedAt ${startedAt.toIso8601String()}');
expect(detailProvider.conversation.id, 'c1');
expect(detailProvider.conversation.structured.title, 'Title');
}
});
test('selected conversation is never replaced by another one in the day group', () {
// The detail page drives delete, visibility and rename off this getter, so
// resolving to a different conversation destroys or publicly shares the
// wrong one. When the selected conversation leaves the group (deleted on
// another device, merged away, or filtered out by the discarded/short
// toggles) the getter must report a miss rather than substitute a sibling.
final selected = _conversationAt('selected', _localHourOnFixedDay(9));
final sibling = _conversationAt('sibling', _localHourOnFixedDay(11));
final conversationProvider = ConversationProvider(
conversationListFetcher: () async => (items: <ServerConversation>[], ok: true),
isSignedIn: () => true,
);
addTearDown(conversationProvider.dispose);
conversationProvider.conversations = [selected, sibling];
conversationProvider.groupConversationsByDate();
final groupDate = conversationProvider.groupedConversations.keys.single;
final detailProvider = ConversationDetailProvider();
addTearDown(detailProvider.dispose);
detailProvider.conversationProvider = conversationProvider;
detailProvider.updateConversation(selected.id, groupDate);
expect(detailProvider.conversation.id, 'selected');
conversationProvider.conversations = [sibling];
conversationProvider.groupConversationsByDate();
// Previously this resolved to the surviving sibling and rebound the tracked
// id to it, so a delete or visibility change hit the wrong conversation.
expect(detailProvider.conversationOrNull?.id, 'selected');
expect(detailProvider.conversationOrNull?.id, 'selected');
});
test('selected conversation survives a transient empty day group', () {
// A refresh can momentarily empty the group; the page must keep showing the
// conversation it was opened with instead of blanking or retargeting.
final selected = _conversationAt('selected', _localHourOnFixedDay(9));
final conversationProvider = ConversationProvider(
conversationListFetcher: () async => (items: <ServerConversation>[], ok: true),
isSignedIn: () => true,
);
addTearDown(conversationProvider.dispose);
conversationProvider.conversations = [selected];
conversationProvider.groupConversationsByDate();
final groupDate = conversationProvider.groupedConversations.keys.single;
final detailProvider = ConversationDetailProvider();
addTearDown(detailProvider.dispose);
detailProvider.conversationProvider = conversationProvider;
detailProvider.updateConversation(selected.id, groupDate);
conversationProvider.conversations = [];
conversationProvider.groupConversationsByDate();
expect(detailProvider.conversationOrNull?.id, 'selected');
});
}
/// A UTC instant that falls at [hour] o'clock local time on a fixed day, so
/// fixtures meant to share one day-group stay in the same group at any UTC
/// offset. Pinning the UTC hour instead split them across two local days at
/// extreme offsets (UTC+14, UTC-11) and blew up on `keys.single`.
DateTime _localHourOnFixedDay(int hour) => DateTime(2026, 7, 18, hour).toUtc();
ServerConversation _conversationAt(String id, DateTime startedAt) {
return ServerConversation(
id: id,
startedAt: startedAt,
createdAt: startedAt,
structured: Structured(id, 'Overview'),
status: ConversationStatus.completed,
);
}