diff --git a/frontend/src/lib/utils/personMention.spec.ts b/frontend/src/lib/utils/personMention.spec.ts new file mode 100644 index 00000000..64684bd8 --- /dev/null +++ b/frontend/src/lib/utils/personMention.spec.ts @@ -0,0 +1,53 @@ +import { describe, it, expect } from 'vitest'; +import { detectPersonMention } from './personMention'; + +describe('detectPersonMention', () => { + it('returns null when text has no @', () => { + expect(detectPersonMention('hello world', 11)).toBeNull(); + }); + + it('returns null when @ is preceded by a non-whitespace character (email pattern)', () => { + expect(detectPersonMention('user@example', 12)).toBeNull(); + }); + + it('returns query for @ at the very start of string', () => { + expect(detectPersonMention('@Aug', 4)).toBe('Aug'); + }); + + it('returns empty string immediately after @', () => { + expect(detectPersonMention('@', 1)).toBe(''); + }); + + it('returns single-word query', () => { + expect(detectPersonMention('hi @Auguste', 11)).toBe('Auguste'); + }); + + it('keeps the trigger active when the query has a trailing space', () => { + expect(detectPersonMention('hi @Auguste ', 12)).toBe('Auguste '); + }); + + it('returns multi-word query (spaces allowed)', () => { + expect(detectPersonMention('hi @Auguste Raddatz', 19)).toBe('Auguste Raddatz'); + }); + + it('returns single-character query', () => { + expect(detectPersonMention('@M', 2)).toBe('M'); + }); + + it('returns null when the query crosses a newline', () => { + expect(detectPersonMention('@Aug\nfoo', 8)).toBeNull(); + }); + + it('returns null when a second @ appears in the query (next mention starts)', () => { + expect(detectPersonMention('@Aug@bar', 8)).toBeNull(); + }); + + it('returns null when cursor is before the @', () => { + expect(detectPersonMention('@Hans', 0)).toBeNull(); + }); + + it('uses the most recent @ in the text', () => { + // cursor is just after the second @ + a few chars + expect(detectPersonMention('hi @Anna and @Bert', 18)).toBe('Bert'); + }); +}); diff --git a/frontend/src/lib/utils/personMention.ts b/frontend/src/lib/utils/personMention.ts new file mode 100644 index 00000000..7611c89f --- /dev/null +++ b/frontend/src/lib/utils/personMention.ts @@ -0,0 +1,23 @@ +/** + * Given the current textarea value and cursor position, returns the + * @-person-mention query being typed (the text after the last triggering @), + * or null if no person-mention is active. + * + * Rules — distinct from comment-mentions in `mention.ts`: + * - @ must be at the start of the string or preceded by whitespace + * - The query may contain spaces (historical persons commonly have multi-word + * display names — "Auguste Raddatz", "Maria von Müller-Schultz") + * - The query stops at a newline or at a second @ (the next mention starts) + */ +export function detectPersonMention(text: string, cursorPos: number): string | null { + const before = text.slice(0, cursorPos); + const atIndex = before.lastIndexOf('@'); + if (atIndex === -1) return null; + + if (atIndex > 0 && !/\s/.test(before[atIndex - 1])) return null; + + const query = before.slice(atIndex + 1); + if (query.includes('\n') || query.includes('@')) return null; + + return query; +}