Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions app/containers/MessageComposer/MessageComposer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { colors } from '../../lib/constants/colors';
import { type IRoomContext, RoomContext } from '../../views/RoomView/context';
import * as EmojiKeyboardHook from './hooks/useEmojiKeyboard';
import { initStore } from '../../lib/store/auxStore';
import { search } from '../../lib/methods/search';
import { searchRemote } from '../../lib/methods/search';
import database from '../../lib/database';
import { useMessageComposerApi } from './context';
import { sendFileMessage } from '../../lib/methods/sendFileMessage';
Expand All @@ -25,7 +25,8 @@ jest.useFakeTimers();

// Ensure search returns at least one item so autocomplete renders
jest.mock('../../lib/methods/search', () => ({
search: jest.fn(() => [{ _id: 'u1', username: 'john', name: 'John' }])
searchLocal: jest.fn(() => []),
searchRemote: jest.fn(() => [{ _id: 'u1', username: 'john', name: 'John' }])
}));

jest.mock('../../lib/services/restApi', () => ({
Expand Down Expand Up @@ -458,7 +459,7 @@ describe('MessageComposer', () => {

test('select @ user inserts mention and sends, autocomplete hides', async () => {
const onSendMessage = jest.fn();
(search as unknown as jest.Mock).mockImplementationOnce(() => [{ _id: 'u1', username: 'john', name: 'John' }]);
(searchRemote as unknown as jest.Mock).mockImplementationOnce(() => [{ _id: 'u1', username: 'john', name: 'John' }]);
render(<Render context={{ onSendMessage }} />);

await fireEvent(screen.getByTestId('message-composer-input'), 'focus');
Expand Down Expand Up @@ -543,7 +544,7 @@ describe('MessageComposer', () => {

test('select # room inserts channel and sends, autocomplete hides', async () => {
const onSendMessage = jest.fn();
(search as unknown as jest.Mock).mockImplementationOnce(() => [{ rid: 'r1', name: 'general', t: 'c' }]);
(searchRemote as unknown as jest.Mock).mockImplementationOnce(() => [{ rid: 'r1', name: 'general', t: 'c' }]);
render(<Render context={{ onSendMessage }} />);

await fireEvent(screen.getByTestId('message-composer-input'), 'focus');
Expand Down
91 changes: 57 additions & 34 deletions app/containers/MessageComposer/hooks/useAutocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react';
import { useEffect, useRef, useState } from 'react';
import { Q } from '@nozbe/watermelondb';

import {
Expand All @@ -7,7 +7,7 @@ import {
type TAutocompleteItem,
type TAutocompleteType
} from '../interfaces';
import { search } from '../../../lib/methods/search';
import { searchLocal, searchRemote, type TSearch } from '../../../lib/methods/search';
import { sanitizeLikeString } from '../../../lib/database/utils';
import database from '../../../lib/database';
import { emojis } from '../../../lib/constants/emojis';
Expand Down Expand Up @@ -54,8 +54,46 @@ export const useAutocomplete = ({
}): TAutocompleteItem[] => {
const [items, setItems] = useState<TAutocompleteItem[]>([]);
const [mentionAll, mentionHere] = usePermissions(['mention-all', 'mention-here']);
// Guards against an older (slower) search overwriting the results of a newer one
const searchId = useRef(0);

useEffect(() => {
const parseUserRoom = (res: TSearch[]): IAutocompleteUserRoom[] => {
const parsedRes: IAutocompleteUserRoom[] = res
// TODO: need to refactor search to have a more predictable return type
.map((item: any) => ({
id: type === '@' ? item._id : item.rid,
title: item.fname || item.name || item.username,
subtitle: item.username || item.name,
outside: item.outside,
t: item.t ?? 'd',
status: item.status,
teamMain: item.teamMain,
type
})) as IAutocompleteUserRoom[];
if (type === '@') {
if (mentionAll && 'all'.includes(text.toLocaleLowerCase())) {
parsedRes.push({
id: 'all',
title: 'all',
subtitle: I18n.t('Notify_all_in_this_room'),
type,
t: 'd'
});
}
if (mentionHere && 'here'.includes(text.toLocaleLowerCase())) {
parsedRes.push({
id: 'here',
title: 'here',
subtitle: I18n.t('Notify_active_in_this_room'),
type,
t: 'd'
});
}
}
return parsedRes;
};

const getAutocomplete = async () => {
try {
if (!rid || !type) {
Expand All @@ -76,39 +114,24 @@ export const useAutocomplete = ({
setItems(items);

if (type === '@' || type === '#') {
const res = await search({ text, filterRooms: type === '#', filterUsers: type === '@', rid });
const parsedRes: IAutocompleteUserRoom[] = res
// TODO: need to refactor search to have a more predictable return type
.map((item: any) => ({
id: type === '@' ? item._id : item.rid,
title: item.fname || item.name || item.username,
subtitle: item.username || item.name,
outside: item.outside,
t: item.t ?? 'd',
status: item.status,
teamMain: item.teamMain,
type
})) as IAutocompleteUserRoom[];
if (type === '@') {
if (mentionAll && 'all'.includes(text.toLocaleLowerCase())) {
parsedRes.push({
id: 'all',
title: 'all',
subtitle: I18n.t('Notify_all_in_this_room'),
type,
t: 'd'
});
}
if (mentionHere && 'here'.includes(text.toLocaleLowerCase())) {
parsedRes.push({
id: 'here',
title: 'here',
subtitle: I18n.t('Notify_active_in_this_room'),
type,
t: 'd'
});
}
searchId.current += 1;
const currentSearchId = searchId.current;
const isStale = () => currentSearchId !== searchId.current;
const searchParams = { text, filterRooms: type === '#', filterUsers: type === '@', rid };

// Paint local results immediately while the backend request is still in flight
const localData = await searchLocal(searchParams);
if (isStale()) return;
const parsedLocal = parseUserRoom(localData);
setItems(parsedLocal);
if (parsedLocal.length > 0) {
updateAutocompleteVisible(true);
accessibilityFocusOnInput();
}

const res = await searchRemote({ ...searchParams, localData });
if (isStale()) return;
const parsedRes = parseUserRoom(res);
setItems(parsedRes);
Comment thread
OtavioStasiak marked this conversation as resolved.
if (parsedRes.length > 0) {
updateAutocompleteVisible(true);
Expand Down
Loading
Loading