A calm place to write long-form, and publish it to the open social web.
skypress.blog/
1import { describe, expect, it, vi } from 'vitest';
2import { renderToString } from 'react-dom/server';
3import {
4 createMentionCompleter,
5 replaceAtMentionCompleter,
6} from './mention-autocompleter';
7import type { ActorPreview } from '../landing/actor-lookup';
8
9const ALICE: ActorPreview = {
10 did: 'did:plc:alice',
11 handle: 'alice.bsky.social',
12 displayName: 'Alice',
13 avatar: null,
14};
15
16const ALICIA: ActorPreview = {
17 did: 'did:plc:alicia',
18 handle: 'alicia.blog',
19 displayName: 'Alicia',
20 avatar: null,
21};
22
23describe( 'mention autocompleter', () => {
24 it( 'has an @ trigger prefix', () => {
25 const completer = createMentionCompleter( async () => [] );
26 expect( completer.triggerPrefix ).toBe( '@' );
27 } );
28
29 it( 'returns the typeahead search results as options for a partial query', async () => {
30 const search = vi.fn( async () => [ ALICE, ALICIA ] );
31 const completer = createMentionCompleter( search );
32 const options = await completer.options( 'ali' );
33 expect( options ).toEqual( [ ALICE, ALICIA ] );
34 expect( search ).toHaveBeenCalledWith( 'ali' );
35 } );
36
37 it( 'returns no options for an empty query without searching', async () => {
38 const search = vi.fn( async () => [ ALICE ] );
39 const completer = createMentionCompleter( search );
40 expect( await completer.options( '' ) ).toEqual( [] );
41 expect( await completer.options( ' ' ) ).toEqual( [] );
42 expect( search ).not.toHaveBeenCalled();
43 } );
44
45 it( 'completes to a mention anchor element (inserted at the caret, not block-replacing)', () => {
46 const completer = createMentionCompleter( async () => [] );
47 // The completion is the anchor element itself, so the editor inserts it inline in
48 // place of the typed `@query` rather than swapping out the whole block.
49 const completion = completer.getOptionCompletion( ALICE );
50 const html = renderToString( completion );
51 expect( html ).toContain( 'href="https://mu.social/profile/alice.bsky.social"' );
52 expect( html ).toContain( 'data-did="did:plc:alice"' );
53 expect( html ).toContain( '@alice.bsky.social' );
54 } );
55} );
56
57describe( 'replaceAtMentionCompleter', () => {
58 const mention = createMentionCompleter( async () => [] );
59
60 it( "drops WordPress's built-in @ user completer and appends ours", () => {
61 const builtInUser = { name: 'user', triggerPrefix: '@' };
62 const blockCompleter = { name: 'block', triggerPrefix: '/' };
63 const result = replaceAtMentionCompleter(
64 [ builtInUser, blockCompleter ],
65 mention
66 );
67 // The '/' completer survives; the '@' user completer is gone; ours is last.
68 expect( result ).toEqual( [ blockCompleter, mention ] );
69 } );
70
71 it( 'returns only our completer when the base set is empty or not an array', () => {
72 expect( replaceAtMentionCompleter( [], mention ) ).toEqual( [ mention ] );
73 expect( replaceAtMentionCompleter( undefined, mention ) ).toEqual( [ mention ] );
74 } );
75} );