fix: add group members sorting

not good but hopefully we won't have to keep for too long
pull/3281/head
Audric Ackermann 5 months ago
parent db22094898
commit cf899ee18c
No known key found for this signature in database

@ -13,11 +13,10 @@ import {
useMemberInviteFailed,
useMemberInviteSending,
useMemberInviteSent,
useMemberIsPromoted,
useMemberPromoteSending,
useMemberPromotionFailed,
useMemberPromotionNotSent,
useMemberPromotionSent,
useMemberIsNominatedAdmin,
} from '../state/selectors/groups';
import { Avatar, AvatarSize, CrownIcon } from './avatar/Avatar';
import { Flex } from './basic/Flex';
@ -28,10 +27,6 @@ import {
SessionButtonType,
} from './basic/SessionButton';
import { SessionRadio } from './basic/SessionRadio';
import { GroupSync } from '../session/utils/job_runners/jobs/GroupSyncJob';
import { RunJobResult } from '../session/utils/job_runners/PersistedJob';
import { SubaccountUnrevokeSubRequest } from '../session/apis/snode_api/SnodeRequestTypes';
import { NetworkTime } from '../util/NetworkTime';
import {
MetaGroupWrapperActions,
UserGroupsWrapperActions,
@ -221,15 +216,10 @@ const GroupStatusContainer = ({
const ResendButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupPubkeyType }) => {
const acceptedInvite = useMemberHasAcceptedInvite(pubkey, groupPk);
const promotionFailed = useMemberPromotionFailed(pubkey, groupPk);
const promotionSent = useMemberPromotionSent(pubkey, groupPk);
const promotionNotSent = useMemberPromotionNotSent(pubkey, groupPk);
const promoted = useMemberIsPromoted(pubkey, groupPk);
const nominatedAdmin = useMemberIsNominatedAdmin(pubkey, groupPk);
// as soon as the `admin` flag is set in the group for that member, we should be able to resend a promote as we cannot remove an admin.
const canResendPromotion =
hasClosedGroupV2QAButtons() &&
(promotionFailed || promotionSent || promotionNotSent || promoted);
const canResendPromotion = hasClosedGroupV2QAButtons() && nominatedAdmin;
// we can always remove/and readd a non-admin member. So we consider that a member who accepted the invite cannot be resent an invite.
const canResendInvite = !acceptedInvite;
@ -252,32 +242,13 @@ const ResendButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupP
window.log.warn('tried to resend invite but we do not have correct details');
return;
}
const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, pubkey);
const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({
groupPk,
revokeTokenHex: [token],
timestamp: NetworkTime.now(),
secretKey: group.secretKey,
});
const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
unrevokeSubRequest,
extraStoreRequests: [],
});
if (sequenceResult !== RunJobResult.Success) {
throw new Error('resend invite: pushChangesToGroupSwarmIfNeeded did not return success');
}
// if we tried to invite that member as admin right away, let's retry it as such.
const inviteAsAdmin =
member.promotionNotSent ||
member.promotionFailed ||
member.promotionPending ||
member.promoted;
const inviteAsAdmin = member.nominatedAdmin;
await GroupInvite.addJob({
groupPk,
member: pubkey,
inviteAsAdmin,
forceUnrevoke: true,
});
}}
/>
@ -286,11 +257,11 @@ const ResendButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupP
const PromoteButton = ({ groupPk, pubkey }: { pubkey: PubkeyType; groupPk: GroupPubkeyType }) => {
const memberAcceptedInvite = useMemberHasAcceptedInvite(pubkey, groupPk);
const memberIsPromoted = useMemberIsPromoted(pubkey, groupPk);
const memberIsNominatedAdmin = useMemberIsNominatedAdmin(pubkey, groupPk);
// When invite-as-admin was used to invite that member, the resend button is available to resend the promote message.
// We want to show that button only to promote a normal member who accepted a normal invite but wasn't promoted yet.
// ^ this is only the case for testing. The UI will be different once we release the promotion process
if (!hasClosedGroupV2QAButtons() || !memberAcceptedInvite || memberIsPromoted) {
if (!hasClosedGroupV2QAButtons() || !memberAcceptedInvite || memberIsNominatedAdmin) {
return null;
}
return (

@ -27,7 +27,10 @@ import { ClosedGroup } from '../../session/group/closed-group';
import { PubKey } from '../../session/types';
import { hasClosedGroupV2QAButtons } from '../../shared/env_vars';
import { groupInfoActions } from '../../state/ducks/metaGroups';
import { useMemberGroupChangePending } from '../../state/selectors/groups';
import {
useMemberGroupChangePending,
useStateOf03GroupMembers,
} from '../../state/selectors/groups';
import { useSelectedIsGroupV2 } from '../../state/selectors/selectedConversation';
import { SessionSpinner } from '../loading';
import { SessionToggle } from '../basic/SessionToggle';
@ -36,7 +39,7 @@ type Props = {
conversationId: string;
};
const StyledClassicMemberList = styled.div`
const StyledMemberList = styled.div`
max-height: 240px;
`;
@ -44,7 +47,7 @@ const StyledClassicMemberList = styled.div`
* Admins are always put first in the list of group members.
* Also, admins have a little crown on their avatar.
*/
const ClassicMemberList = (props: {
const MemberList = (props: {
convoId: string;
selectedMembers: Array<string>;
onSelect: (m: string) => void;
@ -56,12 +59,15 @@ const ClassicMemberList = (props: {
const groupAdmins = useGroupAdmins(convoId);
const groupMembers = useSortedGroupMembers(convoId);
const groupMembers03Group = useStateOf03GroupMembers(convoId);
const sortedMembers = useMemo(
const sortedMembersNon03 = useMemo(
() => [...groupMembers].sort(m => (groupAdmins?.includes(m) ? -1 : 0)),
[groupMembers, groupAdmins]
);
const sortedMembers = isV2Group ? groupMembers03Group.map(m => m.pubkeyHex) : sortedMembersNon03;
return (
<>
{sortedMembers.map(member => {
@ -230,14 +236,14 @@ export const UpdateGroupMembersDialog = (props: Props) => {
/>
</>
) : null}
<StyledClassicMemberList className="contact-selection-list">
<ClassicMemberList
<StyledMemberList className="contact-selection-list">
<MemberList
convoId={conversationId}
onSelect={onSelect}
onUnselect={onUnselect}
selectedMembers={membersToRemove}
/>
</StyledClassicMemberList>
</StyledMemberList>
{showNoMembersMessage && <p>{window.i18n('groupMembersNone')}</p>}
<SpacerLG />

@ -125,11 +125,11 @@ async function handleMetaMergeResults(groupPk: GroupPubkeyType) {
}
}
// mark ourselves as accepting the invite if needed
if (usMember?.invitePending && keysAlreadyHaveAdmin) {
if (usMember?.memberStatus === 'INVITE_SENT' && keysAlreadyHaveAdmin) {
await MetaGroupWrapperActions.memberSetAccepted(groupPk, us);
}
// mark ourselves as accepting the promotion if needed
if (usMember?.promotionPending && keysAlreadyHaveAdmin) {
if (usMember?.memberStatus === 'PROMOTION_SENT' && keysAlreadyHaveAdmin) {
await MetaGroupWrapperActions.memberSetPromotionAccepted(groupPk, us);
}
// this won't do anything if there is no need for a sync, so we can safely plan one

@ -327,7 +327,7 @@ class ConvoController {
const us = UserUtils.getOurPubKeyStrFromCache();
const allMembers = await MetaGroupWrapperActions.memberGetAll(groupPk);
const otherAdminsCount = allMembers
.filter(m => m.promoted)
.filter(m => m.nominatedAdmin)
.filter(m => m.pubkeyHex !== us).length;
const weAreLastAdmin = otherAdminsCount === 0;
const infos = await MetaGroupWrapperActions.infoGet(groupPk);

@ -43,6 +43,7 @@ export interface GroupInvitePersistedData extends PersistedJobData {
groupPk: GroupPubkeyType;
member: PubkeyType;
inviteAsAdmin: boolean;
forceUnrevoke: boolean;
}
export interface GroupPromotePersistedData extends PersistedJobData {

@ -21,6 +21,9 @@ import { LibSessionUtil } from '../../libsession/libsession_utils';
import { showUpdateGroupMembersByConvoId } from '../../../../interactions/conversationInteractions';
import { ConvoHub } from '../../../conversations';
import { MessageQueue } from '../../../sending';
import { NetworkTime } from '../../../../util/NetworkTime';
import { SubaccountUnrevokeSubRequest } from '../../../apis/snode_api/SnodeRequestTypes';
import { GroupSync } from './GroupSyncJob';
const defaultMsBetweenRetries = 10000;
const defaultMaxAttempts = 1;
@ -29,6 +32,12 @@ type JobExtraArgs = {
groupPk: GroupPubkeyType;
member: PubkeyType;
inviteAsAdmin: boolean;
/**
* When inviting a member, we usually only want to sent a message to his swarm.
* In the case of a invitation resend process though, we also want to make sure his token is unrevoked from the group's swarm.
*
*/
forceUnrevoke: boolean;
};
export function shouldAddJob(args: JobExtraArgs) {
@ -47,12 +56,13 @@ const invitesFailed = new Map<
}
>();
async function addJob({ groupPk, member, inviteAsAdmin }: JobExtraArgs) {
if (shouldAddJob({ groupPk, member, inviteAsAdmin })) {
async function addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke }: JobExtraArgs) {
if (shouldAddJob({ groupPk, member, inviteAsAdmin, forceUnrevoke })) {
const groupInviteJob = new GroupInviteJob({
groupPk,
member,
inviteAsAdmin,
forceUnrevoke,
nextAttemptTimestamp: Date.now(),
});
window.log.debug(`addGroupInviteJob: adding group invite for ${groupPk}:${member} `);
@ -135,8 +145,9 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
nextAttemptTimestamp,
maxAttempts,
currentRetry,
forceUnrevoke,
identifier,
}: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin'> &
}: Pick<GroupInvitePersistedData, 'groupPk' | 'member' | 'inviteAsAdmin' | 'forceUnrevoke'> &
Partial<
Pick<
GroupInvitePersistedData,
@ -153,6 +164,7 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
member,
groupPk,
inviteAsAdmin,
forceUnrevoke,
delayBetweenRetries: defaultMsBetweenRetries,
maxAttempts: isNumber(maxAttempts) ? maxAttempts : defaultMaxAttempts,
nextAttemptTimestamp: nextAttemptTimestamp || Date.now() + defaultMsBetweenRetries,
@ -177,6 +189,26 @@ class GroupInviteJob extends PersistedJob<GroupInvitePersistedData> {
}
let failed = true;
try {
if (this.persistedData.forceUnrevoke) {
const token = await MetaGroupWrapperActions.swarmSubAccountToken(groupPk, member);
const unrevokeSubRequest = new SubaccountUnrevokeSubRequest({
groupPk,
revokeTokenHex: [token],
timestamp: NetworkTime.now(),
secretKey: group.secretKey,
});
const sequenceResult = await GroupSync.pushChangesToGroupSwarmIfNeeded({
groupPk,
unrevokeSubRequest,
extraStoreRequests: [],
});
if (sequenceResult !== RunJobResult.Success) {
throw new Error(
'GroupInviteJob: SubaccountUnrevokeSubRequest push() did not return success'
);
}
}
const inviteDetails = inviteAsAdmin
? await SnodeGroupSignature.getGroupPromoteMessage({
groupName: group.name,

@ -87,7 +87,7 @@ async function checkWeAreAdmin(groupPk: GroupPubkeyType) {
const usInGroup = await MetaGroupWrapperActions.memberGet(groupPk, us);
const inUserGroup = await UserGroupsWrapperActions.getGroup(groupPk);
// if the secretKey is not empty AND we are a member of the group, we are a current admin
return Boolean(!isEmpty(inUserGroup?.secretKey) && usInGroup?.promoted);
return Boolean(!isEmpty(inUserGroup?.secretKey) && usInGroup?.nominatedAdmin);
}
async function checkWeAreAdminOrThrow(groupPk: GroupPubkeyType, context: string) {
@ -243,14 +243,12 @@ const initNewGroupInWrapper = createAsyncThunk(
// privately and asynchronously, and gracefully handle errors with toasts.
// Let's do all of this part of a job to handle app crashes and make sure we
// can update the group wrapper with a failed state if a message fails to be sent.
for (let index = 0; index < membersFromWrapper.length; index++) {
const member = membersFromWrapper[index];
await GroupInvite.addJob({
member: member.pubkeyHex,
groupPk,
inviteAsAdmin: window.sessionFeatureFlags.useGroupV2InviteAsAdmin,
});
}
await scheduleGroupInviteJobs(
groupPk,
membersFromWrapper.map(m => m.pubkeyHex),
[],
window.sessionFeatureFlags.useGroupV2InviteAsAdmin
);
await openConversationWithMessages({ conversationKey: groupPk, messageId: null });
@ -1429,6 +1427,8 @@ async function scheduleGroupInviteJobs(
const merged = uniq(concat(withHistory, withoutHistory));
for (let index = 0; index < merged.length; index++) {
const member = merged[index];
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin });
// Note: forceUnrevoke is false, because `scheduleGroupInviteJobs` is always called after we've done
// a batch unrevoke of all the members' pk
await GroupInvite.addJob({ groupPk, member, inviteAsAdmin, forceUnrevoke: false });
}
}

@ -1,8 +1,17 @@
import { GroupMemberGet, GroupPubkeyType, PubkeyType } from 'libsession_util_nodejs';
import {
GroupMemberGet,
GroupPubkeyType,
MemberStateGroupV2,
PubkeyType,
} from 'libsession_util_nodejs';
import { useSelector } from 'react-redux';
import { compact, concat, differenceBy, sortBy, uniqBy } from 'lodash';
import { PubKey } from '../../session/types';
import { GroupState } from '../ducks/metaGroups';
import { StateType } from '../reducer';
import { assertUnreachable } from '../../types/sqlSharedTypes';
import { UserUtils } from '../../session/utils';
import { useConversationsNicknameRealNameOrShortenPubkey } from '../../hooks/useParamSelector';
const getLibGroupsState = (state: StateType): GroupState => state.groups;
const getInviteSendingState = (state: StateType) => getLibGroupsState(state).membersInviteSending;
@ -44,48 +53,57 @@ function getGroupNameChangeFromUIPending(state: StateType): boolean {
export function getLibAdminsPubkeys(state: StateType, convo?: string): Array<string> {
const members = getMembersOfGroup(state, convo);
return members.filter(m => m.promoted).map(m => m.pubkeyHex);
return members.filter(m => m.nominatedAdmin).map(m => m.pubkeyHex);
}
function getMemberInviteFailed(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.inviteFailed || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'INVITE_FAILED' || false;
}
function getMemberInviteNotSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.inviteNotSent || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'INVITE_NOT_SENT' || false;
}
function getMemberInviteSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.invitePending || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'INVITE_SENT' || false;
}
function getMemberIsPromoted(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
function getMemberHasAcceptedPromotion(
state: StateType,
pubkey: PubkeyType,
convo?: GroupPubkeyType
) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.promoted || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'PROMOTION_ACCEPTED' || false;
}
function getMemberIsNominatedAdmin(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.nominatedAdmin || false;
}
function getMemberHasAcceptedInvite(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.inviteAccepted || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'INVITE_ACCEPTED' || false;
}
function getMemberPromotionFailed(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.promotionFailed || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'PROMOTION_FAILED' || false;
}
function getMemberPromotionSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.promotionPending || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'PROMOTION_SENT' || false;
}
function getMemberPromotionNotSent(state: StateType, pubkey: PubkeyType, convo?: GroupPubkeyType) {
const members = getMembersOfGroup(state, convo);
return findMemberInMembers(members, pubkey)?.promotionNotSent || false;
return findMemberInMembers(members, pubkey)?.memberStatus === 'PROMOTION_NOT_SENT' || false;
}
export function getLibMembersCount(state: StateType, convo?: GroupPubkeyType): Array<string> {
@ -155,9 +173,14 @@ export function useMemberInviteNotSent(member: PubkeyType, groupPk: GroupPubkeyT
return useSelector((state: StateType) => getMemberInviteNotSent(state, member, groupPk));
}
export function useMemberIsPromoted(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberIsPromoted(state, member, groupPk));
export function useMemberHasAcceptedPromotion(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberHasAcceptedPromotion(state, member, groupPk));
}
export function useMemberIsNominatedAdmin(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberIsNominatedAdmin(state, member, groupPk));
}
export function useMemberHasAcceptedInvite(member: PubkeyType, groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getMemberHasAcceptedInvite(state, member, groupPk));
}
@ -188,18 +211,128 @@ export function useGroupNameChangeFromUIPending() {
* An example is the "sending invite" or "sending promote" state of a member in a group.
*/
function useMembersInviteSending(groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getInviteSendingState(state)[groupPk] || []);
function useMembersInviteSending(groupPk?: string) {
return useSelector((state: StateType) =>
groupPk && PubKey.is03Pubkey(groupPk) ? getInviteSendingState(state)[groupPk] || [] : []
);
}
export function useMemberInviteSending(groupPk: GroupPubkeyType, memberPk: PubkeyType) {
return useMembersInviteSending(groupPk).includes(memberPk);
}
function useMembersPromoteSending(groupPk: GroupPubkeyType) {
return useSelector((state: StateType) => getPromoteSendingState(state)[groupPk] || []);
function useMembersPromoteSending(groupPk?: string) {
return useSelector((state: StateType) =>
groupPk && PubKey.is03Pubkey(groupPk) ? getPromoteSendingState(state)[groupPk] || [] : []
);
}
export function useMemberPromoteSending(groupPk: GroupPubkeyType, memberPk: PubkeyType) {
return useMembersPromoteSending(groupPk).includes(memberPk);
}
type MemberStateGroupV2WithSending = MemberStateGroupV2 | 'INVITE_SENDING' | 'PROMOTION_SENDING';
export function useStateOf03GroupMembers(convoId?: string) {
const us = UserUtils.getOurPubKeyStrFromCache();
let unsortedMembers = useSelector((state: StateType) => getMembersOfGroup(state, convoId));
const invitesSendingPk = useMembersInviteSending(convoId);
const promotionsSendingPk = useMembersPromoteSending(convoId);
let invitesSending = compact(
invitesSendingPk.map(sending => unsortedMembers.find(m => m.pubkeyHex === sending))
);
const promotionSending = compact(
promotionsSendingPk.map(sending => unsortedMembers.find(m => m.pubkeyHex === sending))
);
// promotionSending has priority against invitesSending, so removing anything in invitesSending found in promotionSending
invitesSending = differenceBy(invitesSending, promotionSending, value => value.pubkeyHex);
const bothSending = concat(promotionSending, invitesSending);
// promotionSending and invitesSending has priority against anything else, so remove anything found in one of those two
// from the unsorted list of members
unsortedMembers = differenceBy(unsortedMembers, bothSending, value => value.pubkeyHex);
// at this point, merging invitesSending, promotionSending and unsortedMembers should create an array of unique members
const sortedByPriorities = concat(bothSending, unsortedMembers);
if (sortedByPriorities.length !== uniqBy(sortedByPriorities, m => m.pubkeyHex).length) {
throw new Error(
'merging invitesSending, promotionSending and unsortedMembers should create an array of unique members'
);
}
// This could have been done now with a `sortedByPriorities.map()` call,
// but we don't want the order as sorted by `sortedByPriorities`, **only** to respect the priorities from it.
// What that means is that a member with a state as inviteSending, should have that state, but not be sorted first.
// The order we (for now) want is:
// - (Invite failed + Invite Not Sent) merged together, sorted as NameSortingOrder
// - Sending invite, sorted as NameSortingOrder
// - Invite sent, sorted as NameSortingOrder
// - (Promotion failed + Promotion Not Sent) merged together, sorted as NameSortingOrder
// - Sending invite, sorted as NameSortingOrder
// - Invite sent, sorted as NameSortingOrder
// - Admin, sorted as NameSortingOrder
// - Accepted Member, sorted as NameSortingOrder
// NameSortingOrder: You first, then "nickname || name || pubkey -> aA-zZ"
const unsortedWithStatuses: Array<
Pick<GroupMemberGet, 'pubkeyHex'> & { memberStatus: MemberStateGroupV2WithSending }
> = [];
unsortedWithStatuses.push(...promotionSending);
unsortedWithStatuses.push(...differenceBy(invitesSending, promotionSending));
unsortedWithStatuses.push(...differenceBy(unsortedMembers, invitesSending, promotionSending));
const names = useConversationsNicknameRealNameOrShortenPubkey(
unsortedWithStatuses.map(m => m.pubkeyHex)
);
// needing an index like this outside of lodash is not pretty,
// but sortBy doesn't provide the index in the callback
let index = 0;
const sorted = sortBy(unsortedWithStatuses, item => {
let stateSortingOrder = 0;
switch (item.memberStatus) {
case 'INVITE_FAILED':
case 'INVITE_NOT_SENT':
stateSortingOrder = -5;
break;
case 'INVITE_SENDING':
stateSortingOrder = -4;
break;
case 'INVITE_SENT':
stateSortingOrder = -3;
break;
case 'PROMOTION_FAILED':
case 'PROMOTION_NOT_SENT':
stateSortingOrder = -2;
break;
case 'PROMOTION_SENDING':
stateSortingOrder = -1;
break;
case 'PROMOTION_SENT':
stateSortingOrder = 0;
break;
case 'PROMOTION_ACCEPTED':
stateSortingOrder = 1;
break;
case 'INVITE_ACCEPTED':
stateSortingOrder = 2;
break;
default:
assertUnreachable(item.memberStatus, 'Unhandled switch case');
}
const sortingOrder = [
stateSortingOrder,
// per section, we want "us first", then "nickname || displayName || pubkey"
item.pubkeyHex === us ? -1 : names[index]?.toLocaleLowerCase(),
];
index++;
return sortingOrder;
});
return sorted;
}

@ -18,20 +18,14 @@ function profilePicture() {
function emptyMember(pubkeyHex: PubkeyType): GroupMemberGet {
return {
inviteFailed: false,
invitePending: false,
memberStatus: 'INVITE_NOT_SENT',
name: '',
profilePicture: {
key: null,
url: null,
},
promoted: false,
promotionFailed: false,
promotionPending: false,
inviteAccepted: false,
inviteNotSent: false,
isRemoved: false,
promotionNotSent: false,
nominatedAdmin: false,
shouldRemoveMessages: false,
pubkeyHex,
};
@ -271,10 +265,8 @@ describe('libsession_metagroup', () => {
expect(metaGroupWrapper.memberGetAll().length).to.be.deep.eq(1);
const expected: GroupMemberGet = {
...emptyMember(member),
promoted: true,
promotionFailed: false,
promotionPending: false,
promotionNotSent: false,
nominatedAdmin: true,
memberStatus: 'PROMOTION_ACCEPTED',
};
expect(metaGroupWrapper.memberGetAll()[0]).to.be.deep.eq(expected);

Loading…
Cancel
Save