Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrates license activation and auto-apply to route loaders, plus general cleanup #990

Merged
merged 19 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/components/app/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import {
} from '../../utils/common';
// import extractNamedExport from '../../utils/extract-named-export';

import createAppRouter from './data/createAppRouter';
import { RouterFallback } from './routes';
import { RouterFallback, createAppRouter } from './routes';

/* eslint-disable max-len */
// const EnterpriseAppPageRoutes = lazy(() => import(/* webpackChunkName: "enterprise-app-routes" */ './EnterpriseAppPageRoutes'));
Expand Down
2 changes: 0 additions & 2 deletions src/components/app/AuthenticatedUserSubsidyPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import React from 'react';
import PropTypes from 'prop-types';

import AuthenticatedPage from './AuthenticatedPage';
import { AutoActivateLicense } from '../enterprise-user-subsidy';

const AuthenticatedUserSubsidyPage = ({ children }) => (
<AuthenticatedPage>
<AutoActivateLicense />
{children}
</AuthenticatedPage>
);
Expand Down
5 changes: 1 addition & 4 deletions src/components/app/AuthenticatedUserSubsidyPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '@testing-library/jest-dom/extend-expect';

import AuthenticatedUserSubsidyPage from './AuthenticatedUserSubsidyPage';
import AuthenticatedPage from './AuthenticatedPage';
import { AutoActivateLicense, UserSubsidy } from '../enterprise-user-subsidy';
import { UserSubsidy } from '../enterprise-user-subsidy';

describe('<AuthenticatedUserSubsidyPage />', () => {
let wrapper;
Expand All @@ -21,9 +21,6 @@ describe('<AuthenticatedUserSubsidyPage />', () => {
it('renders <UserSubsidy>', () => {
expect(wrapper.find(UserSubsidy)).toBeTruthy();
});
it('renders <AutoActivateLicense>', () => {
expect(wrapper.find(AutoActivateLicense)).toBeTruthy();
});
it('renders children', () => {
expect(wrapper.find('div.did-i-render')).toBeTruthy();
});
Expand Down
2 changes: 0 additions & 2 deletions src/components/app/EnterpriseAppPageRoutes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const ProgramPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "p
const ProgramProgressRedirect = lazy(() => extractNamedExport(import(/* webpackChunkName: "program-progress-redirect" */ '../program-progress'), 'ProgramProgressRedirect'));
const ProgramProgressPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "program-progress" */ '../program-progress'), 'ProgramProgressPage'));
const SkillsQuizPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "skills-quiz" */ '../skills-quiz'), 'SkillsQuizPage'));
const LicenseActivationPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "license-activation" */ '../license-activation'), 'LicenseActivationPage'));
const PathwayProgressPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "pathway-progress" */ '../pathway-progress'), 'PathwayProgressPage'));
const AcademyDetailPage = lazy(() => extractNamedExport(import(/* webpackChunkName: "academy" */ '../academies'), 'AcademyDetailPage'));

