diff --git a/src/containers/CourseCard/__snapshots__/index.test.jsx.snap b/src/containers/CourseCard/__snapshots__/index.test.jsx.snap deleted file mode 100644 index 08f36c4..0000000 --- a/src/containers/CourseCard/__snapshots__/index.test.jsx.snap +++ /dev/null @@ -1,111 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCard component snapshot: collapsed 1`] = ` -
- -
-
- - - - } - title={ - - } - /> - - - - - - - -
- -
-
-
-`; - -exports[`CourseCard component snapshot: not collapsed 1`] = ` -
- -
-
- - - - } - title={ - - } - /> - - - - - - - -
- -
-
-
-`; diff --git a/src/containers/CourseCard/components/CourseCardImage.test.jsx b/src/containers/CourseCard/components/CourseCardImage.test.jsx index 05d0636..eee03c0 100644 --- a/src/containers/CourseCard/components/CourseCardImage.test.jsx +++ b/src/containers/CourseCard/components/CourseCardImage.test.jsx @@ -1,61 +1,72 @@ -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 track from 'tracking'; import useActionDisabledState from './hooks'; -import CourseCardImage from './CourseCardImage'; +import { CourseCardImage } from './CourseCardImage'; +import messages from '../messages'; -const homeUrl = 'home-url'; +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); -jest.mock('tracking', () => ({ - course: { - courseImageClicked: jest.fn().mockName('segment.courseImageClicked'), - }, -})); +const homeUrl = 'https://example.com'; +const bannerImgSrc = 'banner-img-src.jpg'; jest.mock('hooks', () => ({ reduxHooks: { - useCardCourseData: jest.fn(() => ({ bannerImgSrc: 'banner-img-src' })), + useCardCourseData: jest.fn(() => ({ bannerImgSrc })), useCardCourseRunData: jest.fn(() => ({ homeUrl })), - useCardEnrollmentData: jest.fn(() => ({ isVerified: true })), + useCardEnrollmentData: jest.fn(), useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({ trackCourseEvent: { eventName, cardId, url }, })), }, })); -jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false }))); + +jest.mock('./hooks', () => jest.fn()); describe('CourseCardImage', () => { const props = { - cardId: 'cardId', - orientation: 'orientation', + cardId: 'test-card-id', + orientation: 'horizontal', }; - beforeEach(() => { - jest.clearAllMocks(); + + it('renders course image with correct attributes', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: true }); + reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true }); + render(); + + const image = screen.getByRole('img', { name: formatMessage(messages.bannerAlt) }); + expect(image).toBeInTheDocument(); + expect(image.src).toContain(bannerImgSrc); + expect(image.parentElement).toHaveClass('horizontal'); }); - describe('snapshot', () => { - test('renders clickable link course Image', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - expect(wrapper.instance.type).toBe('a'); - expect(wrapper.instance.props.onClick).toEqual( - reduxHooks.useTrackCourseEvent( - track.course.courseImageClicked, - props.cardId, - homeUrl, - ), - ); - }); - test('renders disabled link', () => { - useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - expect(wrapper.instance.type).toBe('div'); - }); + + it('isVerified, should render badge', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true }); + render(); + + const badge = screen.getByText(formatMessage(messages.verifiedBanner)); + expect(badge).toBeInTheDocument(); + const badgeImg = screen.getByRole('img', { name: formatMessage(messages.verifiedBannerRibbonAlt) }); + expect(badgeImg).toBeInTheDocument(); }); - describe('behavior', () => { + + it('renders link with correct href if disableCourseTitle is false', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: false }); + render(); + + const link = screen.getByRole('link'); + expect(link).toHaveAttribute('href', homeUrl); + }); + describe('hooks', () => { it('initializes', () => { - shallow(); + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + reduxHooks.useCardEnrollmentData.mockReturnValue({ isVerified: true }); + render(); expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId); expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith( props.cardId, diff --git a/src/containers/CourseCard/components/CourseCardTitle.test.jsx b/src/containers/CourseCard/components/CourseCardTitle.test.jsx index aa3dcdd..306df11 100644 --- a/src/containers/CourseCard/components/CourseCardTitle.test.jsx +++ b/src/containers/CourseCard/components/CourseCardTitle.test.jsx @@ -1,12 +1,10 @@ -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { reduxHooks } from 'hooks'; import track from 'tracking'; import useActionDisabledState from './hooks'; import CourseCardTitle from './CourseCardTitle'; -const homeUrl = 'home-url'; - jest.mock('tracking', () => ({ course: { courseTitleClicked: jest.fn().mockName('segment.courseTitleClicked'), @@ -15,53 +13,65 @@ jest.mock('tracking', () => ({ jest.mock('hooks', () => ({ reduxHooks: { - useCardCourseData: jest.fn(() => ({ courseName: 'course-name' })), - useCardCourseRunData: jest.fn(() => ({ homeUrl })), - useTrackCourseEvent: jest.fn((eventName, cardId, url) => ({ - trackCourseEvent: { eventName, cardId, url }, - })), + useCardCourseData: jest.fn(), + useCardCourseRunData: jest.fn(), + useTrackCourseEvent: jest.fn(), }, })); + jest.mock('./hooks', () => jest.fn(() => ({ disableCourseTitle: false }))); +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); + describe('CourseCardTitle', () => { const props = { - cardId: 'cardId', + cardId: 'test-card-id', }; + + const courseName = 'Test Course'; + const homeUrl = 'http://test.com'; + const handleTitleClick = jest.fn(); + beforeEach(() => { jest.clearAllMocks(); + reduxHooks.useCardCourseData.mockReturnValue({ courseName }); + reduxHooks.useCardCourseRunData.mockReturnValue({ homeUrl }); + reduxHooks.useTrackCourseEvent.mockReturnValue(handleTitleClick); }); - describe('snapshot', () => { - test('renders clickable link course title', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - const title = wrapper.instance.findByTestId('CourseCardTitle'); - expect(title[0].type).toBe('a'); - expect(title[0].props.onClick).toEqual( - reduxHooks.useTrackCourseEvent( - track.course.courseTitleClicked, - props.cardId, - homeUrl, - ), - ); - }); - test('renders disabled link', () => { - useActionDisabledState.mockReturnValueOnce({ disableCourseTitle: true }); - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); - const title = wrapper.instance.findByTestId('CourseCardTitle'); - expect(title[0].type).toBe('span'); - expect(title[0].props.onClick).toBeUndefined(); - }); + + it('renders course name as link when not disabled', async () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + render(); + + const user = userEvent.setup(); + const link = screen.getByRole('link', { name: courseName }); + expect(link).toHaveAttribute('href', homeUrl); + + await user.click(link); + expect(handleTitleClick).toHaveBeenCalled(); }); - describe('behavior', () => { - it('initializes', () => { - shallow(); - expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId); - expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith( - props.cardId, - ); - expect(useActionDisabledState).toHaveBeenCalledWith(props.cardId); - }); + + it('renders course name as span when disabled', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: true }); + render(); + + const text = screen.getByText(courseName); + expect(text).toBeInTheDocument(); + expect(text.tagName.toLowerCase()).toBe('span'); + }); + + it('uses correct hooks with cardId', () => { + useActionDisabledState.mockReturnValue({ disableCourseTitle: false }); + render(); + + expect(reduxHooks.useCardCourseData).toHaveBeenCalledWith(props.cardId); + expect(reduxHooks.useCardCourseRunData).toHaveBeenCalledWith(props.cardId); + expect(reduxHooks.useTrackCourseEvent).toHaveBeenCalledWith( + track.course.courseTitleClicked, + props.cardId, + homeUrl, + ); }); }); diff --git a/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap b/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap deleted file mode 100644 index 964a146..0000000 --- a/src/containers/CourseCard/components/__snapshots__/CourseCardImage.test.jsx.snap +++ /dev/null @@ -1,72 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardImage snapshot renders clickable link course Image 1`] = ` - - - Course thumbnail - - - Verified - - ID Verified Ribbon/Badge - - - -`; - -exports[`CourseCardImage snapshot renders disabled link 1`] = ` -
- - Course thumbnail - - - Verified - - ID Verified Ribbon/Badge - - -
-`; diff --git a/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap b/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap deleted file mode 100644 index 34446ad..0000000 --- a/src/containers/CourseCard/components/__snapshots__/CourseCardTitle.test.jsx.snap +++ /dev/null @@ -1,33 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CourseCardTitle snapshot renders clickable link course title 1`] = ` -

