From 055c6fa1ac1547dbb2c5c7b38bd7f2466e4c09c9 Mon Sep 17 00:00:00 2001 From: Marcos Date: Mon, 16 Dec 2024 13:19:04 -0300 Subject: [PATCH] chore: Added unit tests for useCourseUpgrade() and useTrackEvent() hooks --- src/context/course-info-context.js | 2 +- src/hooks/index.js | 1 - src/hooks/use-course-upgrade.js | 1 + src/hooks/use-course-upgrade.test.jsx | 159 ++++++++++++++++++++++++++ src/hooks/use-track-event.test.jsx | 54 +++++++++ 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 src/hooks/use-course-upgrade.test.jsx create mode 100644 src/hooks/use-track-event.test.jsx diff --git a/src/context/course-info-context.js b/src/context/course-info-context.js index 107dd0df..081b173f 100644 --- a/src/context/course-info-context.js +++ b/src/context/course-info-context.js @@ -1,6 +1,6 @@ import { createContext } from 'react'; -export const CourseInfoContext = createContext('course-info', { +export const CourseInfoContext = createContext({ courseId: null, unitId: null, isUpgradeEligible: false, diff --git a/src/hooks/index.js b/src/hooks/index.js index 22f1a63c..bcc999ad 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,3 +1,2 @@ -/* eslint-disable import/prefer-default-export */ export { default as useCourseUpgrade } from './use-course-upgrade'; export { default as useTrackEvent } from './use-track-event'; diff --git a/src/hooks/use-course-upgrade.js b/src/hooks/use-course-upgrade.js index f5544890..8870dc5a 100644 --- a/src/hooks/use-course-upgrade.js +++ b/src/hooks/use-course-upgrade.js @@ -23,6 +23,7 @@ export default function useCourseUpgrade() { if (auditTrial?.expirationDate) { const auditTrialExpirationDate = new Date(auditTrial.expirationDate); + auditTrialDaysRemaining = Math.ceil((auditTrialExpirationDate - Date.now()) / millisecondsInOneDay); auditTrialExpired = auditTrialDaysRemaining < 0; diff --git a/src/hooks/use-course-upgrade.test.jsx b/src/hooks/use-course-upgrade.test.jsx new file mode 100644 index 00000000..2bb4aeb7 --- /dev/null +++ b/src/hooks/use-course-upgrade.test.jsx @@ -0,0 +1,159 @@ +import { renderHook as rtlRenderHook } from '@testing-library/react-hooks'; +import { useSelector } from 'react-redux'; +import { useModel } from '@src/generic/model-store'; // eslint-disable-line import/no-unresolved +import { CourseInfoProvider } from '../context'; +import useCourseUpgrade from './use-course-upgrade'; + +jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() }), { virtual: true }); +jest.mock('react-redux', () => ({ useSelector: jest.fn() })); + +const mockedUpgradeUrl = 'https://upgrade.edx/course/test'; +const mockedAuditTrialLengthDays = 7; + +const contextWrapper = ({ courseInfo }) => function Wrapper({ children }) { // eslint-disable-line react/prop-types + return ( + + {children} + + ); +}; + +const renderHook = ({ + courseInfo, offer = {}, verifiedMode = {}, state = { learningAssistant: {} }, +}) => { + useModel.mockImplementation((model) => { + switch (model) { + case 'coursewareMeta': return { offer }; + case 'courseHomeMeta': return { verifiedMode }; + default: { + throw new Error('Model not mocked'); + } + } + }); + + useSelector.mockReturnValue(state.learningAssistant); + + return rtlRenderHook( + () => useCourseUpgrade(), + { wrapper: contextWrapper({ courseInfo }) }, + ); +}; + +describe('useCourseUpgrade()', () => { + beforeAll(() => jest.useFakeTimers().setSystemTime(new Date('2024-01-10 09:00:00'))); + afterAll(() => jest.useRealTimers()); + afterEach(() => jest.resetAllMocks()); + + it('should return { upgradeable: false } if not eligible', () => { + const { result } = renderHook({ courseInfo: { isUpgradeEligible: false } }); + + expect(result.current).toEqual({ upgradeable: false }); + }); + + it('should return { upgradeable: false } if missing upgradeUrl', () => { + const { result } = renderHook({ courseInfo: { isUpgradeEligible: true } }); + + expect(result.current).toEqual({ upgradeable: false }); + }); + + it('should return { upgradeable: true } if eligible and upgradeable and no trial info for both offer and verifiedMode urls', () => { + const expected = { + upgradeable: true, + auditTrial: undefined, + auditTrialDaysRemaining: undefined, + auditTrialExpired: false, + auditTrialLengthDays: mockedAuditTrialLengthDays, + upgradeUrl: mockedUpgradeUrl, + }; + + const { result: resultOffer } = renderHook({ + courseInfo: { isUpgradeEligible: true }, + offer: { + upgradeUrl: mockedUpgradeUrl, + }, + state: { + learningAssistant: { + auditTrialLengthDays: mockedAuditTrialLengthDays, + }, + }, + }); + + expect(resultOffer.current).toEqual(expected); + + const { result: resultVerified } = renderHook({ + courseInfo: { isUpgradeEligible: true }, + verifiedMode: { + upgradeUrl: mockedUpgradeUrl, + }, + state: { + learningAssistant: { + auditTrialLengthDays: mockedAuditTrialLengthDays, + }, + }, + }); + + expect(resultVerified.current).toEqual(expected); + }); + + it('should return trial info if enabled and not expired', () => { + const { result } = renderHook({ + courseInfo: { isUpgradeEligible: true }, + offer: { + upgradeUrl: mockedUpgradeUrl, + }, + verifiedMode: { + upgradeUrl: mockedUpgradeUrl, + }, + state: { + learningAssistant: { + auditTrialLengthDays: mockedAuditTrialLengthDays, + auditTrial: { + expirationDate: '2024-01-15 09:00:00', + }, + }, + }, + }); + + expect(result.current).toEqual({ + auditTrial: { + expirationDate: '2024-01-15 09:00:00', + }, + auditTrialDaysRemaining: 5, + auditTrialExpired: false, + auditTrialLengthDays: mockedAuditTrialLengthDays, + upgradeUrl: 'https://upgrade.edx/course/test', + upgradeable: true, + }); + }); + + it('should return trial info if expired', () => { + const { result } = renderHook({ + courseInfo: { isUpgradeEligible: true }, + offer: { + upgradeUrl: mockedUpgradeUrl, + }, + verifiedMode: { + upgradeUrl: mockedUpgradeUrl, + }, + state: { + learningAssistant: { + auditTrialLengthDays: mockedAuditTrialLengthDays, + auditTrial: { + expirationDate: '2024-01-05 09:00:00', + }, + }, + }, + }); + + expect(result.current).toEqual({ + auditTrial: { + expirationDate: '2024-01-05 09:00:00', + }, + auditTrialDaysRemaining: -5, + auditTrialExpired: true, + auditTrialLengthDays: mockedAuditTrialLengthDays, + upgradeUrl: 'https://upgrade.edx/course/test', + upgradeable: true, + }); + }); +}); diff --git a/src/hooks/use-track-event.test.jsx b/src/hooks/use-track-event.test.jsx new file mode 100644 index 00000000..846f356f --- /dev/null +++ b/src/hooks/use-track-event.test.jsx @@ -0,0 +1,54 @@ +import { renderHook as rtlRenderHook } from '@testing-library/react-hooks'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; +import { CourseInfoProvider } from '../context'; + +import useTrackEvent from './use-track-event'; + +const mockedUserId = 123; +const mockedCourseId = 'some-course-id'; +const mockedModuleId = 'some-module-id'; + +jest.mock('@edx/frontend-platform/analytics', () => ({ + sendTrackEvent: jest.fn(), +})); + +const mockedAuthenticatedUser = { userId: mockedUserId }; +jest.mock('@edx/frontend-platform/auth', () => ({ + getAuthenticatedUser: () => mockedAuthenticatedUser, +})); + +const contextWrapper = ({ courseInfo }) => function Wrapper({ children }) { // eslint-disable-line react/prop-types + return ( + + {children} + + ); +}; + +const renderHook = ({ + courseInfo, +}) => rtlRenderHook( + () => useTrackEvent(), + { wrapper: contextWrapper({ courseInfo }) }, +); + +describe('useCourseUpgrade()', () => { + afterEach(() => jest.resetAllMocks()); + + it('should return a track method that calls sendTrackEvent with the contextual information', () => { + const { result } = renderHook({ courseInfo: { courseId: mockedCourseId, moduleId: mockedModuleId } }); + + const { track } = result.current; + + const eventLabel = 'some-cool-event-to-track'; + + track(eventLabel, { some_extra_prop: 42 }); + + expect(sendTrackEvent).toHaveBeenCalledWith(eventLabel, { + course_id: mockedCourseId, + user_id: mockedUserId, + module_id: mockedModuleId, + some_extra_prop: 42, + }); + }); +});