mirror of https://github.com/artifacthub/hub.git
Address some security vulnerabilities in frontend (#2435)
Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
parent
6587198222
commit
ae3cf31700
|
|
@ -0,0 +1,2 @@
|
|||
paths-ignore:
|
||||
- docs/api/*.js
|
||||
|
|
@ -22,6 +22,7 @@ jobs:
|
|||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Perform CodeQL Analysis
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import useSystemThemeMode from '../hooks/useSystemThemeMode';
|
|||
import { Prefs, Profile, ThemePrefs, UserFullName } from '../types';
|
||||
import cleanLoginUrlParams from '../utils/cleanLoginUrlParams';
|
||||
import detectActiveThemeMode from '../utils/detectActiveThemeMode';
|
||||
import history from '../utils/history';
|
||||
import browserHistory from '../utils/history';
|
||||
import isControlPanelSectionAvailable from '../utils/isControlPanelSectionAvailable';
|
||||
import lsPreferences from '../utils/localStoragePreferences';
|
||||
import lsStorage from '../utils/localStoragePreferences';
|
||||
|
|
@ -92,16 +92,20 @@ export async function refreshUserProfile(dispatch: Dispatch<any>, redirectUrl?:
|
|||
}`;
|
||||
if (!isUndefined(redirectUrl)) {
|
||||
if (redirectUrl === currentUrl) {
|
||||
history.replace(redirectUrl);
|
||||
browserHistory.replace(redirectUrl);
|
||||
} else {
|
||||
const redirection = redirectUrl.split('?');
|
||||
// Redirect to correct route when necessary
|
||||
history.push(redirectUrl);
|
||||
browserHistory.push({
|
||||
pathname: redirection[0],
|
||||
search: !isUndefined(redirection[1]) ? `?${redirection[1]}` : '',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
dispatch({ type: 'signOut' });
|
||||
if (err.message === 'invalid session') {
|
||||
history.push(
|
||||
browserHistory.push(
|
||||
`${window.location.pathname}${
|
||||
window.location.search === '' ? '?' : `${window.location.search}&`
|
||||
}modal=login&redirect=${encodeURIComponent(`${window.location.pathname}${window.location.search}`)}`
|
||||
|
|
@ -111,10 +115,10 @@ export async function refreshUserProfile(dispatch: Dispatch<any>, redirectUrl?:
|
|||
}
|
||||
|
||||
function redirectToControlPanel(context: 'user' | 'org') {
|
||||
if (history.location.pathname.startsWith('/control-panel')) {
|
||||
const sections = history.location.pathname.split('/');
|
||||
if (browserHistory.location.pathname.startsWith('/control-panel')) {
|
||||
const sections = browserHistory.location.pathname.split('/');
|
||||
if (!isControlPanelSectionAvailable(context, sections[2], sections[3])) {
|
||||
history.push('/control-panel/repositories');
|
||||
browserHistory.push('/control-panel/repositories');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Route, Router, Switch } from 'react-router-dom';
|
|||
|
||||
import { AppCtxProvider } from '../context/AppCtx';
|
||||
import buildSearchParams from '../utils/buildSearchParams';
|
||||
import history from '../utils/history';
|
||||
import browserHistory from '../utils/history';
|
||||
import AlertController from './common/AlertController';
|
||||
import UserNotificationsController from './common/userNotifications';
|
||||
import ControlPanelView from './controlPanel';
|
||||
|
|
@ -38,7 +38,7 @@ export default function App() {
|
|||
|
||||
return (
|
||||
<AppCtxProvider>
|
||||
<Router history={history}>
|
||||
<Router history={browserHistory}>
|
||||
<div className="d-flex flex-column min-vh-100 position-relative whiteBranded">
|
||||
<div className="visually-hidden visually-hidden-focusable">
|
||||
<a href="#content">Skip to Main Content</a>
|
||||
|
|
@ -59,29 +59,37 @@ export default function App() {
|
|||
'/delete-user',
|
||||
]}
|
||||
exact
|
||||
render={({ location }) => (
|
||||
render={(routeProps) => (
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
<Navbar
|
||||
isSearching={isSearching}
|
||||
redirect={getQueryParam(location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
redirect={getQueryParam(routeProps.location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
fromHome
|
||||
/>
|
||||
<HomeView
|
||||
isSearching={isSearching}
|
||||
emailCode={
|
||||
location.pathname === '/verify-email' ? getQueryParam(location.search, 'code') : undefined
|
||||
routeProps.location.pathname === '/verify-email'
|
||||
? getQueryParam(routeProps.location.search, 'code')
|
||||
: undefined
|
||||
}
|
||||
deleteCode={
|
||||
location.pathname === '/delete-user' ? getQueryParam(location.search, 'code') : undefined
|
||||
routeProps.location.pathname === '/delete-user'
|
||||
? getQueryParam(routeProps.location.search, 'code')
|
||||
: undefined
|
||||
}
|
||||
resetPwdCode={
|
||||
location.pathname === '/reset-password' ? getQueryParam(location.search, 'code') : undefined
|
||||
routeProps.location.pathname === '/reset-password'
|
||||
? getQueryParam(routeProps.location.search, 'code')
|
||||
: undefined
|
||||
}
|
||||
orgToConfirm={
|
||||
location.pathname === '/accept-invitation' ? getQueryParam(location.search, 'org') : undefined
|
||||
routeProps.location.pathname === '/accept-invitation'
|
||||
? getQueryParam(routeProps.location.search, 'org')
|
||||
: undefined
|
||||
}
|
||||
onOauthFailed={location.pathname === '/oauth-failed'}
|
||||
onOauthFailed={routeProps.location.pathname === '/oauth-failed'}
|
||||
/>
|
||||
<Footer />
|
||||
</div>
|
||||
|
|
@ -91,13 +99,13 @@ export default function App() {
|
|||
<Route
|
||||
path="/packages/search"
|
||||
exact
|
||||
render={({ location }: any) => {
|
||||
const searchParams = buildSearchParams(location.search);
|
||||
render={(routeProps: any) => {
|
||||
const searchParams = buildSearchParams(routeProps.location.search);
|
||||
return (
|
||||
<>
|
||||
<Navbar
|
||||
redirect={getQueryParam(location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
redirect={getQueryParam(routeProps.location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
isSearching={isSearching}
|
||||
searchText={searchParams.tsQueryWeb}
|
||||
/>
|
||||
|
|
@ -108,8 +116,10 @@ export default function App() {
|
|||
setIsSearching={setIsSearching}
|
||||
scrollPosition={scrollPosition}
|
||||
setScrollPosition={setScrollPosition}
|
||||
fromDetail={location.state ? location.state.hasOwnProperty('from-detail') : false}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
fromDetail={
|
||||
routeProps.location.state ? routeProps.location.state.hasOwnProperty('from-detail') : false
|
||||
}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -120,32 +130,32 @@ export default function App() {
|
|||
<Route
|
||||
path="/packages/:repositoryKind/:repositoryName/:packageName/:version?"
|
||||
exact
|
||||
render={({ location, match }) => {
|
||||
const state = location.state ? (location.state as Object) : {};
|
||||
render={(routeProps) => {
|
||||
const state = routeProps.location.state ? (routeProps.location.state as Object) : {};
|
||||
return (
|
||||
<>
|
||||
<Navbar
|
||||
isSearching={isSearching}
|
||||
redirect={getQueryParam(location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
redirect={getQueryParam(routeProps.location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
/>
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
<PackageView
|
||||
hash={location.hash}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
visibleTemplate={getQueryParam(location.search, 'template') || undefined}
|
||||
visibleLine={getQueryParam(location.search, 'line') || undefined}
|
||||
compareVersionTo={getQueryParam(location.search, 'compare-to') || undefined}
|
||||
visibleExample={getQueryParam(location.search, 'example') || undefined}
|
||||
visibleFile={getQueryParam(location.search, 'file') || undefined}
|
||||
visibleVersion={getQueryParam(location.search, 'version') || undefined}
|
||||
visibleValuesPath={getQueryParam(location.search, 'path') || undefined}
|
||||
visibleImage={getQueryParam(location.search, 'image') || undefined}
|
||||
visibleTarget={getQueryParam(location.search, 'target') || undefined}
|
||||
visibleSection={getQueryParam(location.search, 'section') || undefined}
|
||||
eventId={getQueryParam(location.search, 'event-id') || undefined}
|
||||
hash={routeProps.location.hash}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
visibleTemplate={getQueryParam(routeProps.location.search, 'template') || undefined}
|
||||
visibleLine={getQueryParam(routeProps.location.search, 'line') || undefined}
|
||||
compareVersionTo={getQueryParam(routeProps.location.search, 'compare-to') || undefined}
|
||||
visibleExample={getQueryParam(routeProps.location.search, 'example') || undefined}
|
||||
visibleFile={getQueryParam(routeProps.location.search, 'file') || undefined}
|
||||
visibleVersion={getQueryParam(routeProps.location.search, 'version') || undefined}
|
||||
visibleValuesPath={getQueryParam(routeProps.location.search, 'path') || undefined}
|
||||
visibleImage={getQueryParam(routeProps.location.search, 'image') || undefined}
|
||||
visibleTarget={getQueryParam(routeProps.location.search, 'target') || undefined}
|
||||
visibleSection={getQueryParam(routeProps.location.search, 'section') || undefined}
|
||||
eventId={getQueryParam(routeProps.location.search, 'event-id') || undefined}
|
||||
{...state}
|
||||
{...match.params}
|
||||
{...routeProps.match.params}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
@ -156,17 +166,17 @@ export default function App() {
|
|||
<Route
|
||||
path="/control-panel/:section?/:subsection?"
|
||||
exact
|
||||
render={({ location, match }) => (
|
||||
render={(routeProps) => (
|
||||
<>
|
||||
<Navbar isSearching={isSearching} privateRoute />
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
<ControlPanelView
|
||||
{...match.params}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
userAlias={getQueryParam(location.search, 'user-alias') || undefined}
|
||||
organizationName={getQueryParam(location.search, 'org-name') || undefined}
|
||||
repoName={getQueryParam(location.search, 'repo-name') || undefined}
|
||||
activePage={getQueryParam(location.search, 'page') || undefined}
|
||||
{...routeProps.match.params}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
userAlias={getQueryParam(routeProps.location.search, 'user-alias') || undefined}
|
||||
organizationName={getQueryParam(routeProps.location.search, 'org-name') || undefined}
|
||||
repoName={getQueryParam(routeProps.location.search, 'repo-name') || undefined}
|
||||
activePage={getQueryParam(routeProps.location.search, 'page') || undefined}
|
||||
/>
|
||||
</div>
|
||||
<Footer />
|
||||
|
|
@ -177,11 +187,11 @@ export default function App() {
|
|||
<Route
|
||||
path="/packages/starred"
|
||||
exact
|
||||
render={({ location }) => (
|
||||
render={(routeProps) => (
|
||||
<>
|
||||
<Navbar isSearching={isSearching} privateRoute />
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
<StarredPackagesView activePage={getQueryParam(location.search, 'page') || undefined} />
|
||||
<StarredPackagesView activePage={getQueryParam(routeProps.location.search, 'page') || undefined} />
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
|
|
@ -191,15 +201,15 @@ export default function App() {
|
|||
<Route
|
||||
path="/stats"
|
||||
exact
|
||||
render={({ location }) => (
|
||||
render={(routeProps) => (
|
||||
<>
|
||||
<Navbar
|
||||
isSearching={isSearching}
|
||||
redirect={getQueryParam(location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(location.search, 'modal') || undefined}
|
||||
redirect={getQueryParam(routeProps.location.search, 'redirect') || undefined}
|
||||
visibleModal={getQueryParam(routeProps.location.search, 'modal') || undefined}
|
||||
/>
|
||||
<div className="d-flex flex-column flex-grow-1">
|
||||
<StatsView hash={location.hash} />
|
||||
<StatsView hash={routeProps.location.hash} />
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import AnchorHeader from './AnchorHeader';
|
||||
|
||||
|
|
@ -41,19 +42,31 @@ describe('AnchorHeader', () => {
|
|||
});
|
||||
|
||||
it('creates snapshot', () => {
|
||||
const { asFragment } = render(<AnchorHeader {...defaultProps} />);
|
||||
const { asFragment } = render(
|
||||
<Router>
|
||||
<AnchorHeader {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders header properly', () => {
|
||||
render(<AnchorHeader {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<AnchorHeader {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
const header = screen.getByText(/Header/i);
|
||||
expect(header).toBeInTheDocument();
|
||||
expect(header.tagName).toBe('H2');
|
||||
});
|
||||
|
||||
it('calls scroll into view', async () => {
|
||||
render(<AnchorHeader {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<AnchorHeader {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
const link = screen.getByRole('button');
|
||||
expect(link).toBeInTheDocument();
|
||||
await userEvent.click(link);
|
||||
|
|
@ -62,7 +75,11 @@ describe('AnchorHeader', () => {
|
|||
|
||||
for (let i = 0; i < tests.length; i++) {
|
||||
it('renders proper id and href from title', () => {
|
||||
render(<AnchorHeader {...defaultProps} title={tests[i].title} anchorName={tests[i].anchor} />);
|
||||
render(
|
||||
<Router>
|
||||
<AnchorHeader {...defaultProps} title={tests[i].title} anchorName={tests[i].anchor} />
|
||||
</Router>
|
||||
);
|
||||
expect(screen.getByText(new RegExp(tests[i].title, 'i'))).toBeInTheDocument();
|
||||
const link = screen.getByRole('button');
|
||||
expect(link).toHaveProperty('href', `http://localhost/#${tests[i].id}`);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { isObject, isString } from 'lodash';
|
|||
import isUndefined from 'lodash/isUndefined';
|
||||
import { ElementType, MouseEvent as ReactMouseEvent } from 'react';
|
||||
import { GoLink } from 'react-icons/go';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import getAnchorValue from '../../utils/getAnchorValue';
|
||||
import history from '../../utils/history';
|
||||
import browserHistory from '../../utils/history';
|
||||
import styles from './AnchorHeader.module.css';
|
||||
interface Props {
|
||||
level: number;
|
||||
|
|
@ -39,8 +40,8 @@ const AnchorHeader: ElementType = (props: Props) => {
|
|||
<span className={styles.header}>
|
||||
<Tag className={`position-relative anchorHeader ${styles.headingWrapper} ${props.className}`}>
|
||||
<div data-testid="anchor" className={`position-absolute ${styles.headerAnchor}`} id={anchor} />
|
||||
<a
|
||||
href={`${history.location.pathname}#${anchor}`}
|
||||
<Link
|
||||
to={{ pathname: browserHistory.location.pathname, hash: anchor }}
|
||||
onClick={(e: ReactMouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -51,7 +52,8 @@ const AnchorHeader: ElementType = (props: Props) => {
|
|||
aria-label={value}
|
||||
>
|
||||
<GoLink />
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
{props.title || props.children}
|
||||
</Tag>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import TOC from './TOC';
|
||||
jest.mock('react-markdown', () => () => <div>Readme</div>);
|
||||
|
|
@ -55,20 +56,32 @@ describe('TOC', () => {
|
|||
});
|
||||
|
||||
it('creates snapshot', () => {
|
||||
const { asFragment } = render(<TOC {...defaultProps} />);
|
||||
const { asFragment } = render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('Render', () => {
|
||||
it('renders properly', () => {
|
||||
render(<TOC {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Readme')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /Table of contents/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('displays dropdown', async () => {
|
||||
render(<TOC {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const btn = screen.getByRole('button', { name: /Table of contents/ });
|
||||
await userEvent.click(btn);
|
||||
|
|
@ -84,7 +97,11 @@ describe('TOC', () => {
|
|||
});
|
||||
|
||||
it('displays dropdown', async () => {
|
||||
render(<TOC {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const btn = screen.getByRole('button', { name: /Table of contents/ });
|
||||
await userEvent.click(btn);
|
||||
|
|
@ -97,12 +114,20 @@ describe('TOC', () => {
|
|||
});
|
||||
|
||||
it('renders support button', () => {
|
||||
render(<TOC {...defaultProps} supportLink="http://link.test" />);
|
||||
render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} supportLink="http://link.test" />
|
||||
</Router>
|
||||
);
|
||||
expect(screen.getByRole('button', { name: 'Open support link' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render component when list is empty', () => {
|
||||
const { container } = render(<TOC {...defaultProps} toc={[]} />);
|
||||
const { container } = render(
|
||||
<Router>
|
||||
<TOC {...defaultProps} toc={[]} />
|
||||
</Router>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import TOCEntry from './TOCEntry';
|
||||
|
||||
|
|
@ -22,13 +23,21 @@ describe('TOCEntry', () => {
|
|||
});
|
||||
|
||||
it('creates snapshot', () => {
|
||||
const { asFragment } = render(<TOCEntry {...defaultProps} />);
|
||||
const { asFragment } = render(
|
||||
<Router>
|
||||
<TOCEntry {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('Render', () => {
|
||||
it('renders properly', () => {
|
||||
render(<TOCEntry {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOCEntry {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const link = screen.getByText('Installing the Chart');
|
||||
expect(link).toBeInTheDocument();
|
||||
|
|
@ -37,7 +46,11 @@ describe('TOCEntry', () => {
|
|||
});
|
||||
|
||||
it('clicks link', async () => {
|
||||
render(<TOCEntry {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOCEntry {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
const link = screen.getByText('Installing the Chart');
|
||||
await userEvent.click(link);
|
||||
|
|
@ -51,7 +64,11 @@ describe('TOCEntry', () => {
|
|||
|
||||
describe('does not render element', () => {
|
||||
it('when value is an empty string', () => {
|
||||
const { container } = render(<TOCEntry {...defaultProps} entry={{ ...defaultProps.entry, value: '' }} />);
|
||||
const { container } = render(
|
||||
<Router>
|
||||
<TOCEntry {...defaultProps} entry={{ ...defaultProps.entry, value: '' }} />
|
||||
</Router>
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Dispatch, MouseEvent as ReactMouseEvent, SetStateAction } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { TOCEntryItem } from '../../../types';
|
||||
import cleanTOCEntry from '../../../utils/cleanTOCEntry';
|
||||
import getAnchorValue from '../../../utils/getAnchorValue';
|
||||
import history from '../../../utils/history';
|
||||
import browserHistory from '../../../utils/history';
|
||||
import styles from './TOCEntry.module.css';
|
||||
|
||||
interface Props {
|
||||
|
|
@ -21,11 +22,11 @@ const TOCEntry = (props: Props) => {
|
|||
|
||||
return (
|
||||
<div className={`${styles.dropdownItem} dropdownItem`}>
|
||||
<a
|
||||
<Link
|
||||
to={{ pathname: browserHistory.location.pathname, hash: link }}
|
||||
className={`btn btn-link d-inline-block w-100 text-decoration-none ms-0 text-muted text-start ${styles.btn} ${
|
||||
styles[`level${props.level}`]
|
||||
}`}
|
||||
href={`${history.location.pathname}#${link}`}
|
||||
onClick={(e: ReactMouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -38,7 +39,7 @@ const TOCEntry = (props: Props) => {
|
|||
aria-selected={false}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import TOCList from './TOCList';
|
||||
|
||||
|
|
@ -53,13 +54,21 @@ describe('TOCList', () => {
|
|||
});
|
||||
|
||||
it('creates snapshot', () => {
|
||||
const { asFragment } = render(<TOCList {...defaultProps} />);
|
||||
const { asFragment } = render(
|
||||
<Router>
|
||||
<TOCList {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('Render', () => {
|
||||
it('renders properly', () => {
|
||||
render(<TOCList {...defaultProps} />);
|
||||
render(
|
||||
<Router>
|
||||
<TOCList {...defaultProps} />
|
||||
</Router>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Title 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Subtitle 1a')).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { mocked } from 'jest-mock';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import API from '../../api';
|
||||
import { AppCtx } from '../../context/AppCtx';
|
||||
|
|
@ -55,7 +56,9 @@ describe('StatsView', () => {
|
|||
|
||||
const { asFragment } = render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -76,7 +79,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -100,7 +105,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -123,7 +130,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -137,7 +146,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -154,7 +165,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
@ -174,7 +187,9 @@ describe('StatsView', () => {
|
|||
|
||||
render(
|
||||
<AppCtx.Provider value={{ ctx: mockCtx, dispatch: jest.fn() }}>
|
||||
<Router>
|
||||
<StatsView hash="#repositories" />
|
||||
</Router>
|
||||
</AppCtx.Provider>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import history from './history';
|
||||
import browserHistory from './history';
|
||||
|
||||
jest.mock('./updateMetaIndex', () => jest.fn());
|
||||
|
||||
|
|
@ -19,14 +19,14 @@ describe('history', () => {
|
|||
});
|
||||
|
||||
it('calls analytics and update meta', () => {
|
||||
history.push('/new-page');
|
||||
browserHistory.push('/new-page');
|
||||
expect(mockAnalytics.page).toHaveBeenCalledTimes(1);
|
||||
expect(mockAnalytics.page).toHaveBeenCalledWith();
|
||||
expect(mockUpdateMeta).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls analytics, but not update meta when pathname starts with /packages/(helm|falco|opa|olm|tbaction|krew|helm-plugin|tekton-task)/', () => {
|
||||
history.push('/packages/helm/123');
|
||||
browserHistory.push('/packages/helm/123');
|
||||
expect(mockAnalytics.page).toHaveBeenCalledTimes(1);
|
||||
expect(mockAnalytics.page).toHaveBeenCalledWith();
|
||||
expect(mockUpdateMeta).toHaveBeenCalledTimes(0);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import getMetaTag from './getMetaTag';
|
|||
import updateMetaIndex from './updateMetaIndex';
|
||||
import notificationsDispatcher from './userNotificationsDispatcher';
|
||||
|
||||
const history = createBrowserHistory();
|
||||
const browserHistory = createBrowserHistory();
|
||||
const analyticsConfig: string | null = getMetaTag('gaTrackingID');
|
||||
let analytics: any = null;
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ if (!isNull(analyticsConfig)) {
|
|||
});
|
||||
}
|
||||
|
||||
history.listen((location) => {
|
||||
browserHistory.listen((location) => {
|
||||
// Updates meta tags every time that history is called for all locations except for package detail page
|
||||
if (!PKG_DETAIL_PATH.test(location.pathname)) {
|
||||
updateMetaIndex();
|
||||
|
|
@ -31,4 +31,4 @@ history.listen((location) => {
|
|||
notificationsDispatcher.dismissNotification(location.pathname);
|
||||
});
|
||||
|
||||
export default history;
|
||||
export default browserHistory;
|
||||
|
|
|
|||
Loading…
Reference in New Issue