fluffychat/test/pangea/knock_notification_utils_test.dart
wcjord 6f32aab48b
fix: show "knock accepted" push notification body instead of "You have been invited" (#5823) (#5835)
* 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>
2026-02-27 12:29:50 -05:00

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);
});
});
}