feat: отображение того что тебя пинговали в чате
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:komet/backend/modules/chat_parsing.dart';
|
||||
import 'package:komet/backend/modules/chats.dart';
|
||||
|
||||
const int _me = 4242;
|
||||
const int _peer = 7331;
|
||||
const int _chatId = -1000000000001;
|
||||
|
||||
const int _readMark = 1700000000000;
|
||||
const int _mentionTime = 1700000005000;
|
||||
const int _lastMsgTime = 1700000010000;
|
||||
|
||||
const int _mentionId = 111411200327680777;
|
||||
const int _lastMsgId = 111411200655360012;
|
||||
const int _oldMentionId = 111411196067840005;
|
||||
|
||||
CachedChat _chat({
|
||||
int? mentionId,
|
||||
int readMark = _readMark,
|
||||
int unread = 2,
|
||||
}) => CachedChat(
|
||||
id: _chatId,
|
||||
accountId: _me,
|
||||
type: 'CHAT',
|
||||
unreadCount: unread,
|
||||
lastEventTime: _lastMsgTime,
|
||||
cachedAt: 0,
|
||||
dontDisturbUntil: 0,
|
||||
isOnline: false,
|
||||
seenTime: 0,
|
||||
participants: {_me: readMark, _peer: _lastMsgTime},
|
||||
lastMentionMsgId: mentionId,
|
||||
);
|
||||
|
||||
void main() {
|
||||
group('message id', () {
|
||||
test('carries the timestamp in its high bits', () {
|
||||
expect(messageIdToTime(_mentionId), _mentionTime);
|
||||
expect(messageIdToTime(_lastMsgId), _lastMsgTime);
|
||||
expect(messageIdToTime(_oldMentionId), _readMark - 60000);
|
||||
});
|
||||
});
|
||||
|
||||
group('hasUnreadMention', () {
|
||||
test('is set when the mention is newer than my read mark', () {
|
||||
expect(_chat(mentionId: _mentionId).hasUnreadMention, isTrue);
|
||||
});
|
||||
|
||||
test('stays off for a mention I have already read', () {
|
||||
expect(_chat(mentionId: _oldMentionId).hasUnreadMention, isFalse);
|
||||
});
|
||||
|
||||
test('clears once the read mark passes the mention', () {
|
||||
final read = _chat(
|
||||
mentionId: _mentionId,
|
||||
readMark: _lastMsgTime,
|
||||
unread: 0,
|
||||
);
|
||||
expect(read.hasUnreadMention, isFalse);
|
||||
});
|
||||
|
||||
test('is false without a mention id', () {
|
||||
expect(_chat().hasUnreadMention, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('parseChatRow', () {
|
||||
CachedChat parse(Map<String, dynamic> chat) => parseChatRow(
|
||||
chat,
|
||||
_me,
|
||||
_me,
|
||||
const {},
|
||||
const {},
|
||||
const {},
|
||||
const {},
|
||||
0,
|
||||
)!;
|
||||
|
||||
test('reads lastMentionMessageId from the server chat', () {
|
||||
final chat = parse({
|
||||
'id': _chatId,
|
||||
'type': 'CHAT',
|
||||
'title': 'test mention',
|
||||
'newMessages': 2,
|
||||
'lastEventTime': _lastMsgTime,
|
||||
'participants': {'$_me': _readMark},
|
||||
'lastMentionMessageId': '$_mentionId',
|
||||
});
|
||||
|
||||
expect(chat.lastMentionMsgId, _mentionId);
|
||||
expect(chat.hasUnreadMention, isTrue);
|
||||
});
|
||||
|
||||
test('a chat without mentions keeps the badge off', () {
|
||||
final chat = parse({
|
||||
'id': _chatId,
|
||||
'type': 'CHAT',
|
||||
'title': 'test',
|
||||
'lastEventTime': _lastMsgTime,
|
||||
'participants': {'$_me': _readMark},
|
||||
});
|
||||
|
||||
expect(chat.lastMentionMsgId, isNull);
|
||||
expect(chat.hasUnreadMention, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('messageMentionsUser', () {
|
||||
test('matches a USER_MENTION addressed to me', () {
|
||||
expect(
|
||||
messageMentionsUser(const {
|
||||
'elements': [
|
||||
{'type': 'USER_MENTION', 'entityId': _me, 'length': 15},
|
||||
],
|
||||
}, _me),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('ignores a mention of somebody else', () {
|
||||
expect(
|
||||
messageMentionsUser(const {
|
||||
'elements': [
|
||||
{'type': 'USER_MENTION', 'entityId': _peer, 'length': 15},
|
||||
],
|
||||
}, _me),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
|
||||
test('ignores messages without elements', () {
|
||||
expect(messageMentionsUser(const {'text': 'privet'}, _me), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
+16
-16
@@ -44,8 +44,8 @@ void main() {
|
||||
);
|
||||
final query = mentionQueryAt(c.text, 3)!;
|
||||
c.insertMention(
|
||||
userId: 3079465,
|
||||
name: 'Алексей Поляков',
|
||||
userId: 555001,
|
||||
name: 'Пётр Синицын',
|
||||
start: query.start,
|
||||
end: query.end,
|
||||
);
|
||||
@@ -55,9 +55,9 @@ void main() {
|
||||
);
|
||||
|
||||
final content = c.buildContent();
|
||||
expect(content.text, 'Алексей Поляков test');
|
||||
expect(content.text, 'Пётр Синицын test');
|
||||
expect(content.elements, [
|
||||
{'type': 'USER_MENTION', 'from': 0, 'length': 15, 'entityId': 3079465},
|
||||
{'type': 'USER_MENTION', 'from': 0, 'length': 12, 'entityId': 555001},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -139,21 +139,21 @@ void main() {
|
||||
test('a non-contact falls back to the ONEME name', () {
|
||||
final contact = info([
|
||||
{
|
||||
'name': 'Алексей',
|
||||
'firstName': 'Алексей',
|
||||
'lastName': 'Поляков',
|
||||
'name': 'Пётр',
|
||||
'firstName': 'Пётр',
|
||||
'lastName': 'Синицын',
|
||||
'type': 'ONEME',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(contact.fullName, 'Алексей Поляков');
|
||||
expect(contact.fullName, 'Пётр Синицын');
|
||||
expect(contact.isSavedContact, isFalse);
|
||||
});
|
||||
|
||||
test('a custom name wins over the oneme one', () {
|
||||
final contact = info([
|
||||
{'firstName': 'Лёша', 'lastName': 'сосед', 'type': 'CUSTOM'},
|
||||
{'firstName': 'Алексей', 'lastName': 'Поляков', 'type': 'ONEME'},
|
||||
{'firstName': 'Пётр', 'lastName': 'Синицын', 'type': 'ONEME'},
|
||||
]);
|
||||
|
||||
expect(contact.fullName, 'Лёша сосед');
|
||||
@@ -163,24 +163,24 @@ void main() {
|
||||
group('parseFormatElements', () {
|
||||
test('reads a server USER_MENTION without an explicit from', () {
|
||||
final ranges = parseFormatElements([
|
||||
{'entityId': 3079465, 'type': 'USER_MENTION', 'length': 15},
|
||||
{'entityId': 555001, 'type': 'USER_MENTION', 'length': 12},
|
||||
]);
|
||||
expect(ranges.single.format, TextFormat.userMention);
|
||||
expect(ranges.single.start, 0);
|
||||
expect(ranges.single.length, 15);
|
||||
expect(ranges.single.entityId, 3079465);
|
||||
expect(ranges.single.length, 12);
|
||||
expect(ranges.single.entityId, 555001);
|
||||
});
|
||||
|
||||
test('segmentizeFormats carries the mention id onto its segment', () {
|
||||
final segments = segmentizeFormats('Алексей Поляков test', const [
|
||||
final segments = segmentizeFormats('Пётр Синицын test', const [
|
||||
FormatRange(
|
||||
format: TextFormat.userMention,
|
||||
start: 0,
|
||||
length: 15,
|
||||
entityId: 3079465,
|
||||
length: 12,
|
||||
entityId: 555001,
|
||||
),
|
||||
]);
|
||||
expect(segments.first.mentionId, 3079465);
|
||||
expect(segments.first.mentionId, 555001);
|
||||
expect(segments.last.mentionId, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ CachedMessage _message({
|
||||
'message': {
|
||||
'id': '9',
|
||||
'sender': _me,
|
||||
'text': 'Алексей Поляков написал очень длинный ответ',
|
||||
'text': 'Пётр Синицын написал очень длинный ответ',
|
||||
'time': 0,
|
||||
'attaches': [],
|
||||
},
|
||||
@@ -68,14 +68,14 @@ Rect _rectOf(WidgetTester tester, Finder finder) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(() => ContactCache.put(_peer, 'Алексей Поляков123'));
|
||||
setUp(() => ContactCache.put(_peer, 'Пётр Синицын'));
|
||||
|
||||
testWidgets('a long sender name pushes the clock to the bubble edge', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpBubble(tester, _message(text: 'нет'));
|
||||
|
||||
final header = _rectOf(tester, find.text('Алексей Поляков123'));
|
||||
final header = _rectOf(tester, find.text('Пётр Синицын'));
|
||||
final clock = _rectOf(tester, find.textContaining('05:46'));
|
||||
final body = _rectOf(tester, find.text('нет'));
|
||||
|
||||
@@ -88,7 +88,7 @@ void main() {
|
||||
) async {
|
||||
await _pumpBubble(tester, _message(text: 'нет', withReply: true));
|
||||
|
||||
final header = _rectOf(tester, find.text('Алексей Поляков123'));
|
||||
final header = _rectOf(tester, find.text('Пётр Синицын'));
|
||||
final label = _rectOf(tester, find.text('Вы'));
|
||||
final quote = _rectOf(
|
||||
tester,
|
||||
|
||||
@@ -4,27 +4,27 @@ import 'package:komet/core/utils/text_entities.dart';
|
||||
void main() {
|
||||
group('detectTextEntities', () {
|
||||
test('finds a phone and a card in one message', () {
|
||||
final found = detectTextEntities('+79231234567 тест 2200123456789019');
|
||||
final found = detectTextEntities('+70001234567 тест 2200123456789019');
|
||||
|
||||
expect(found, hasLength(2));
|
||||
expect(found.first.kind, TextEntityKind.phone);
|
||||
expect(found.first.value, '+79231234567');
|
||||
expect(found.first.value, '+70001234567');
|
||||
expect(found.last.kind, TextEntityKind.card);
|
||||
expect(found.last.value, '2200123456789019');
|
||||
});
|
||||
|
||||
test('finds a bare russian phone and a spaced card', () {
|
||||
final found = detectTextEntities('89231234567 и 2200 1234 5678 9019');
|
||||
final found = detectTextEntities('80001234567 и 2200 1234 5678 9019');
|
||||
expect(found.map((e) => e.kind), [
|
||||
TextEntityKind.phone,
|
||||
TextEntityKind.card,
|
||||
]);
|
||||
expect(found.first.value, '+89231234567');
|
||||
expect(found.first.value, '+80001234567');
|
||||
expect(found.last.value, '2200123456789019');
|
||||
});
|
||||
|
||||
test('ignores digits that are not a valid card', () {
|
||||
expect(detectTextEntities('116984447620359334'), isEmpty);
|
||||
expect(detectTextEntities('111411200327680777'), isEmpty);
|
||||
expect(detectTextEntities('2200123456789018'), isEmpty);
|
||||
expect(detectTextEntities('1234567890123456'), isEmpty);
|
||||
});
|
||||
@@ -34,23 +34,23 @@ void main() {
|
||||
});
|
||||
|
||||
test('finds a nickname but not an email', () {
|
||||
final found = detectTextEntities('привет @GroupGuardBot и mail@ya.ru');
|
||||
final found = detectTextEntities('привет @ExampleBot и mail@ya.ru');
|
||||
expect(found, hasLength(1));
|
||||
expect(found.single.kind, TextEntityKind.mention);
|
||||
expect(found.single.value, 'GroupGuardBot');
|
||||
expect(found.single.value, 'ExampleBot');
|
||||
expect(found.single.start, 7);
|
||||
expect(found.single.end, 21);
|
||||
expect(found.single.end, 18);
|
||||
});
|
||||
|
||||
test('finds a formatted profile phone', () {
|
||||
final found = detectTextEntities('+7 (923) 123-45-67');
|
||||
final found = detectTextEntities('+7 (000) 123-45-67');
|
||||
expect(found, hasLength(1));
|
||||
expect(found.single.kind, TextEntityKind.phone);
|
||||
expect(found.single.value, '+79231234567');
|
||||
expect(found.single.value, '+70001234567');
|
||||
});
|
||||
|
||||
test('skips ranges that are already claimed', () {
|
||||
const text = 'https://max.ru/GroupGuardBot';
|
||||
const text = 'https://max.ru/ExampleBot';
|
||||
expect(
|
||||
detectTextEntities(text, skip: [(start: 0, end: text.length)]),
|
||||
isEmpty,
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:komet/frontend/widgets/message_bubble.dart';
|
||||
import 'package:komet/frontend/widgets/text_entity_actions.dart';
|
||||
import 'package:komet/l10n/app_localizations.dart';
|
||||
|
||||
const String _sample = '+79231234567 тест 2200123456789019 @GroupGuardBot';
|
||||
const String _sample = '+70001234567 тест 2200123456789019 @ExampleBot';
|
||||
|
||||
CachedMessage _message(String text) => CachedMessage(
|
||||
id: '1',
|
||||
@@ -67,9 +67,9 @@ void main() {
|
||||
);
|
||||
|
||||
final accent = ThemeData().colorScheme.primary;
|
||||
final phone = _spanWithText(tester, '+79231234567');
|
||||
final phone = _spanWithText(tester, '+70001234567');
|
||||
final card = _spanWithText(tester, '2200123456789019');
|
||||
final mention = _spanWithText(tester, '@GroupGuardBot');
|
||||
final mention = _spanWithText(tester, '@ExampleBot');
|
||||
final plain = _spanWithText(tester, ' тест ');
|
||||
|
||||
expect(phone?.style?.color, accent);
|
||||
@@ -90,12 +90,12 @@ void main() {
|
||||
accountId: 1,
|
||||
chatId: 2,
|
||||
senderId: 1,
|
||||
text: '@GroupGuardBot test',
|
||||
text: '@ExampleBot test',
|
||||
time: DateTime(2026, 1, 1, 5, 46).millisecondsSinceEpoch,
|
||||
status: 'sent',
|
||||
payload: const {
|
||||
'elements': [
|
||||
{'entityName': 'GroupGuardBot', 'type': 'USER_MENTION', 'length': 14},
|
||||
{'entityName': 'ExampleBot', 'type': 'USER_MENTION', 'length': 11},
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -105,7 +105,7 @@ void main() {
|
||||
MessageBubble(message: message, isMe: false, myId: 1, chatType: 'DIALOG'),
|
||||
);
|
||||
|
||||
final mention = _spanWithText(tester, '@GroupGuardBot');
|
||||
final mention = _spanWithText(tester, '@ExampleBot');
|
||||
expect(mention?.style?.color, ThemeData().colorScheme.primary);
|
||||
expect(mention?.recognizer, isA<TapGestureRecognizer>());
|
||||
});
|
||||
@@ -122,7 +122,7 @@ void main() {
|
||||
);
|
||||
|
||||
expect(
|
||||
_spanWithText(tester, '+79231234567')?.recognizer,
|
||||
_spanWithText(tester, '+70001234567')?.recognizer,
|
||||
isA<TapGestureRecognizer>(),
|
||||
);
|
||||
expect(
|
||||
|
||||
Reference in New Issue
Block a user