313 lines
11 KiB
Dart
313 lines
11 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:motorcycle_dash/features/social/clubs/club_membership_presentation.dart';
|
|
import 'package:motorcycle_dash/services/clubs/club_models.dart';
|
|
|
|
void main() {
|
|
group('ClubMembershipPresentation Unit Tests', () {
|
|
final baseClub = Club(
|
|
id: 'test_club_1',
|
|
name: 'NorCal Adventurers',
|
|
slug: 'norcal-adv',
|
|
description: 'Adventure riding club',
|
|
ownerUid: 'owner_uid',
|
|
memberCount: 10,
|
|
visibility: ClubVisibility.public,
|
|
admissionPolicy: ClubAdmissionPolicy.open,
|
|
createdAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
test('resolves unavailable when club is null', () {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: null,
|
|
member: null,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.unavailable);
|
|
expect(p.statusBadge, 'UNAVAILABLE');
|
|
expect(p.isActionable, isFalse);
|
|
expect(p.primaryAction, isNull);
|
|
});
|
|
|
|
test('root owner cannot join even with a missing or stale member record', () {
|
|
for (final status in [null, ...ClubMemberStatus.values]) {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.request),
|
|
member: status == null ? null : ClubMember(
|
|
uid: 'owner_uid',
|
|
clubId: baseClub.id,
|
|
role: ClubRole.member,
|
|
status: status,
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
),
|
|
currentUserId: 'owner_uid',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.activeOwner);
|
|
expect(p.primaryAction, isNull);
|
|
expect(p.secondaryAction, isNull);
|
|
expect(p.canLeave, isFalse);
|
|
}
|
|
});
|
|
|
|
test('request admission actions respect active, pending and outsider states', () {
|
|
for (final role in ClubRole.values) {
|
|
for (final status in [null, ClubMemberStatus.active, ClubMemberStatus.requested]) {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.request),
|
|
member: status == null ? null : ClubMember(
|
|
uid: 'rider_1',
|
|
clubId: baseClub.id,
|
|
role: role,
|
|
status: status,
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
),
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.primaryAction, status == null
|
|
? ClubMembershipAction.requestToJoin
|
|
: status == ClubMemberStatus.requested
|
|
? ClubMembershipAction.withdrawRequest
|
|
: role == ClubRole.owner ? null : ClubMembershipAction.leaveClub);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('resolves archived state when club.isArchived is true', () {
|
|
final archivedClub = baseClub.copyWith(archivedAt: DateTime(2026, 2, 1));
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: archivedClub,
|
|
member: null,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.archived);
|
|
expect(p.statusBadge, 'ARCHIVED');
|
|
expect(p.badgeType, PresentationBadgeType.amber);
|
|
expect(p.canLeave, isFalse);
|
|
expect(p.isActionable, isFalse);
|
|
expect(p.primaryAction, isNull);
|
|
});
|
|
|
|
test('resolves unauthenticated state when signed out or anonymous', () {
|
|
final p1 = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: null,
|
|
currentUserId: null,
|
|
isAuthenticated: false,
|
|
);
|
|
expect(p1.state, ClubMembershipPresentationState.unauthenticated);
|
|
expect(p1.statusBadge, 'GUEST');
|
|
expect(p1.primaryAction, ClubMembershipAction.signIn);
|
|
expect(p1.isActionable, isTrue);
|
|
|
|
final p2 = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: null,
|
|
currentUserId: '',
|
|
isAuthenticated: true,
|
|
);
|
|
expect(p2.state, ClubMembershipPresentationState.unauthenticated);
|
|
});
|
|
|
|
test('resolves outsiderOpen for open admission when unjoined', () {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.open),
|
|
member: null,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.outsiderOpen);
|
|
expect(p.statusBadge, 'OPEN ADMISSION');
|
|
expect(p.badgeType, PresentationBadgeType.cyan);
|
|
expect(p.primaryAction, ClubMembershipAction.joinOpen);
|
|
expect(p.primaryActionLabel, 'Join Club');
|
|
expect(p.canLeave, isFalse);
|
|
});
|
|
|
|
test('resolves outsiderRequest for request admission when unjoined', () {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.request),
|
|
member: null,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.outsiderRequest);
|
|
expect(p.statusBadge, 'REQUEST REQUIRED');
|
|
expect(p.primaryAction, ClubMembershipAction.requestToJoin);
|
|
expect(p.primaryActionLabel, 'Request to Join');
|
|
});
|
|
|
|
test('resolves outsiderInviteOnly for invite-only admission when unjoined', () {
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.inviteOnly),
|
|
member: null,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.outsiderInviteOnly);
|
|
expect(p.statusBadge, 'INVITE ONLY');
|
|
expect(p.primaryAction, isNull);
|
|
expect(p.isActionable, isFalse);
|
|
});
|
|
|
|
test('resolves historical states (left, declined, removed) as re-joinable outsiders', () {
|
|
for (final status in [ClubMemberStatus.left, ClubMemberStatus.declined, ClubMemberStatus.removed]) {
|
|
final member = ClubMember(
|
|
uid: 'rider_1',
|
|
clubId: baseClub.id,
|
|
status: status,
|
|
role: ClubRole.member,
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub.copyWith(admissionPolicy: ClubAdmissionPolicy.open),
|
|
member: member,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.outsiderOpen);
|
|
expect(p.primaryAction, ClubMembershipAction.joinOpen);
|
|
}
|
|
});
|
|
|
|
test('resolves invited state with accept and decline actions', () {
|
|
final invitedMember = ClubMember(
|
|
uid: 'rider_1',
|
|
clubId: baseClub.id,
|
|
status: ClubMemberStatus.invited,
|
|
role: ClubRole.member,
|
|
invitedByUid: 'owner_uid',
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: invitedMember,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.invited);
|
|
expect(p.statusBadge, 'INVITED');
|
|
expect(p.primaryAction, ClubMembershipAction.acceptInvite);
|
|
expect(p.primaryActionLabel, 'Accept Invitation');
|
|
expect(p.secondaryAction, ClubMembershipAction.declineInvite);
|
|
expect(p.secondaryActionLabel, 'Decline');
|
|
expect(p.isSecondaryDestructive, isTrue);
|
|
expect(p.canLeave, isFalse);
|
|
});
|
|
|
|
test('resolves requested state with withdraw request action', () {
|
|
final requestedMember = ClubMember(
|
|
uid: 'rider_1',
|
|
clubId: baseClub.id,
|
|
status: ClubMemberStatus.requested,
|
|
role: ClubRole.member,
|
|
requestedAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: requestedMember,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.requested);
|
|
expect(p.statusBadge, 'REQUEST PENDING');
|
|
expect(p.badgeType, PresentationBadgeType.amber);
|
|
expect(p.primaryAction, ClubMembershipAction.withdrawRequest);
|
|
expect(p.primaryActionLabel, 'Withdraw Request');
|
|
expect(p.isPrimaryDestructive, isTrue);
|
|
expect(p.canLeave, isFalse);
|
|
});
|
|
|
|
test('resolves activeMember state with leave club action', () {
|
|
final activeMember = ClubMember(
|
|
uid: 'rider_1',
|
|
clubId: baseClub.id,
|
|
status: ClubMemberStatus.active,
|
|
role: ClubRole.member,
|
|
joinedAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: activeMember,
|
|
currentUserId: 'rider_1',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.activeMember);
|
|
expect(p.statusBadge, 'MEMBER');
|
|
expect(p.primaryAction, ClubMembershipAction.leaveClub);
|
|
expect(p.primaryActionLabel, 'Leave Club');
|
|
expect(p.isPrimaryDestructive, isTrue);
|
|
expect(p.canLeave, isTrue);
|
|
});
|
|
|
|
test('resolves activeAdmin state with leave club action', () {
|
|
final activeAdmin = ClubMember(
|
|
uid: 'rider_admin',
|
|
clubId: baseClub.id,
|
|
status: ClubMemberStatus.active,
|
|
role: ClubRole.admin,
|
|
joinedAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: activeAdmin,
|
|
currentUserId: 'rider_admin',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.activeAdmin);
|
|
expect(p.statusBadge, 'ADMIN');
|
|
expect(p.primaryAction, ClubMembershipAction.leaveClub);
|
|
expect(p.canLeave, isTrue);
|
|
});
|
|
|
|
test('resolves activeOwner state where owner cannot leave', () {
|
|
final activeOwner = ClubMember(
|
|
uid: 'owner_uid',
|
|
clubId: baseClub.id,
|
|
status: ClubMemberStatus.active,
|
|
role: ClubRole.owner,
|
|
joinedAt: DateTime(2026, 1, 1),
|
|
updatedAt: DateTime(2026, 1, 1),
|
|
);
|
|
|
|
final p = ClubMembershipPresentation.resolve(
|
|
club: baseClub,
|
|
member: activeOwner,
|
|
currentUserId: 'owner_uid',
|
|
isAuthenticated: true,
|
|
);
|
|
|
|
expect(p.state, ClubMembershipPresentationState.activeOwner);
|
|
expect(p.statusBadge, 'OWNER');
|
|
expect(p.badgeType, PresentationBadgeType.green);
|
|
expect(p.primaryAction, isNull);
|
|
expect(p.canLeave, isFalse);
|
|
expect(p.isActionable, isFalse);
|
|
expect(p.statusDescription, contains('Ownership must be transferred'));
|
|
});
|
|
});
|
|
}
|