Skip to content

Commit

Permalink
test: add tests
Browse files Browse the repository at this point in the history
- Add unit testing with Jest, and React Testing Library.
- Add Husky to prevent a push if tests are failing.
- Perform patch and minor dependency updates.
- Make a few refactoring changes.
- Add new skills to profile.
- Update version.
  • Loading branch information
Bro3Simon committed Sep 24, 2023
1 parent 3731125 commit 1ef4a0d
Show file tree
Hide file tree
Showing 47 changed files with 5,980 additions and 750 deletions.
5 changes: 3 additions & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@
"error",
{
"code": 100,
"ignoreTemplateLiterals": true,
"ignoreStrings": true
"ignoreRegExpLiterals": true,
"ignoreStrings": true,
"ignoreTemplateLiterals": true
}
],
"no-duplicate-imports": "error",
Expand Down
6 changes: 6 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# The git command throws an error if there are local uncommitted changes thus preventing a push.
# The test command runs all tests not in watch mode and prevents a push if any test fails.
git diff HEAD --quiet && npm run test -- --watchAll=false
4,539 changes: 4,045 additions & 494 deletions package-lock.json

Large diffs are not rendered by default.

33 changes: 21 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,40 @@
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@fontsource/roboto": "^5.0.5",
"@mui/icons-material": "^5.14.1",
"@fontsource/roboto": "^5.0.8",
"@mui/icons-material": "^5.14.9",
"@mui/lab": "^5.0.0-alpha.131",
"@mui/material": "^5.14.1",
"@mui/material": "^5.14.10",
"react": "^18.2.0",
"react-awesome-reveal": "^4.2.5",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.2"
"react-hook-form": "^7.46.1"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@types/node": "^20.4.4",
"@types/react": "^18.2.15",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/css-mediaquery": "^0.1.2",
"@types/jest": "^29.5.5",
"@types/node": "^20.6.3",
"@types/react": "^18.2.22",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^5.60.1",
"@typescript-eslint/parser": "^5.60.1",
"eslint": "^8.45.0",
"css-mediaquery": "^0.1.2",
"eslint": "^8.49.0",
"eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-plugin-import": "^2.27.5",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-sort-destructure-keys": "^1.5.0",
"eslint-plugin-sort-keys-fix": "^1.1.2",
"husky": "^8.0.0",
"msw": "^1.3.1",
"prettier": "^2.8.8",
"react-scripts": "^5.0.1",
"typescript": "^4.9.5"
Expand All @@ -48,7 +56,8 @@
"build": "react-scripts build",
"eject": "react-scripts eject",
"start": "react-scripts start",
"test": "react-scripts test"
"test": "react-scripts test",
"prepare": "husky install"
},
"version": "2.0.0"
"version": "2.1.0"
}
64 changes: 64 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { render, screen } from '@testing-library/react';

import { App } from 'src/App';
import * as useNavBar from 'src/components/NavBar/useNavBar';
import { IMAGES } from 'src/data/images';
import { PROJECTS } from 'src/data/projects';
import { setupIntersectionObserverMock, testCategoriesIsRendered } from 'src/testUtilities';

describe('test App', () => {
test('renders all of the project titles', () => {
setupIntersectionObserverMock();
render(<App />);

PROJECTS.forEach(({ title }) => {
expect(screen.getByText(title)).toBeInTheDocument();
});
});

describe('renders dependent components', () => {
afterEach(() => {
jest.restoreAllMocks();
});

test('renders NavBar', () => {
const useNavBarSpy = jest.spyOn(useNavBar, 'useNavBar');

render(<App />);

expect(useNavBarSpy).toHaveBeenCalled();
});

test('renders Welcome', () => {
render(<App />);

expect(screen.getByTestId('welcome')).toBeInTheDocument();
});

test('renders Section three times', () => {
render(<App />);

expect(screen.getAllByRole('heading', { level: 2 }).length).toEqual(3);
});

test('renders Profile', () => {
render(<App />);

// Categories is rendered by Profile,
// So if at least one category item is rendered
// Then we know Profile is rendered
testCategoriesIsRendered();
});

test('renders photography', () => {
// If at least one image is rendered then we know Photography was rendered
const { source, title } = IMAGES[0];

render(<App />);

expect((screen.getAllByRole('img', { name: title })[0] as HTMLImageElement).src).toContain(
source,
);
});
});
});
5 changes: 4 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ export function App() {
pb: title === 'Scoreboard' ? 0 : 2,
}}
>
<CardHeader title={title} titleTypographyProps={{ textAlign: 'center' }} />
<CardHeader
title={title}
titleTypographyProps={{ component: 'h3', textAlign: 'center', variant: 'h5' }}
/>

<Component />
</Card>
Expand Down
11 changes: 11 additions & 0 deletions src/components/ButtonAnimation/ButtonAnimation.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { render, screen } from '@testing-library/react';

import { ButtonAnimation } from 'src/components/ButtonAnimation/ButtonAnimation';

describe('test ButtonAnimation', () => {
test('renders buttons', () => {
render(<ButtonAnimation />);

expect(screen.getAllByRole('button')).not.toHaveLength(0);
});
});
91 changes: 91 additions & 0 deletions src/components/ClientWork/ClientWork.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import { ClientWork } from 'src/components/ClientWork/ClientWork';
import { CLIENT_WORK } from 'src/data/clientWork';

describe('test ClientWork', () => {
test('renders all the tabs with the correct label', () => {
render(<ClientWork />);

CLIENT_WORK.forEach(({ name }) => {
expect(screen.getByRole('tab', { name })).toBeInTheDocument();
});
});

test('only renders a single tab panel', () => {
render(<ClientWork />);

expect(screen.getAllByRole('tabpanel')).toHaveLength(1);
});

test('renders the first tab panel initially', () => {
render(<ClientWork />);

expect(screen.getByRole('tabpanel', { name: CLIENT_WORK[0].name })).toBeInTheDocument();
});

test('renders the correct tab panel after the its tab is clicked', () => {
const user = userEvent.setup();

render(<ClientWork />);

CLIENT_WORK.forEach(async ({ name }) => {
await user.click(
screen.getByRole('tab', {
name,
}),
);

expect(screen.getByRole('tabpanel', { name })).toBeInTheDocument();
});
});

test('renders the correct image for each tab panel', () => {
const user = userEvent.setup();

render(<ClientWork />);

CLIENT_WORK.forEach(async ({ image, name }) => {
await user.click(
screen.getByRole('tab', {
name,
}),
);

expect(screen.getByRole('img', { name: image })).toBeInTheDocument();
});
});

test('renders the correct description for each tab panel', () => {
const user = userEvent.setup();

render(<ClientWork />);

CLIENT_WORK.forEach(async ({ description, name }) => {
await user.click(
screen.getByRole('tab', {
name,
}),
);

expect(screen.getByText(description)).toBeInTheDocument();
});
});

test('renders the correct link for each tab panel', () => {
const user = userEvent.setup();

render(<ClientWork />);

CLIENT_WORK.forEach(async ({ href, name }) => {
await user.click(
screen.getByRole('tab', {
name,
}),
);

expect(screen.getByRole('link')).toHaveAttribute('href', href);
});
});
});
39 changes: 11 additions & 28 deletions src/components/ClientWork/ClientWork.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,23 @@
import { Fragment } from 'react';

import { Box, Button, CardActions, CardContent, CardMedia, Link, Tab, Tabs } from '@mui/material';

import { useClientWork } from 'src/components/ClientWork/useClientWork';
import { CLIENT_WORK } from 'src/data/clientWork';
import { useTabs } from 'src/hooks/useTabs';
import { computeTabAndPanelProps } from 'src/utilities';

export function ClientWork() {
const { handleChangeTab, tab } = useClientWork();
const { handleChangeTab, tab } = useTabs();

return (
<>
<Tabs onChange={handleChangeTab} value={tab} variant="scrollable">
{CLIENT_WORK.map(({ name }) => {
const accessibleName = name.replace(' ', '-');

return (
<Tab
aria-controls={`panel-${accessibleName}`}
id={`tab-${accessibleName}`}
key={name}
label={name}
/>
);
})}
{CLIENT_WORK.map(({ name }) => (
<Tab key={name} {...computeTabAndPanelProps(name, 'tab')} />
))}
</Tabs>

{CLIENT_WORK.map(({ description, href, image, name }, index) => {
const accessibleName = name.replace(' ', '-');

return tab === index ? (
<Box
aria-labelledby={`tab-${accessibleName}`}
id={`panel-${accessibleName}`}
key={name}
role="tabpanel"
>
{CLIENT_WORK.map(({ description, href, image, name }, index) =>
tab === index ? (
<Box key={name} {...computeTabAndPanelProps(name, 'panel')}>
<CardMedia image={image} sx={{ height: 400 }} title={name} />

<CardContent sx={{ bgcolor: 'primary.main', color: 'primary.contrastText' }}>
Expand All @@ -53,8 +36,8 @@ export function ClientWork() {
</Button>
</CardActions>
</Box>
) : null;
})}
) : null,
)}
</>
);
}
Loading

0 comments on commit 1ef4a0d

Please sign in to comment.