Merge branch 'soru/fix-too-early-event-update' into 'master'
fix issue with sending messages See merge request famedly/famedlysdk!393
This commit is contained in:
commit
ad8135990d
|
@ -787,7 +787,7 @@ class MatrixApi {
|
|||
String stateKey = '',
|
||||
]) async {
|
||||
final response = await request(RequestType.PUT,
|
||||
'/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/state/${Uri.encodeQueryComponent(eventType)}/${Uri.encodeQueryComponent(stateKey)}',
|
||||
'/client/r0/rooms/${Uri.encodeComponent(roomId)}/state/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(stateKey)}',
|
||||
data: content);
|
||||
return response['event_id'];
|
||||
}
|
||||
|
@ -803,7 +803,7 @@ class MatrixApi {
|
|||
Map<String, dynamic> content,
|
||||
) async {
|
||||
final response = await request(RequestType.PUT,
|
||||
'/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/send/${Uri.encodeQueryComponent(eventType)}/${Uri.encodeQueryComponent(txnId)}',
|
||||
'/client/r0/rooms/${Uri.encodeComponent(roomId)}/send/${Uri.encodeComponent(eventType)}/${Uri.encodeComponent(txnId)}',
|
||||
data: content);
|
||||
return response['event_id'];
|
||||
}
|
||||
|
@ -818,7 +818,7 @@ class MatrixApi {
|
|||
String reason,
|
||||
}) async {
|
||||
final response = await request(RequestType.PUT,
|
||||
'/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/redact/${Uri.encodeQueryComponent(eventId)}/${Uri.encodeQueryComponent(txnId)}',
|
||||
'/client/r0/rooms/${Uri.encodeComponent(roomId)}/redact/${Uri.encodeComponent(eventId)}/${Uri.encodeComponent(txnId)}',
|
||||
data: {
|
||||
if (reason != null) 'reason': reason,
|
||||
});
|
||||
|
@ -1734,7 +1734,7 @@ class MatrixApi {
|
|||
Future<Map<String, Tag>> requestRoomTags(String userId, String roomId) async {
|
||||
final response = await request(
|
||||
RequestType.GET,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags',
|
||||
);
|
||||
return (response['tags'] as Map).map(
|
||||
(k, v) => MapEntry(k, Tag.fromJson(v)),
|
||||
|
@ -1750,7 +1750,7 @@ class MatrixApi {
|
|||
double order,
|
||||
}) async {
|
||||
await request(RequestType.PUT,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags/${Uri.encodeQueryComponent(tag)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}',
|
||||
data: {
|
||||
if (order != null) 'order': order,
|
||||
});
|
||||
|
@ -1762,7 +1762,7 @@ class MatrixApi {
|
|||
Future<void> removeRoomTag(String userId, String roomId, String tag) async {
|
||||
await request(
|
||||
RequestType.DELETE,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/tags/${Uri.encodeQueryComponent(tag)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/tags/${Uri.encodeComponent(tag)}',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -1777,7 +1777,7 @@ class MatrixApi {
|
|||
) async {
|
||||
await request(
|
||||
RequestType.PUT,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/account_data/${Uri.encodeQueryComponent(type)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}',
|
||||
data: content,
|
||||
);
|
||||
return;
|
||||
|
@ -1791,7 +1791,7 @@ class MatrixApi {
|
|||
) async {
|
||||
return await request(
|
||||
RequestType.GET,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/account_data/${Uri.encodeQueryComponent(type)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/account_data/${Uri.encodeComponent(type)}',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1806,7 +1806,7 @@ class MatrixApi {
|
|||
) async {
|
||||
await request(
|
||||
RequestType.PUT,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/account_data/${Uri.encodeQueryComponent(type)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}',
|
||||
data: content,
|
||||
);
|
||||
return;
|
||||
|
@ -1821,7 +1821,7 @@ class MatrixApi {
|
|||
) async {
|
||||
return await request(
|
||||
RequestType.GET,
|
||||
'/client/r0/user/${Uri.encodeQueryComponent(userId)}/rooms/${Uri.encodeQueryComponent(roomId)}/account_data/${Uri.encodeQueryComponent(type)}',
|
||||
'/client/r0/user/${Uri.encodeComponent(userId)}/rooms/${Uri.encodeComponent(roomId)}/account_data/${Uri.encodeComponent(type)}',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1830,7 +1830,7 @@ class MatrixApi {
|
|||
Future<WhoIsInfo> requestWhoIsInfo(String userId) async {
|
||||
final response = await request(
|
||||
RequestType.GET,
|
||||
'/client/r0/admin/whois/${Uri.encodeQueryComponent(userId)}',
|
||||
'/client/r0/admin/whois/${Uri.encodeComponent(userId)}',
|
||||
);
|
||||
return WhoIsInfo.fromJson(response);
|
||||
}
|
||||
|
@ -1845,7 +1845,7 @@ class MatrixApi {
|
|||
String filter,
|
||||
}) async {
|
||||
final response = await request(RequestType.GET,
|
||||
'/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/context/${Uri.encodeQueryComponent(eventId)}',
|
||||
'/client/r0/rooms/${Uri.encodeComponent(roomId)}/context/${Uri.encodeComponent(eventId)}',
|
||||
query: {
|
||||
if (filter != null) 'filter': filter,
|
||||
if (limit != null) 'limit': limit.toString(),
|
||||
|
@ -1862,7 +1862,7 @@ class MatrixApi {
|
|||
int score,
|
||||
) async {
|
||||
await request(RequestType.POST,
|
||||
'/client/r0/rooms/${Uri.encodeQueryComponent(roomId)}/report/${Uri.encodeQueryComponent(eventId)}',
|
||||
'/client/r0/rooms/${Uri.encodeComponent(roomId)}/report/${Uri.encodeComponent(eventId)}',
|
||||
data: {
|
||||
'reason': reason,
|
||||
'score': score,
|
||||
|
|
|
@ -361,9 +361,32 @@ class Database extends _$Database {
|
|||
if ((status == 1 || status == -1) &&
|
||||
eventContent['unsigned'] is Map<String, dynamic> &&
|
||||
eventContent['unsigned']['transaction_id'] is String) {
|
||||
// status changed and we have an old transaction id --> update event id and stuffs
|
||||
await updateEventStatus(status, eventContent['event_id'], clientId,
|
||||
eventContent['unsigned']['transaction_id'], chatId);
|
||||
final allOldEvents =
|
||||
await getEvent(clientId, eventContent['event_id'], chatId).get();
|
||||
if (allOldEvents.isNotEmpty) {
|
||||
// we were likely unable to change transaction_id -> event_id.....because the event ID already exists!
|
||||
// So, we try to fetch the old event
|
||||
// the transaction id event will automatically be deleted further down
|
||||
final oldEvent = allOldEvents.first;
|
||||
// do we update the status? We should allow 0 -> -1 updates and status increases
|
||||
if (status > oldEvent.status ||
|
||||
(oldEvent.status == 0 && status == -1)) {
|
||||
// update the status
|
||||
await updateEventStatusOnly(
|
||||
status, clientId, eventContent['event_id'], chatId);
|
||||
}
|
||||
} else {
|
||||
// status changed and we have an old transaction id --> update event id and stuffs
|
||||
try {
|
||||
await updateEventStatus(status, eventContent['event_id'], clientId,
|
||||
eventContent['unsigned']['transaction_id'], chatId);
|
||||
} catch (err) {
|
||||
// we could not update the transaction id to the event id....so it already exists
|
||||
// as we just tried to fetch the event previously this is a race condition if the event comes down sync in the mean time
|
||||
// that means that the status we already have in the database is likely more accurate
|
||||
// than our status. So, we just ignore this error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DbEvent oldEvent;
|
||||
if (type == 'history') {
|
||||
|
|
|
@ -6033,6 +6033,21 @@ abstract class _$Database extends GeneratedDatabase {
|
|||
);
|
||||
}
|
||||
|
||||
Future<int> updateEventStatusOnly(
|
||||
int status, int client_id, String event_id, String room_id) {
|
||||
return customUpdate(
|
||||
'UPDATE events SET status = :status WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id',
|
||||
variables: [
|
||||
Variable.withInt(status),
|
||||
Variable.withInt(client_id),
|
||||
Variable.withString(event_id),
|
||||
Variable.withString(room_id)
|
||||
],
|
||||
updates: {events},
|
||||
updateKind: UpdateKind.update,
|
||||
);
|
||||
}
|
||||
|
||||
DbRoomState _rowToDbRoomState(QueryRow row) {
|
||||
return DbRoomState(
|
||||
clientId: row.readInt('client_id'),
|
||||
|
|
|
@ -208,6 +208,7 @@ getAllAccountData: SELECT * FROM account_data WHERE client_id = :client_id;
|
|||
storeAccountData: INSERT OR REPLACE INTO account_data (client_id, type, content) VALUES (:client_id, :type, :content);
|
||||
updateEvent: UPDATE events SET unsigned = :unsigned, content = :content, prev_content = :prev_content WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id;
|
||||
updateEventStatus: UPDATE events SET status = :status, event_id = :new_event_id WHERE client_id = :client_id AND event_id = :old_event_id AND room_id = :room_id;
|
||||
updateEventStatusOnly: UPDATE events SET status = :status WHERE client_id = :client_id AND event_id = :event_id AND room_id = :room_id;
|
||||
getImportantRoomStates: SELECT * FROM room_states WHERE client_id = :client_id AND type IN :events;
|
||||
getAllRoomStates: SELECT * FROM room_states WHERE client_id = :client_id;
|
||||
getUnimportantRoomStatesForRoom: SELECT * FROM room_states WHERE client_id = :client_id AND room_id = :room_id AND type NOT IN :events;
|
||||
|
|
|
@ -312,7 +312,8 @@ class Event extends MatrixEvent {
|
|||
/// Try to send this event again. Only works with events of status -1.
|
||||
Future<String> sendAgain({String txid}) async {
|
||||
if (status != -1) return null;
|
||||
await remove();
|
||||
// we do not remove the event here. It will automatically be updated
|
||||
// in the `sendEvent` method to transition -1 -> 0 -> 1 -> 2
|
||||
final eventID = await room.sendEvent(
|
||||
content,
|
||||
txid: txid ?? unsigned['transaction_id'],
|
||||
|
|
|
@ -122,10 +122,31 @@ class Timeline {
|
|||
}
|
||||
|
||||
int _findEvent({String event_id, String unsigned_txid}) {
|
||||
// we want to find any existing event where either the passed event_id or the passed unsigned_txid
|
||||
// matches either the event_id or transaction_id of the existing event.
|
||||
// For that we create two sets, searchNeedle, what we search, and searchHaystack, where we check if there is a match.
|
||||
// Now, after having these two sets, if the intersect between them is non-empty, we know that we have at least one match in one pair,
|
||||
// thus meaning we found our element.
|
||||
final searchNeedle = <String>{};
|
||||
if (event_id != null) {
|
||||
searchNeedle.add(event_id);
|
||||
}
|
||||
if (unsigned_txid != null) {
|
||||
searchNeedle.add(unsigned_txid);
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < events.length; i++) {
|
||||
if (events[i].eventId == event_id ||
|
||||
(unsigned_txid != null && events[i].eventId == unsigned_txid)) break;
|
||||
final searchHaystack = <String>{};
|
||||
if (events[i].eventId != null) {
|
||||
searchHaystack.add(events[i].eventId);
|
||||
}
|
||||
if (events[i].unsigned != null &&
|
||||
events[i].unsigned['transaction_id'] != null) {
|
||||
searchHaystack.add(events[i].unsigned['transaction_id']);
|
||||
}
|
||||
if (searchNeedle.intersection(searchHaystack).isNotEmpty) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
@ -135,6 +156,7 @@ class Timeline {
|
|||
if (eventUpdate.roomID != room.id) return;
|
||||
|
||||
if (eventUpdate.type == 'timeline' || eventUpdate.type == 'history') {
|
||||
var status = eventUpdate.content['status'] ?? 2;
|
||||
// Redaction events are handled as modification for existing events.
|
||||
if (eventUpdate.eventType == EventTypes.Redaction) {
|
||||
final eventId = _findEvent(event_id: eventUpdate.content['redacts']);
|
||||
|
@ -142,13 +164,10 @@ class Timeline {
|
|||
events[eventId].setRedactionEvent(Event.fromJson(
|
||||
eventUpdate.content, room, eventUpdate.sortOrder));
|
||||
}
|
||||
} else if (eventUpdate.content['status'] == -2) {
|
||||
} else if (status == -2) {
|
||||
var i = _findEvent(event_id: eventUpdate.content['event_id']);
|
||||
if (i < events.length) events.removeAt(i);
|
||||
}
|
||||
// Is this event already in the timeline?
|
||||
else if (eventUpdate.content['unsigned'] is Map &&
|
||||
eventUpdate.content['unsigned']['transaction_id'] is String) {
|
||||
} else {
|
||||
var i = _findEvent(
|
||||
event_id: eventUpdate.content['event_id'],
|
||||
unsigned_txid: eventUpdate.content['unsigned'] is Map
|
||||
|
@ -156,34 +175,29 @@ class Timeline {
|
|||
: null);
|
||||
|
||||
if (i < events.length) {
|
||||
// we want to preserve the old sort order
|
||||
final tempSortOrder = events[i].sortOrder;
|
||||
// if the old status is larger than the new one, we also want to preserve the old status
|
||||
final oldStatus = events[i].status;
|
||||
events[i] = Event.fromJson(
|
||||
eventUpdate.content, room, eventUpdate.sortOrder);
|
||||
events[i].sortOrder = tempSortOrder;
|
||||
// do we preserve the status? we should allow 0 -> -1 updates and status increases
|
||||
if (status < oldStatus && !(status == -1 && oldStatus == 0)) {
|
||||
events[i].status = oldStatus;
|
||||
}
|
||||
} else {
|
||||
var newEvent = Event.fromJson(
|
||||
eventUpdate.content, room, eventUpdate.sortOrder);
|
||||
|
||||
if (eventUpdate.type == 'history' &&
|
||||
events.indexWhere(
|
||||
(e) => e.eventId == eventUpdate.content['event_id']) !=
|
||||
-1) return;
|
||||
|
||||
events.insert(0, newEvent);
|
||||
if (onInsert != null) onInsert(0);
|
||||
}
|
||||
} else {
|
||||
Event newEvent;
|
||||
var senderUser = room
|
||||
.getState(
|
||||
EventTypes.RoomMember, eventUpdate.content['sender'])
|
||||
?.asUser ??
|
||||
await room.client.database?.getUser(
|
||||
room.client.id, eventUpdate.content['sender'], room);
|
||||
if (senderUser != null) {
|
||||
eventUpdate.content['displayname'] = senderUser.displayName;
|
||||
eventUpdate.content['avatar_url'] = senderUser.avatarUrl.toString();
|
||||
}
|
||||
|
||||
newEvent =
|
||||
Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder);
|
||||
|
||||
if (eventUpdate.type == 'history' &&
|
||||
events.indexWhere(
|
||||
(e) => e.eventId == eventUpdate.content['event_id']) !=
|
||||
-1) return;
|
||||
|
||||
events.insert(0, newEvent);
|
||||
if (onInsert != null) onInsert(0);
|
||||
}
|
||||
}
|
||||
sortAndUpdate();
|
||||
|
|
|
@ -89,7 +89,7 @@ void main() {
|
|||
// account_data for this test
|
||||
final content = FakeMatrixApi
|
||||
.calledEndpoints[
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best+animal']
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best%20animal']
|
||||
.first;
|
||||
client.accountData['best animal'] = BasicEvent.fromJson({
|
||||
'type': 'best animal',
|
||||
|
|
|
@ -80,7 +80,7 @@ class FakeMatrixApi extends MockClient {
|
|||
res = {'displayname': ''};
|
||||
} else if (method == 'PUT' &&
|
||||
action.contains(
|
||||
'/client/r0/rooms/%211234%3AfakeServer.notExisting/send/')) {
|
||||
'/client/r0/rooms/!1234%3AfakeServer.notExisting/send/')) {
|
||||
res = {'event_id': '\$event${FakeMatrixApi.eventCounter++}'};
|
||||
} else {
|
||||
res = {
|
||||
|
@ -748,7 +748,7 @@ class FakeMatrixApi extends MockClient {
|
|||
'app_url': 'https://custom.app.example.org'
|
||||
}
|
||||
},
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags':
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags':
|
||||
(var req) => {
|
||||
'tags': {
|
||||
'm.favourite': {'order': 0.1},
|
||||
|
@ -1982,21 +1982,21 @@ class FakeMatrixApi extends MockClient {
|
|||
(var req) => {},
|
||||
'/client/r0/pushrules/global/content/nocake/enabled': (var req) => {},
|
||||
'/client/r0/pushrules/global/content/nocake/actions': (var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.history_visibility':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.history_visibility':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.join_rules':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.join_rules':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.guest_access':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.guest_access':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.invite/1234':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.call.invite/1234':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.answer/1234':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.call.answer/1234':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.candidates/1234':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.call.candidates/1234':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.call.hangup/1234':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.call.hangup/1234':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%211234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234':
|
||||
'/client/r0/rooms/!1234%3Aexample.com/redact/1143273582443PhrSn%3Aexample.org/1234':
|
||||
(var req) => {'event_id': '1234'},
|
||||
'/client/r0/pushrules/global/room/!localpart%3Aserver.abc': (var req) =>
|
||||
{},
|
||||
|
@ -2006,23 +2006,27 @@ class FakeMatrixApi extends MockClient {
|
|||
(var req) => {},
|
||||
'/client/r0/devices/QBUAZIFURK': (var req) => {},
|
||||
'/client/r0/directory/room/%23testalias%3Aexample.com': (var reqI) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.message/testtxid':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.message/testtxid':
|
||||
(var reqI) => {
|
||||
'event_id': '\$event${FakeMatrixApi.eventCounter++}',
|
||||
},
|
||||
'/client/r0/rooms/!localpart%3Aexample.com/typing/%40alice%3Aexample.com':
|
||||
(var req) => {},
|
||||
'/client/r0/rooms/%211234%3Aexample.com/send/m.room.message/1234':
|
||||
'/client/r0/rooms/!1234%3Aexample.com/send/m.room.message/1234':
|
||||
(var reqI) => {
|
||||
'event_id': '\$event${FakeMatrixApi.eventCounter++}',
|
||||
},
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/rooms/%21localpart%3Aserver.abc/tags/m.favourite':
|
||||
'/client/r0/rooms/!1234%3Aexample.com/send/m.room.message/newresend':
|
||||
(var reqI) => {
|
||||
'event_id': '\$event${FakeMatrixApi.eventCounter++}',
|
||||
},
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags/testtag':
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40alice%3Aexample.com/account_data/test.account.data':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best+animal':
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/account_data/best%20animal':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/1234/account_data/test.account.data':
|
||||
(var req) => {},
|
||||
|
@ -2034,27 +2038,27 @@ class FakeMatrixApi extends MockClient {
|
|||
'/client/r0/profile/%40alice%3Aexample.com/avatar_url': (var reqI) => {},
|
||||
'/client/r0/profile/%40test%3AfakeServer.notExisting/avatar_url':
|
||||
(var reqI) => {},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.encryption':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.encryption':
|
||||
(var reqI) => {'event_id': 'YUwRidLecu:example.com'},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.avatar':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.avatar':
|
||||
(var reqI) => {'event_id': 'YUwRidLecu:example.com'},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/send/m.room.message/1234':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/send/m.room.message/1234':
|
||||
(var reqI) => {'event_id': 'YUwRidLecu:example.com'},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/redact/1234/1234':
|
||||
(var reqI) => {'event_id': 'YUwRidLecu:example.com'},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.name':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/redact/1234/1234': (var reqI) =>
|
||||
{'event_id': 'YUwRidLecu:example.com'},
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.name':
|
||||
(var reqI) => {
|
||||
'event_id': '42',
|
||||
},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.topic':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.topic':
|
||||
(var reqI) => {
|
||||
'event_id': '42',
|
||||
},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.pinned_events':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.pinned_events':
|
||||
(var reqI) => {
|
||||
'event_id': '42',
|
||||
},
|
||||
'/client/r0/rooms/%21localpart%3Aserver.abc/state/m.room.power_levels':
|
||||
'/client/r0/rooms/!localpart%3Aserver.abc/state/m.room.power_levels':
|
||||
(var reqI) => {
|
||||
'event_id': '42',
|
||||
},
|
||||
|
@ -2083,9 +2087,9 @@ class FakeMatrixApi extends MockClient {
|
|||
'/client/r0/pushrules/global/content/nocake': (var req) => {},
|
||||
'/client/r0/pushrules/global/override/!localpart%3Aserver.abc':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/rooms/%21localpart%3Aserver.abc/tags/m.favourite':
|
||||
'/client/r0/user/%40test%3AfakeServer.notExisting/rooms/!localpart%3Aserver.abc/tags/m.favourite':
|
||||
(var req) => {},
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags/testtag':
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags/testtag':
|
||||
(var req) => {},
|
||||
'/client/unstable/room_keys/version/5': (var req) => {},
|
||||
'/client/unstable/room_keys/keys/${Uri.encodeComponent('!726s6s6q:example.com')}/${Uri.encodeComponent('ciM/JWTPrmiWPPZNkRLDPQYf9AW/I46bxyLSr+Bx5oU')}?version=5':
|
||||
|
|
|
@ -1377,7 +1377,7 @@ void main() {
|
|||
'@alice:example.com', '!localpart:example.com');
|
||||
expect(
|
||||
FakeMatrixApi.api['GET'][
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/%21localpart%3Aexample.com/tags']({}),
|
||||
'/client/r0/user/%40alice%3Aexample.com/rooms/!localpart%3Aexample.com/tags']({}),
|
||||
{'tags': response.map((k, v) => MapEntry(k, v.toJson()))},
|
||||
);
|
||||
|
||||
|
|
184
test/matrix_database_test.dart
Normal file
184
test/matrix_database_test.dart
Normal file
|
@ -0,0 +1,184 @@
|
|||
/*
|
||||
* Famedly Matrix SDK
|
||||
* Copyright (C) 2020 Famedly GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import 'package:famedlysdk/famedlysdk.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'fake_database.dart';
|
||||
|
||||
void main() {
|
||||
group('Databse', () {
|
||||
final database = getDatabase();
|
||||
var clientId = -1;
|
||||
var room = Room(id: '!room:blubb');
|
||||
test('setupDatabase', () async {
|
||||
clientId = await database.insertClient(
|
||||
'testclient',
|
||||
'https://example.org',
|
||||
'blubb',
|
||||
'@test:example.org',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
});
|
||||
test('storeEventUpdate', () async {
|
||||
// store a simple update
|
||||
var update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-1',
|
||||
'sender': '@blah:blubb',
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
var event = await database.getEventById(clientId, '\$event-1', room);
|
||||
expect(event.eventId, '\$event-1');
|
||||
|
||||
// insert a transaction id
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': 'transaction-1',
|
||||
'sender': '@blah:blubb',
|
||||
'status': 0,
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, 'transaction-1', room);
|
||||
expect(event.eventId, 'transaction-1');
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-2',
|
||||
'sender': '@blah:blubb',
|
||||
'unsigned': {
|
||||
'transaction_id': 'transaction-1',
|
||||
},
|
||||
'status': 1,
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, 'transaction-1', room);
|
||||
expect(event, null);
|
||||
event = await database.getEventById(clientId, '\$event-2', room);
|
||||
|
||||
// insert a transaction id if the event id for it already exists
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-3',
|
||||
'sender': '@blah:blubb',
|
||||
'status': 0,
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, '\$event-3', room);
|
||||
expect(event.eventId, '\$event-3');
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-3',
|
||||
'sender': '@blah:blubb',
|
||||
'status': 1,
|
||||
'unsigned': {
|
||||
'transaction_id': 'transaction-2',
|
||||
},
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, '\$event-3', room);
|
||||
expect(event.eventId, '\$event-3');
|
||||
expect(event.status, 1);
|
||||
event = await database.getEventById(clientId, 'transaction-2', room);
|
||||
expect(event, null);
|
||||
|
||||
// insert transaction id and not update status
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-4',
|
||||
'sender': '@blah:blubb',
|
||||
'status': 2,
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, '\$event-4', room);
|
||||
expect(event.eventId, '\$event-4');
|
||||
update = EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: room.id,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'origin_server_ts': 100,
|
||||
'content': {'blah': 'blubb'},
|
||||
'event_id': '\$event-4',
|
||||
'sender': '@blah:blubb',
|
||||
'status': 1,
|
||||
'unsigned': {
|
||||
'transaction_id': 'transaction-3',
|
||||
},
|
||||
},
|
||||
sortOrder: 0.0,
|
||||
);
|
||||
await database.storeEventUpdate(clientId, update);
|
||||
event = await database.getEventById(clientId, '\$event-4', room);
|
||||
expect(event.eventId, '\$event-4');
|
||||
expect(event.status, 2);
|
||||
event = await database.getEventById(clientId, 'transaction-3', room);
|
||||
expect(event, null);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -214,14 +214,29 @@ void main() {
|
|||
});
|
||||
|
||||
test('Resend message', () async {
|
||||
await timeline.events[0].sendAgain(txid: '1234');
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': -1,
|
||||
'event_id': 'new-test-event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'newresend'},
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
await timeline.events[0].sendAgain();
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
expect(updateCount, 17);
|
||||
|
||||
expect(insertList, [0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
expect(timeline.events.length, 6);
|
||||
expect(timeline.events.length, 7);
|
||||
expect(timeline.events[0].status, 1);
|
||||
});
|
||||
|
||||
|
@ -231,12 +246,12 @@ void main() {
|
|||
await Future.delayed(Duration(milliseconds: 50));
|
||||
|
||||
expect(updateCount, 20);
|
||||
expect(timeline.events.length, 9);
|
||||
expect(timeline.events[6].eventId, '3143273582443PhrSn:example.org');
|
||||
expect(timeline.events[7].eventId, '2143273582443PhrSn:example.org');
|
||||
expect(timeline.events[8].eventId, '1143273582443PhrSn:example.org');
|
||||
expect(timeline.events.length, 10);
|
||||
expect(timeline.events[7].eventId, '3143273582443PhrSn:example.org');
|
||||
expect(timeline.events[8].eventId, '2143273582443PhrSn:example.org');
|
||||
expect(timeline.events[9].eventId, '1143273582443PhrSn:example.org');
|
||||
expect(room.prev_batch, 't47409-4357353_219380_26003_2265');
|
||||
await timeline.events[8].redact(reason: 'test', txid: '1234');
|
||||
await timeline.events[9].redact(reason: 'test', txid: '1234');
|
||||
});
|
||||
|
||||
test('Clear cache on limited timeline', () async {
|
||||
|
@ -251,5 +266,253 @@ void main() {
|
|||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events.isEmpty, true);
|
||||
});
|
||||
|
||||
test('sending event to failed update', () async {
|
||||
timeline.events.clear();
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 0,
|
||||
'event_id': 'will-fail',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 0);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': -1,
|
||||
'event_id': 'will-fail',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, -1);
|
||||
expect(timeline.events.length, 1);
|
||||
});
|
||||
test('sending an event and the http request finishes first, 0 -> 1 -> 2',
|
||||
() async {
|
||||
timeline.events.clear();
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 0,
|
||||
'event_id': 'transaction',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 0);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 1,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'}
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 1);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 2,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'}
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
});
|
||||
test('sending an event where the sync reply arrives first, 0 -> 2 -> 1',
|
||||
() async {
|
||||
timeline.events.clear();
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 0,
|
||||
'event_id': 'transaction',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 0);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 2,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'}
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 1,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'}
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
});
|
||||
test('sending an event 0 -> -1 -> 2', () async {
|
||||
timeline.events.clear();
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 0,
|
||||
'event_id': 'transaction',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 0);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': -1,
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'},
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, -1);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 2,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'},
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
});
|
||||
test('sending an event 0 -> 2 -> -1', () async {
|
||||
timeline.events.clear();
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 0,
|
||||
'event_id': 'transaction',
|
||||
'origin_server_ts': testTimeStamp
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 0);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': 2,
|
||||
'event_id': '\$event',
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'},
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
client.onEvent.add(EventUpdate(
|
||||
type: 'timeline',
|
||||
roomID: roomID,
|
||||
eventType: 'm.room.message',
|
||||
content: {
|
||||
'type': 'm.room.message',
|
||||
'content': {'msgtype': 'm.text', 'body': 'Testcase'},
|
||||
'sender': '@alice:example.com',
|
||||
'status': -1,
|
||||
'origin_server_ts': testTimeStamp,
|
||||
'unsigned': {'transaction_id': 'transaction'},
|
||||
},
|
||||
sortOrder: room.newSortOrder));
|
||||
await Future.delayed(Duration(milliseconds: 50));
|
||||
expect(timeline.events[0].status, 2);
|
||||
expect(timeline.events.length, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue