Skip to content

Commit

Permalink
feat: adding useAuthenticatedUser and useConfig hooks
Browse files Browse the repository at this point in the history
Convenience hooks for getting data out of AppContext.
  • Loading branch information
davidjoy committed Oct 10, 2024
1 parent a833eb0 commit 9168495
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 31 deletions.
2 changes: 2 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ export {
subscribe,
unsubscribe,
useAppEvent,
useAuthenticatedUser,
useConfig,
useIntl
} from './runtime';

Expand Down
4 changes: 3 additions & 1 deletion runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ export {
ErrorPage,
LoginRedirect,
PageWrap,
useAppEvent
useAppEvent,
useAuthenticatedUser,
useConfig
} from './react';

export {
Expand Down
5 changes: 2 additions & 3 deletions runtime/react/AuthenticatedPageRoute.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import PropTypes from 'prop-types';
import { useContext } from 'react';

import { getLoginRedirectUrl } from '../auth';
import AppContext from './AppContext';
import PageWrap from './PageWrap';
import { useAuthenticatedUser } from './hooks';

/**
* A react-router route that redirects to the login page when the route becomes active and the user
Expand All @@ -23,7 +22,7 @@ import PageWrap from './PageWrap';
* viewing the route's contents.
*/
export default function AuthenticatedPageRoute({ redirectUrl, children }) {
const { authenticatedUser } = useContext(AppContext);
const authenticatedUser = useAuthenticatedUser();
if (authenticatedUser === null) {
const destination = redirectUrl || getLoginRedirectUrl(global.location.href);
global.location.assign(destination);
Expand Down
13 changes: 12 additions & 1 deletion runtime/react/hooks.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable import/prefer-default-export */
import { useEffect } from 'react';
import { useContext, useEffect } from 'react';
import { sendTrackEvent } from '../analytics';
import AppContext from './AppContext';

/**
* A React hook that allows functional components to subscribe to application events. This should
Expand Down Expand Up @@ -47,3 +48,13 @@ export const useTrackColorSchemeChoice = () => {
};
}, []);
};

export function useAuthenticatedUser() {
const { authenticatedUser } = useContext(AppContext);
return authenticatedUser;
}

export function useConfig() {
const { config } = useContext(AppContext);
return config;
}
49 changes: 48 additions & 1 deletion runtime/react/hooks.test.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { renderHook } from '@testing-library/react-hooks';
import { useTrackColorSchemeChoice } from './hooks';
import { EnvironmentTypes } from '../../types';
import { sendTrackEvent } from '../analytics';
import { setAuthenticatedUser } from '../auth';
import { initializeMockApp } from '../testing';
import AppProvider from './AppProvider';
import { useAuthenticatedUser, useConfig, useTrackColorSchemeChoice } from './hooks';

jest.mock('../analytics');

Expand Down Expand Up @@ -52,3 +56,46 @@ describe('useTrackColorSchemeChoice hook', () => {
expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function));
});
});

describe('useAuthenticatedUser', () => {
it('returns null when the user is anonymous', () => {
const { result } = renderHook(() => useAuthenticatedUser());
expect(result.current).toBeNull();
});

describe('with a user', () => {
const user = {
administrator: true,
email: 'admin@example.com',
name: 'Admin',
roles: ['admin'],
userId: 1,
username: 'admin-user',
avatar: 'http://localhost/admin.png',
};

beforeEach(() => {
initializeMockApp({
authenticatedUser: user,
});
});

afterEach(() => {
setAuthenticatedUser(null);
});

it('returns a User when the user exists', () => {
const { result } = renderHook(() => useAuthenticatedUser(), { wrapper: AppProvider });
expect(result.current).toBe(user);
});
});
});

describe('useConfig', () => {
it('returns the site config', () => {
const { result } = renderHook(() => useConfig());
expect(result.current).toHaveProperty('apps', []);
expect(result.current).toHaveProperty('ENVIRONMENT', EnvironmentTypes.TEST);
expect(result.current).toHaveProperty('BASE_URL', 'http://localhost:8080');
});
});
2 changes: 1 addition & 1 deletion runtime/react/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ export { default as AppProvider } from './AppProvider';
export { default as AuthenticatedPageRoute } from './AuthenticatedPageRoute';
export { default as ErrorBoundary } from './ErrorBoundary';
export { default as ErrorPage } from './ErrorPage';
export { useAppEvent } from './hooks';
export { useAppEvent, useAuthenticatedUser, useConfig } from './hooks';
export { default as LoginRedirect } from './LoginRedirect';
export { default as PageWrap } from './PageWrap';
13 changes: 6 additions & 7 deletions shell/header/learning-header/LearningHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useContext } from 'react';
import {
AppContext,
getConfig,
useAuthenticatedUser,
useIntl
} from '../../../runtime';

Expand All @@ -21,7 +20,7 @@ export default function LearningHeader({
courseOrg, courseNumber, courseTitle, showUserDropdown = true,
}: LearningHeaderProps) {
const intl = useIntl();
const { authenticatedUser } = useContext(AppContext);
const authenticatedUser = useAuthenticatedUser();

return (
<header className="learning-header">
Expand All @@ -38,12 +37,12 @@ export default function LearningHeader({
<span className="d-block m-0 font-weight-bold course-title">{courseTitle}</span>
</div>
{showUserDropdown && authenticatedUser && (
<AuthenticatedUserDropdown
username={authenticatedUser.username}
/>
<AuthenticatedUserDropdown
username={authenticatedUser.username}
/>
)}
{showUserDropdown && !authenticatedUser && (
<AnonymousUserMenu />
<AnonymousUserMenu />
)}
</div>
</header>
Expand Down
6 changes: 3 additions & 3 deletions shell/header/studio-header/StudioHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useContext } from 'react';
import Responsive from 'react-responsive';
import { AppContext } from '../../../runtime';
import { useAuthenticatedUser, useConfig } from '../../../runtime';

import HeaderBody from './HeaderBody';
import MobileHeader from './MobileHeader';
Expand Down Expand Up @@ -31,7 +30,8 @@ export default function StudioHeader({
outlineLink,
searchButtonAction,
}: StudioHeaderProps) {
const { authenticatedUser, config } = useContext(AppContext);
const authenticatedUser = useAuthenticatedUser();
const config = useConfig();
const props = {
logo: config.LOGO_URL,
logoAltText: `Studio ${config.SITE_NAME}`,
Expand Down
6 changes: 3 additions & 3 deletions test-project/src/authenticated-page/AuthenticatedPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useContext } from 'react';
import { Link } from 'react-router-dom';

import { AppContext } from '@openedx/frontend-base';
import { useAuthenticatedUser, useConfig } from '@openedx/frontend-base';

export default function AuthenticatedPage() {
const { authenticatedUser, config } = useContext<AppContext>(AppContext);
const authenticatedUser = useAuthenticatedUser();
const config = useConfig();

return (
<main className="p-3">
Expand Down
23 changes: 12 additions & 11 deletions test-project/src/example-page/ExamplePage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
AppContext,
getAuthenticatedUser,
getConfig,
logInfo,
mergeConfig,
useAuthenticatedUser,
useConfig,
useIntl
} from '@openedx/frontend-base';
import { Container } from '@openedx/paragon';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { Link } from 'react-router-dom';

import messages from '../messages';
import Image from './Image';
import ParagonPreview from './ParagonPreview';
Expand All @@ -27,7 +27,8 @@ mergeConfig({
});

export default function ExamplePage() {
const context = useContext<AppContext>(AppContext);
const config = useConfig();
const authenticatedUser = useAuthenticatedUser();

const intl = useIntl();

Expand All @@ -37,16 +38,16 @@ export default function ExamplePage() {

return (
<Container>
<h1>{context.config.SITE_NAME} test page</h1>
<h1>{config.SITE_NAME} test page</h1>

<h2>Links</h2>
<p>Visit <Link to="/authenticated">authenticated page</Link>.</p>
<p>Visit <Link to="/plugins">plugins page</Link>.</p>
<p>Visit <Link to="/error">error page</Link>.</p>

<h2>Context Config Test</h2>
<p>Is context.config equal to getConfig()? {printTestResult(context.config === getConfig())}</p>
<p>Is context.authenticatedUser equal to getAuthenticatedUser()? {printTestResult(context.authenticatedUser === getAuthenticatedUser())}</p>
<p>Is context.config equal to getConfig()? {printTestResult(config === getConfig())}</p>
<p>Is context.authenticatedUser equal to getAuthenticatedUser()? {printTestResult(authenticatedUser === getAuthenticatedUser())}</p>

<h2>SCSS parsing tests</h2>
<p><span className="red-text">"The Apples"</span> should be red (<code>color: red;</code> via <code>.red-text</code> CSS class in SCSS stylesheet)</p>
Expand All @@ -65,17 +66,17 @@ export default function ExamplePage() {
<p>{intl.formatMessage(messages['test-project.message'])}</p>

<h2>Authenticated User Test</h2>
{context.authenticatedUser !== null ? (
{authenticatedUser !== null ? (
<div>
<p>Authenticated Username: <strong>{context.authenticatedUser.username}</strong></p>
<p>Authenticated Username: <strong>{authenticatedUser.username}</strong></p>
<p>
Authenticated user&apos;s name:
<strong>{context.authenticatedUser.username}</strong>
<strong>{authenticatedUser.username}</strong>
(Only available if user account has been fetched)
</p>
</div>
) : (
<p>Unauthenticated {printTestResult(context.authenticatedUser === null)}</p>
<p>Unauthenticated {printTestResult(authenticatedUser === null)}</p>
)}

<h2>Config tests</h2>
Expand Down

0 comments on commit 9168495

Please sign in to comment.