Skip to content

Commit

Permalink
upgrade to react 18, refactor tests (#335)
Browse files Browse the repository at this point in the history
* upgrade react to 18

* remove deprecated method

* upgrade to v6

* temporarily remove markdown loader

* rewrite tests

* switch out markdown loading

* update mocks

* bump gh-pages

* remove unused package

* expect assertions

* move dependencies into dev dependencies
  • Loading branch information
mldangelo committed Mar 7, 2023
1 parent 1f120ee commit 91ace24
Show file tree
Hide file tree
Showing 16 changed files with 2,793 additions and 4,028 deletions.
10 changes: 1 addition & 9 deletions .eslintrc
@@ -1,14 +1,6 @@
{
"extends": "airbnb",
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false,
"babelOptions": {
"babelrc": false,
"configFile": false,
"presets": ["@babel/preset-react"]
}
},
"env": {
"browser": true,
"node": true,
Expand Down Expand Up @@ -39,7 +31,7 @@
"declaration": true,
"assignment": true,
"return": true
}],
}]
},
"plugins": [
"react"
Expand Down
6 changes: 6 additions & 0 deletions babel.config.js
@@ -0,0 +1,6 @@
module.exports = {
presets: [
'@babel/preset-env',
['@babel/preset-react', { runtime: 'automatic' }],
],
};
8 changes: 8 additions & 0 deletions jest.config.js
@@ -0,0 +1,8 @@
const config = {
moduleNameMapper: {
'^.+\\.(css|less|scss)$': 'babel-jest',
'^.+\\.md$': 'markdown-to-jsx',
},
};

module.exports = config;
6,476 changes: 2,599 additions & 3,877 deletions package-lock.json

Large diffs are not rendered by default.

34 changes: 18 additions & 16 deletions package.json
Expand Up @@ -16,40 +16,42 @@
"deploy": "gh-pages -d build",
"start": "react-scripts start",
"lint": "eslint $(git ls-files '*.js')",
"test": "react-scripts test --watchAll=false",
"test": "npx jest",
"analyze": "npm run build && source-map-explorer build/static/js/*.chunk.js"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-brands-svg-icons": "^6.3.0",
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/react-fontawesome": "0.2.0",
"dayjs": "^1.11.3",
"gh-pages": "^4.0.0",
"dayjs": "^1.11.7",
"gh-pages": "^5.0.0",
"markdown-to-jsx": "^7.1.9",
"prop-types": "^15.8.1",
"raw.macro": "^0.4.2",
"react": "^17.0.1",
"react-burger-menu": "^3.0.8",
"react-dom": "^17.0.1",
"react-ga": "^3.3.0",
"react": "^18.2.0",
"react-burger-menu": "^3.0.9",
"react-dom": "^18.2.0",
"react-ga": "^3.3.1",
"react-helmet-async": "^1.3.0",
"react-markdown": "^5.0.3",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.1",
"react-snap": "^1.23.0",
"rimraf": "^4.3.1"
"react-router-dom": "^6.8.2",
"react-scripts": "^5.0.1"
},
"devDependencies": {
"react-snap": "^1.23.0",
"rimraf": "^4.3.1",
"babel-jest": "^29.5.0",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^11.2.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"eslint": "^8.35.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-react": "^7.32.2",
"sass": "^1.52.3",
"sass": "^1.58.3",
"source-map-explorer": "^2.5.3"
},
"browserslist": {
Expand Down
20 changes: 10 additions & 10 deletions src/App.js
@@ -1,5 +1,5 @@
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Main from './layouts/Main'; // fallback for lazy pages
import './static/css/main.scss'; // All of our styles

Expand All @@ -19,15 +19,15 @@ const Stats = lazy(() => import('./pages/Stats'));
const App = () => (
<BrowserRouter basename={PUBLIC_URL}>
<Suspense fallback={<Main />}>
<Switch>
<Route exact path="/" component={Index} />
<Route path="/about" component={About} />
<Route path="/projects" component={Projects} />
<Route path="/stats" component={Stats} />
<Route path="/contact" component={Contact} />
<Route path="/resume" component={Resume} />
<Route component={NotFound} status={404} />
</Switch>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/about" element={<About />} />
<Route path="/projects" element={<Projects />} />
<Route path="/stats" element={<Stats />} />
<Route path="/contact" element={<Contact />} />
<Route path="/resume" element={<Resume />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</BrowserRouter>
);
Expand Down
107 changes: 107 additions & 0 deletions src/__tests__/App.test.js
@@ -0,0 +1,107 @@
/**
* @jest-environment jsdom
*/

import '@testing-library/jest-dom';
import '@testing-library/react';
import React from 'react';
import ReactDOM from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import App from '../App';

describe('renders the app', () => {
// mocks the fetch API used on the stats page and the about page.
const jsonMock = jest.fn(() => Promise.resolve({}));
const textMock = jest.fn(() => Promise.resolve(''));
global.fetch = jest.fn(() => Promise.resolve({
json: jsonMock,
text: textMock,
}));
// mocks the scrollTo API used when navigating to a new page.
window.scrollTo = jest.fn();

let container;

beforeEach(async () => {
container = document.createElement('div');
document.body.appendChild(container);
await act(async () => {
await ReactDOM.createRoot(container).render(<App />);
});
});

afterEach(() => {
document.body.removeChild(container);
container = null;
jest.clearAllMocks();
});

it('should render the app', async () => {
expect(document.body).toBeInTheDocument();
});

it('should render the title', async () => {
expect(document.title).toBe("Michael D'Angelo");
});

it('can navigate to /about', async () => {
expect.assertions(7);
const aboutLink = document.querySelector('#header > nav > ul > li:nth-child(1) > a');
expect(aboutLink).toBeInTheDocument();
await act(async () => {
await aboutLink.click();
});
expect(document.title).toContain('About |');
expect(window.location.pathname).toBe('/about');
expect(window.scrollTo).toHaveBeenNthCalledWith(1, 0, 0);
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(jsonMock).toHaveBeenCalledTimes(0);
expect(textMock).toHaveBeenCalledTimes(1);
});

it('can navigate to /resume', async () => {
expect.assertions(3);
const contactLink = document.querySelector('#header > nav > ul > li:nth-child(2) > a');
expect(contactLink).toBeInTheDocument();
await act(async () => {
await contactLink.click();
});
expect(document.title).toContain('Resume |');
expect(window.location.pathname).toBe('/resume');
});

it('can navigate to /projects', async () => {
expect.assertions(3);
const contactLink = document.querySelector('#header > nav > ul > li:nth-child(3) > a');
expect(contactLink).toBeInTheDocument();
await act(async () => {
await contactLink.click();
});
expect(document.title).toContain('Projects |');
expect(window.location.pathname).toBe('/projects');
});

it('can navigate to /stats', async () => {
expect.assertions(5);
const contactLink = document.querySelector('#header > nav > ul > li:nth-child(4) > a');
expect(contactLink).toBeInTheDocument();
await act(async () => {
await contactLink.click();
});
expect(document.title).toContain('Stats |');
expect(window.location.pathname).toBe('/stats');
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(jsonMock).toHaveBeenCalledTimes(1);
});

it('can navigate to /contact', async () => {
expect.assertions(3);
const contactLink = document.querySelector('#header > nav > ul > li:nth-child(5) > a');
expect(contactLink).toBeInTheDocument();
await act(async () => {
await contactLink.click();
});
expect(document.title).toContain('Contact |');
expect(window.location.pathname).toBe('/contact');
});
});
7 changes: 4 additions & 3 deletions src/index.js
@@ -1,5 +1,5 @@
import React from 'react';
import { hydrate, render } from 'react-dom';
import { createRoot, hydrateRoot } from 'react-dom/client';
import App from './App';

// See https://reactjs.org/docs/strict-mode.html
Expand All @@ -13,7 +13,8 @@ const rootElement = document.getElementById('root');

// hydrate is required by react-snap.
if (rootElement.hasChildNodes()) {
hydrate(<StrictApp />, rootElement);
hydrateRoot(rootElement, <StrictApp />);
} else {
render(<StrictApp />, rootElement);
const root = createRoot(rootElement);
root.render(<StrictApp />);
}
66 changes: 34 additions & 32 deletions src/pages/About.js
@@ -1,41 +1,43 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import raw from 'raw.macro';
import Markdown from 'markdown-to-jsx';

import Main from '../layouts/Main';

// uses babel to load contents of file
const markdown = raw('../data/about.md');
const About = () => {
const [markdown, setMarkdown] = useState('');

const count = markdown.split(/\s+/)
.map((s) => s.replace(/\W/g, ''))
.filter((s) => s.length).length;
useEffect(() => {
import('../data/about.md')
.then((res) => {
fetch(res.default)
.then((r) => r.text())
.then(setMarkdown);
}).catch(console.error);
});

// Make all hrefs react router links
const LinkRenderer = ({ ...children }) => <Link {...children} />;
const count = markdown.split(/\s+/)
.map((s) => s.replace(/\W/g, ''))
.filter((s) => s.length).length;

const About = () => (
<Main
title="About"
description="Learn about Michael D'Angelo"
>
<article className="post markdown" id="about">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/about">About Me</Link></h2>
<p>(in about {count} words)</p>
</div>
</header>
<ReactMarkdown
source={markdown}
renderers={{
Link: LinkRenderer,
}}
escapeHtml={false}
/>
</article>
</Main>
);
return (
<Main
title="About"
description="Learn about Michael D'Angelo"
>
<article className="post markdown" id="about">
<header>
<div className="title">
<h2><Link to="/about">About Me</Link></h2>
<p>(in about {count} words)</p>
</div>
</header>
<Markdown>
{markdown}
</Markdown>
</article>
</Main>
);
};

export default About;
2 changes: 1 addition & 1 deletion src/pages/Contact.js
Expand Up @@ -13,7 +13,7 @@ const Contact = () => (
<article className="post" id="contact">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/contact">Contact</Link></h2>
<h2><Link to="/contact">Contact</Link></h2>
</div>
</header>
<div className="email-at">
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Index.js
Expand Up @@ -11,7 +11,7 @@ const Index = () => (
<article className="post" id="index">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/">About this site</Link></h2>
<h2><Link to="/">About this site</Link></h2>
<p>
A beautiful, responsive, statically-generated,
react application written with modern Javascript.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/NotFound.js
Expand Up @@ -8,7 +8,7 @@ const PageNotFound = () => (
<Helmet title="404 Not Found">
<meta name="description" content="The content you are looking for cannot be found." />
</Helmet>
<h1 data-testid="heading">Page Not Found</h1>
<h1>Page Not Found</h1>
<p>Return <Link to="/">home</Link>.</p>
</div>
</HelmetProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Projects.js
Expand Up @@ -14,7 +14,7 @@ const Projects = () => (
<article className="post" id="projects">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/projects">Projects</Link></h2>
<h2><Link to="/projects">Projects</Link></h2>
<p>A selection of projects that I&apos;m not too ashamed of</p>
</div>
</header>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Resume.js
Expand Up @@ -30,7 +30,7 @@ const Resume = () => (
<article className="post" id="resume">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="resume">Resume</Link></h2>
<h2><Link to="resume">Resume</Link></h2>
<div className="link-container">
{sections.map((sec) => (
<h4 key={sec}>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/Stats.js
Expand Up @@ -14,7 +14,7 @@ const Stats = () => (
<article className="post" id="stats">
<header>
<div className="title">
<h2 data-testid="heading"><Link to="/stats">Stats</Link></h2>
<h2><Link to="/stats">Stats</Link></h2>
</div>
</header>
<Personal />
Expand Down

0 comments on commit 91ace24

Please sign in to comment.