- - course-name - -

-`; - -exports[`CourseCardTitle snapshot renders disabled link 1`] = ` -

- - course-name - -

-`; diff --git a/src/containers/CourseCard/index.test.jsx b/src/containers/CourseCard/index.test.jsx index ffab80d..ec26e00 100644 --- a/src/containers/CourseCard/index.test.jsx +++ b/src/containers/CourseCard/index.test.jsx @@ -1,5 +1,5 @@ -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 CourseCard from '.'; import hooks from './hooks'; @@ -8,22 +8,47 @@ jest.mock('./hooks', () => ({ useIsCollapsed: jest.fn(), })); -jest.mock('./components/CourseCardBanners', () => 'CourseCardBanners'); -jest.mock('./components/CourseCardImage', () => 'CourseCardImage'); -jest.mock('./components/CourseCardMenu', () => 'CourseCardMenu'); -jest.mock('./components/CourseCardActions', () => 'CourseCardActions'); -jest.mock('./components/CourseCardDetails', () => 'CourseCardDetails'); -jest.mock('./components/CourseCardTitle', () => 'CourseCardTitle'); +const namesMockComponents = [ + 'CourseCardBanners', + 'CourseCardImage', + 'CourseCardMenu', + 'CourseCardActions', + 'CourseCardDetails', + 'CourseCardTitle', +]; + +jest.mock('./components/CourseCardBanners', () => jest.fn(() =>
CourseCardBanners
)); +jest.mock('./components/CourseCardImage', () => jest.fn(() =>
CourseCardImage
)); +jest.mock('./components/CourseCardMenu', () => jest.fn(() =>
CourseCardMenu
)); +jest.mock('./components/CourseCardActions', () => jest.fn(() =>
CourseCardActions
)); +jest.mock('./components/CourseCardDetails', () => jest.fn(() =>
CourseCardDetails
)); +jest.mock('./components/CourseCardTitle', () => jest.fn(() =>
CourseCardTitle
)); + +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); const cardId = 'test-card-id'; describe('CourseCard component', () => { - test('snapshot: collapsed', () => { + it('collapsed', () => { hooks.useIsCollapsed.mockReturnValueOnce(true); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const cardImage = screen.getByText('CourseCardImage'); + expect(cardImage.parentElement).not.toHaveClass('d-flex'); }); - test('snapshot: not collapsed', () => { + it('not collapsed', () => { hooks.useIsCollapsed.mockReturnValueOnce(false); - expect(shallow().snapshot).toMatchSnapshot(); + render(); + const cardImage = screen.getByText('CourseCardImage'); + expect(cardImage.parentElement).toHaveClass('d-flex'); + }); + it('renders courseCard child components', () => { + hooks.useIsCollapsed.mockReturnValueOnce(false); + render(); + namesMockComponents.map((courseCardName) => { + const courseCardComponent = screen.getByText(courseCardName); + return expect(courseCardComponent).toBeInTheDocument(); + }); }); }); diff --git a/src/containers/CourseFilterControls/components/Checkbox.test.jsx b/src/containers/CourseFilterControls/components/Checkbox.test.jsx index de5eafa..2bce7b7 100644 --- a/src/containers/CourseFilterControls/components/Checkbox.test.jsx +++ b/src/containers/CourseFilterControls/components/Checkbox.test.jsx @@ -1,14 +1,21 @@ -import { shallow } from '@edx/react-unit-test-utils'; +import { render, screen } from '@testing-library/react'; +import { formatMessage } from 'testUtils'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; import { FilterKeys } from 'data/constants/app'; import Checkbox from './Checkbox'; +import messages from '../messages'; + +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); describe('Checkbox', () => { - describe('snapshot', () => { + describe('renders correctly', () => { Object.keys(FilterKeys).forEach((filterKey) => { it(`renders ${filterKey}`, () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + render(); + expect(screen.getByText(formatMessage(messages[filterKey]))).toBeInTheDocument(); }); }); }); diff --git a/src/containers/CourseFilterControls/components/FilterForm.test.jsx b/src/containers/CourseFilterControls/components/FilterForm.test.jsx index 2905d3d..9a18490 100644 --- a/src/containers/CourseFilterControls/components/FilterForm.test.jsx +++ b/src/containers/CourseFilterControls/components/FilterForm.test.jsx @@ -1,29 +1,58 @@ -import { shallow } from '@edx/react-unit-test-utils'; - +import { render, screen, fireEvent } from '@testing-library/react'; +import { IntlProvider } from '@edx/frontend-platform/i18n'; +import { formatMessage } from 'testUtils'; import { FilterKeys } from 'data/constants/app'; -import FilterForm, { filterOrder } from './FilterForm'; +import { FilterForm, filterOrder } from './FilterForm'; +import messages from '../messages'; -jest.mock('./Checkbox', () => 'Checkbox'); +jest.unmock('@edx/frontend-platform/i18n'); +jest.unmock('@openedx/paragon'); +jest.unmock('react'); + +const mockHandleFilterChange = jest.fn(); + +const defaultProps = { + filters: [FilterKeys.inProgress], + handleFilterChange: mockHandleFilterChange, +}; + +const renderComponent = (props = defaultProps) => render( + + + , +); describe('FilterForm', () => { - const props = { - filters: ['test-filter'], - handleFilterChange: jest.fn().mockName('handleFilterChange'), - }; - describe('snapshot', () => { - test('renders', () => { - const wrapper = shallow(); - expect(wrapper.snapshot).toMatchSnapshot(); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders all filter checkboxes in correct order', () => { + renderComponent(); + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes).toHaveLength(filterOrder.length); + checkboxes.forEach((checkbox, index) => { + expect(checkbox).toHaveAttribute('value', filterOrder[index]); }); }); - test('filterOrder', () => { - expect(filterOrder).toEqual([ - FilterKeys.inProgress, - FilterKeys.notStarted, - FilterKeys.done, - FilterKeys.notEnrolled, - FilterKeys.upgraded, - ]); + it('checks boxes based on filters prop', () => { + const filters = [FilterKeys.inProgress, FilterKeys.done]; + renderComponent({ ...defaultProps, filters }); + filters.forEach(filter => { + expect(screen.getByRole('checkbox', { name: formatMessage(messages[filter]) })).toBeChecked(); + }); + }); + + it('calls handleFilterChange when checkbox is clicked', () => { + renderComponent(); + const checkbox = screen.getByRole('checkbox', { name: formatMessage(messages.notStarted) }); + fireEvent.click(checkbox); + expect(mockHandleFilterChange).toHaveBeenCalled(); + }); + + it('displays course status heading', () => { + renderComponent(); + expect(screen.getByText(/course status/i)).toBeInTheDocument(); }); }); diff --git a/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap b/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap deleted file mode 100644 index 35226ca..0000000 --- a/src/containers/CourseFilterControls/components/__snapshots__/Checkbox.test.jsx.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Checkbox snapshot renders done 1`] = ` - - Done - -`; - -exports[`Checkbox snapshot renders inProgress 1`] = ` - - In-Progress - -`; - -exports[`Checkbox snapshot renders notEnrolled 1`] = ` - - Not Enrolled - -`; - -exports[`Checkbox snapshot renders notStarted 1`] = ` - - Not Started - -`; - -exports[`Checkbox snapshot renders upgraded 1`] = ` - - Upgraded - -`; diff --git a/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap b/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap deleted file mode 100644 index 01f2167..0000000 --- a/src/containers/CourseFilterControls/components/__snapshots__/FilterForm.test.jsx.snap +++ /dev/null @@ -1,41 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`FilterForm snapshot renders 1`] = ` - -
- Course Status -
- - - - - - - -
-`;