Skip to content

Commit d678291

Browse files
authored
[SDK-1569] Sample app with E2E tests (auth0#31)
* Vanilla CRA with react-router-dom * move into const to look neater * Turn off codecov comments in PRs * package-lock overides parent * Can't use ci without a package-lock
1 parent f2f6e8a commit d678291

34 files changed

+2507
-12
lines changed

.circleci/config.yml

+7-4
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@ version: 2.1
33
jobs:
44
build:
55
docker:
6-
- image: cypress/browsers:node12.13.0-chrome80-ff74
6+
- image: circleci/node:12.9.1-browsers
77
steps:
88
- checkout
99
- restore_cache:
10-
keys:
11-
- dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
10+
key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/cra-react-router/package.json" }}-{{ checksum "examples/users-api/package-lock.json" }}
1211
- run: npm ci
12+
- run: npm run install:examples
1313
- save_cache:
14-
key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}
14+
key: dependencies-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "examples/cra-react-router/package.json" }}-{{ checksum "examples/users-api/package-lock.json" }}
1515
paths:
1616
- ~/.npm
1717
- ~/.cache
1818
- run: npm run build
1919
- run: npm test
20+
# TODO: remove when facebook/create-react-app#8845 is shipped
21+
- run: sed -i '/process.env.CI/ s/isInteractive [|]*//' examples/cra-react-router/node_modules/react-scripts/scripts/start.js
22+
- run: npm run test:integration
2023
- run: npm run codecov
2124
- store_test_results:
2225
path: test-results

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module.exports = {
2020
rules: {
2121
'@typescript-eslint/camelcase': 'off',
2222
},
23+
ignorePatterns: ['examples/**'],
2324
overrides: [
2425
{
2526
files: ['*.js'],

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,6 @@ dist
105105

106106
.idea
107107
test-results
108+
109+
cypress/screenshots
110+
cypress/videos

EXAMPLES.md

+1
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export default withAuthenticationRequired(Profile);
164164
```js
165165
// use-api.js
166166
import { useEffect, useState } from 'react';
167+
import { useAuth0 } from '@auth0/auth0-react';
167168

168169
export const useApi = (url, options = {}) => {
169170
const { getAccessTokenSilently } = useAuth0();

codecov.yml

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
comment: false

cypress.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"baseUrl": "http://localhost:3000",
3+
"chromeWebSecurity": false,
4+
"viewportWidth": 1000,
5+
"viewportHeight": 1000,
6+
"fixturesFolder": false,
7+
"pluginsFile": false,
8+
"supportFile": false
9+
}

cypress/integration/smoke.test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const loginToAuth0 = (): void => {
2+
cy.get('.auth0-lock-input-username .auth0-lock-input')
3+
.clear()
4+
5+
cy.get('.auth0-lock-input-password .auth0-lock-input').clear().type('1234');
6+
cy.get('.auth0-lock-submit').click();
7+
};
8+
9+
describe('Smoke tests', () => {
10+
it('do basic login and show user', () => {
11+
cy.visit('/');
12+
cy.get('#login').should('exist');
13+
cy.get('#login').click();
14+
15+
loginToAuth0();
16+
17+
cy.get('#hello').contains(`Hello, ${Cypress.env('USER_EMAIL')}!`);
18+
cy.get('#logout').click();
19+
cy.get('#login').should('exist');
20+
});
21+
22+
it('should protect a route and return to path after login', () => {
23+
cy.visit('/users');
24+
25+
loginToAuth0();
26+
27+
cy.url().should('include', '/users');
28+
cy.get('#logout').click();
29+
});
30+
31+
it('should access an api', () => {
32+
cy.visit('/users');
33+
34+
loginToAuth0();
35+
36+
cy.get('table').contains('[email protected]');
37+
cy.get('#logout').click();
38+
});
39+
});

cypress/tsconfig.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"strict": true,
4+
"baseUrl": "../node_modules",
5+
"target": "es5",
6+
"lib": ["es5", "dom"],
7+
"types": ["cypress"]
8+
},
9+
"include": ["**/*.ts"]
10+
}

examples/README.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# @auth0/auth0-react Examples
2+
3+
To run the examples:
4+
5+
- Follow the steps to configure an Auth0 Single-Page Application (SPA) in https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0
6+
- Follow the steps to create an API in https://auth0.com/docs/quickstart/spa/react/02-calling-an-api#create-an-api
7+
- Add a permission to your API of `read:users` following the steps in https://auth0.com/docs/dashboard/guides/apis/add-permissions-apis
8+
- Add a `.env` file to `./examples/cra-react-router/.env` With the `domain` and `clientId` of the application and `audience` (your API identifier)
9+
10+
```dotenv
11+
REACT_APP_DOMAIN=your_domain
12+
REACT_APP_CLIENT_ID=your_client_id
13+
REACT_APP_AUDIENCE=your_audience
14+
SKIP_PREFLIGHT_CHECK=true # To workaround issues with nesting create-react-app in another package
15+
```
16+
17+
- Add a `.env` file to `./examples/users-api/.env` With the `domain` and `audience` (your API identifier)
18+
19+
```dotenv
20+
DOMAIN=your_domain
21+
AUDIENCE=your_audience
22+
```
23+
24+
- Start the api and the web application by running the 2 start commands (from the project root)
25+
26+
```bash
27+
$ npm run start:api && npm run start:cra
28+
```

examples/cra-react-router/.env.sample

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SKIP_PREFLIGHT_CHECK=true
2+
REACT_APP_DOMAIN=your-tenant.auth0.com
3+
REACT_APP_CLIENT_ID=yourclientid
4+
REACT_APP_AUDIENCE=https://api.example.com/users
5+
REACT_APP_API_PORT=3001

examples/cra-react-router/.gitignore

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
# misc
15+
.DS_Store
16+
.env.local
17+
.env.development.local
18+
.env.test.local
19+
.env.production.local
20+
21+
npm-debug.log*
22+
yarn-debug.log*
23+
yarn-error.log*
24+
25+
# Intentionally removing package lock because it has problems when using local file resolutions
26+
package-lock.json

examples/cra-react-router/README.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2+
3+
## Available Scripts
4+
5+
In the project directory, you can run:
6+
7+
### `npm start`
8+
9+
Runs the app in the development mode.<br />
10+
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11+
12+
The page will reload if you make edits.<br />
13+
You will also see any lint errors in the console.
14+
15+
### `npm run build`
16+
17+
Builds the app for production to the `build` folder.<br />
18+
It correctly bundles React in production mode and optimizes the build for the best performance.
19+
20+
The build is minified and the filenames include the hashes.<br />
21+
Your app is ready to be deployed!
22+
23+
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
24+
25+
## Learn More
26+
27+
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
28+
29+
To learn React, check out the [React documentation](https://reactjs.org/).
+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "cra-react-router",
3+
"version": "0.1.0",
4+
"private": true,
5+
"dependencies": {
6+
"@auth0/auth0-react": "file:../..",
7+
"@testing-library/jest-dom": "^4.2.4",
8+
"@testing-library/react": "^9.5.0",
9+
"@testing-library/user-event": "^7.2.1",
10+
"@types/history": "^4.7.6",
11+
"@types/jest": "^24.9.1",
12+
"@types/node": "^12.12.43",
13+
"@types/react": "^16.9.35",
14+
"@types/react-dom": "^16.9.8",
15+
"@types/react-router-dom": "^5.1.5",
16+
"history": "^4.10.1",
17+
"react": "file:../../node_modules/react",
18+
"react-dom": "file:../../node_modules/react-dom",
19+
"react-router-dom": "^5.2.0",
20+
"react-scripts": "^3.4.1",
21+
"typescript": "^3.7.5"
22+
},
23+
"scripts": {
24+
"start": "react-scripts start",
25+
"build": "react-scripts build"
26+
},
27+
"eslintConfig": {
28+
"extends": "react-app"
29+
},
30+
"browserslist": {
31+
"production": [
32+
">0.2%",
33+
"not dead",
34+
"not op_mini all"
35+
],
36+
"development": [
37+
"last 1 chrome version",
38+
"last 1 firefox version",
39+
"last 1 safari version"
40+
]
41+
}
42+
}
3.08 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta
8+
name="description"
9+
content="Web site created using create-react-app"
10+
/>
11+
12+
<link
13+
rel="stylesheet"
14+
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
15+
integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
16+
crossorigin="anonymous"
17+
/>
18+
<title>React App</title>
19+
</head>
20+
<body>
21+
<noscript>You need to enable JavaScript to run this app.</noscript>
22+
<div id="root"></div>
23+
</body>
24+
</html>

examples/cra-react-router/src/App.css

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.spinner-border {
2+
top: 50%;
3+
position: fixed;
4+
margin-top: -1rem;
5+
}

examples/cra-react-router/src/App.tsx

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import { useAuth0 } from '@auth0/auth0-react';
3+
import { createBrowserHistory } from 'history';
4+
import { Route, Router, Switch } from 'react-router-dom';
5+
import './App.css';
6+
import { ProtectedRoute } from './ProtectedRoute';
7+
import { Nav } from './Nav';
8+
import { Error } from './Error';
9+
import { Loading } from './Loading';
10+
import { Users } from './Users';
11+
12+
export const history = createBrowserHistory();
13+
14+
function App() {
15+
const { isLoading, error } = useAuth0();
16+
17+
if (isLoading) {
18+
return <Loading />;
19+
}
20+
21+
return (
22+
<Router history={history}>
23+
<Nav />
24+
{error && <Error message={error.message} />}
25+
<Switch>
26+
<Route path="/" exact />
27+
<ProtectedRoute path="/users" component={Users} />
28+
</Switch>
29+
</Router>
30+
);
31+
}
32+
33+
export default App;
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
3+
export function Error({ message }: { message: string }) {
4+
return (
5+
<div className="alert alert-danger" role="alert">
6+
Oops... {message}
7+
</div>
8+
);
9+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
export function Loading() {
4+
return (
5+
<div className="text-center">
6+
<div className="spinner-border" role="status">
7+
<span className="sr-only">Loading...</span>
8+
</div>
9+
</div>
10+
);
11+
}

examples/cra-react-router/src/Nav.tsx

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { useHistory, Link } from 'react-router-dom';
3+
import { useAuth0 } from '@auth0/auth0-react';
4+
5+
export function Nav() {
6+
const { isAuthenticated, user, loginWithRedirect, logout } = useAuth0();
7+
const history = useHistory();
8+
const [pathname, setPathname] = useState(() => history.location.pathname);
9+
10+
useEffect(() => {
11+
return history.listen(({ pathname }) => setPathname(pathname));
12+
}, [history]);
13+
14+
return (
15+
<nav className="navbar navbar-expand-lg navbar-light bg-light">
16+
<span className="navbar-brand">@auth0/auth0-react</span>
17+
<div className="collapse navbar-collapse">
18+
<div className="navbar-nav">
19+
<Link
20+
to="/"
21+
className={`nav-item nav-link${pathname === '/' ? ' active' : ''}`}
22+
>
23+
Home
24+
</Link>
25+
<Link
26+
to="/users"
27+
className={`nav-item nav-link${
28+
pathname === '/users' ? ' active' : ''
29+
}`}
30+
>
31+
Users
32+
</Link>
33+
</div>
34+
</div>
35+
36+
{isAuthenticated ? (
37+
<div>
38+
<span id="hello">Hello, {user.name}!</span>{' '}
39+
<button
40+
className="btn btn-outline-secondary"
41+
id="logout"
42+
onClick={() => logout()}
43+
>
44+
logout
45+
</button>
46+
</div>
47+
) : (
48+
<button
49+
className="btn btn-outline-success"
50+
id="login"
51+
onClick={() => loginWithRedirect()}
52+
>
53+
login
54+
</button>
55+
)}
56+
</nav>
57+
);
58+
}

0 commit comments

Comments
 (0)