Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into pr1771
Browse files Browse the repository at this point in the history
  • Loading branch information
chownces committed Jun 21, 2021
2 parents f1cf705 + 434513f commit f0c7a00
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 139 deletions.
68 changes: 21 additions & 47 deletions src/commons/navigationBar/subcomponents/SicpNavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { IconNames } from '@blueprintjs/icons';
import * as React from 'react';
import { useHistory, useParams } from 'react-router';
import controlButton from 'src/commons/ControlButton';
import { getNext, getPrev } from 'src/features/sicp/TableOfContentsHelper';

import tocNavigation from '../../../features/sicp/data/toc-navigation.json';
import { TableOfContentsButton } from '../../../features/sicp/TableOfContentsButton';
import SicpToc from '../../../pages/sicp/subcomponents/SicpToc';

Expand All @@ -16,59 +16,33 @@ const SicpNavigationBar: React.FC = () => {
const { section } = useParams<{ section: string }>();
const history = useHistory();

const prev = getPrev(section);
const next = getNext(section);

const handleCloseToc = () => setIsTocOpen(false);
const handleOpenToc = () => setIsTocOpen(true);
const handleNavigation = (sect: string) => {
history.push('/interactive-sicp/' + sect);
};

// Button to open table of contents
const tocButton = React.useMemo(() => {
const handleOpenToc = () => setIsTocOpen(true);
return <TableOfContentsButton key="toc" handleOpenToc={handleOpenToc} />;
}, []);
const tocButton = <TableOfContentsButton key="toc" handleOpenToc={handleOpenToc} />;

// Previous button only displayed when next page is valid.
const prevButton = React.useMemo(() => {
const sect = tocNavigation[section];
if (!sect) {
return;
}

const prev = sect['prev'];
if (!prev) {
return;
}

const handlePrev = () => {
history.push('/interactive-sicp/' + prev);
};

return (
prev && <div key="prev">{controlButton('Previous', IconNames.ARROW_LEFT, handlePrev)}</div>
);
}, [history, section]);
const prevButton = prev && (
<div key="prev">
{controlButton('Previous', IconNames.ARROW_LEFT, () => handleNavigation(prev))}
</div>
);

// Next button only displayed when next page is valid.
const nextButton = React.useMemo(() => {
const sect = tocNavigation[section];
if (!sect) {
return;
}

const next = sect['next'];
if (!next) {
return;
}

const handleNext = () => {
history.push('/interactive-sicp/' + next);
};

return (
next && (
<div key="next">
{controlButton('Next', IconNames.ARROW_RIGHT, handleNext, { iconOnRight: true })}
</div>
)
);
}, [history, section]);
const nextButton = next && (
<div key="next">
{controlButton('Next', IconNames.ARROW_RIGHT, () => handleNavigation(next), {
iconOnRight: true
})}
</div>
);

const drawerProps = {
onClose: handleCloseToc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ exports[`Navbar renders correctly 1`] = `
</Blueprint3.NavbarGroup>
<Blueprint3.NavbarGroup align=\\"right\\">
<div>
<Blueprint3.Button disabled={false} fill={false} intent=\\"none\\" minimal={true} className=\\"\\" type={[undefined]} rightIcon={{...}} onClick={[Function: handleNext]}>
<Blueprint3.Button disabled={false} fill={false} intent=\\"none\\" minimal={true} className=\\"\\" type={[undefined]} rightIcon={{...}} onClick={[Function (anonymous)]}>
Next
</Blueprint3.Button>
</div>
Expand Down
13 changes: 13 additions & 0 deletions src/features/sicp/TableOfContentsHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import tocNavigation from './data/toc-navigation.json';

export const getNext = (section: string): string | undefined => {
const node = tocNavigation[section];

return node && node['next'];
};

export const getPrev = (section: string): string | undefined => {
const node = tocNavigation[section];

return node && node['prev'];
};
31 changes: 31 additions & 0 deletions src/features/sicp/__tests__/TableOfContentsHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getNext, getPrev } from '../TableOfContentsHelper';

const data = {
'1': { next: '2' },
'2': { next: '3', prev: '1' },
'3': { prev: '2' }
};

jest.mock('../data/toc-navigation.json', () => data);

describe('Table of contents helper', () => {
test('generate next correctly', () => {
expect(getNext('1')).toBe('2');
expect(getNext('2')).toBe('3');
expect(getNext('3')).toBeUndefined();
});

test('generate prev correctly', () => {
expect(getPrev('1')).toBeUndefined();
expect(getPrev('2')).toBe('1');
expect(getPrev('3')).toBe('2');
});

test('handle invalid values correctly', () => {
expect(getNext('invalid')).toBeUndefined();
expect(getNext('')).toBeUndefined();

expect(getPrev('invalid')).toBeUndefined();
expect(getPrev('')).toBeUndefined();
});
});
36 changes: 36 additions & 0 deletions src/features/sicp/errors/SicpErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, ErrorInfo, ReactNode } from 'react';

import getSicpError, { SicpErrorType } from './SicpErrors';

type Props = {
children: ReactNode;
};

type State = {
hasError: boolean;
};

class SicpErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false
};

public static getDerivedStateFromError(_: Error): State {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}

public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Uncaught error:', error, errorInfo);
}

public render() {
if (this.state.hasError) {
return getSicpError(SicpErrorType.UNEXPECTED_ERROR);
}

return this.props.children;
}
}

export default SicpErrorBoundary;
58 changes: 58 additions & 0 deletions src/features/sicp/errors/SicpErrors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { NonIdealState } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';

export enum SicpErrorType {
UNEXPECTED_ERROR,
PAGE_NOT_FOUND_ERROR,
PARSING_ERROR
}

const unexpectedError = (
<div>
Something unexpected went wrong trying to load this page. Please try refreshing the page. If the
issue persists, kindly let us know by filing an issue at{' '}
<a href="https://github.com/source-academy/cadet-frontend">
https://github.com/source-academy/cadet-frontend
</a>
.
</div>
);

const pageNotFoundError = (
<div>
We could not find the page you were looking for. Please check the URL again. If you believe the
URL is correct, kindly let us know by filing an issue at{' '}
<a href="https://github.com/source-academy/cadet-frontend">
https://github.com/source-academy/cadet-frontend
</a>
.
</div>
);

const parsingError = (
<div>
An error occured while loading the page. Kindly let us know by filing an issue at{' '}
<a href="https://github.com/source-academy/cadet-frontend">
https://github.com/source-academy/cadet-frontend
</a>{' '}
and we will get it fixed as soon as possible.
</div>
);

const errorComponent = (description: JSX.Element) => (
<NonIdealState title="Something went wrong :(" description={description} icon={IconNames.ERROR} />
);

const getSicpError = (type: SicpErrorType) => {
switch (type) {
case SicpErrorType.PAGE_NOT_FOUND_ERROR:
return errorComponent(pageNotFoundError);
case SicpErrorType.PARSING_ERROR:
return errorComponent(parsingError);
default:
// handle unexpected error case
return errorComponent(unexpectedError);
}
};

export default getSicpError;
20 changes: 20 additions & 0 deletions src/features/sicp/errors/__tests__/SicpErrors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { mount } from 'enzyme';

import getSicpError, { SicpErrorType } from '../SicpErrors';

describe('Sicp errors:', () => {
test('unexpected error renders correctly', () => {
const tree = mount(getSicpError(SicpErrorType.UNEXPECTED_ERROR));
expect(tree.debug()).toMatchSnapshot();
});

test('page not found error renders correctly', () => {
const tree = mount(getSicpError(SicpErrorType.PAGE_NOT_FOUND_ERROR));
expect(tree.debug()).toMatchSnapshot();
});

test('unexpected error renders correctly', () => {
const tree = mount(getSicpError(SicpErrorType.PARSING_ERROR));
expect(tree.debug()).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Sicp errors: page not found error renders correctly 1`] = `
"<Blueprint3.NonIdealState title=\\"Something went wrong :(\\" description={{...}} icon=\\"error\\">
<div className=\\"bp3-non-ideal-state\\">
<div className=\\"bp3-non-ideal-state-visual\\">
<Blueprint3.Icon icon=\\"error\\" iconSize={60}>
<span icon=\\"error\\" className=\\"bp3-icon bp3-icon-error\\" title={[undefined]}>
<svg fill={[undefined]} data-icon=\\"error\\" width={60} height={60} viewBox=\\"0 0 20 20\\">
<desc>
error
</desc>
<path d=\\"M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 16H9v-2h2v2zm0-3H9V4h2v9z\\" fillRule=\\"evenodd\\" />
</svg>
</span>
</Blueprint3.Icon>
</div>
<Component>
<h4 className=\\"bp3-heading\\">
Something went wrong :(
</h4>
</Component>
<div>
We could not find the page you were looking for. Please check the URL again. If you believe the URL is correct, kindly let us know by filing an issue at
<a href=\\"https://github.com/source-academy/cadet-frontend\\">
https://github.com/source-academy/cadet-frontend
</a>
.
</div>
</div>
</Blueprint3.NonIdealState>"
`;
exports[`Sicp errors: unexpected error renders correctly 1`] = `
"<Blueprint3.NonIdealState title=\\"Something went wrong :(\\" description={{...}} icon=\\"error\\">
<div className=\\"bp3-non-ideal-state\\">
<div className=\\"bp3-non-ideal-state-visual\\">
<Blueprint3.Icon icon=\\"error\\" iconSize={60}>
<span icon=\\"error\\" className=\\"bp3-icon bp3-icon-error\\" title={[undefined]}>
<svg fill={[undefined]} data-icon=\\"error\\" width={60} height={60} viewBox=\\"0 0 20 20\\">
<desc>
error
</desc>
<path d=\\"M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 16H9v-2h2v2zm0-3H9V4h2v9z\\" fillRule=\\"evenodd\\" />
</svg>
</span>
</Blueprint3.Icon>
</div>
<Component>
<h4 className=\\"bp3-heading\\">
Something went wrong :(
</h4>
</Component>
<div>
Something unexpected went wrong trying to load this page. Please try refreshing the page. If the issue persists, kindly let us know by filing an issue at
<a href=\\"https://github.com/source-academy/cadet-frontend\\">
https://github.com/source-academy/cadet-frontend
</a>
.
</div>
</div>
</Blueprint3.NonIdealState>"
`;
exports[`Sicp errors: unexpected error renders correctly 2`] = `
"<Blueprint3.NonIdealState title=\\"Something went wrong :(\\" description={{...}} icon=\\"error\\">
<div className=\\"bp3-non-ideal-state\\">
<div className=\\"bp3-non-ideal-state-visual\\">
<Blueprint3.Icon icon=\\"error\\" iconSize={60}>
<span icon=\\"error\\" className=\\"bp3-icon bp3-icon-error\\" title={[undefined]}>
<svg fill={[undefined]} data-icon=\\"error\\" width={60} height={60} viewBox=\\"0 0 20 20\\">
<desc>
error
</desc>
<path d=\\"M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 16H9v-2h2v2zm0-3H9V4h2v9z\\" fillRule=\\"evenodd\\" />
</svg>
</span>
</Blueprint3.Icon>
</div>
<Component>
<h4 className=\\"bp3-heading\\">
Something went wrong :(
</h4>
</Component>
<div>
An error occured while loading the page. Kindly let us know by filing an issue at
<a href=\\"https://github.com/source-academy/cadet-frontend\\">
https://github.com/source-academy/cadet-frontend
</a>
and we will get it fixed as soon as possible.
</div>
</div>
</Blueprint3.NonIdealState>"
`;
Loading

0 comments on commit f0c7a00

Please sign in to comment.