Merge remote-tracking branch 'fork/main'

# Conflicts:
#	public/configuration/UITexts_en.json5.example
#	public/configuration/UITexts_it.json5.example
#	src/components/MainView.tsx
#	src/css/friends/FriendsView.css
This commit is contained in:
simoleo89
2026-06-06 00:17:32 +02:00
23 changed files with 3621 additions and 22 deletions
+26
View File
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { MessengerFriend } from './MessengerFriend';
import { MessengerThread } from './MessengerThread';
import { MessengerThreadChat } from './MessengerThreadChat';
const makeThread = (participantId: number): MessengerThread =>
{
const friend = new MessengerFriend();
friend.id = participantId;
return new MessengerThread(friend);
};
describe('MessengerThread.setMessagesReadFromUser', () =>
{
it('marks only the given user\'s messages as READ', () =>
{
const thread = makeThread(7);
const mine = thread.addMessage(100, 'a', 0, null, MessengerThreadChat.CHAT);
const theirs = thread.addMessage(7, 'b', 0, null, MessengerThreadChat.CHAT);
thread.setMessagesReadFromUser(100);
expect(mine.status).toBe(MessengerThreadChat.READ);
expect(theirs.status).toBe(MessengerThreadChat.SENT);
});
});
+10
View File
@@ -99,6 +99,16 @@ export class MessengerThread
this._unreadCount = 0;
}
public setMessagesReadFromUser(userId: number): void
{
for(const group of this._groups)
{
if(group.userId !== userId) continue;
for(const chat of group.chats) chat.setStatus(MessengerThreadChat.READ);
}
}
public get threadId(): number
{
return this._threadId;
@@ -0,0 +1,45 @@
import { describe, expect, it } from 'vitest';
import { MessengerThreadChat } from './MessengerThreadChat';
describe('MessengerThreadChat.offlineDelivered', () =>
{
it('is true for a CHAT message with extraData "offline"', () =>
{
const chat = new MessengerThreadChat(5, 'hello', 60, 'offline', MessengerThreadChat.CHAT);
expect(chat.offlineDelivered).toBe(true);
});
it('is false for a normal CHAT message with no extraData', () =>
{
const chat = new MessengerThreadChat(5, 'hello', 0, null, MessengerThreadChat.CHAT);
expect(chat.offlineDelivered).toBe(false);
});
it('is false when extraData is some other value (e.g. group chat data)', () =>
{
const chat = new MessengerThreadChat(5, 'hi', 0, 'Bob/figurestr/5', MessengerThreadChat.CHAT);
expect(chat.offlineDelivered).toBe(false);
});
it('is false for a non-CHAT type even if extraData is "offline"', () =>
{
const chat = new MessengerThreadChat(5, 'hi', 0, 'offline', MessengerThreadChat.ROOM_INVITE);
expect(chat.offlineDelivered).toBe(false);
});
});
describe('MessengerThreadChat status', () =>
{
it('defaults to SENT', () =>
{
const chat = new MessengerThreadChat(5, 'hi', 0, null, MessengerThreadChat.CHAT);
expect(chat.status).toBe(MessengerThreadChat.SENT);
});
it('can be set to READ', () =>
{
const chat = new MessengerThreadChat(5, 'hi', 0, null, MessengerThreadChat.CHAT);
chat.setStatus(MessengerThreadChat.READ);
expect(chat.status).toBe(MessengerThreadChat.READ);
});
});
+18
View File
@@ -4,10 +4,13 @@ export class MessengerThreadChat
public static ROOM_INVITE: number = 1;
public static STATUS_NOTIFICATION: number = 2;
public static SECURITY_NOTIFICATION: number = 3;
public static SENT: number = 0;
public static READ: number = 1;
private static CHAT_ID: number = 0;
private _id: number;
private _type: number;
private _status: number = MessengerThreadChat.SENT;
private _senderId: number;
private _message: string;
private _secondsSinceSent: number;
@@ -74,6 +77,21 @@ export class MessengerThreadChat
return this._extraData;
}
public get offlineDelivered(): boolean
{
return (this._type === MessengerThreadChat.CHAT) && (this._extraData === 'offline');
}
public get status(): number
{
return this._status;
}
public setStatus(status: number): void
{
this._status = status;
}
public get date(): Date
{
return this._date;
@@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest';
import { MessengerFriend } from './MessengerFriend';
import { countFriendsByCategory, filterFriendsByCategory } from './friendCategory.helpers';
const makeFriend = (id: number, categoryId: number): MessengerFriend =>
{
const friend = new MessengerFriend();
friend.id = id;
friend.categoryId = categoryId;
return friend;
};
describe('filterFriendsByCategory', () =>
{
const friends = [ makeFriend(1, 0), makeFriend(2, 5), makeFriend(3, 5), makeFriend(4, 8) ];
it('returns all friends when categoryId is 0 (All)', () =>
{
expect(filterFriendsByCategory(friends, 0)).toHaveLength(4);
});
it('returns only the friends in the given category', () =>
{
expect(filterFriendsByCategory(friends, 5).map(f => f.id)).toEqual([ 2, 3 ]);
});
it('returns an empty array for a category with no members', () =>
{
expect(filterFriendsByCategory(friends, 99)).toEqual([]);
});
it('is null-safe', () =>
{
expect(filterFriendsByCategory(null, 5)).toEqual([]);
});
});
describe('countFriendsByCategory', () =>
{
const friends = [ makeFriend(1, 0), makeFriend(2, 5), makeFriend(3, 5) ];
it('counts members per category id', () =>
{
const counts = countFriendsByCategory(friends);
expect(counts.get(0)).toBe(1);
expect(counts.get(5)).toBe(2);
});
it('is null-safe', () =>
{
expect(countFriendsByCategory(null).size).toBe(0);
});
});
+32
View File
@@ -0,0 +1,32 @@
import { MessengerFriend } from './MessengerFriend';
/**
* Filter a friend list to a single category. categoryId 0 means
* "All" (no filtering) and returns the list unchanged.
*/
export const filterFriendsByCategory = (friends: MessengerFriend[], categoryId: number): MessengerFriend[] =>
{
if(!friends) return [];
if(!categoryId) return friends;
return friends.filter(friend => (friend.categoryId === categoryId));
};
/**
* Count how many friends belong to each category id. Used to render
* member counts on the group chips.
*/
export const countFriendsByCategory = (friends: MessengerFriend[]): Map<number, number> =>
{
const counts = new Map<number, number>();
if(!friends) return counts;
for(const friend of friends)
{
counts.set(friend.categoryId, (counts.get(friend.categoryId) ?? 0) + 1);
}
return counts;
};
+1
View File
@@ -1,3 +1,4 @@
export * from './friendCategory.helpers';
export * from './GetGroupChatData';
export * from './IGroupChatData';
export * from './MessengerFriend';