* fix: show knock-accepted notification body when invite follows a knock (#5823) When Synapse accepts a knock it sends an m.room.member invite event. The client was displaying the generic 'You have been invited by X' push notification body because the invite event alone doesn't carry prev_content on the push path. Use KnockTracker (already used for auto-join) to detect knock-accepted invites and show a dedicated 'Your join request was accepted!' notification body instead. - Add knockAccepted string to intl_en.arb - Extract condition into isKnockAcceptedInvite() pure util for testability - Expose KnockTracker.getKnockedRoomIds() publicly (was _getKnockedRoomIds) - Override notification body in push_helper.dart (background) and local_notifications_extension.dart (foreground/web) - Unit tests for all isKnockAcceptedInvite() branches (9 tests) * formatting * fix up pangea comments * fix: avoid race condition with knocked room account data updates and local push notification content * translations --------- Co-authored-by: ggurdin <ggurdin@gmail.com>
155 lines
4.7 KiB
Dart
155 lines
4.7 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:matrix/matrix.dart';
|
|
|
|
import 'package:fluffychat/pangea/join_codes/knock_notification_utils.dart';
|
|
import 'package:fluffychat/pangea/join_codes/knocked_rooms_model.dart';
|
|
|
|
void main() {
|
|
const roomId = '!course:staging.pangea.chat';
|
|
const userId = '@learner:staging.pangea.chat';
|
|
const adminId = '@teacher:staging.pangea.chat';
|
|
|
|
group('isKnockAcceptedInvite', () {
|
|
test('returns true when all conditions match a knock-accepted invite', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isTrue);
|
|
});
|
|
|
|
test('returns false when event is not m.room.member', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: 'm.room.message',
|
|
newMembership: 'invite',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test('returns false when membership is not invite (e.g. join)', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'join',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test('returns false when membership is null', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: null,
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test(
|
|
'returns false when invite targets a different user (not current user)',
|
|
() {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: adminId, // <-- someone else was invited
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
},
|
|
);
|
|
|
|
test('returns false when stateKey is null', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [roomId],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: null,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test('returns false when the room was not previously knocked on', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [], // <-- no prior knock recorded
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test('returns false when a different room was knocked', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: ['!other:staging.pangea.chat'],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isFalse);
|
|
});
|
|
|
|
test('handles multiple knocked rooms and matches the correct one', () {
|
|
final model = KnockedRoomsModel(
|
|
knockedRoomIds: [
|
|
'!other1:staging.pangea.chat',
|
|
roomId,
|
|
'!other2:staging.pangea.chat',
|
|
],
|
|
acceptedInviteRoomIds: [],
|
|
);
|
|
final result = isKnockAcceptedInvite(
|
|
eventType: EventTypes.RoomMember,
|
|
newMembership: 'invite',
|
|
stateKey: userId,
|
|
currentUserId: userId,
|
|
hasKnocked: model.hasEverKnocked(roomId),
|
|
);
|
|
expect(result, isTrue);
|
|
});
|
|
});
|
|
}
|