Files
Nitro-V3/src/hooks/rooms/widgets/useChatMentions.helpers.test.ts
T
simoleo89 dcbf44aedb feat(mentions): overhaul, refactor, notification bubble & window update
Chat tagging:
- Any @user is a visible tag in chat bubbles (the .mention-tag CSS never
  existed, so highlighting was invisible); self/alias mentions get a gold
  emphasis. Fixes cross-room tags not being highlighted.

Mentions window:
- Redesigned: unread count in the header, restyled filter chips + a refresh
  button, CSS-driven list/date-groups, adaptive height (compact when few,
  capped + scroll when many), polished empty state.
- Rows: framed avatar (friends-list head crop so the face is never clipped),
  per-row unread dot, type marker, icon action buttons (goto / remove).
- Re-requests from the server each time it opens.

Autocomplete:
- Never suggests the viewer themselves; suggests room users + online friends +
  aliases.

Notifications:
- Mention toast removed; mentions flow through the client's standard
  notification stream via a dedicated mention bubble (avatar + actions) in the
  default position. EVERY received mention surfaces (independent of the generic
  info-feed toggle, gated only by mentions_ui.enabled).

Refactor (behaviour-preserving):
- Centralised @-token classification in api/mentions/mentionTokens.
- Moved mentionsFormat -> api/mentions, useMentionActions -> hooks/mentions.
- Extracted ChatInputView @-autocomplete into a tested useChatMentions hook +
  pure helper; removed the dead duplicate useMentionAutocomplete.
2026-06-06 23:37:17 +02:00

102 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { buildChatMentionSuggestions, computeMentionContext, MentionAlias } from './useChatMentions.helpers';
const ALIASES: MentionAlias[] = [
{ key: 'all', scope: 'everyone', description: '' },
{ key: 'room', scope: 'room', description: '' }
];
const ROOM = [
{ webID: 1, type: 1, name: 'tester', figure: 'a' },
{ webID: 2, type: 1, name: 'alice', figure: 'b' },
{ webID: 3, type: 1, name: 'bob', figure: 'c' },
{ webID: 9, type: 2, name: 'petbot', figure: 'd' } // non-real user (pet/bot)
];
describe('computeMentionContext', () =>
{
it('detects a trailing @query', () =>
{
expect(computeMentionContext('hi @al', false)).toEqual({ atIndex: 3, replaceFrom: 3, replaceTo: 6, query: 'al' });
});
it('detects @ at the very start', () =>
{
expect(computeMentionContext('@al', false)).toEqual({ atIndex: 0, replaceFrom: 0, replaceTo: 3, query: 'al' });
});
it('returns null when a command popover is open', () =>
{
expect(computeMentionContext('hi @al', true)).toBeNull();
});
it('returns null when @ is glued to a previous word (e.g. an email)', () =>
{
expect(computeMentionContext('mail me@al', false)).toBeNull();
});
it('returns null when the @token is not at the end', () =>
{
expect(computeMentionContext('@al ready', false)).toBeNull();
});
it('returns null when there is no @', () =>
{
expect(computeMentionContext('plain text', false)).toBeNull();
});
});
describe('buildChatMentionSuggestions', () =>
{
it('excludes the viewer by id and keeps the other real users + aliases', () =>
{
const names = buildChatMentionSuggestions('', ROOM, ALIASES, 1, 'tester').map(s => s.name);
expect(names).not.toContain('tester'); // own user (webID 1)
expect(names).toContain('alice');
expect(names).toContain('bob');
expect(names).toContain('all');
expect(names).toContain('room');
});
it('excludes the viewer by name when the id does not line up', () =>
{
const names = buildChatMentionSuggestions('', ROOM, ALIASES, -1, 'TESTER').map(s => s.name);
expect(names).not.toContain('tester');
});
it('skips non-real users (pets/bots)', () =>
{
const names = buildChatMentionSuggestions('', ROOM, [], 1, 'tester').map(s => s.name);
expect(names).not.toContain('petbot');
});
it('prefix-filters users and aliases by the query', () =>
{
const names = buildChatMentionSuggestions('al', ROOM, ALIASES, 1, 'tester').map(s => s.name);
expect(names).toContain('alice');
expect(names).toContain('all');
expect(names).not.toContain('bob');
expect(names).not.toContain('room');
});
it('tags user vs alias kinds', () =>
{
const out = buildChatMentionSuggestions('', ROOM, ALIASES, 1, 'tester');
expect(out.find(s => s.name === 'alice')?.kind).toBe('user');
expect(out.find(s => s.name === 'all')?.kind).toBe('alias');
});
it('caps the total at max', () =>
{
const many = Array.from({ length: 20 }, (_, i) => ({ webID: 100 + i, type: 1, name: `u${ i }`, figure: '' }));
const out = buildChatMentionSuggestions('', many, ALIASES, -1, 'me', 5);
expect(out).toHaveLength(5);
});
});