[MM-61577]: Ensure all interactive functionality is operable with the keyboard in emoji picker (#29629)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
316cde2569
Коммит
e8e40c79f6
@@ -128,11 +128,11 @@ describe('Custom emojis', () => {
|
|||||||
cy.findByText('Custom').should('exist').and('is.visible');
|
cy.findByText('Custom').should('exist').and('is.visible');
|
||||||
|
|
||||||
// * Verify that first custom emoji exists and is visible to user
|
// * 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,
|
// * Verify second custom emoji exists and is visible to user,
|
||||||
// if both custom emojis are visible we can conclude that they are not overlapping
|
// 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');
|
cy.findByText(/Recently Used/i).should('exist').and('be.visible');
|
||||||
|
|
||||||
// * Assert first emoji should equal with second recent emoji
|
// * 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
|
// * 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', () => {
|
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');
|
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
|
// * 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
|
// # Go to custom emoji page
|
||||||
cy.findByText('Custom Emoji').should('be.visible').click();
|
cy.findByText('Custom Emoji').should('be.visible').click();
|
||||||
@@ -172,6 +172,6 @@ describe('Recent Emoji', () => {
|
|||||||
cy.findByText('Recently Used').should('exist').and('be.visible');
|
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
|
// * 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');
|
cy.findByText('Recently Used').should('exist').and('be.visible');
|
||||||
|
|
||||||
// * Verify most recent one is the thumbsup emoji with default skin
|
// * 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
|
// # Open skin picker again to change the skin
|
||||||
cy.findByAltText('emoji skin tone picker').should('exist').parent().click().wait(TIMEOUTS.ONE_SEC);
|
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();
|
cy.findByTestId('skin-pick-1F3FF').should('exist').click();
|
||||||
|
|
||||||
// * Verify most recent one is the same thumbsup emoji but now with a dark skin tone
|
// * 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
|
// # 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
|
// # Assert first recently used emoji has the data-test-id value of 'cat' which was the last one we sent
|
||||||
cy.findAllByTestId('emojiItem').
|
cy.findAllByTestId('emojiItem').
|
||||||
findByRole('button', {name: 'cat emoji'}).
|
each(($btn) => {
|
||||||
should('exist');
|
// 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 = [];
|
const emojiList = [];
|
||||||
|
|
||||||
|
|||||||
@@ -99,13 +99,13 @@ describe('MM-T154 Use keyboard navigation in emoji picker', () => {
|
|||||||
cy.uiGetPostTextBox().type('{enter}');
|
cy.uiGetPostTextBox().type('{enter}');
|
||||||
|
|
||||||
// * Compare selected emoji with last post
|
// * 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) => {
|
const testSelectedIndex = (done) => {
|
||||||
cy.get('div.emoji-picker__item.selected').then((selectedEmoji) => {
|
cy.get('button.emoji-picker__item.selected').then((selectedEmoji) => {
|
||||||
done(selectedEmoji.index());
|
done(selectedEmoji.index());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = `
|
|||||||
style="height: 290px;"
|
style="height: 290px;"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
aria-labelledby="emojiPickerSearch"
|
||||||
class="emoji-picker__container"
|
class="emoji-picker__container"
|
||||||
|
role="grid"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="overflow: visible; height: 0px; width: 0px;"
|
style="overflow: visible; height: 0px; width: 0px;"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ function EmojiPickerCategoryOrEmojiRow({index, style, data, cursorRowIndex, curs
|
|||||||
<div
|
<div
|
||||||
style={style}
|
style={style}
|
||||||
className='emoji-picker__row'
|
className='emoji-picker__row'
|
||||||
|
role='row'
|
||||||
>
|
>
|
||||||
{row.items.map((emojiColumn) => {
|
{row.items.map((emojiColumn) => {
|
||||||
const emoji = emojiColumn.item;
|
const emoji = emojiColumn.item;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ function EmojiPickerCategoryRow({categoryName, style}: Props) {
|
|||||||
<div
|
<div
|
||||||
className='emoji-picker-items__container'
|
className='emoji-picker-items__container'
|
||||||
style={style}
|
style={style}
|
||||||
|
role='row'
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className='emoji-picker__category-header'
|
className='emoji-picker__category-header'
|
||||||
|
|||||||
@@ -91,7 +91,11 @@ const EmojiPickerCurrentResults = forwardRef<InfiniteLoader, Props>(({categoryOr
|
|||||||
className='emoji-picker__items'
|
className='emoji-picker__items'
|
||||||
style={{height: EMOJI_CONTAINER_HEIGHT}}
|
style={{height: EMOJI_CONTAINER_HEIGHT}}
|
||||||
>
|
>
|
||||||
<div className='emoji-picker__container'>
|
<div
|
||||||
|
className='emoji-picker__container'
|
||||||
|
role='grid'
|
||||||
|
aria-labelledby='emojiPickerSearch'
|
||||||
|
>
|
||||||
<AutoSizer>
|
<AutoSizer>
|
||||||
{({height, width}) => (
|
{({height, width}) => (
|
||||||
<InfiniteLoader
|
<InfiniteLoader
|
||||||
|
|||||||
@@ -55,32 +55,21 @@ function EmojiPickerItem({emoji, rowIndex, isSelected, onClick, onMouseOver}: Pr
|
|||||||
let content;
|
let content;
|
||||||
|
|
||||||
if (isSystemEmoji(emoji)) {
|
if (isSystemEmoji(emoji)) {
|
||||||
const emojiName = emoji.short_name ? emoji.short_name : emoji.name;
|
|
||||||
const emojiUnified = emoji.unified ? emoji.unified.toLowerCase() : emoji.name.toLowerCase();
|
const emojiUnified = emoji.unified ? emoji.unified.toLowerCase() : emoji.name.toLowerCase();
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<img
|
<img
|
||||||
alt={'emoji image'}
|
alt={`${emoji.name.toLocaleLowerCase()} emoji`}
|
||||||
data-testid={emoji.short_names}
|
data-testid={emoji.short_names}
|
||||||
src={imgTrans}
|
src={imgTrans}
|
||||||
className={`emojisprite emoji-category-${emoji.category} emoji-${emojiUnified}`}
|
className={`emojisprite emoji-category-${emoji.category} emoji-${emojiUnified}`}
|
||||||
id={`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 {
|
} else {
|
||||||
content = (
|
content = (
|
||||||
<img
|
<img
|
||||||
alt={'custom emoji image'}
|
alt={'custom emoji'}
|
||||||
data-testid={emoji.name}
|
data-testid={emoji.name}
|
||||||
src={getEmojiImageUrl(emoji)}
|
src={getEmojiImageUrl(emoji)}
|
||||||
className={'emoji-category--custom'}
|
className={'emoji-category--custom'}
|
||||||
@@ -89,15 +78,26 @@ function EmojiPickerItem({emoji, rowIndex, isSelected, onClick, onMouseOver}: Pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
className={itemClassName}
|
className={itemClassName}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseOver={throttledMouseOver}
|
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}
|
||||||
{content}
|
</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {useIntl} from 'react-intl';
|
|||||||
import {EMOJI_PER_ROW} from 'components/emoji_picker/constants';
|
import {EMOJI_PER_ROW} from 'components/emoji_picker/constants';
|
||||||
import {NavigationDirection} from 'components/emoji_picker/types';
|
import {NavigationDirection} from 'components/emoji_picker/types';
|
||||||
|
|
||||||
|
import Constants from 'utils/constants';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
cursorCategoryIndex: number;
|
cursorCategoryIndex: number;
|
||||||
@@ -24,6 +26,8 @@ interface Props {
|
|||||||
resetCursorPosition: () => void;
|
resetCursorPosition: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KeyCodes = Constants.KeyCodes;
|
||||||
|
|
||||||
const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCategoryIndex, cursorEmojiIndex, onChange, resetCursorPosition, onKeyDown, focus, onEnter}: Props, ref) => {
|
const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCategoryIndex, cursorEmojiIndex, onChange, resetCursorPosition, onKeyDown, focus, onEnter}: Props, ref) => {
|
||||||
const {formatMessage} = useIntl();
|
const {formatMessage} = useIntl();
|
||||||
|
|
||||||
@@ -39,7 +43,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
|
|||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||||
switch (event.key) {
|
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 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)) {
|
if ((event.currentTarget?.selectionStart ?? 0) + 1 > value.length || (cursorCategoryIndex !== -1 || cursorEmojiIndex !== -1)) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -48,7 +52,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
|
|||||||
onKeyDown(NavigationDirection.NextEmoji);
|
onKeyDown(NavigationDirection.NextEmoji);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ArrowLeft':
|
case KeyCodes.LEFT[0]:
|
||||||
if (cursorCategoryIndex > 0 || cursorEmojiIndex > 0) {
|
if (cursorCategoryIndex > 0 || cursorEmojiIndex > 0) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -65,7 +69,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
|
|||||||
focus();
|
focus();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ArrowUp':
|
case KeyCodes.UP[0]:
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -88,7 +92,7 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
|
|||||||
onKeyDown(NavigationDirection.PreviousEmojiRow);
|
onKeyDown(NavigationDirection.PreviousEmojiRow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'ArrowDown':
|
case KeyCodes.DOWN[0]:
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
@@ -104,7 +108,14 @@ const EmojiPickerSearch = forwardRef<HTMLInputElement, Props>(({value, cursorCat
|
|||||||
onKeyDown(NavigationDirection.NextEmojiRow);
|
onKeyDown(NavigationDirection.NextEmojiRow);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'Enter': {
|
case KeyCodes.SPACE[0]: {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
onEnter();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case KeyCodes.ENTER[0]: {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ const EmojiPicker = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
searchInputRef.current?.setAttribute('aria-activedescendant', newCursorEmoji.name.toLocaleLowerCase().replaceAll(' ', '_'));
|
||||||
setCursor({
|
setCursor({
|
||||||
rowIndex: newCursor.rowIndex,
|
rowIndex: newCursor.rowIndex,
|
||||||
emojiId: newCursor.emojiId,
|
emojiId: newCursor.emojiId,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const PostEmoji = ({children, name, imageUrl}: Props) => {
|
|||||||
className='emoticon'
|
className='emoticon'
|
||||||
data-testid={`postEmoji.${emojiText}`}
|
data-testid={`postEmoji.${emojiText}`}
|
||||||
style={{backgroundImage: backgroundImageUrl}}
|
style={{backgroundImage: backgroundImageUrl}}
|
||||||
|
aria-label={emojiText}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -538,7 +538,9 @@ $emoji-footer-height: $emoji-footer-border-width + $emoji-half-height + $emoji-
|
|||||||
height: 36px;
|
height: 36px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
background-color: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user