[MM-61577]: Ensure all interactive functionality is operable with the keyboard in emoji picker (#29629)

Этот коммит содержится в:
Saurabh Sharma
2025-02-03 12:26:49 +05:30
коммит произвёл GitHub
родитель 316cde2569
Коммит e8e40c79f6
14 изменённых файлов: 64 добавлений и 36 удалений

Просмотреть файл

@@ -128,11 +128,11 @@ describe('Custom emojis', () => {
cy.findByText('Custom').should('exist').and('is.visible');
// * Verify that first custom emoji exists and is visible to user
cy.findAllByAltText('custom emoji image').should('exist').eq(0).and('is.visible');
cy.findAllByAltText('custom emoji').should('exist').eq(0).and('is.visible');
// * Verify second custom emoji exists and is visible to user,
// if both custom emojis are visible we can conclude that they are not overlapping
cy.findAllByAltText('custom emoji image').should('exist').eq(1).and('is.visible');
cy.findAllByAltText('custom emoji').should('exist').eq(1).and('is.visible');
});
});
});

Просмотреть файл

@@ -69,10 +69,10 @@ describe('Recent Emoji', () => {
cy.findByText(/Recently Used/i).should('exist').and('be.visible');
// * Assert first emoji should equal with second recent emoji
cy.findAllByTestId('emojiItem').eq(0).find('img').should('have.attr', 'aria-label', 'grin emoji');
cy.findAllByTestId('emojiItem').eq(0).should('have.attr', 'aria-label', 'grin emoji');
// * Assert second emoji should equal with first recent emoji
cy.findAllByTestId('emojiItem').eq(1).find('img').should('have.attr', 'aria-label', 'joy emoji');
cy.findAllByTestId('emojiItem').eq(1).should('have.attr', 'aria-label', 'joy emoji');
});
it('MM-T4463 Recently used custom emoji, when is deleted should be removed from recent emoji category and quick reactions', () => {
@@ -130,7 +130,7 @@ describe('Recent Emoji', () => {
cy.findAllByTestId('emojiItem').eq(0).find('img').should('have.attr', 'class', 'emoji-category--custom');
// * Verify second most recent one is the system emoji in emoji picker
cy.findAllByTestId('emojiItem').eq(1).find('img').should('have.attr', 'aria-label', 'lemon emoji');
cy.findAllByTestId('emojiItem').eq(1).should('have.attr', 'aria-label', 'lemon emoji');
// # Go to custom emoji page
cy.findByText('Custom Emoji').should('be.visible').click();
@@ -172,6 +172,6 @@ describe('Recent Emoji', () => {
cy.findByText('Recently Used').should('exist').and('be.visible');
// * Verify most recent one is the system emoji in emoji picker and not the custom emoji
cy.findAllByTestId('emojiItem').eq(0).find('img').should('have.attr', 'aria-label', 'lemon emoji');
cy.findAllByTestId('emojiItem').eq(0).should('have.attr', 'aria-label', 'lemon emoji');
});
});

Просмотреть файл

@@ -56,7 +56,7 @@ describe('Recent Emoji', () => {
cy.findByText('Recently Used').should('exist').and('be.visible');
// * Verify most recent one is the thumbsup emoji with default skin
cy.findAllByTestId('emojiItem').eq(0).find('img').should('have.attr', 'aria-label', '+1 emoji');
cy.findAllByTestId('emojiItem').eq(0).should('have.attr', 'aria-label', '+1 emoji');
// # Open skin picker again to change the skin
cy.findByAltText('emoji skin tone picker').should('exist').parent().click().wait(TIMEOUTS.ONE_SEC);
@@ -65,7 +65,7 @@ describe('Recent Emoji', () => {
cy.findByTestId('skin-pick-1F3FF').should('exist').click();
// * Verify most recent one is the same thumbsup emoji but now with a dark skin tone
cy.findAllByTestId('emojiItem').eq(0).find('img').should('have.attr', 'aria-label', '+1 dark skin tone emoji');
cy.findAllByTestId('emojiItem').eq(0).should('have.attr', 'aria-label', '+1 dark skin tone emoji');
});
// # Close emoji picker

Просмотреть файл

@@ -32,8 +32,13 @@ describe('Emoji sorting', () => {
// # Assert first recently used emoji has the data-test-id value of 'cat' which was the last one we sent
cy.findAllByTestId('emojiItem').
findByRole('button', {name: 'cat emoji'}).
should('exist');
each(($btn) => {
// Check if the button has the specific aria-label
if ($btn.attr('aria-label') === 'cat emoji') {
// If the aria-label matches, check if the button exists
cy.wrap($btn).should('exist');
}
});
const emojiList = [];

Просмотреть файл

@@ -99,13 +99,13 @@ describe('MM-T154 Use keyboard navigation in emoji picker', () => {
cy.uiGetPostTextBox().type('{enter}');
// * Compare selected emoji with last post
cy.getLastPost().find('.emoticon').should('have.attr', 'alt', selectedEmoji);
cy.getLastPost().find('.emoticon').should('have.attr', 'aria-label', selectedEmoji);
});
});
});
const testSelectedIndex = (done) => {
cy.get('div.emoji-picker__item.selected').then((selectedEmoji) => {
cy.get('button.emoji-picker__item.selected').then((selectedEmoji) => {
done(selectedEmoji.index());
});
};

Просмотреть файл

@@ -134,7 +134,9 @@ exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = `
style="height: 290px;"
>
<div
aria-labelledby="emojiPickerSearch"
class="emoji-picker__container"
role="grid"
>
<div
style="overflow: visible; height: 0px; width: 0px;"

Просмотреть файл

@@ -35,6 +35,7 @@ function EmojiPickerCategoryOrEmojiRow({index, style, data, cursorRowIndex, curs
<div
style={style}
className='emoji-picker__row'
role='row'
>
{row.items.map((emojiColumn) => {
const emoji = emojiColumn.item;

Просмотреть файл

@@ -17,6 +17,7 @@ function EmojiPickerCategoryRow({categoryName, style}: Props) {
<div
className='emoji-picker-items__container'
style={style}
role='row'
>
<div
className='emoji-picker__category-header'

Просмотреть файл

@@ -91,7 +91,11 @@ const EmojiPickerCurrentResults = forwardRef<InfiniteLoader, Props>(({categoryOr
className='emoji-picker__items'
style={{height: EMOJI_CONTAINER_HEIGHT}}
>
<div className='emoji-picker__container'>
<div
className='emoji-picker__container'
role='grid'
aria-labelledby='emojiPickerSearch'
>
<AutoSizer>
{({height, width}) => (
<InfiniteLoader

Просмотреть файл

@@ -55,32 +55,21 @@ function EmojiPickerItem({emoji, rowIndex, isSelected, onClick, onMouseOver}: Pr
let content;
if (isSystemEmoji(emoji)) {
const emojiName = emoji.short_name ? emoji.short_name : emoji.name;
const emojiUnified = emoji.unified ? emoji.unified.toLowerCase() : emoji.name.toLowerCase();
content = (
<img
alt={'emoji image'}
alt={`${emoji.name.toLocaleLowerCase()} emoji`}
data-testid={emoji.short_names}
src={imgTrans}
className={`emojisprite emoji-category-${emoji.category} emoji-${emojiUnified}`}
id={`emoji-${emojiUnified}`}
aria-label={formatMessage(
{
id: 'emoji_picker_item.emoji_aria_label',
defaultMessage: '{emojiName} emoji',
},
{
emojiName: (emojiName).replace(/_/g, ' '),
},
)}
role='button'
/>
);
} else {
content = (
<img
alt={'custom emoji image'}
alt={'custom emoji'}
data-testid={emoji.name}
src={getEmojiImageUrl(emoji)}
className={'emoji-category--custom'}
@@ -89,15 +78,26 @@ function EmojiPickerItem({emoji, rowIndex, isSelected, onClick, onMouseOver}: Pr
}
return (
<div
<button
className={itemClassName}
onClick={handleClick}
onMouseOver={throttledMouseOver}
data-testid='emojiItem'
tabIndex={-1}
type='button'
id={emoji.name.toLocaleLowerCase().replaceAll(' ', '_')}
aria-label={formatMessage(
{
id: 'emoji_picker_item.emoji_aria_label',
defaultMessage: '{emojiName} emoji',
},
{
emojiName: (isSystemEmoji(emoji) ? emoji.short_name : emoji.name).replace(/_/g, ' '),
},
)}
>
<div data-testid='emojiItem'>
{content}
</div>
</div>
{content}
</button>
);
}

Просмотреть файл

@@ -13,6 +13,8 @@ import {useIntl} from 'react-intl';
import {EMOJI_PER_ROW} from 'components/emoji_picker/constants';
import {NavigationDirection} from 'components/emoji_picker/types';
import Constants from 'utils/constants';
interface Props {
value: string;
cursorCategoryIndex: number;
@@ -24,6 +26,8 @@ interface Props {
resetCursorPosition: () => void;
}
const KeyCodes = Constants.KeyCodes;
const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCategoryIndex, cursorEmojiIndex, onChange, resetCursorPosition, onKeyDown, focus, onEnter}: Props, ref) => {
const {formatMessage} = useIntl();
@@ -39,7 +43,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
switch (event.key) {
case 'ArrowRight':
case KeyCodes.RIGHT[0]:
// If the cursor is at the end of the textbox and an emoji is currently selected, move it to the next emoji
if ((event.currentTarget?.selectionStart ?? 0) + 1 > value.length || (cursorCategoryIndex !== -1 || cursorEmojiIndex !== -1)) {
event.stopPropagation();
@@ -48,7 +52,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
onKeyDown(NavigationDirection.NextEmoji);
}
break;
case 'ArrowLeft':
case KeyCodes.LEFT[0]:
if (cursorCategoryIndex > 0 || cursorEmojiIndex > 0) {
event.stopPropagation();
event.preventDefault();
@@ -65,7 +69,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
focus();
}
break;
case 'ArrowUp':
case KeyCodes.UP[0]:
event.stopPropagation();
event.preventDefault();
@@ -88,7 +92,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
onKeyDown(NavigationDirection.PreviousEmojiRow);
}
break;
case 'ArrowDown':
case KeyCodes.DOWN[0]:
event.stopPropagation();
event.preventDefault();
@@ -104,7 +108,14 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
onKeyDown(NavigationDirection.NextEmojiRow);
}
break;
case 'Enter': {
case KeyCodes.SPACE[0]: {
event.stopPropagation();
event.preventDefault();
onEnter();
break;
}
case KeyCodes.ENTER[0]: {
event.stopPropagation();
event.preventDefault();

Просмотреть файл

@@ -340,6 +340,7 @@ const EmojiPicker = ({
return;
}
searchInputRef.current?.setAttribute('aria-activedescendant', newCursorEmoji.name.toLocaleLowerCase().replaceAll(' ', '_'));
setCursor({
rowIndex: newCursor.rowIndex,
emojiId: newCursor.emojiId,

Просмотреть файл

@@ -29,6 +29,7 @@ const PostEmoji = ({children, name, imageUrl}: Props) => {
className='emoticon'
data-testid={`postEmoji.${emojiText}`}
style={{backgroundImage: backgroundImageUrl}}
aria-label={emojiText}
>
{children}
</span>

Просмотреть файл

@@ -538,7 +538,9 @@ $emoji-footer-height: $emoji-footer-border-width + $emoji-half-height + $emoji-
height: 36px;
align-items: center;
justify-content: center;
border: none;
border-radius: 3px;
background-color: transparent;
cursor: pointer;
vertical-align: middle;