diff --git a/src/components/app/data/hooks/index.js b/src/components/app/data/hooks/index.js index 3eee9934f..691d7f99d 100644 --- a/src/components/app/data/hooks/index.js +++ b/src/components/app/data/hooks/index.js @@ -48,3 +48,4 @@ export { default as useVideoCourseMetadata } from './useVideoCourseMetadata'; export { default as useVideoCourseReviews } from './useVideoCourseReviews'; export { default as useBFF } from './useBFF'; export { default as useIsBFFEnabled } from './useIsBFFEnabled'; +export { default as useHasValidLicenseOrSubscriptionRequestsEnabled } from './useHasValidLicenseOrSubscriptionRequestsEnabled'; diff --git a/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.js b/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.js new file mode 100644 index 000000000..6507a12a0 --- /dev/null +++ b/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.js @@ -0,0 +1,14 @@ +import { SUBSIDY_TYPE } from '../../../../constants'; +import { LICENSE_STATUS } from '../../../enterprise-user-subsidy/data/constants'; +import { useBrowseAndRequestConfiguration } from './useBrowseAndRequest'; +import useSubscriptions from './useSubscriptions'; + +export default function useHasValidLicenseOrSubscriptionRequestsEnabled() { + const { data: { subscriptionLicense } } = useSubscriptions(); + const { data: browseAndRequestConfiguration } = useBrowseAndRequestConfiguration(); + const hasActivatedAndCurrentLicense = subscriptionLicense?.status === LICENSE_STATUS.ACTIVATED + && subscriptionLicense?.subscriptionPlan?.isCurrent; + const hasRequestsEnabledForSubscriptions = browseAndRequestConfiguration?.subsidyRequestsEnabled + && browseAndRequestConfiguration.subsidyType === SUBSIDY_TYPE.LICENSE; + return hasActivatedAndCurrentLicense || hasRequestsEnabledForSubscriptions; +} diff --git a/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.test.jsx b/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.test.jsx new file mode 100644 index 000000000..3760b2f00 --- /dev/null +++ b/src/components/app/data/hooks/useHasValidLicenseOrSubscriptionRequestsEnabled.test.jsx @@ -0,0 +1,94 @@ +import { renderHook } from '@testing-library/react-hooks'; +import useHasValidLicenseOrSubscriptionRequestsEnabled from './useHasValidLicenseOrSubscriptionRequestsEnabled'; +import { LICENSE_STATUS } from '../../../enterprise-user-subsidy/data/constants'; +import { SUBSIDY_TYPE } from '../../../../constants'; +import { useBrowseAndRequestConfiguration } from './useBrowseAndRequest'; +import useSubscriptions from './useSubscriptions'; + +jest.mock('./useBrowseAndRequest'); +jest.mock('./useSubscriptions'); + +describe('useHasValidLicenseOrSubscriptionRequestsEnabled', () => { + it('should return true when the subscription license is activated and current', () => { + useSubscriptions.mockReturnValue({ + data: { + subscriptionLicense: { + status: LICENSE_STATUS.ACTIVATED, + subscriptionPlan: { + isCurrent: true, + }, + }, + }, + }); + useBrowseAndRequestConfiguration.mockReturnValue({ + data: { + subsidyRequestsEnabled: false, + subsidyType: SUBSIDY_TYPE.LICENSE, + }, + }); + + const { result } = renderHook(() => useHasValidLicenseOrSubscriptionRequestsEnabled()); + expect(result.current).toBe(true); + }); + + it('should return true when subsidy requests are enabled for subscriptions', () => { + useSubscriptions.mockReturnValue({ + data: { + subscriptionLicense: { + status: LICENSE_STATUS.REVOKED, + subscriptionPlan: { + isCurrent: false, + }, + }, + }, + }); + useBrowseAndRequestConfiguration.mockReturnValue({ + data: { + subsidyRequestsEnabled: true, + subsidyType: SUBSIDY_TYPE.LICENSE, + }, + }); + + const { result } = renderHook(() => useHasValidLicenseOrSubscriptionRequestsEnabled()); + expect(result.current).toBe(true); + }); + + it('should return false when the subscription license is not activated and not current, and subsidy requests are not enabled', () => { + useSubscriptions.mockReturnValue({ + data: { + subscriptionLicense: { + status: LICENSE_STATUS.REVOKED, + subscriptionPlan: { + isCurrent: false, + }, + }, + }, + }); + useBrowseAndRequestConfiguration.mockReturnValue({ + data: { + subsidyRequestsEnabled: false, + subsidyType: SUBSIDY_TYPE.LICENSE, + }, + }); + + const { result } = renderHook(() => useHasValidLicenseOrSubscriptionRequestsEnabled()); + expect(result.current).toBe(false); + }); + + it('should return false when subscriptionLicense is undefined and subsidy requests are not enabled', () => { + useSubscriptions.mockReturnValue({ + data: { + subscriptionLicense: undefined, + }, + }); + useBrowseAndRequestConfiguration.mockReturnValue({ + data: { + subsidyRequestsEnabled: false, + subsidyType: SUBSIDY_TYPE.LICENSE, + }, + }); + + const { result } = renderHook(() => useHasValidLicenseOrSubscriptionRequestsEnabled()); + expect(result.current).toBe(false); + }); +}); diff --git a/src/components/microlearning/VideoDetailPage.jsx b/src/components/microlearning/VideoDetailPage.jsx index 11399ef67..6c02bc11c 100644 --- a/src/components/microlearning/VideoDetailPage.jsx +++ b/src/components/microlearning/VideoDetailPage.jsx @@ -10,7 +10,7 @@ import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Person, Speed, Timelapse } from '@openedx/paragon/icons'; import { Link } from 'react-router-dom'; import { - useEnterpriseCustomer, useSubscriptions, useVideoCourseMetadata, useVideoDetails, + useEnterpriseCustomer, useHasValidLicenseOrSubscriptionRequestsEnabled, useVideoCourseMetadata, useVideoDetails, } from '../app/data'; import './styles/VideoDetailPage.scss'; import DelayedFallbackContainer from '../DelayedFallback/DelayedFallbackContainer'; @@ -19,7 +19,6 @@ import { getContentPriceDisplay, getCoursePrice, useCoursePacingType } from '../ import VideoCourseReview from './VideoCourseReview'; import { hasTruthyValue, isDefinedAndNotNull } from '../../utils/common'; import { getLevelType } from './data/utils'; -import { hasActivatedAndCurrentSubscription } from '../search/utils'; import { features } from '../../config'; import VideoFeedbackCard from './VideoFeedbackCard'; @@ -38,7 +37,7 @@ const VideoDetailPage = () => { const { data: courseMetadata } = useVideoCourseMetadata(videoData?.courseKey); const coursePrice = getCoursePrice(courseMetadata); const [pacingType, pacingTypeContent] = useCoursePacingType(courseMetadata?.activeCourseRun); - const { data: { subscriptionLicense } } = useSubscriptions(); + const hasValidLicenseOrSubRequest = useHasValidLicenseOrSubscriptionRequestsEnabled(); const playerRef = useRef(null); const intl = useIntl(); @@ -49,7 +48,7 @@ const VideoDetailPage = () => { }; const enableVideos = ( features.FEATURE_ENABLE_VIDEO_CATALOG - && hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest) + && hasValidLicenseOrSubRequest ); useEffect(() => { diff --git a/src/components/microlearning/tests/VideoDetailPage.test.jsx b/src/components/microlearning/tests/VideoDetailPage.test.jsx index d88fd3ee0..514d44b2e 100644 --- a/src/components/microlearning/tests/VideoDetailPage.test.jsx +++ b/src/components/microlearning/tests/VideoDetailPage.test.jsx @@ -15,6 +15,7 @@ import { useVideoCourseMetadata, useVideoCourseReviews, useVideoDetails, + useHasValidLicenseOrSubscriptionRequestsEnabled, } from '../../app/data'; import { COURSE_PACING_MAP } from '../../course/data'; import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; @@ -54,6 +55,7 @@ jest.mock('../../app/data', () => ({ useVideoCourseMetadata: jest.fn(() => ({ data: { courseKey: 'test-course-key' } })), useVideoCourseReviews: jest.fn(() => ({ data: { courseKey: 'test-course-key' } })), useSubscriptions: jest.fn(), + useHasValidLicenseOrSubscriptionRequestsEnabled: jest.fn(), })); jest.mock('react-router-dom', () => ({ @@ -140,6 +142,7 @@ describe('VideoDetailPage', () => { }, }); features.FEATURE_ENABLE_VIDEO_CATALOG = true; + useHasValidLicenseOrSubscriptionRequestsEnabled.mockReturnValue(true); }); it('Renders video details when data is available', () => { @@ -197,17 +200,7 @@ describe('VideoDetailPage', () => { expect(screen.getByTestId('not-found-page')).toBeInTheDocument(); }); it('renders a not found page when user do not have active subscription', () => { - useSubscriptions.mockReturnValue({ - data: { - subscriptionLicense: { - status: LICENSE_STATUS.ACTIVATED, - subscriptionPlan: { - enterpriseCatalogUuid: 'test-catalog-uuid', - isCurrent: false, - }, - }, - }, - }); + useHasValidLicenseOrSubscriptionRequestsEnabled.mockReturnValue(false); renderWithRouter(); expect(screen.getByTestId('not-found-page')).toBeInTheDocument(); }); diff --git a/src/components/search/Search.jsx b/src/components/search/Search.jsx index 923674d19..980a392da 100644 --- a/src/components/search/Search.jsx +++ b/src/components/search/Search.jsx @@ -24,13 +24,12 @@ import { useDefaultSearchFilters, useEnterpriseCustomer, useEnterpriseOffers, + useHasValidLicenseOrSubscriptionRequestsEnabled, useIsAssignmentsOnlyLearner, - useSubscriptions, } from '../app/data'; import { useAlgoliaSearch } from '../../utils/hooks'; import ContentTypeSearchResultsContainer from './ContentTypeSearchResultsContainer'; import SearchVideo from './SearchVideo'; -import { hasActivatedAndCurrentSubscription } from './utils'; import VideoBanner from '../microlearning/VideoBanner'; import CustomSubscriptionExpirationModal from '../custom-expired-subscription-modal'; @@ -54,6 +53,7 @@ function useSearchPathwayModal() { const Search = () => { const config = getConfig(); const { data: enterpriseCustomer } = useEnterpriseCustomer(); + const hasValidLicenseOrSubRequest = useHasValidLicenseOrSubscriptionRequestsEnabled(); const intl = useIntl(); const navigate = useNavigate(); @@ -79,11 +79,10 @@ const Search = () => { closePathwayModal, } = useSearchPathwayModal(); - const { data: { subscriptionLicense } } = useSubscriptions(); const enableVideos = ( canOnlyViewHighlightSets === false && features.FEATURE_ENABLE_VIDEO_CATALOG - && hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest) + && hasValidLicenseOrSubRequest ); const PAGE_TITLE = intl.formatMessage({ diff --git a/src/components/search/SearchPage.jsx b/src/components/search/SearchPage.jsx index 2f7b58bf4..1b88a680e 100644 --- a/src/components/search/SearchPage.jsx +++ b/src/components/search/SearchPage.jsx @@ -1,20 +1,19 @@ import { SearchData } from '@edx/frontend-enterprise-catalog-search'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { useEnterpriseCustomer, useSubscriptions } from '../app/data'; import Search from './Search'; import { SEARCH_TRACKING_NAME } from './constants'; -import { getSearchFacetFilters, hasActivatedAndCurrentSubscription } from './utils'; +import { getSearchFacetFilters } from './utils'; import { features } from '../../config'; +import { useHasValidLicenseOrSubscriptionRequestsEnabled } from '../app/data'; const SearchPage = () => { - const { data: enterpriseCustomer } = useEnterpriseCustomer(); - const intl = useIntl(); + const hasValidLicenseOrSubRequest = useHasValidLicenseOrSubscriptionRequestsEnabled(); - const { data: { subscriptionLicense } } = useSubscriptions(); + const intl = useIntl(); const enableVideos = ( features.FEATURE_ENABLE_VIDEO_CATALOG - && hasActivatedAndCurrentSubscription(subscriptionLicense, enterpriseCustomer.enableBrowseAndRequest) + && hasValidLicenseOrSubRequest ); return ( diff --git a/src/components/search/tests/Search.test.jsx b/src/components/search/tests/Search.test.jsx index 094b7996a..e44f5eb8b 100644 --- a/src/components/search/tests/Search.test.jsx +++ b/src/components/search/tests/Search.test.jsx @@ -7,7 +7,7 @@ import '../../skills-quiz/__mocks__/react-instantsearch-dom'; import { queryClient, renderWithRouter } from '../../../utils/tests'; import '@testing-library/jest-dom'; import Search from '../Search'; -import { useDefaultSearchFilters, useEnterpriseCustomer } from '../../app/data'; +import { useDefaultSearchFilters, useEnterpriseCustomer, useHasValidLicenseOrSubscriptionRequestsEnabled } from '../../app/data'; import { useAlgoliaSearch } from '../../../utils/hooks'; import { enterpriseCustomerFactory } from '../../app/data/services/data/__factories__'; import { features } from '../../../config'; @@ -31,6 +31,7 @@ jest.mock('../../app/data', () => ({ useCanOnlyViewHighlights: jest.fn(() => ({ data: false })), useIsAssignmentsOnlyLearner: jest.fn().mockReturnValue(false), useDefaultSearchFilters: jest.fn(), + useHasValidLicenseOrSubscriptionRequestsEnabled: jest.fn(), })); jest.mock('../../../utils/hooks', () => ({ ...jest.requireActual('../../../utils/hooks'), @@ -75,6 +76,7 @@ describe('', () => { jest.clearAllMocks(); useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer }); useDefaultSearchFilters.mockReturnValue(mockFilter); + useHasValidLicenseOrSubscriptionRequestsEnabled.mockReturnValue(true); useAlgoliaSearch.mockReturnValue([{ search: jest.fn(), appId: 'test-app-id' }, { indexName: 'mock-index-name' }]); }); test('renders the video beta banner component', () => { diff --git a/src/components/search/tests/SearchPage.test.jsx b/src/components/search/tests/SearchPage.test.jsx index d2e2336d7..e6303aff2 100644 --- a/src/components/search/tests/SearchPage.test.jsx +++ b/src/components/search/tests/SearchPage.test.jsx @@ -2,7 +2,7 @@ import { screen } from '@testing-library/react'; import '@testing-library/jest-dom/extend-expect'; import { AppContext } from '@edx/frontend-platform/react'; import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { useEnterpriseCustomer, useSubscriptions } from '../../app/data'; +import { useEnterpriseCustomer, useHasValidLicenseOrSubscriptionRequestsEnabled, useSubscriptions } from '../../app/data'; import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; import SearchPage from '../SearchPage'; import { features } from '../../../config'; @@ -12,11 +12,11 @@ import { enterpriseCustomerFactory } from '../../app/data/services/data/__factor jest.mock('../../app/data', () => ({ useSubscriptions: jest.fn(), useEnterpriseCustomer: jest.fn(), + useHasValidLicenseOrSubscriptionRequestsEnabled: jest.fn(), })); jest.mock('../utils', () => ({ getSearchFacetFilters: jest.fn().mockReturnValue([]), - hasActivatedAndCurrentSubscription: jest.fn().mockReturnValue(true), })); jest.mock('../Search', () => function Search() { @@ -40,6 +40,7 @@ describe('SearchPage', () => { jest.clearAllMocks(); features.FEATURE_ENABLE_VIDEO_CATALOG = true; useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer }); + useHasValidLicenseOrSubscriptionRequestsEnabled.mockReturnValue(true); }); it('renders SearchPage component', () => { diff --git a/src/components/search/tests/SearchSections.test.jsx b/src/components/search/tests/SearchSections.test.jsx index 18a556072..d4557887d 100644 --- a/src/components/search/tests/SearchSections.test.jsx +++ b/src/components/search/tests/SearchSections.test.jsx @@ -10,7 +10,9 @@ import '@testing-library/jest-dom'; import SearchProgram from '../SearchProgram'; import SearchPathway from '../SearchPathway'; import Search from '../Search'; -import { useDefaultSearchFilters, useEnterpriseCustomer } from '../../app/data'; +import { + useDefaultSearchFilters, useEnterpriseCustomer, useHasValidLicenseOrSubscriptionRequestsEnabled, +} from '../../app/data'; import { useAlgoliaSearch } from '../../../utils/hooks'; import { enterpriseCustomerFactory } from '../../app/data/services/data/__factories__'; import SearchVideo from '../SearchVideo'; @@ -28,6 +30,7 @@ jest.mock('../../app/data', () => ({ useIsAssignmentsOnlyLearner: jest.fn().mockReturnValue(false), useEnterpriseFeatures: jest.fn().mockReturnValue({ data: undefined }), useDefaultSearchFilters: jest.fn(), + useHasValidLicenseOrSubscriptionRequestsEnabled: jest.fn(), })); jest.mock('../../../utils/hooks', () => ({ @@ -85,6 +88,7 @@ describe('', () => { jest.clearAllMocks(); useEnterpriseCustomer.mockReturnValue({ data: mockEnterpriseCustomer }); useDefaultSearchFilters.mockReturnValue(mockFilter); + useHasValidLicenseOrSubscriptionRequestsEnabled.mockReturnValue(true); useAlgoliaSearch.mockReturnValue([{ search: jest.fn(), appId: 'test-app-id' }, { indexName: 'mock-index-name' }]); }); diff --git a/src/components/search/tests/utils.test.js b/src/components/search/tests/utils.test.js index 6c9a3b4ab..b0bb13010 100644 --- a/src/components/search/tests/utils.test.js +++ b/src/components/search/tests/utils.test.js @@ -1,5 +1,4 @@ -import { getSearchFacetFilters, hasActivatedAndCurrentSubscription } from '../utils'; -import { LICENSE_STATUS } from '../../enterprise-user-subsidy/data/constants'; +import { getSearchFacetFilters } from '../utils'; jest.mock('../../../config', () => ({ features: { PROGRAM_TYPE_FACET: true }, @@ -15,42 +14,3 @@ describe('getSearchFacetFilters', () => { expect(result.find(item => item.attribute === 'program_type')).toBeDefined(); }); }); - -describe('hasActivatedAndCurrentSubscription', () => { - it('should return true when the subscription is activated and current', () => { - const subscriptionLicense = { - status: LICENSE_STATUS.ACTIVATED, - subscriptionPlan: { - isCurrent: true, - }, - }; - const enableBrowseAndRequest = false; - - const result = hasActivatedAndCurrentSubscription(subscriptionLicense, enableBrowseAndRequest); - expect(result).toBe(true); - }); - - it('should return false when the subscription is not activated and not current', () => { - const subscriptionLicense = { - status: LICENSE_STATUS.REVOKED, - subscriptionPlan: { - isCurrent: false, - }, - }; - const enableBrowseAndRequest = false; - - const result = hasActivatedAndCurrentSubscription(subscriptionLicense, enableBrowseAndRequest); - expect(result).toBe(false); - }); - - it('should return false when subscriptionLicense is undefined', () => { - const enableBrowseAndRequest = false; - const result = hasActivatedAndCurrentSubscription(undefined, enableBrowseAndRequest); - expect(result).toBe(false); - }); - it('should return true when enableBrowseAndRequest is true', () => { - const enableBrowseAndRequest = true; - const result = hasActivatedAndCurrentSubscription(undefined, enableBrowseAndRequest); - expect(result).toBe(true); - }); -}); diff --git a/src/components/search/utils.js b/src/components/search/utils.js index d41fde3ab..b7015bf9d 100644 --- a/src/components/search/utils.js +++ b/src/components/search/utils.js @@ -1,7 +1,6 @@ import { defineMessages } from '@edx/frontend-platform/i18n'; import { getSearchFacetFilters as getBaseSearchFacetFilters } from '@edx/frontend-enterprise-catalog-search'; import { features } from '../../config'; -import { LICENSE_STATUS } from '../enterprise-user-subsidy/data/constants'; export function isShortCourse(course) { return course.course_length === 'short'; @@ -59,11 +58,3 @@ export function getSearchFacetFilters(intl) { return searchFilters; } - -export const hasActivatedAndCurrentSubscription = ( - subscriptionLicense, - enableBrowseAndRequest, -) => ( - (subscriptionLicense?.status === LICENSE_STATUS.ACTIVATED - && subscriptionLicense?.subscriptionPlan?.isCurrent) || enableBrowseAndRequest -);