Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/fifty-candies-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/meteor': patch
---

fix: Buttons from emoji picker misbehaving on clicks

An infinite render loop was preventing proper behavior when clicking on the emoji picker buttons. It was fixed by removing the unnecessary state update that was causing the loop and replacing multiple fires of the same mouseover event (when a mouseenter event was the right one to use). There is a chance this pre-existing bug was hidden by React 18's event delegation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { renderHook, act } from '@testing-library/react';
import { useState, type ReactNode } from 'react';

import EmojiPickerProvider from './EmojiPickerProvider';
import { usePreviewEmoji } from '../../contexts/EmojiPickerContext';

jest.mock('@rocket.chat/fuselage-hooks', () => {
const originalModule = jest.requireActual('@rocket.chat/fuselage-hooks');

return {
...originalModule,
useDebouncedState: <T,>(initialValue: T) => useState<T>(initialValue),
useLocalStorage: <T,>(_: string, initialValue: T) => useState<T>(initialValue),
useStableCallback: (callback: (...args: any[]) => unknown) => callback,
};
});

jest.mock('../../../app/emoji/client', () => ({
emoji: { packages: { base: { emojisByCategory: { recent: [] } } } },
getFrequentEmoji: jest.fn(() => []),
createEmojiListByCategorySubscription: jest.fn(() => {
const snapshot = [[], {}];
return [() => () => undefined, () => snapshot];
}),
}));

jest.mock('../../views/composer/EmojiPicker', () => () => null);
jest.mock('./useUpdateCustomEmoji', () => ({ useUpdateCustomEmoji: () => undefined }));

const wrapper = ({ children }: { children: ReactNode }) => <EmojiPickerProvider>{children}</EmojiPickerProvider>;

describe('EmojiPickerProvider', () => {
it('should keep the same preview reference when receiving the same emoji and name', () => {
const { result } = renderHook(() => usePreviewEmoji(), { wrapper });

act(() => {
result.current.handlePreview('image://smile', 'smile');
});

const firstPreview = result.current.emojiToPreview;
expect(firstPreview).toEqual({ emoji: 'image://smile', name: 'smile' });

act(() => {
result.current.handlePreview('image://smile', 'smile');
});

expect(result.current.emojiToPreview).toBe(firstPreview);

act(() => {
result.current.handlePreview('image://rocket', 'rocket');
});

expect(result.current.emojiToPreview).toEqual({ emoji: 'image://rocket', name: 'rocket' });
expect(result.current.emojiToPreview).not.toBe(firstPreview);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,16 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }) => {
return setEmojiPicker(<EmojiPicker reference={ref} onClose={() => setEmojiPicker(null)} onPickEmoji={(emoji) => callback(emoji)} />);
}, []);

const handlePreview = useCallback((emoji: string, name: string) => setEmojiToPreview({ emoji, name }), [setEmojiToPreview]);
const handlePreview = useCallback(
(emoji: string, name: string) =>
setEmojiToPreview((preview) => {
if (preview?.emoji === emoji && preview?.name === name) {
return preview;
}
return { emoji, name };
}),
[setEmojiToPreview],
);

const handleRemovePreview = useCallback(() => setEmojiToPreview(null), [setEmojiToPreview]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const EmojiElement = ({ emoji, image, onClick, small = false, ...props }: EmojiE
{...(small && { className: emojiSmallClass })}
small={small}
medium={!small}
onMouseOver={() => handlePreview(image, emoji)}
onMouseEnter={() => handlePreview(image, emoji)}
onMouseLeave={handleRemovePreview}
onClick={onClick}
data-emoji={emoji}
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10830,7 +10830,7 @@ __metadata:
"@react-aria/toolbar": "*"
"@rocket.chat/fuselage": "*"
"@rocket.chat/icons": "*"
"@rocket.chat/ui-client": 32.0.0-rc.0
"@rocket.chat/ui-client": 32.0.0
react: "*"
react-dom: "*"
languageName: unknown
Expand Down Expand Up @@ -11084,7 +11084,7 @@ __metadata:
peerDependencies:
"@rocket.chat/layout": "*"
"@rocket.chat/tools": 0.3.0
"@rocket.chat/ui-contexts": 32.0.0-rc.0
"@rocket.chat/ui-contexts": 32.0.0
"@tanstack/react-query": "*"
react: "*"
react-hook-form: "*"
Expand Down
Loading