Skip to content

Commit

Permalink
feat: expose getRoutes to share routes with private env.config.js f…
Browse files Browse the repository at this point in the history
…iles (#1143)
  • Loading branch information
adamstankiewicz authored Aug 12, 2024
1 parent 42c06b0 commit 10362ba
Show file tree
Hide file tree
Showing 65 changed files with 4,888 additions and 5,698 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ const config = createConfig('eslint', {
'@edx/eslint-config',
'plugin:@tanstack/eslint-plugin-query/recommended',
],
ignorePatterns: ['*.config.js'],
ignorePatterns: [
'webpack.*.config.js',
],
overrides: [
{
files: ['*.test.js', '*.test.jsx'],
Expand All @@ -25,6 +27,7 @@ const config = createConfig('eslint', {
'template-curly-spacing': 'off',
'import/prefer-default-export': 'off',
'no-underscore-dangle': ['error', { allow: ['_ctx', '_def'] }],
'@typescript-eslint/no-throw-literal': 'off',
},
});

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# https://github.com/openedx/frontend-build#local-module-configuration-for-webpack
.env.private
module.config.js
env.config.*

# Logs
logs
Expand Down
1 change: 0 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// eslint-disable-next-line import/no-extraneous-dependencies
const { createConfig } = require('@openedx/frontend-build');

process.env.TZ = 'UTC';
Expand Down
7,676 changes: 3,211 additions & 4,465 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
],
"dependencies": {
"@edx/brand": "npm:@openedx/[email protected]",
"@edx/frontend-component-footer": "13.0.2",
"@edx/frontend-component-footer": "13.0.3",
"@edx/frontend-enterprise-catalog-search": "10.6.0",
"@edx/frontend-enterprise-hotjar": "6.0.0",
"@edx/frontend-enterprise-logistration": "8.0.0",
Expand All @@ -18,6 +18,7 @@
"@edx/reactifex": "2.2.0",
"@loadable/component": "5.16.3",
"@lukemorales/query-key-factory": "1.3.4",
"@openedx/frontend-slot-footer": "1.0.3",
"@openedx/paragon": "21.13.1",
"@tanstack/react-query": "4.28.0",
"@tanstack/react-query-devtools": "4.29.0",
Expand Down Expand Up @@ -46,8 +47,8 @@
"react-instantsearch-dom": "6.38.1",
"react-parallax": "3.3.0",
"react-redux": "7.2.2",
"react-router": "6.16.0",
"react-router-dom": "6.16.0",
"react-router": "6.18.0",
"react-router-dom": "6.18.0",
"react-router-hash-link": "2.3.1",
"react-scroll": "1.8.4",
"react-string-replace": "1.1.1",
Expand All @@ -61,8 +62,9 @@
},
"devDependencies": {
"@edx/browserslist-config": "1.1.1",
"@edx/typescript-config": "1.1.0",
"@faker-js/faker": "8.4.1",
"@openedx/frontend-build": "13.0.28",
"@openedx/frontend-build": "14.0.14",
"@tanstack/eslint-plugin-query": "4.29.9",
"@testing-library/jest-dom": "5.11.9",
"@testing-library/react": "12.1.5",
Expand All @@ -81,16 +83,17 @@
"react-test-renderer": "17.0.2",
"reactifex": "1.1.1",
"resize-observer-polyfill": "1.5.1",
"rosie": "2.1.1"
"rosie": "2.1.1",
"ts-loader": "9.5.1"
},
"keywords": [],
"license": "AGPL-3.0",
"scripts": {
"build": "fedx-scripts webpack",
"build:with-theme": "THEME=npm:@edx/brand-edx.org@latest npm run install-theme && npm run build",
"i18n_extract": "fedx-scripts formatjs extract --throws",
"lint": "fedx-scripts eslint --ext .js --ext .jsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .",
"lint": "fedx-scripts eslint --ext .js --ext .jsx --ext .ts --ext .tsx .",
"lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx --ext .ts --ext .tsx .",
"precommit": "npm run lint",
"snapshot": "fedx-scripts jest --updateSnapshot",
"install-theme": "npm install \"@edx/brand@${THEME}\" --no-save",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '@testing-library/jest-dom/extend-expect';
import { renderWithRouterProvider } from '../../../utils/tests';
import { ensureAuthenticatedUser } from '../../app/routes/data';
import { extractEnterpriseCustomer, queryAcademiesDetail } from '../../app/data';
import makeAcademiesLoader from './academiesLoader';
import makeAcademiesLoader from './academyLoader';
import { authenticatedUserFactory, enterpriseCustomerFactory } from '../../app/data/services/data/__factories__';

jest.mock('../../app/routes/data', () => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { ensureAuthenticatedUser } from '../../app/routes/data';
import { extractEnterpriseCustomer, queryAcademiesDetail } from '../../app/data';

export default function makeAcademiesLoader(queryClient) {
return async function academiesLoader({ params = {}, request }) {
type AcademyRouteParams<Key extends string = string> = Types.RouteParams<Key> & {
readonly academyUUID: string;
readonly enterpriseSlug: string;
};
interface AcademyLoaderFunctionArgs extends Types.RouteLoaderFunctionArgs {
params: AcademyRouteParams;
}

const makeAcademiesLoader: Types.MakeRouteLoaderFunctionWithQueryClient = function makeAcademiesLoader(queryClient) {
return async function academiesLoader({ params, request }: AcademyLoaderFunctionArgs) {
const requestUrl = new URL(request.url);
const authenticatedUser = await ensureAuthenticatedUser(requestUrl, params);
// User is not authenticated, so we can't do anything in this loader.
Expand All @@ -21,4 +29,6 @@ export default function makeAcademiesLoader(queryClient) {

return null;
};
}
};

export default makeAcademiesLoader;
2 changes: 1 addition & 1 deletion src/components/academies/data/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default as makeAcademiesLoader } from './academiesLoader';
export { default as makeAcademiesLoader } from './academyLoader';
4 changes: 2 additions & 2 deletions src/components/app/Layout.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Helmet } from 'react-helmet';
import { Outlet } from 'react-router-dom';
import SiteFooter from '@edx/frontend-component-footer';
import FooterSlot from '@openedx/frontend-slot-footer';
import { getConfig } from '@edx/frontend-platform/config';

import { isSystemMaintenanceAlertOpen, useEnterpriseCustomer } from './data';
Expand Down Expand Up @@ -44,7 +44,7 @@ const Layout = () => {
<main id="content" className="fill-vertical-space">
<Outlet />
</main>
<SiteFooter />
<FooterSlot />
</EnterprisePage>
);
};
Expand Down
10 changes: 9 additions & 1 deletion src/components/app/Layout.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 { getLoggingService } from '@edx/frontend-platform/logging';
import dayjs from 'dayjs';
import '@testing-library/jest-dom/extend-expect';

Expand All @@ -21,7 +22,14 @@ const mockDefaultAppContextValue = {
},
};

jest.mock('@edx/frontend-component-footer', () => jest.fn(() => <div data-testid="site-footer" />));
jest.mock('@openedx/frontend-slot-footer', () => jest.fn(() => <div data-testid="site-footer" />));
jest.mock('@edx/frontend-platform/logging', () => ({
getLoggingService: jest.fn(),
}));
const mockSetCustomAttribute = jest.fn();
getLoggingService.mockReturnValue({
setCustomAttribute: mockSetCustomAttribute,
});
jest.mock('../site-header', () => ({
...jest.requireActual('../site-header'),
SiteHeader: jest.fn(() => <div data-testid="site-header" />),
Expand Down
103 changes: 103 additions & 0 deletions src/components/app/data/queries/extractEnterpriseCustomer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { when, resetAllWhenMocks } from 'jest-when';
import { authenticatedUserFactory, enterpriseCustomerFactory, enterpriseCustomerUserFactory } from '../services/data/__factories__';
import extractEnterpriseCustomer from './extractEnterpriseCustomer';
import { queryEnterpriseLearner } from './queries';

const mockEnsureQueryData = jest.fn();
const mockQueryClient = {
ensureQueryData: mockEnsureQueryData,
};
const mockAuthenticatedUser = authenticatedUserFactory();
const mockEnterpriseCustomer = enterpriseCustomerFactory();
const mockEnterpriseCustomerUser = enterpriseCustomerUserFactory({
enterprise_customer: mockEnterpriseCustomer,
});

const getQueryEnterpriseLearner = ({ hasEnterpriseSlug = true } = {}) => queryEnterpriseLearner(
mockAuthenticatedUser.username,
hasEnterpriseSlug ? mockEnterpriseCustomer.slug : undefined,
);

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

it.each([
{
routeEnterpriseSlug: mockEnterpriseCustomer.slug,
enterpriseCustomerUser: mockEnterpriseCustomerUser,
staffEnterpriseCustomer: undefined,
expectedEnterpriseCustomer: mockEnterpriseCustomer,
},
{
routeEnterpriseSlug: mockEnterpriseCustomer.slug,
enterpriseCustomerUser: undefined,
staffEnterpriseCustomer: mockEnterpriseCustomer,
expectedEnterpriseCustomer: mockEnterpriseCustomer,
},
{
routeEnterpriseSlug: mockEnterpriseCustomer.slug,
enterpriseCustomerUser: mockEnterpriseCustomerUser,
staffEnterpriseCustomer: mockEnterpriseCustomer,
expectedEnterpriseCustomer: mockEnterpriseCustomer,
},
{
routeEnterpriseSlug: undefined,
enterpriseCustomerUser: mockEnterpriseCustomerUser,
staffEnterpriseCustomer: undefined,
expectedEnterpriseCustomer: mockEnterpriseCustomer,
},
{
routeEnterpriseSlug: undefined,
enterpriseCustomerUser: undefined,
staffEnterpriseCustomer: mockEnterpriseCustomer,
expectedEnterpriseCustomer: undefined,
},
{
routeEnterpriseSlug: undefined,
enterpriseCustomerUser: mockEnterpriseCustomerUser,
staffEnterpriseCustomer: mockEnterpriseCustomer,
expectedEnterpriseCustomer: mockEnterpriseCustomer,
},
{
routeEnterpriseSlug: mockEnterpriseCustomer.slug,
enterpriseCustomerUser: undefined,
staffEnterpriseCustomer: undefined,
expectedEnterpriseCustomer: undefined,
},
])('should return or throw error as expected (%s)', async ({
routeEnterpriseSlug,
enterpriseCustomerUser,
staffEnterpriseCustomer,
expectedEnterpriseCustomer,
}) => {
const args = {
queryClient: mockQueryClient,
authenticatedUser: mockAuthenticatedUser,
enterpriseSlug: routeEnterpriseSlug,
};
const queryEnterpriseLearnerResult = {
activeEnterpriseCustomer: enterpriseCustomerUser?.enterpriseCustomer,
allLinkedEnterpriseCustomerUsers: enterpriseCustomerUser ? [enterpriseCustomerUser] : [],
staffEnterpriseCustomer,
};
const queryEnterpriseLearnerQueryKey = getQueryEnterpriseLearner({
hasEnterpriseSlug: !!routeEnterpriseSlug,
}).queryKey;
when(mockEnsureQueryData)
.calledWith(
expect.objectContaining({
queryKey: queryEnterpriseLearnerQueryKey,
}),
)
.mockResolvedValue(queryEnterpriseLearnerResult);
try {
const enterpriseCustomer = await extractEnterpriseCustomer(args);
expect(enterpriseCustomer).toEqual(expectedEnterpriseCustomer);
} catch (error) {
expect(error.message).toBe(`Could not find enterprise customer for slug ${routeEnterpriseSlug}`);
}
});
});
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { queryEnterpriseLearner } from './queries';

interface ExtractEnterpriseCustomerArgs {
queryClient: Types.QueryClient;
authenticatedUser: Types.AuthenticatedUser;
enterpriseSlug?: string;
}

/**
* Extracts the appropriate enterprise ID for the current user and enterprise slug.
* @param {Object} params - The parameters object.
* @param {Object} params.queryClient - The query client.
* @param {Object} params.authenticatedUser - The authenticated user.
* @param {string} params.enterpriseSlug - The enterprise slug.
* @returns {Promise<string>} - The enterprise ID to use for subsquent queries in route loaders.
*/
async function extractEnterpriseCustomer({
queryClient,
authenticatedUser,
enterpriseSlug,
}) {
} : ExtractEnterpriseCustomerArgs) : Promise<Types.EnterpriseCustomer> {
// Retrieve linked enterprise customers for the current user from query cache, or
// fetch from the server if not available.
const linkedEnterpriseCustomersQuery = queryEnterpriseLearner(authenticatedUser.username, enterpriseSlug);
const enterpriseLearnerData = await queryClient.ensureQueryData(linkedEnterpriseCustomersQuery);
const enterpriseLearnerData = await queryClient.ensureQueryData<Types.EnterpriseLearnerData>(
linkedEnterpriseCustomersQuery,
);
const {
activeEnterpriseCustomer,
allLinkedEnterpriseCustomerUsers,
Expand Down
Loading

0 comments on commit 10362ba

Please sign in to comment.