PB138: Forms, routing, testing
Forms
Basic form
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
React form
class NameForm extends React.Component {
// ...
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
React form
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
//...
}
}
Select
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
}
const handleChange = (event) => this.setState({value: event.target.value});
const handleSubmit(event) = () => {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
File input
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} multiple />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
File input
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} multiple />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
Simple approach to forms
React-hook-form
import * as React from "react";
import { useForm } from "react-hook-form";
import Headers from "./Header";
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = (data) => alert(JSON.stringify(data));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Headers />
<input {...register("firstName")} placeholder="First name" />
<input {...register("lastName")} placeholder="Last name" />
<select {...register("category")}>
<option value="">Select...</option>
<option value="A">Category A</option>
<option value="B">Category B</option>
</select>
<input type="submit" />
</form>
);
}
Controlled components
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Checkbox } from "@material-ui/core";
function App() {
const { handleSubmit, control, reset } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="MyCheckbox"
control={control}
defaultValue={false}
rules={{ required: true }}
render={({ field }) => <Checkbox {...field} />}
/>
<input type="submit" />
</form>
);
}
Validation
import React from "react";
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("firstName", { required: true, maxLength: 20 })} />
<input {...register("lastName", { pattern: /^[A-Za-z]+$/i })} />
<input type="number" {...register("age", { min: 18, max: 99 })} />
<input type="submit" />
</form>
);
}
Using Typescript
type FormData = {
firstName: string;
lastName: string;
};
const { register, setValue, handleSubmit, formState: { errors } }
= useForm<FormData>();
Routing
URL parts
https://react-hook-form.com:80/advanced-usage#WizardFormFunnel
hostname : port
host
using Location API
pathname
hash
https://react-hook-form.com:80/advanced-usage?time=now
search
https://anonymous:flabada@developer.mozilla.org/
username
protocol
searchParams
password
React Router
npm install react-router-dom
Router Wrapper
import React from "react";
import {
BrowserRouter as Router
} from "react-router-dom";
export default function App() {
return (
<Router>
<Layout />
</Router>
);
}
Switch
import React from "react";
import {
Switch,
Route,
Link
} from "react-router-dom";
export default function MyComponent() {
return (
<div>
<Switch>
<Route path="/about">
<About />
</Route>
<Route path="/users">
<Users />
</Route>
<Route path="/">
<Home />
</Route>
</Switch>
</div>
);
}
Link
import React from "react";
import {
Link
} from "react-router-dom";
export default function MainManu() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/users">Users</Link>
</li>
</ul>
</nav>
</div>
);
}
URL Params
<Switch>
<Route path="/:id" children={<Child />} />
</Switch>
function Child() {
let { id } = useParams();
return (
<div>
<h3>ID: {id}</h3>
</div>
);
}
Testing
Test pyramid
Unit tests
Unit tests
Unit tests
Functional Tests
Unit tests
Integration tests
number of tests
time to run
Other tests
- API Tests
- Performance test
- Workflow tests
- Smoke tests
Jest
npm install --save-dev jest
Writing tests
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
test('zero', () => {
const z = 0;
expect(z).not.toBeNull();
expect(z).toBeDefined();
expect(z).not.toBeUndefined();
expect(z).not.toBeTruthy();
expect(z).toBeFalsy();
});
Array tests
const shoppingList = [
'diapers',
'kleenex',
'trash bags',
'paper towels',
'beer',
];
test('the shopping list has beer on it', () => {
expect(shoppingList).toContain('beer');
expect(new Set(shoppingList)).toContain('beer');
});
Exceptions
function compileAndroidCode() {
throw new Error('you are using the wrong JDK');
}
test('compiling android goes as expected', () => {
expect(() => compileAndroidCode()).toThrow();
expect(() => compileAndroidCode()).toThrow(Error);
// You can also use the exact error message or a regexp
expect(() => compileAndroidCode()).toThrow('you are using the wrong JDK');
expect(() => compileAndroidCode()).toThrow(/JDK/);
});
Testing react comp.
// Link.react.test.js
import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';
test('Link changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>,
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseLeave();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
Playwright
npm i -D playwright
Playwright
const { webkit, chromium, firefox } = require('playwright');
(async () => {
const browser = await webkit.launch();
const page = await browser.newPage();
await page.goto('http://whatsmyuseragent.org/');
await page.screenshot({ path: `example.png` });
await browser.close();
})();
Browser context
const { devices } = require('playwright');
const iPhone = devices['iPhone 11 Pro'];
const context = await browser.newContext({
...iPhone,
permissions: ['geolocation'],
geolocation: { latitude: 52.52, longitude: 13.39},
colorScheme: 'dark',
locale: 'de-DE'
});
Mixing with Jest
const { chromium } = require('playwright');
let browser;
let page;
beforeAll(async () => {
browser = await chromium.launch();
});
afterAll(async () => {
await browser.close();
});
beforeEach(async () => {
page = await browser.newPage();
});
afterEach(async () => {
await page.close();
});
it('should work', async () => {
await page.goto('https://www.example.com');
expect(await page.title()).toBe('Example Domain');
});
npm install -D jest jest-playwright-preset playwright
k6
choco install k6
brew install k6
sudo apt-get install k6
Basic perf test
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
vus: 10,
duration: '30s',
};
export default function () {
http.get('http://test.k6.io');
sleep(1);
}
Scaling perf test
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
stages: [
{ duration: '30s', target: 20 },
{ duration: '1m30s', target: 10 },
{ duration: '20s', target: 0 },
],
};
export default function () {
let res = http.get('https://httpbin.org/');
check(res, { 'status was 200': (r) => r.status == 200 });
sleep(1);
And that's it
PB138: Formuláře a routing
By Lukáš Grolig
PB138: Formuláře a routing
- 453