From 9ce153563c3f884ca35dac3b30dd07c5c6c99a13 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Thu, 23 Feb 2023 20:34:46 +0100 Subject: [PATCH] test(Reactions): adjust tests to fit new implementation --- .../Message/__tests__/MessageText.test.js | 2 + src/components/Reactions/ReactionsList.tsx | 9 +- .../Reactions/SimpleReactionsList.tsx | 4 +- .../__tests__/ReactionSelector.test.js | 67 ++++++------ .../Reactions/__tests__/ReactionsList.test.js | 100 ++++++++++-------- .../__tests__/SimpleReactionsList.test.js | 74 ++++++------- .../SimpleReactionsList.test.js.snap | 75 +++++++++++++ 7 files changed, 200 insertions(+), 131 deletions(-) create mode 100644 src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap diff --git a/src/components/Message/__tests__/MessageText.test.js b/src/components/Message/__tests__/MessageText.test.js index 3641162326..3907ebd091 100644 --- a/src/components/Message/__tests__/MessageText.test.js +++ b/src/components/Message/__tests__/MessageText.test.js @@ -25,6 +25,7 @@ import { } from '../../../mock-builders'; import { Attachment } from '../../Attachment'; +import { defaultReactionOptions } from '../../Reactions'; import { Message } from '../Message'; import { MessageOptions as MessageOptionsMock } from '../MessageOptions'; import { MessageSimple } from '../MessageSimple'; @@ -84,6 +85,7 @@ async function renderMessageText(customProps, channelConfigOverrides = {}, rende Emoji: EmojiComponentMock, // eslint-disable-next-line react/display-name Message: () => , + reactionOptions: defaultReactionOptions, }} > diff --git a/src/components/Reactions/ReactionsList.tsx b/src/components/Reactions/ReactionsList.tsx index d1275763c6..d1c7e58931 100644 --- a/src/components/Reactions/ReactionsList.tsx +++ b/src/components/Reactions/ReactionsList.tsx @@ -113,14 +113,13 @@ const UnMemoizedReactionsList = < > - { - - - - } + + +   setTooltipReactionType(undefined)} > - {latestReactionTypes.map((reactionType, i) => { + {latestReactionTypes.map((reactionType) => { const ReactionOption = getEmojiByReactionType(reactionType); const isOwnReaction = iHaveReactedWithReaction(reactionType); const tooltipVisible = tooltipReactionType === reactionType; @@ -121,7 +121,7 @@ const UnMemoizedSimpleReactionsList = < className={clsx('str-chat__simple-reactions-list-item', { 'str-chat__message-reaction-own': isOwnReaction, })} - key={`${reactionType}-${i}`} + key={reactionType} onClick={(event) => handleReaction(reactionType, event)} onKeyUp={(event) => handleReaction(reactionType, event)} > diff --git a/src/components/Reactions/__tests__/ReactionSelector.test.js b/src/components/Reactions/__tests__/ReactionSelector.test.js index 16ae031666..1393df48c6 100644 --- a/src/components/Reactions/__tests__/ReactionSelector.test.js +++ b/src/components/Reactions/__tests__/ReactionSelector.test.js @@ -1,26 +1,20 @@ import React from 'react'; -import { fireEvent, render } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; -import EmojiComponentMock from 'emoji-mart/dist-modern/components/emoji/nimble-emoji'; import { toHaveNoViolations } from 'jest-axe'; import { axe } from '../../../../axe-helper'; expect.extend(toHaveNoViolations); import { ReactionSelector } from '../ReactionSelector'; +import { defaultReactionOptions } from '../reactionOptions'; +import * as utils from '../utils/utils'; import { Avatar as AvatarMock } from '../../Avatar'; -import { defaultMinimalEmojis } from '../../Channel/emojiData'; import { ComponentProvider } from '../../../context/ComponentContext'; -import { EmojiProvider } from '../../../context/EmojiContext'; import { MessageProvider } from '../../../context/MessageContext'; -import { - emojiComponentMock, - emojiDataMock, - generateReaction, - generateUser, -} from '../../../mock-builders'; +import { generateReaction, generateUser } from '../../../mock-builders'; jest.mock('emoji-mart/dist-modern/components/emoji/nimble-emoji', () => jest.fn(({ emoji }) =>
), @@ -45,18 +39,9 @@ const handleReactionMock = jest.fn(); const renderComponent = (props) => render( - + - - - + , ); @@ -66,28 +51,42 @@ describe('ReactionSelector', () => { jest.clearAllMocks(); }); - it('should render each of default emojis if reactionOptions prop is not specified', async () => { - const { container } = renderComponent(); + it('should render each of default emojis from image sprite', async () => { + jest.spyOn(utils, 'getImageDimensions').mockResolvedValue([128, 192]); - defaultMinimalEmojis.forEach((emoji) => { - expect(EmojiComponentMock).toHaveBeenCalledWith(expect.objectContaining({ emoji }), {}); + const { container, queryAllByTestId } = renderComponent(); + + await waitFor(() => { + expect(queryAllByTestId('sprite-image')).toHaveLength(6); + }); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + + it('should render fallbacks of each of the default emojis if sprite did not load', async () => { + jest.spyOn(utils, 'getImageDimensions').mockRejectedValue('Error'); + jest.spyOn(console, 'error').mockImplementation(null); + + const { container, getByText } = renderComponent(); + + await waitFor(() => { + expect(getByText('❤️')).toBeInTheDocument(); }); + const results = await axe(container); expect(results).toHaveNoViolations(); }); it('should render each of reactionOptions if specified', async () => { const reactionOptions = [ - { emoji: 'angry', id: 'angry' }, - { emoji: 'banana', id: 'banana' }, + { Component: jest.fn(() => null), type: 'test1' }, + { Component: jest.fn(() => null), type: 'test2' }, ]; const { container } = renderComponent({ reactionOptions }); - reactionOptions.forEach((emoji) => { - expect(EmojiComponentMock).toHaveBeenCalledWith( - expect.objectContaining({ emoji: expect.objectContaining(emoji) }), - {}, - ); + reactionOptions.forEach((option) => { + expect(option.Component).toHaveBeenCalledTimes(1); }); const results = await axe(container); expect(results).toHaveNoViolations(); @@ -181,9 +180,9 @@ describe('ReactionSelector', () => { }); it('should call handleReaction if an emoji is clicked', async () => { - const { container, getByTestId } = renderComponent(); + const { container, getByText } = renderComponent(); - const emoji = getByTestId('emoji-love'); + const emoji = getByText('❤️'); fireEvent.click(emoji); diff --git a/src/components/Reactions/__tests__/ReactionsList.test.js b/src/components/Reactions/__tests__/ReactionsList.test.js index 3a5c0365c5..f51e263a60 100644 --- a/src/components/Reactions/__tests__/ReactionsList.test.js +++ b/src/components/Reactions/__tests__/ReactionsList.test.js @@ -1,60 +1,47 @@ +/* eslint-disable react/display-name */ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; -import EmojiComponentMock from 'emoji-mart/dist-modern/components/emoji/nimble-emoji'; import { toHaveNoViolations } from 'jest-axe'; + import { axe } from '../../../../axe-helper'; expect.extend(toHaveNoViolations); import { ReactionsList } from '../ReactionsList'; - -import { EmojiProvider } from '../../../context/EmojiContext'; +import * as utils from '../utils/utils'; import { MessageProvider } from '../../../context/MessageContext'; -import { emojiComponentMock, emojiDataMock, generateReaction } from '../../../mock-builders'; +import { generateReaction } from '../../../mock-builders'; +import { ComponentProvider } from '../../../context'; +import { defaultReactionOptions } from '../reactionOptions'; -jest.mock('emoji-mart/dist-modern/components/emoji/nimble-emoji', () => - jest.fn(({ emoji }) =>
), -); +const USER_ID = 'mark'; -const renderComponent = ({ reaction_counts = {}, ...props }) => { - const reactions = Object.entries(reaction_counts) - .map(([type, count]) => - Array(count) - .fill() - .map(() => generateReaction({ type })), - ) - .flat(); +const generateButtonTitle = (count) => + Array.from({ length: count }, (_, i) => `${USER_ID}-${i}`).join(', '); - return render( - - - - - , - , +const renderComponent = ({ reaction_counts = {}, ...props }) => { + const reactions = Object.entries(reaction_counts).flatMap(([type, count]) => + Array(count) + .fill() + .map((_, i) => generateReaction({ type, user: { id: `${USER_ID}-${i}` } })), ); -}; -const expectEmojiToHaveBeenRendered = (id) => { - expect(EmojiComponentMock).toHaveBeenCalledWith( - expect.objectContaining({ - emoji: expect.objectContaining({ id }), - }), - {}, + return render( + + + , + + , ); }; describe('ReactionsList', () => { afterEach(jest.clearAllMocks); + // disable warnings (unreachable context) + jest.spyOn(console, 'warn').mockImplementation(null); + it('should render the total reaction count', async () => { const { container, getByText } = renderComponent({ reaction_counts: { @@ -71,14 +58,26 @@ describe('ReactionsList', () => { it('should render an emoji for each type of reaction', async () => { const reaction_counts = { - angry: 2, + haha: 2, love: 5, }; - const { container } = renderComponent({ reaction_counts }); + // make sure to render default fallbacks + jest.spyOn(utils, 'getImageDimensions').mockRejectedValue('Error'); + jest.spyOn(console, 'error').mockImplementation(null); + + const { container, getByTestId } = renderComponent({ reaction_counts }); - expect(EmojiComponentMock).toHaveBeenCalledTimes(Object.keys(reaction_counts).length); + const hahaButton = getByTestId('reactions-list-button-haha'); + const loveButton = getByTestId('reactions-list-button-love'); + + expect(hahaButton).toHaveAttribute('title', generateButtonTitle(reaction_counts['haha'])); + expect(hahaButton.lastChild).toHaveTextContent(reaction_counts['haha']); + expect(hahaButton.firstChild).toHaveTextContent('😂'); + + expect(loveButton).toHaveAttribute('title', generateButtonTitle(reaction_counts['love'])); + expect(loveButton.lastChild).toHaveTextContent(reaction_counts['love']); + expect(loveButton.firstChild).toHaveTextContent('❤️'); - Object.keys(reaction_counts).forEach(expectEmojiToHaveBeenRendered); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -89,17 +88,24 @@ describe('ReactionsList', () => { cowboy: 2, }; - const { container } = renderComponent({ + const { container, getByTestId } = renderComponent({ reaction_counts, reactionOptions: [ - { emoji: '🍌', id: 'banana' }, - { emoji: '🤠', id: 'cowboy' }, + { Component: () => <>🍌, type: 'banana' }, + { Component: () => <>🤠, type: 'cowboy' }, ], }); - expect(EmojiComponentMock).toHaveBeenCalledTimes(Object.keys(reaction_counts).length); + const bananaButton = getByTestId('reactions-list-button-banana'); + const cowboyButton = getByTestId('reactions-list-button-cowboy'); + + expect(bananaButton).toHaveAttribute('title', generateButtonTitle(reaction_counts['banana'])); + expect(bananaButton.lastChild).toHaveTextContent(reaction_counts['banana']); + expect(bananaButton.firstChild).toHaveTextContent('🍌'); + expect(cowboyButton).toHaveAttribute('title', generateButtonTitle(reaction_counts['cowboy'])); + expect(cowboyButton.lastChild).toHaveTextContent(reaction_counts['cowboy']); + expect(cowboyButton.firstChild).toHaveTextContent('🤠'); - Object.keys(reaction_counts).forEach(expectEmojiToHaveBeenRendered); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -110,8 +116,8 @@ describe('ReactionsList', () => { cowboy: 2, }; const reactionOptions = [ - { emoji: '🍌', id: 'banana' }, - { emoji: '🤠', id: 'cowboy' }, + { Component: () => <>🍌, type: 'banana' }, + { Component: () => <>🤠, type: 'cowboy' }, ]; expect( diff --git a/src/components/Reactions/__tests__/SimpleReactionsList.test.js b/src/components/Reactions/__tests__/SimpleReactionsList.test.js index e8ac915e4f..1f17ee8849 100644 --- a/src/components/Reactions/__tests__/SimpleReactionsList.test.js +++ b/src/components/Reactions/__tests__/SimpleReactionsList.test.js @@ -1,69 +1,57 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react'; import '@testing-library/jest-dom'; -import EmojiComponentMock from 'emoji-mart/dist-modern/components/emoji/nimble-emoji'; import { toHaveNoViolations } from 'jest-axe'; import { axe } from '../../../../axe-helper'; expect.extend(toHaveNoViolations); +import * as utils from '../utils/utils'; import { SimpleReactionsList } from '../SimpleReactionsList'; +import { defaultReactionOptions } from '../reactionOptions'; import { ChatProvider } from '../../../context/ChatContext'; -import { EmojiProvider } from '../../../context/EmojiContext'; import { MessageProvider } from '../../../context/MessageContext'; -import { emojiComponentMock, emojiDataMock, generateReaction } from '../../../mock-builders'; +import { generateReaction } from '../../../mock-builders'; +import { ComponentProvider } from '../../../context'; jest.mock('emoji-mart/dist-modern/components/emoji/nimble-emoji', () => jest.fn(({ emoji }) =>
), ); const handleReactionMock = jest.fn(); -const loveEmojiTestId = 'emoji-love'; +// const loveEmojiTestId = 'emoji-love'; const renderComponent = ({ reaction_counts = {}, themeVersion = '1', ...props }) => { - const reactions = Object.entries(reaction_counts) - .map(([type, count]) => - Array(count) - .fill() - .map(() => generateReaction({ type })), - ) - .flat(); + const reactions = Object.entries(reaction_counts).flatMap(([type, count]) => + Array(count) + .fill() + .map((_, i) => generateReaction({ type, user: { id: `${USER_ID}-${i}` } })), + ); return { ...render( - - + + - - + + , ), reactions, }; }; -const expectEmojiToHaveBeenRendered = (id) => { - expect(EmojiComponentMock).toHaveBeenCalledWith( - expect.objectContaining({ - emoji: expect.objectContaining({ id }), - }), - {}, - ); -}; +const USER_ID = 'mark'; + +const generateButtonTitle = (count) => + Array.from({ length: count }, (_, i) => `${USER_ID}-${i}`).join(', '); describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { afterEach(jest.clearAllMocks); @@ -81,7 +69,7 @@ describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { it('should render the total reaction count', async () => { const { container, getByText } = renderComponent({ reaction_counts: { - angry: 2, + haha: 2, love: 5, }, themeVersion, @@ -95,14 +83,16 @@ describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { it('should render an emoji for each type of reaction', async () => { const reaction_counts = { - angry: 2, + haha: 2, love: 5, }; - const { container } = renderComponent({ reaction_counts, themeVersion }); + // force to render default fallbacks + jest.spyOn(utils, 'getImageDimensions').mockRejectedValue('Error'); + jest.spyOn(console, 'error').mockImplementation(null); - expect(EmojiComponentMock).toHaveBeenCalledTimes(Object.keys(reaction_counts).length); + const { container } = renderComponent({ reaction_counts, themeVersion }); - Object.keys(reaction_counts).forEach(expectEmojiToHaveBeenRendered); + expect(container).toMatchSnapshot(); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -122,9 +112,7 @@ describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { themeVersion, }); - expect(EmojiComponentMock).toHaveBeenCalledTimes(Object.keys(reaction_counts).length); - - Object.keys(reaction_counts).forEach(expectEmojiToHaveBeenRendered); + expect(container).toMatchSnapshot(); const results = await axe(container); expect(results).toHaveNoViolations(); }); @@ -134,9 +122,9 @@ describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { love: 1, }; - const { container, getByTestId } = renderComponent({ reaction_counts, themeVersion }); + const { container, getByText } = renderComponent({ reaction_counts, themeVersion }); - fireEvent.click(getByTestId(loveEmojiTestId)); + fireEvent.click(getByText('❤️')); expect(handleReactionMock).toHaveBeenCalledWith('love', expect.any(Object)); const results = await axe(container); @@ -148,18 +136,18 @@ describe.each(['1', '2'])('SimpleReactionsList v%s', (themeVersion) => { love: 3, }; - const { container, getByTestId, queryByText, reactions } = renderComponent({ + const { container, getByText, queryByText, reactions } = renderComponent({ reaction_counts, themeVersion, }); - fireEvent.mouseEnter(getByTestId(loveEmojiTestId)); + fireEvent.mouseEnter(getByText('❤️')); reactions.forEach(({ user }) => { expect(queryByText(user.name || user.id, { exact: false })).toBeInTheDocument(); }); - fireEvent.mouseLeave(getByTestId(loveEmojiTestId)); + fireEvent.mouseLeave(getByText('❤️')); reactions.forEach(({ user }) => { expect(queryByText(user.id, { exact: false })).not.toBeInTheDocument(); diff --git a/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap b/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap new file mode 100644 index 0000000000..a01ea96c66 --- /dev/null +++ b/src/components/Reactions/__tests__/__snapshots__/SimpleReactionsList.test.js.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SimpleReactionsList v1 should handle custom reaction options 1`] = `
`; + +exports[`SimpleReactionsList v1 should render an emoji for each type of reaction 1`] = ` +
+
+
    +
  • + + 😂 +   + +
  • +
  • + + ❤️ +   + +
  • +
  • + 7 +
  • +
+
+
+`; + +exports[`SimpleReactionsList v2 should handle custom reaction options 1`] = `
`; + +exports[`SimpleReactionsList v2 should render an emoji for each type of reaction 1`] = ` +
+
+
    +
  • + + 😂 +   + +
  • +
  • + + ❤️ +   + +
  • +
  • + 7 +
  • +
+
+
+`;