From 5c4dfd5de383f79a89e8c45ba61c433ec50086ef Mon Sep 17 00:00:00 2001 From: Diana Villalvazo Date: Tue, 17 Jun 2025 18:00:44 -0600 Subject: [PATCH] test: Deprecate react-unit-test-utils 3/15 (#655) --- .../views/EligibleContent.test.jsx | 74 +++++------ .../views/MustRequestContent.test.jsx | 122 +++++++++++------- .../views/PendingContent.test.jsx | 66 +++++----- .../views/RejectedContent.test.jsx | 42 +++--- .../__snapshots__/index.test.jsx.snap | 14 -- .../WidgetSidebarSlot/index.test.jsx | 26 ++-- 6 files changed, 185 insertions(+), 159 deletions(-) delete mode 100644 src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx index bbdfd16..fb75912 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/EligibleContent.test.jsx @@ -1,8 +1,8 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { reduxHooks } from 'hooks'; -import { formatMessage } from 'testUtils'; import track from 'tracking'; import messages from './messages'; @@ -14,15 +14,16 @@ jest.mock('hooks', () => ({ useCardCourseRunData: jest.fn(), }, })); -jest.mock('./components/CreditContent', () => 'CreditContent'); + jest.mock('tracking', () => ({ credit: { - purchase: (...args) => ({ trackCredit: args }), + purchase: jest.fn(), }, })); -let el; -let component; +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); const cardId = 'test-card-id'; const courseId = 'test-course-id'; @@ -32,50 +33,45 @@ const credit = { reduxHooks.useCardCreditData.mockReturnValue(credit); reduxHooks.useCardCourseRunData.mockReturnValue({ courseId }); -const render = () => { - el = shallow(); -}; -const loadComponent = () => { - component = el.instance.findByType('CreditContent'); -}; +const renderEligibleContent = () => render(); + describe('EligibleContent component', () => { - beforeEach(() => { - render(); - }); - describe('behavior', () => { + describe('hooks', () => { it('initializes credit data with cardId', () => { + renderEligibleContent(); expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId); }); it('initializes course run data with cardId', () => { + renderEligibleContent(); expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(cardId); }); }); - describe('render', () => { + describe('behavior', () => { describe('rendered CreditContent component', () => { - beforeEach(() => { - loadComponent(); + it('action message is formatted getCredit message', () => { + renderEligibleContent(); + const button = screen.getByRole('button', { name: messages.getCredit.defaultMessage }); + expect(button).toBeInTheDocument(); }); - test('action.onClick sends credit purchase track event', () => { - expect(component[0].props.action.onClick).toEqual( - track.credit.purchase(courseId), - ); + it('onClick sends credit purchase track event', async () => { + const user = userEvent.setup(); + renderEligibleContent(); + const button = screen.getByRole('button', { name: messages.getCredit.defaultMessage }); + await user.click(button); + expect(track.credit.purchase).toHaveBeenCalledWith(courseId); }); - test('action.message is formatted getCredit message', () => { - expect(component[0].props.action.message).toEqual(formatMessage(messages.getCredit)); + it('message is formatted eligible message if provider', () => { + renderEligibleContent(); + const eligibleMessage = screen.getByTestId('credit-msg'); + expect(eligibleMessage).toBeInTheDocument(); + expect(eligibleMessage).toHaveTextContent(credit.providerName); }); - test('message is formatted eligible message if no provider', () => { - reduxHooks.useCardCreditData.mockReturnValueOnce({}); - render(); - loadComponent(); - expect(component[0].props.message).toEqual(formatMessage( - messages.eligible, - { getCredit: ({formatMessage(messages.getCredit)}) }, - )); - }); - test('message is formatted eligible message if provider', () => { - expect(component[0].props.message).toEqual( - formatMessage(messages.eligibleFromProvider, { providerName: credit.providerName }), - ); + it('message is formatted eligible message if no provider', () => { + reduxHooks.useCardCreditData.mockReturnValue({}); + renderEligibleContent(); + const eligibleMessage = screen.getByTestId('credit-msg'); + expect(eligibleMessage).toBeInTheDocument(); + expect(eligibleMessage).toHaveTextContent(messages.getCredit.defaultMessage); }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx index 9eb4200..7509316 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/MustRequestContent.test.jsx @@ -1,73 +1,107 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { formatMessage } from 'testUtils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import userEvent from '@testing-library/user-event'; import { reduxHooks } from 'hooks'; import messages from './messages'; import hooks from './hooks'; -import ProviderLink from './components/ProviderLink'; import MustRequestContent from './MustRequestContent'; jest.mock('./hooks', () => ({ useCreditRequestData: jest.fn(), })); -jest.mock('hooks', () => ({ - reduxHooks: { useMasqueradeData: jest.fn() }, -})); -jest.mock('./components/CreditContent', () => 'CreditContent'); -jest.mock('./components/ProviderLink', () => 'ProviderLink'); -let el; -let component; +jest.mock('hooks', () => ({ + reduxHooks: { + useMasqueradeData: jest.fn(), + useCardCreditData: jest.fn(), + }, +})); + +jest.unmock('@openedx/paragon'); +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('react'); const cardId = 'test-card-id'; -const requestData = { test: 'requestData' }; -const createCreditRequest = jest.fn().mockName('createCreditRequest'); -hooks.useCreditRequestData.mockReturnValue({ - requestData, - createCreditRequest, -}); -reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false }); - -const render = () => { - el = shallow(); +const requestData = { + url: 'test-request-data-url', + parameters: { + key1: 'val1', + key2: 'val2', + key3: 'val3', + }, }; +const providerName = 'test-credit-provider-name'; +const providerStatusUrl = 'test-credit-provider-status-url'; +const createCreditRequest = jest.fn().mockName('createCreditRequest'); + +const renderMustRequestContent = () => render( + + + , +); + describe('MustRequestContent component', () => { beforeEach(() => { - render(); + jest.clearAllMocks(); + hooks.useCreditRequestData.mockReturnValue({ + requestData, + createCreditRequest, + }); + reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false }); + reduxHooks.useCardCreditData.mockReturnValue({ + providerName, + providerStatusUrl, + }); }); - describe('behavior', () => { + + describe('hooks', () => { it('initializes credit request data with cardId', () => { + renderMustRequestContent(); expect(hooks.useCreditRequestData).toHaveBeenCalledWith(cardId); }); }); - describe('render', () => { - describe('rendered CreditContent component', () => { + + describe('behavior', () => { + describe('rendered content', () => { beforeEach(() => { - component = el.instance.findByType('CreditContent'); + renderMustRequestContent(); }); - test('action.onClick calls createCreditRequest from useCreditRequestData hook', () => { - expect(component[0].props.action.onClick).toEqual(createCreditRequest); + + it('calls createCreditRequest when request credit button is clicked', async () => { + const user = userEvent.setup(); + const button = screen.getByRole('button', { name: /request credit/i }); + await user.click(button); + expect(createCreditRequest).toHaveBeenCalled(); }); - test('action.message is formatted requestCredit message', () => { - expect(component[0].props.action.message).toEqual( - formatMessage(messages.requestCredit), - ); + + it('shows request credit button that is enabled', () => { + const button = screen.getByRole('button', { name: /request credit/i }); + expect(button).toBeEnabled(); }); - test('action.disabled is false', () => { - expect(component[0].props.action.disabled).toEqual(false); + + it('displays must request message with provider link', () => { + expect(screen.getByTestId('credit-msg')).toHaveTextContent(/request credit/i); }); - test('message is formatted mustRequest message', () => { - expect(component[0].props.message).toEqual( - formatMessage(messages.mustRequest, { - linkToProviderSite: , - requestCredit: {formatMessage(messages.requestCredit)}, - }), - ); + + it('renders credit request form with correct data', () => { + const { container } = renderMustRequestContent(); + const form = container.querySelector('form'); + expect(form).toBeInTheDocument(); + expect(form).toHaveAttribute('action', requestData.url); }); - test('requestData drawn from useCreditRequestData hook', () => { - expect(component[0].props.requestData).toEqual(requestData); + }); + + describe('when masquerading', () => { + beforeEach(() => { + reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true }); + renderMustRequestContent(); + }); + + it('disables the request credit button', () => { + const button = screen.getByRole('button', { name: /request credit/i }); + expect(button).toHaveClass('disabled'); + expect(button).toHaveAttribute('aria-disabled', 'true'); }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx index 0711412..4edefa2 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/PendingContent.test.jsx @@ -1,7 +1,6 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { formatMessage } from 'testUtils'; import { reduxHooks } from 'hooks'; import messages from './messages'; @@ -10,11 +9,10 @@ import PendingContent from './PendingContent'; jest.mock('hooks', () => ({ reduxHooks: { useCardCreditData: jest.fn(), useMasqueradeData: jest.fn() }, })); -jest.mock('./components/CreditContent', () => 'CreditContent'); -jest.mock('./components/ProviderLink', () => 'ProviderLink'); -let el; -let component; +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); const cardId = 'test-card-id'; const providerName = 'test-credit-provider-name'; @@ -25,38 +23,48 @@ reduxHooks.useCardCreditData.mockReturnValue({ }); reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: false }); -const render = () => { - el = shallow(); -}; +const renderPendingContent = () => render( + + + , +); describe('PendingContent component', () => { - beforeEach(() => { - render(); - }); - describe('behavior', () => { + describe('hooks', () => { it('initializes card credit data with cardId', () => { + renderPendingContent(); expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId); }); }); - describe('render', () => { + describe('behavior', () => { describe('rendered CreditContent component', () => { - beforeEach(() => { - component = el.instance.findByType('CreditContent'); + it('action message is formatted requestCredit message', () => { + renderPendingContent(); + const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage }); + expect(button).toBeInTheDocument(); }); - test('action.href will go to provider status site', () => { - expect(component[0].props.action.href).toEqual(providerStatusUrl); + it('action href will go to provider status site', () => { + renderPendingContent(); + const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage }); + expect(button).toHaveAttribute('href', providerStatusUrl); }); - test('action.message is formatted requestCredit message', () => { - expect(component[0].props.action.message).toEqual( - formatMessage(messages.viewDetails), - ); + it('action.disabled is false', () => { + renderPendingContent(); + const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage }); + expect(button).not.toHaveClass('disabled'); }); - test('action.disabled is false', () => { - expect(component[0].props.action.disabled).toEqual(false); + it('message is formatted pending message with provider name', () => { + renderPendingContent(); + const component = screen.getByTestId('credit-msg'); + expect(component).toBeInTheDocument(); + expect(component).toHaveTextContent(`${providerName} has received`); }); - test('message is formatted pending message', () => { - expect(component[0].props.message).toEqual( - formatMessage(messages.received, { providerName }), - ); + describe('when masqueradeData is true', () => { + it('disables the view details button', () => { + reduxHooks.useMasqueradeData.mockReturnValue({ isMasquerading: true }); + renderPendingContent(); + const button = screen.getByRole('link', { name: messages.viewDetails.defaultMessage }); + expect(button).toHaveClass('disabled'); + }); }); }); }); diff --git a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx index b9aea8f..b61a6e4 100644 --- a/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx +++ b/src/containers/CourseCard/components/CourseCardBanners/CreditBanner/views/RejectedContent.test.jsx @@ -1,10 +1,7 @@ -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { formatMessage } from 'testUtils'; import { reduxHooks } from 'hooks'; -import messages from './messages'; -import ProviderLink from './components/ProviderLink'; import RejectedContent from './RejectedContent'; jest.mock('hooks', () => ({ @@ -12,8 +9,10 @@ jest.mock('hooks', () => ({ useCardCreditData: jest.fn(), }, })); -jest.mock('./components/CreditContent', () => 'CreditContent'); -jest.mock('./components/ProviderLink', () => 'ProviderLink'); + +jest.unmock('@openedx/paragon'); +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('react'); const cardId = 'test-card-id'; const credit = { @@ -22,32 +21,27 @@ const credit = { }; reduxHooks.useCardCreditData.mockReturnValue(credit); -let el; -let component; -const render = () => { el = shallow(); }; -const loadComponent = () => { component = el.instance.findByType('CreditContent'); }; +const renderRejectedContent = () => render(); describe('RejectedContent component', () => { - beforeEach(render); - describe('behavior', () => { + describe('hooks', () => { it('initializes credit data with cardId', () => { + renderRejectedContent(); expect(reduxHooks.useCardCreditData).toHaveBeenCalledWith(cardId); }); }); describe('render', () => { describe('rendered CreditContent component', () => { - beforeAll(loadComponent); - test('no action is passed', () => { - expect(component[0].props.action).toEqual(undefined); + it('no action is passed', () => { + renderRejectedContent(); + const action = screen.queryByTestId('action-row-btn'); + expect(action).not.toBeInTheDocument(); }); - test('message is formatted rejected message', () => { - expect(component[0].props.message).toEqual(formatMessage( - messages.rejected, - { - linkToProviderSite: , - providerName: credit.providerName, - }, - )); + it('message is formatted rejected message', () => { + renderRejectedContent(); + const message = screen.getByTestId('credit-msg'); + expect(message).toBeInTheDocument(); + expect(message).toHaveTextContent(`${credit.providerName} did not approve your request for course credit.`); }); }); }); diff --git a/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap b/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 38ac325..0000000 --- a/src/plugin-slots/WidgetSidebarSlot/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`WidgetSidebar snapshots 1`] = ` - - - -`; diff --git a/src/plugin-slots/WidgetSidebarSlot/index.test.jsx b/src/plugin-slots/WidgetSidebarSlot/index.test.jsx index 591ddcc..e000426 100644 --- a/src/plugin-slots/WidgetSidebarSlot/index.test.jsx +++ b/src/plugin-slots/WidgetSidebarSlot/index.test.jsx @@ -1,18 +1,26 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { reduxHooks } from 'hooks'; import WidgetSidebarSlot from '.'; -jest.mock('widgets/LookingForChallengeWidget', () => 'LookingForChallengeWidget'); +jest.unmock('react'); +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); -jest.mock('@openedx/frontend-plugin-framework', () => ({ - PluginSlot: 'PluginSlot', +jest.mock('hooks', () => ({ + reduxHooks: { + usePlatformSettingsData: jest.fn(), + }, })); -describe('WidgetSidebar', () => { - beforeEach(() => jest.resetAllMocks()); +const courseSearchUrl = 'mock-url'; - test('snapshots', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); +describe('WidgetSidebar', () => { + it('renders PluginSlot with correct children', () => { + reduxHooks.usePlatformSettingsData.mockReturnValueOnce({ courseSearchUrl }); + render(); + const pluginSlot = screen.getByText('Looking for a new challenge?'); + expect(pluginSlot).toBeDefined(); }); });