1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Added with-jest-typescript example (#4124)

* Added with-jest-typescript example with files and readme

* Updated package.json

As per the comment, updated the package.json so the info is same as in other examples.

* Proper name in package.json
This commit is contained in:
Harun Đulić 2018-04-13 16:53:59 +02:00 committed by Tim Neutkens
parent f6949349c1
commit c5c5564dbf
19 changed files with 490 additions and 0 deletions

View file

@ -0,0 +1,44 @@
# Example app with Jest tests inside a NextJS TypeScript app
## How to use
### Using `create-next-app`
Execute [`create-next-app`](https://github.com/segmentio/create-next-app) with [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) or [npx](https://github.com/zkat/npx#readme) to bootstrap the example:
```bash
npx create-next-app --example with-jest-typescript with-jest-typescript-app
# or
yarn create next-app --example with-jest-typescript with-jest-typescript-app
```
### Download manually
Download the example [or clone the repo](https://github.com/zeit/next.js):
```bash
curl https://codeload.github.com/zeit/next.js/tar.gz/canary | tar -xz --strip=2 next.js-canary/examples/with-jest-typescript
cd with-jest-typescript
```
Install it and run:
```bash
npm install
npm run dev
# or
yarn
yarn dev
```
## Run Jest tests
```bash
npm run test
# or
yarn test
```
## The idea behind the example
This example shows a configuration and several examples for a running Jest tests in a NextJS TypeScript app

View file

@ -0,0 +1,21 @@
const TEST_REGEX = '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|js?|tsx?|ts?)$'
module.exports = {
setupFiles: ['<rootDir>/jest.setup.js'],
globals: {
'ts-jest': {
'useBabelrc': true
}
},
testRegex: TEST_REGEX,
transform: {
'^.+\\.tsx?$': 'ts-jest'
},
testPathIgnorePatterns: [
'<rootDir>/.next/', '<rootDir>/node_modules/'
],
moduleFileExtensions: [
'ts', 'tsx', 'js', 'jsx'
],
collectCoverage: true
}

View file

@ -0,0 +1,4 @@
const Enzyme = require('enzyme')
const Adapter = require('enzyme-adapter-react-16')
Enzyme.configure({adapter: new Adapter()})

View file

@ -0,0 +1,2 @@
const withTypescript = require('@zeit/next-typescript')
module.exports = withTypescript()

View file

@ -0,0 +1,29 @@
{
"name": "with-jest-typescript",
"version": "1.0.0",
"scripts": {
"test": "NODE_ENV=test jest",
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^5.0.0",
"react": "^16.2.0",
"react-dom": "^16.2.0"
},
"devDependencies": {
"@types/jest": "^22.2.2",
"@types/next": "^2.4.8",
"@types/react": "^16.0.41",
"@types/react-dom": "^16.0.4",
"@zeit/next-typescript": "^0.0.11",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"jest": "^22.4.3",
"react-addons-test-utils": "^15.6.2",
"react-test-renderer": "^16.2.0",
"ts-jest": "^22.4.2",
"typescript": "^2.7.2"
}
}

View file

@ -0,0 +1,5 @@
import CarsOverview from './../src/modules/cars/Overview';
const CarsPage = () => <CarsOverview/>;
export default CarsPage;

View file

@ -0,0 +1,3 @@
const IndexPage = () => <h1>Testing Next.js App written in TypeScript with Jest</h1>;
export default IndexPage;

View file

@ -0,0 +1,5 @@
import Login from './../src/modules/auth/Login';
const LoginPage = () => <Login/>;
export default LoginPage;

View file

@ -0,0 +1,20 @@
import * as React from 'react';
interface NiceCheckboxProps {
rootID : string;
id : string;
name : string;
value : string;
label : string;
}
const NiceCheckbox : React.SFC < NiceCheckboxProps > = (props : NiceCheckboxProps) : JSX.Element => {
return (
<div className="NiceCheckbox" id={props.rootID}>
<input type="checkbox" id={props.id} name={props.name} value={props.value}/>
<label htmlFor={props.id}>{props.label}</label>
</div>
);
};
export default NiceCheckbox;

View file

@ -0,0 +1,17 @@
/* eslint-env jest */
import React from 'react';
import {shallow} from 'enzyme';
import NiceCheckbox from './index';
describe('NiceCheckbox', () => {
it('renders the checkbox with correct label', () => {
const wrapper = shallow(<NiceCheckbox
rootID="NiceCarCheckbox"
id="CarAvailability"
name="is_available"
value="yes"
label="Is this car available?"/>);
expect(wrapper.find('#NiceCarCheckbox').find('label').text()).toEqual('Is this car available?');
});
});

View file

@ -0,0 +1,70 @@
import React, {ChangeEvent} from 'react';
import Router from 'next/router';
import * as T from './types';
export interface LoginProps {}
export interface LoginState {
credentials : T.LoginCredentials;
isLoginLoading : boolean;
}
export default class Login extends React.Component < LoginProps,
LoginState > {
constructor(props : LoginProps) {
super(props);
this.state = {
isLoginLoading: false,
credentials: {
email: null,
password: null
}
}
}
handleCredentialsChange = (e : ChangeEvent < HTMLInputElement >) => {
let {credentials} = this.state;
credentials[e.target.name] = e.target.value;
this.setState({credentials});
}
handleLoginSubmit = (e : React.MouseEvent < HTMLElement >) => {
e.preventDefault();
this.setState({isLoginLoading: true});
setTimeout(() => {
this.setState({isLoginLoading: false});
Router.replace('/cars');
}, 500);
}
render() {
const {credentials} = this.state;
return (
<div>
<h1>Login</h1>
<form>
<input
id="formEmail"
name="email"
type="text"
value={credentials.email}
onChange={this.handleCredentialsChange}/>
<input
name="password"
type="password"
value={credentials.password}
onChange={this.handleCredentialsChange}/>
<button id="loginSubmit" onClick={this.handleLoginSubmit}>{this.state.isLoginLoading
? 'Logging in...'
: 'Log in'}</button>
</form>
</div>
);
}
}

View file

@ -0,0 +1,38 @@
/* eslint-env jest */
import React from 'react';
import {shallow} from 'enzyme';
import Login from './../Login';
describe('Login', () => {
it('renders the h1 title', () => {
const login = shallow(<Login/>);
expect(login.find('h1').text()).toEqual('Login');
});
it('renders the form', () => {
const login = shallow(<Login/>);
expect(login.find('form')).toHaveLength(1);
});
it('changes the text of email', () => {
const login = shallow(<Login/>);
login
.find('#formEmail')
.simulate('change', {
target: {
name: 'email',
value: 'some@test.com'
}
});
expect(login.update().find('#formEmail').props().value).toEqual('some@test.com');
});
it('changes the text of login button after clicking it', () => {
const login = shallow(<Login/>);
login
.find('#loginSubmit')
.simulate('click', {preventDefault() {}});
expect(login.update().find('#loginSubmit').text()).toEqual('Logging in...');
});
});

View file

@ -0,0 +1,4 @@
export interface LoginCredentials {
email: string;
password: string;
}

View file

@ -0,0 +1,25 @@
import * as React from 'react';
import * as T from './types';
interface DetailProps {
car : T.Car;
}
const Detail : React.SFC < DetailProps > = ({car} : DetailProps) => {
return (
<div className="CarDetail">
<h1>{`${car.make} ${car.model}`}</h1>
<p>Engine : {car.engine}</p>
<p>Year : {car.year}</p>
<p>Mileage : {car.mileage}</p>
<p>Equipment :
</p>
<ul>{car.equipment && car
.equipment
.map((e : string, index : number) => <li key={index}>{e}</li>)}</ul>
</div>
);
};
export default Detail;

View file

@ -0,0 +1,65 @@
import * as React from 'react';
import * as T from './types';
export interface CarsOverviewProps {
cars : T.CarList;
}
export interface CarsOverviewState {
selectedCar : T.Car;
}
export default class CarsOverview extends React.Component < CarsOverviewProps,
CarsOverviewState > {
constructor(props : CarsOverviewProps) {
super(props);
this.state = {
selectedCar: null
}
}
handleSelectCar = (car : T.Car) : void => {
this.setState({selectedCar: car});
}
renderCarsList = (cars : T.CarList) : JSX.Element => {
if (!cars || cars.length === 0) {
return (
<p>No cars</p>
);
}
return (
<ul>{cars.map((car : T.Car, index : number) : JSX.Element => <li key={index} onClick={() => this.handleSelectCar(car)}>{car.make} {car.model}</li>)}</ul>
);
}
renderCarInfo = (car : T.Car) : JSX.Element => {
if (!car) {
return null;
}
return (
<div className="CarInfo">
<h2>{`${car.make} ${car.model}`}</h2>
<section>{car.engine}</section>
</div>
);
}
render() {
return (
<div>
<h1>Cars Overview</h1>
<div className="Cars__List">
{this.renderCarsList(this.props.cars)}
</div>
{this.renderCarInfo(this.state.selectedCar)}
</div>
);
}
}

View file

@ -0,0 +1,31 @@
/* eslint-env jest */
import React from 'react';
import {shallow} from 'enzyme';
import Detail from './../Detail';
const __CAR__ = {
make: 'Volvo',
model: 'XC60',
engine: 'T5',
year: 2018,
mileage: 123,
equipment: ['Leather', 'Seat heating', 'City Safety']
};
describe('Car detail', () => {
it('renders correct title', () => {
const detail = shallow(<Detail car={__CAR__}/>);
expect(detail.find('h1').text()).toEqual('Volvo XC60');
});
it('renders correct list item', () => {
const detail = shallow(<Detail car={__CAR__}/>);
expect(detail.childAt(1).text()).toEqual('Engine : T5');
});
it('renders correct equipment list item', () => {
const detail = shallow(<Detail car={__CAR__}/>);
expect(detail.children().find('ul').childAt(1).text()).toEqual('Seat heating');
});
});

View file

@ -0,0 +1,64 @@
/* eslint-env jest */
import React from 'react';
import {shallow} from 'enzyme';
import Overview from './../Overview';
const __CARS__ = [
{
make: 'Volvo',
model: 'C30',
engine: 'T5',
year: 2018,
mileage: 123,
equipment: ['Leather', 'Seat heating', 'City Safety']
}, {
make: 'Volvo',
model: 'XC60',
engine: 'D5',
year: 2018,
mileage: 456,
equipment: ['Leather', 'Seat heating', 'City Safety']
}, {
make: 'Volvo',
model: 'XC90',
engine: 'T6',
year: 2018,
mileage: 789,
equipment: ['Leather', 'Seat heating', 'City Safety']
}
];
describe('Cars overview', () => {
it('renders the h1 title', () => {
const overview = shallow(<Overview cars={[]}/>);
expect(overview.find('h1').text()).toEqual('Cars Overview');
});
it('renders empty cars list when no cars are provided', () => {
const overview = shallow(<Overview cars={[]}/>);
expect(overview.find('.Cars__List').children().find('p').text()).toEqual('No cars');
});
it('renders cars list with 3 items when 3 cars are provided', () => {
const overview = shallow(<Overview cars={__CARS__}/>);
expect(overview.find('.Cars__List').children().find('ul').children()).toHaveLength(3);
});
it('renders cars list with the expected item on third place', () => {
const overview = shallow(<Overview cars={__CARS__}/>);
expect(overview.find('.Cars__List').children().find('ul').childAt(2).text()).toEqual('Volvo XC90');
});
it('renders car detail after clicking on an item in cars list', () => {
const overview = shallow(<Overview cars={__CARS__}/>);
overview
.find('.Cars__List')
.children()
.find('ul')
.childAt(1)
.simulate('click', {preventDefault() {}});
expect(overview.update().find('.CarInfo').find('h2').text()).toEqual('Volvo XC60');
});
});

View file

@ -0,0 +1,10 @@
export interface Car {
make : string;
model : string;
engine : string;
year : number;
mileage : number;
equipment : string[];
}
export type CarList = Array < Car >;

View file

@ -0,0 +1,33 @@
{
"compileOnSave": false,
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"jsx": "preserve",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": false,
"preserveConstEnums": true,
"sourceMap": true,
"skipLibCheck": true,
"baseUrl": ".",
"typeRoots": [
"./node_modules/@types"
],
"lib": [
"dom",
"es2015",
"es2016"
]
},
"exclude": [
"node_modules",
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.test.ts",
"**/*.test.tsx",
]
}