Expand Down Expand Up @@ -46,7 +45,6 @@ const EnterpriseAppPageRoutes = () => (
<Route path="program-progress/:programUUID" element={<PageWrap><ProgramProgressRedirect /></PageWrap>} />
<Route path="program/:programUUID/progress" element={<PageWrap><ProgramProgressPage /></PageWrap>} />
<Route path="skills-quiz" element={<PageWrap><SkillsQuizPage /></PageWrap>} />
<Route path="licenses/:activationKey/activate" element={<PageWrap><LicenseActivationPage /></PageWrap>} />
{features.FEATURE_ENABLE_PATHWAY_PROGRESS && (
<Route exact path="pathway/:pathwayUUID/progress" element={<PageWrap><PathwayProgressPage /></PageWrap>} />
)}
Expand Down
18 changes: 2 additions & 16 deletions src/components/app/Layout.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { AppContext } from '@edx/frontend-platform/react';
import { useContext } from 'react';
import { Helmet } from 'react-helmet';
import { Outlet } from 'react-router-dom';
import SiteFooter from '@edx/frontend-component-footer';
import { getConfig } from '@edx/frontend-platform/config';

import { useEnterpriseLearner, isSystemMaintenanceAlertOpen } from './data';
import { useStylesForCustomBrandColors } from '../layout/data/hooks';
import NotFoundPage from '../NotFoundPage';
import DelayedFallbackContainer from '../DelayedFallback/DelayedFallbackContainer';
import { SiteHeader } from '../site-header';
import { EnterpriseBanner } from '../enterprise-banner';
import { SystemWideWarningBanner } from '../system-wide-banner';
Expand All @@ -16,7 +14,7 @@ export const TITLE_TEMPLATE = '%s - edX';
export const DEFAULT_TITLE = 'edX';

const Layout = () => {
const { authenticatedUser, config } = useContext(AppContext);
const config = getConfig();
const { data: enterpriseLearnerData } = useEnterpriseLearner();

const brandStyles = useStylesForCustomBrandColors(enterpriseLearnerData.enterpriseCustomer);
Expand All @@ -27,18 +25,6 @@ const Layout = () => {
return <NotFoundPage />;
}

// User is authenticated with an active enterprise customer, but
// the user account API data is still hydrating. If it is still
// hydrating, render a loading state.
if (!authenticatedUser.profileImage) {
return (
<DelayedFallbackContainer
className="py-5 text-center"
screenReaderText="Loading your account details. Please wait."
/>
);
}

return (
<>
<Helmet titleTemplate={TITLE_TEMPLATE} defaultTitle={DEFAULT_TITLE}>
Expand Down
147 changes: 147 additions & 0 deletions src/components/app/Layout.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { screen } from '@testing-library/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { AppContext } from '@edx/frontend-platform/react';
import { IntlProvider } from '@edx/frontend-platform/i18n';
import { mergeConfig } from '@edx/frontend-platform';
import dayjs from 'dayjs';
import '@testing-library/jest-dom/extend-expect';

import Layout from './Layout';
import { queryClient, renderWithRouterProvider } from '../../utils/tests';
import { useEnterpriseLearner } from './data';

const mockDefaultAppContextValue = {
authenticatedUser: {
userId: 3,
},
config: {
LMS_BASE_URL: 'https://test-lms.url',
},
};

const mockEnterpriseCustomer = {
uuid: 'test-enterprise-uuid',
brandingConfiguration: {
logo: 'https://test-logo.url',
primaryColor: '#000000',
secondaryColor: '#FF0000',
tertiaryColor: '#0000FF',
},
};

jest.mock('@edx/frontend-component-footer', () => jest.fn(() => <div data-testid="site-footer" />));
jest.mock('../site-header', () => ({
...jest.requireActual('../site-header'),
SiteHeader: jest.fn(() => <div data-testid="site-header" />),
}));
jest.mock('../enterprise-banner', () => ({
...jest.requireActual('../enterprise-banner'),
EnterpriseBanner: jest.fn(() => <div data-testid="enterprise-banner" />),
}));
jest.mock('../../utils/common', () => ({
...jest.requireActual('../../utils/common'),
getBrandColorsFromCSSVariables: jest.fn().mockReturnValue({
white: '#FFFFFF',
dark: '#000000',
}),
}));

jest.mock('./data', () => ({
...jest.requireActual('./data'),
useEnterpriseLearner: jest.fn().mockReturnValue({
data: {
enterpriseCustomer: null,
},
}),
}));

const LayoutWrapper = ({
appContextValue = mockDefaultAppContextValue,
}) => (
<QueryClientProvider client={queryClient()}>
<IntlProvider locale="en">
<AppContext.Provider value={appContextValue}>
<Layout />
</AppContext.Provider>
</IntlProvider>
</QueryClientProvider>
);

describe('Layout', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('renders the not found page when the user is not linked to an enterprise customer', () => {
renderWithRouterProvider(<LayoutWrapper />);
expect(screen.getByText('404', { selector: 'h1' })).toBeInTheDocument();
});

it.each([
{
isSystemMaintenanceAlertOpen: false,
maintenanceMessage: undefined,
maintenanceStartTimestamp: undefined,
},
{
isSystemMaintenanceAlertOpen: true,
maintenanceMessage: 'Hello World!',
maintenanceStartTimestamp: undefined,
},
{
isSystemMaintenanceAlertOpen: true,
maintenanceMessage: 'Hello World!',
maintenanceStartTimestamp: dayjs().subtract(1, 'm').toISOString(),
},
{
isSystemMaintenanceAlertOpen: false,
maintenanceMessage: 'Hello World!',
maintenanceStartTimestamp: dayjs().add(1, 'm').toISOString(),
},
])('renders with enterprise customer (%s)', ({
isSystemMaintenanceAlertOpen,
maintenanceMessage,
maintenanceStartTimestamp,
}) => {
useEnterpriseLearner.mockReturnValue({
data: {
enterpriseCustomer: mockEnterpriseCustomer,
},
});

if (maintenanceMessage) {
mergeConfig({
IS_MAINTENANCE_ALERT_ENABLED: isSystemMaintenanceAlertOpen,
MAINTENANCE_ALERT_MESSAGE: maintenanceMessage,
});
}
if (maintenanceStartTimestamp) {
mergeConfig({
MAINTENANCE_ALERT_START_TIMESTAMP: maintenanceStartTimestamp ?? '',
});
}

renderWithRouterProvider({
path: '/:enterpriseSlug',
element: <LayoutWrapper />,
children: [
{
path: '',
element: <div data-testid="child-route" />,
},
],
}, {
initialEntries: ['/test-enterprise'],
});
expect(screen.getByTestId('site-header')).toBeInTheDocument();
expect(screen.getByTestId('enterprise-banner')).toBeInTheDocument();
expect(screen.getByTestId('child-route')).toBeInTheDocument();
expect(screen.getByTestId('site-footer')).toBeInTheDocument();

if (isSystemMaintenanceAlertOpen) {
expect(screen.getByText(maintenanceMessage)).toBeInTheDocument();
} else if (maintenanceMessage) {
expect(screen.queryByText(maintenanceMessage)).not.toBeInTheDocument();
}
});
});
80 changes: 0 additions & 80 deletions src/components/app/data/createAppRouter.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import useEnterpriseLearner from './useEnterpriseLearner';
import { queryBrowseAndRequestConfiguration } from '../../routes/data';
import { queryBrowseAndRequestConfiguration } from '../queries';

/**
* Retrieves the course metadata for the given enterprise customer and course key.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import useEnterpriseLearner from './useEnterpriseLearner';
import { queryContentHighlightsConfiguration } from '../../routes/data/queries';
import { queryContentHighlightsConfiguration } from '../queries';

/**
* Retrieves the content highlights configuration for the active enterprise customer user.
Expand Down
2 changes: 1 addition & 1 deletion src/components/app/data/hooks/useCourseMetadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';
import { useParams } from 'react-router-dom';

import useEnterpriseLearner from './useEnterpriseLearner';
import { queryCourseMetadata } from '../../routes/data/queries';
import { queryCourseMetadata } from '../queries';

/**
* Retrieves the course metadata for the given enterprise customer and course key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useQuery } from '@tanstack/react-query';

import useCourseMetadata from './useCourseMetadata';
import useEnterpriseLearner from './useEnterpriseLearner';
import { queryCanRedeem } from '../../routes/data/queries';
import { queryCanRedeem } from '../queries';

/**
* Retrieves the course redemption eligibility for the given enterprise customer and course key.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery } from '@tanstack/react-query';

import useEnterpriseLearner from './useEnterpriseLearner';
import { queryEnterpriseCourseEnrollments } from '../../routes/data/queries';
import { queryEnterpriseCourseEnrollments } from '../queries';

/**
* Retrieves the enterprise course enrollments for the active enterprise customer user.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
queryBrowseAndRequestConfiguration,
queryLicenseRequests,
queryCouponCodeRequests,
} from '../../routes/data/queries';
} from '../queries';
/**
* Retrieves the subsidies present for the active enterprise customer user.
* @returns {Types.UseQueryResult}} The query results for the enterprise customer user subsidies.
Expand Down
2 changes: 1 addition & 1 deletion src/components/app/data/hooks/useEnterpriseLearner.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AppContext } from '@edx/frontend-platform/react';
import { useQuery } from '@tanstack/react-query';
import { useContext } from 'react';
import { useParams } from 'react-router-dom';
import { queryEnterpriseLearner } from '../../routes/data/queries';
import { queryEnterpriseLearner } from '../queries';

/**
* Retrieves the enterprise learner data for the authenticated user.
Expand Down
Loading
Loading