From 5ffa99d994fbcfb861c8b62e65cf42f89aec3222 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 17:49:50 +0100 Subject: [PATCH 1/7] fix: Do not assume a missing timestamp means "now" This causes issues with state handling as we prefer the newer event. It also has knock-on effects in other places. Instead set such events to have an obviously invalid timestamp, which makes issues easier to identify. This may break invites showing a timestamp or the timestamps for just sent events, if you rely on the timestamp getting set to "now". --- lib/src/event.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/event.dart b/lib/src/event.dart index b5549d1d..650f3d93 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -210,10 +210,8 @@ class Event extends MatrixEvent { type: jsonPayload['type'], eventId: jsonPayload['event_id'] ?? '', senderId: jsonPayload['sender'], - originServerTs: jsonPayload['origin_server_ts'] != null - ? DateTime.fromMillisecondsSinceEpoch( - jsonPayload['origin_server_ts']) - : DateTime.now(), + originServerTs: DateTime.fromMillisecondsSinceEpoch( + jsonPayload['origin_server_ts'] ?? 0), unsigned: unsigned, room: room, originalSource: originalSource.isEmpty From 6f82e92f2c5c1ac7544de2c919e0a4716713a2f4 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 17:52:06 +0100 Subject: [PATCH 2/7] fix: properly overwrite loaded state for partial loaded rooms Otherwise invites, which get converted to normal rooms, might still have old member events set in memory, that don't match the current state. --- lib/src/client.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/client.dart b/lib/src/client.dart index 4ba8ce9b..e84dd1c4 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -2463,6 +2463,10 @@ class Client extends MatrixApi { final importantOrRoomLoaded = eventUpdate.type == EventUpdateType.inviteState || !room.partial || + // make sure we do overwrite events we have already loaded. + room.states[stateEvent.type] + ?.containsKey(stateEvent.stateKey ?? '') == + true || importantStateEvents.contains(stateEvent.type); if ((noMessageOrNoEdit || editingLastEvent || consecutiveEdit) && importantOrRoomLoaded) { From 582963ab91a6a18ceaff8175beac1c508ac3eb00 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 17:53:23 +0100 Subject: [PATCH 3/7] fix: Do not compare timestamps when setting roomstate We now handle state in the correct order in the sync handler. Using the timestamp lead to false results when we still generated a default timestamp of "now" for events, however even without that state events can have an older timestamp like when accepting an invite on a server with the clock behind by a minute and we should instead rely on the sync handler giving us state in the right order. --- lib/src/room.dart | 9 --------- 1 file changed, 9 deletions(-) diff --git a/lib/src/room.dart b/lib/src/room.dart index f330b72a..2a619a91 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -196,15 +196,6 @@ class Room { return; } - // Do not set old events as state events - final prevEvent = getState(state.type, stateKey); - if (prevEvent != null && - prevEvent.eventId != state.eventId && - prevEvent.originServerTs.millisecondsSinceEpoch > - state.originServerTs.millisecondsSinceEpoch) { - return; - } - (states[state.type] ??= {})[stateKey] = state; client.onRoomState.add(state); From 1bde841cb25562bcca1fc5b63e1c9494b75656f6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 17:55:29 +0100 Subject: [PATCH 4/7] fix: properly fetch participants when transitioning from invite to join We used to add app the member counts of invited users and joined users, which would make us assume the participants list is complete. Which would mean we always return the wrong state. Additionally there is no need to rely on if we have a cached response. Instead we always only check for completeness instead of possibly returning stale responses just because we fetched the members once. --- lib/src/room.dart | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/src/room.dart b/lib/src/room.dart index 2a619a91..cf4fbf1f 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -1565,8 +1565,6 @@ class Room { return []; } - bool _requestedParticipants = false; - /// Request the full list of participants from the server. The local list /// from the store is not complete if the client uses lazy loading. /// List `membershipFilter` defines with what membership do you want the @@ -1582,17 +1580,20 @@ class Room { ], bool suppressWarning = false, bool cache = true]) async { - if (!participantListComplete && partial) { + if (!participantListComplete || partial) { // we aren't fully loaded, maybe the users are in the database + // We always need to check the database in the partial case, since state + // events won't get written to memory in this case and someone new could + // have joined, while someone else left, which might lead to the same + // count in the completeness check. final users = await client.database?.getUsers(this) ?? []; for (final user in users) { setState(user); } } - // Do not request users from the server if we have already done it - // in this session or have a complete list locally. - if (_requestedParticipants || participantListComplete) { + // Do not request users from the server if we have already have a complete list locally. + if (participantListComplete) { return getParticipants(membershipFilter); } @@ -1617,7 +1618,6 @@ class Room { } } - _requestedParticipants = cache; users.removeWhere((u) => !membershipFilter.contains(u.membership)); return users; } @@ -1625,10 +1625,14 @@ class Room { /// Checks if the local participant list of joined and invited users is complete. bool get participantListComplete { final knownParticipants = getParticipants(); - knownParticipants.removeWhere( - (u) => ![Membership.join, Membership.invite].contains(u.membership)); - return knownParticipants.length == - (summary.mJoinedMemberCount ?? 0) + (summary.mInvitedMemberCount ?? 0); + final joinedCount = + knownParticipants.where((u) => u.membership == Membership.join).length; + final invitedCount = knownParticipants + .where((u) => u.membership == Membership.invite) + .length; + + return (summary.mJoinedMemberCount ?? 0) == joinedCount && + (summary.mInvitedMemberCount ?? 0) == invitedCount; } @Deprecated( From 69fe7845a96003df4be383c4c4fea9a9fd652ea6 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 17:58:39 +0100 Subject: [PATCH 5/7] chore: Add regression test for invite->join state handling This is a reduced test case which made me investigate the issue and is fixed by the previous few commits. --- test/client_test.dart | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/client_test.dart b/test/client_test.dart index 6244e431..8f41d652 100644 --- a/test/client_test.dart +++ b/test/client_test.dart @@ -653,6 +653,32 @@ void main() { 'mxc://example.org/SEsfnsuifSDFSSEF'); expect(aliceProfile.displayName, 'Alice Margatroid'); }); + test('joinAfterInviteMembership', () async { + final client = await getClient(); + await client.abortSync(); + client.rooms.clear(); + await client.database?.clearCache(); + + await client.handleSync(SyncUpdate.fromJson(jsonDecode( + '{"next_batch":"s198510_227245_8_1404_23586_11_51065_267416_0_2639","rooms":{"invite":{"!bWEUQDujMKwjxkCXYr:tim-alpha.staging.famedly.de":{"invite_state":{"events":[{"type":"m.room.create","content":{"type":"de.gematik.tim.roomtype.default.v1","room_version":"10","creator":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":""},{"type":"m.room.encryption","content":{"algorithm":"m.megolm.v1.aes-sha2"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":""},{"type":"m.room.join_rules","content":{"join_rule":"invite"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":""},{"type":"m.room.member","content":{"membership":"join","displayname":"Tóboggen, Veronika Freifrau"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"},{"type":"m.room.member","content":{"is_direct":true,"membership":"invite","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de"}]}}}},"presence":{"events":[{"type":"m.presence","content":{"presence":"online","last_active_ago":5948,"currently_active":true},"sender":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de"}]},"device_one_time_keys_count":{"signed_curve25519":66},"device_unused_fallback_key_types":["signed_curve25519"],"org.matrix.msc2732.device_unused_fallback_key_types":["signed_curve25519"]}'))); + await client.handleSync(SyncUpdate.fromJson(jsonDecode( + '{"next_batch":"s198511_227245_8_1404_23588_11_51066_267416_0_2639","account_data":{"events":[{"type":"m.direct","content":{"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de":["!bWEUQDujMKwjxkCXYr:tim-alpha.staging.famedly.de"]}}]},"device_one_time_keys_count":{"signed_curve25519":65},"device_unused_fallback_key_types":["signed_curve25519"],"org.matrix.msc2732.device_unused_fallback_key_types":["signed_curve25519"]}'))); + await client.handleSync(SyncUpdate.fromJson(jsonDecode( + '{"next_batch":"s198512_227245_8_1404_23588_11_51066_267416_0_2639","rooms":{"join":{"!bWEUQDujMKwjxkCXYr:tim-alpha.staging.famedly.de":{"summary":{"m.heroes":["@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"],"m.joined_member_count":2,"m.invited_member_count":0},"state":{"events":[{"type":"m.room.create","content":{"type":"de.gematik.tim.roomtype.default.v1","room_version":"10","creator":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$qSgXGXjly6p5Kwbdb_PMBC_EF7nzHDbM23mvJFVeoiE","origin_server_ts":1709565579735,"unsigned":{"age":2255}}]},"timeline":{"events":[{"type":"m.room.member","content":{"membership":"join","displayname":"Tóboggen, Veronika Freifrau"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","event_id":"\$rQzxxTrSd9Y0koxIGlkalPAV_lwu94jLOA-8PSunY24","origin_server_ts":1709565579871,"unsigned":{"age":2119,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.power_levels","content":{"users":{"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de":100,"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de":100},"users_default":0,"events":{"m.room.name":50,"m.room.power_levels":100,"m.room.history_visibility":100,"m.room.canonical_alias":50,"m.room.avatar":50,"m.room.tombstone":100,"m.room.server_acl":100,"m.room.encryption":100},"events_default":0,"state_default":50,"ban":50,"kick":50,"redact":50,"invite":0,"historical":100},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$d6sgGs8PmkAbC3Iw3CkPT1QSub2zFTTvytegOxkPYPs","origin_server_ts":1709565579966,"unsigned":{"age":2024,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.join_rules","content":{"join_rule":"invite"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$EnA2Podch5181X4G1ZX34zaFGS_V4ZCZzLkBEfS_qyg","origin_server_ts":1709565579979,"unsigned":{"age":2011,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.history_visibility","content":{"history_visibility":"shared"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$6tNNo6ZkpZZrHrn8ZjXhMqI0CNv-VNNBw4R0h3_O-Tc","origin_server_ts":1709565579979,"unsigned":{"age":2011,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.guest_access","content":{"guest_access":"can_join"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$ViuL_LpN1sY9oYcGwycNjtp6FcGj__smUg8mzj3oa2o","origin_server_ts":1709565579980,"unsigned":{"age":2010,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.encryption","content":{"algorithm":"m.megolm.v1.aes-sha2"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"","event_id":"\$_e0az7OP7D78QU7DItiRAtlHlZmA07B5wenR93x5V1E","origin_server_ts":1709565579981,"unsigned":{"age":2009,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.member","content":{"is_direct":true,"membership":"invite","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","state_key":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","event_id":"\$4sZ3CF67SUh0n5WG0ZKS47Epj9B_d842RJjnrQmUKQo","origin_server_ts":1709565580185,"unsigned":{"age":1805,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.notsoencrypted","content":{"algorithm":"m.megolm.v1.aes-sha2","ciphertext":"AwgAEpACEZw8Ymg99Yfl7VsXRIdczlQ3+YSJ6te3o6ka/XXP0h4ZsgR2bu1Q8puQ77fOpwX5dPnrrCi5SQg9Zv5/u+0QbFV4FKE/k03Vxao/tiswb6wST14x9kYkwViOrZe7fzg7VF9tCi8U88TqxGsPDDOVjO+WNxG8I9ldP1zvPsxYzVSyGPhaB5E+q6llwlXcQ56wvpf7Ke7gX4Ly2Dlxa8Bmy7aUSCBoWAt/xFRdzCOsE9qI8oxzuvk4RF0H/7bY+4DkGTsP1rIYgA7Q0JueIFb47Yu6pK26BCKo1yPAR8qvpe8vGBICm4slMbKaJN4RqBHtR0zc12E5DXud91o3mArqTksv1NEbI1F4XgDREl76WBw8a7MafDSuun09JuWpGxzPHvLVOUVny6tTJPRutsZLkmnTeMTiXnsPexUiY7UTYlzOMeeoUSTDuJXJz6CM+gSc52CiKoHK/gE","device_id":"TNLOYXJFXM","sender_key":"e9W0gpUcSEKOQ8P/xIdroHUpP7yG4EjQfueiAngESRk","session_id":"hhZ8TBs9Xp0dmuvC6XpDBYsAKnTqb8WiBhZMzHcbBXI"},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","event_id":"\$KKIZX8cuB3S3uzS7CDtRlTkcaJRW73e2HW2NuW6OTEg","origin_server_ts":1709565580991,"unsigned":{"age":999,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.member","content":{"membership":"join","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"sender":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","state_key":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","event_id":"\$UNSLEyhC_93oQlt0tWoai4CCd3LH2GJJexM0WN2wxCA","origin_server_ts":1709565581813,"unsigned":{"replaces_state":"\$4sZ3CF67SUh0n5WG0ZKS47Epj9B_d842RJjnrQmUKQo","prev_content":{"is_direct":true,"membership":"invite","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"prev_sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","age":177,"com.famedly.famedlysdk.message_sending_status":2}},{"type":"m.room.member","content":{"membership":"join","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"sender":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","state_key":"@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","event_id":"\$UNSLEyhC_93oQlt0tWoai4CCd3LH2GJJexM0WN2wxCA","origin_server_ts":1709565581813,"unsigned":{"replaces_state":"\$4sZ3CF67SUh0n5WG0ZKS47Epj9B_d842RJjnrQmUKQo","prev_content":{"is_direct":true,"membership":"invite","displayname":"Düsterbehn-Hardenbergshausen, Michael von"},"prev_sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de","age":177,"com.famedly.famedlysdk.message_sending_status":2}}],"limited":true,"prev_batch":"s198503_227245_8_1404_23588_11_51066_267416_0_2639"},"ephemeral":{"events":[]},"account_data":{"events":[]},"unread_notifications":{"highlight_count":0,"notification_count":0}}}},"presence":{"events":[{"type":"m.presence","content":{"presence":"online","last_active_ago":843,"currently_active":true},"sender":"@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"}]},"device_lists":{"changed":["@duesterbehn-hardenbergshausen_michael_von:tim-alpha.staging.famedly.de","@toboggen_veronika_freifrau:tim-alpha.staging.famedly.de"],"left":[]},"device_one_time_keys_count":{"signed_curve25519":65},"device_unused_fallback_key_types":["signed_curve25519"],"org.matrix.msc2732.device_unused_fallback_key_types":["signed_curve25519"]}'))); + //await client.handleSync(SyncUpdate.fromJson(jsonDecode(''))); + final room = client + .getRoomById('!bWEUQDujMKwjxkCXYr:tim-alpha.staging.famedly.de')!; + await room.postLoad(); + final participants = await room.requestParticipants(); + + expect( + participants.where((u) => u.membership == Membership.join).length, 2); + + await client.abortSync(); + client.rooms.clear(); + await client.database?.clearCache(); + await client.dispose(closeDatabase: true); + }); test('ownProfile', () async { final client = await getClient(); await client.abortSync(); From f6fa9df2c544e55eb9be73186ba371ff58199c4d Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 19:31:35 +0100 Subject: [PATCH 6/7] fix: archived room state store logic This was implicitly relying to the timestamp of state events getting compared in the setState function. Fix this by using the helper functions already used for invite and join rooms. --- lib/src/client.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/client.dart b/lib/src/client.dart index e84dd1c4..12004caa 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1071,19 +1071,19 @@ class Client extends MatrixApi { [])); archivedRoom.prev_batch = update.timeline?.prevBatch; - update.state?.forEach((event) { - archivedRoom.setState(Event.fromMatrixEvent( - event, - archivedRoom, - )); - }); - update.timeline?.events?.forEach((event) { - archivedRoom.setState(Event.fromMatrixEvent( - event, - archivedRoom, - )); - }); + final stateEvents = roomUpdate.state; + if (stateEvents != null) { + await _handleRoomEvents(archivedRoom, stateEvents, EventUpdateType.state, + store: false); + } + + final timelineEvents = roomUpdate.timeline?.events; + if (timelineEvents != null) { + await _handleRoomEvents(archivedRoom, timelineEvents.reversed.toList(), + EventUpdateType.timeline, + store: false); + } for (var i = 0; i < timeline.events.length; i++) { // Try to decrypt encrypted events but don't update the database. From cf4a2c7929857ce7f7122e4035044af51204d4da Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 4 Mar 2024 19:33:47 +0100 Subject: [PATCH 7/7] fix: some tests fail with the "fixed" membership fetch logic We need to ensure the room summary and members are set to the expected values, since otherwise these tests will try to fetch the current members and then break our expectations. --- test/room_test.dart | 63 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 9 deletions(-) diff --git a/test/room_test.dart b/test/room_test.dart index a1a2efe8..2cf313e7 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -70,6 +70,7 @@ void main() { ), }, ); + room.setState(Event( room: room, eventId: '143273582443PhrSn:example.org', @@ -80,6 +81,46 @@ void main() { content: {'join_rule': 'public'}, stateKey: '', )); + room.setState(Event( + room: room, + eventId: '143273582443PhrSnY:example.org', + originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653), + senderId: matrix.userID!, + type: 'm.room.member', + unsigned: {'age': 1234}, + content: {'membership': 'join', 'displayname': 'YOU'}, + stateKey: matrix.userID!, + )); + room.setState(Event( + room: room, + eventId: '143273582443PhrSnA:example.org', + originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653), + senderId: '@alice:matrix.org', + type: 'm.room.member', + unsigned: {'age': 1234}, + content: {'membership': 'join', 'displayname': 'Alice Margatroid'}, + stateKey: '@alice:matrix.org', + )); + room.setState(Event( + room: room, + eventId: '143273582443PhrSnB:example.org', + originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653), + senderId: '@bob:example.com', + type: 'm.room.member', + unsigned: {'age': 1234}, + content: {'membership': 'invite', 'displayname': 'Bob'}, + stateKey: '@bob:example.com', + )); + room.setState(Event( + room: room, + eventId: '143273582443PhrSnC:example.org', + originServerTs: DateTime.fromMillisecondsSinceEpoch(1432735824653), + senderId: '@charley:example.org', + type: 'm.room.member', + unsigned: {'age': 1234}, + content: {'membership': 'invite', 'displayname': 'Charley'}, + stateKey: '@charley:example.org', + )); final heroUsers = await room.loadHeroUsers(); expect(heroUsers.length, 3); @@ -89,9 +130,10 @@ void main() { expect(room.notificationCount, notificationCount); expect(room.highlightCount, highlightCount); expect(room.summary.mJoinedMemberCount, notificationCount); - expect(room.summary.mInvitedMemberCount, notificationCount); + expect(room.summary.mInvitedMemberCount, 2); expect(room.summary.mHeroes, heroes); - expect(room.getLocalizedDisplayname(), 'Group with Alice, Bob, Charley'); + expect(room.getLocalizedDisplayname(), + 'Group with Alice Margatroid, Bob, Charley'); expect( room.getState('m.room.join_rules')?.content['join_rule'], 'public'); expect(room.roomAccountData['com.test.foo']?.content['foo'], 'bar'); @@ -434,12 +476,12 @@ void main() { test('requestParticipants', () async { final participants = await room.requestParticipants(); - expect(participants.length, 1); - final user = participants[0]; - expect(user.id, '@alice:example.org'); + expect(participants.length, 4); + final user = participants.singleWhere((u) => u.id == '@alice:matrix.org'); + expect(user.id, '@alice:matrix.org'); expect(user.displayName, 'Alice Margatroid'); expect(user.membership, Membership.join); - expect(user.avatarUrl.toString(), 'mxc://example.org/SEsfnsuifSDFSSEF'); + //expect(user.avatarUrl.toString(), 'mxc://example.org/SEsfnsuifSDFSSEF'); expect(user.room.id, '!localpart:server.abc'); }); @@ -1405,10 +1447,13 @@ void main() { test('getMention', () async { expect(room.getMention('@invalid'), null); - expect(room.getMention('@[Alice Margatroid]'), '@alice:example.org'); - expect(room.getMention('@[Alice Margatroid]#1754'), '@alice:example.org'); + expect(room.getMention('@[Alice Margatroid]'), '@alice:matrix.org'); + expect(room.getMention('@[Alice Margatroid]#1667'), '@alice:matrix.org'); }); test('inviteLink', () async { + // ensure we don't rerequest members + room.summary.mJoinedMemberCount = 4; + var matrixToLink = await room.matrixToInviteLink(); expect(matrixToLink.toString(), 'https://matrix.to/#/%23testalias%3Aexample.com'); @@ -1424,7 +1469,7 @@ void main() { ); matrixToLink = await room.matrixToInviteLink(); expect(matrixToLink.toString(), - 'https://matrix.to/#/!localpart%3Aserver.abc?via=example.org&via=example.com&via=test.abc'); + 'https://matrix.to/#/!localpart%3Aserver.abc?via=example.com&via=test.abc&via=example.org'); }); test('callMemberStateIsExpired', () {