Add full test for LoginForm

This commit is contained in:
Jerko Steiner 2019-01-20 21:39:32 +01:00
parent bdf0aa57be
commit 5c847b94e6
9 changed files with 177 additions and 80 deletions

View File

@ -1,4 +1,3 @@
export * from './Button'
// export * from './Component'
export * from './Input'
export * from './LoginForm'

View File

@ -1,26 +0,0 @@
import {HTTPClientMock} from './HTTPClientMock'
import {getError} from '../test-utils'
describe('HTTPClientMock', () => {
const http = new HTTPClientMock<any>()
describe('mockAdd and mockClear', () => {
it('adds a mock', async () => {
const value = {a: 1}
http.mockAdd({
method: 'get',
url: '/test',
}, value)
const result = await http.get('/test')
expect(result).toBe(value)
http.mockClear()
const error = await getError(http.get('/test'))
expect(error.message).toMatch(/mock/i)
})
})
})

View File

@ -1,44 +0,0 @@
import {HTTPClient} from './HTTPClient'
import {IRoutes} from '@rondo/common'
import {IRequest} from './IRequest'
import {IResponse} from './IResponse'
export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
mocks: {[key: string]: any} = {}
constructor() {
super()
}
createRequestor() {
return {
request: (r: IRequest): Promise<IResponse> => {
return new Promise((resolve, reject) => {
const key = this.serialize(r)
if (!this.mocks.hasOwnProperty(key)) {
setImmediate(() => {
reject(new Error('No mock for request: ' + key))
})
return
}
setImmediate(() => {
resolve({data: this.mocks[key]})
})
})
},
}
}
serialize(r: IRequest) {
return JSON.stringify(r, null, ' ')
}
mockAdd(r: IRequest, response: any) {
this.mocks[this.serialize(r)] = response
}
mockClear() {
this.mocks = []
}
}

View File

@ -4,7 +4,8 @@ import {ICredentials} from '@rondo/common'
export interface ILoginFormProps {
error?: string
onSubmit: (credentials: ICredentials) => void
onSubmit: (credentials: ICredentials) => Promise<void>
onSuccess: () => void
}
export interface ILoginFormState extends ICredentials {}
@ -21,8 +22,9 @@ export class LoginForm extends React.PureComponent<
password: '',
}
}
handleSubmit = () => {
this.props.onSubmit(this.state)
handleSubmit = async () => {
await this.props.onSubmit(this.state)
this.props.onSuccess()
}
handleChange = (name: string, value: string) => {
this.setState(
@ -32,6 +34,7 @@ export class LoginForm extends React.PureComponent<
render() {
return (
<form onSubmit={this.handleSubmit}>
<p className='error'>{this.props.error}</p>
<Input
name='username'
type='text'

View File

@ -1,12 +1,14 @@
import * as Feature from './'
// import React from 'react'
import {TestUtils} from '../test-utils'
import {HTTPClientMock, TestUtils} from '../test-utils'
import {IAPIDef} from '@rondo/common'
import T from 'react-dom/test-utils'
const test = new TestUtils()
describe('Login', () => {
const loginActions = new Feature.LoginActions({} as any)
const http = new HTTPClientMock<IAPIDef>()
const loginActions = new Feature.LoginActions(http)
const t = test.withProvider({
reducers: {Login: Feature.Login},
@ -19,4 +21,40 @@ describe('Login', () => {
t.render()
})
describe('submit', () => {
const data = {username: 'user', password: 'pass'}
const onSuccess = jest.fn()
let node: Element
beforeEach(() => {
http.mockAdd({
method: 'post',
url: '/auth/login',
data,
}, { id: 123 })
node = t.render({onSuccess}).node
T.Simulate.change(
node.querySelector('input[name="username"]')!,
{target: {value: 'user'}} as any,
)
T.Simulate.change(
node.querySelector('input[name="password"]')!,
{target: {value: 'pass'}} as any,
)
})
it('should submit a form', async () => {
T.Simulate.submit(node)
const {req} = await http.wait()
expect(req).toEqual({
method: 'post',
url: '/auth/login',
data,
})
expect(onSuccess.mock.calls.length).toBe(1)
})
})
})

View File

@ -0,0 +1,44 @@
import {HTTPClientMock} from './HTTPClientMock'
import {getError} from './getError'
describe('HTTPClientMock', () => {
const http = new HTTPClientMock<any>()
const value = {a: 1}
beforeEach(() => {
http.mockClear()
http.mockAdd({
method: 'get',
url: '/test',
}, value)
})
describe('mockAdd and mockClear', () => {
it('adds a mock', async () => {
const result = await http.get('/test')
expect(result).toBe(value)
http.mockClear()
const error = await getError(http.get('/test'))
expect(error.message).toMatch(/mock/i)
})
})
describe('await wait', () => {
it('waits for a request', async () => {
const promise = http.get('/test')
const result1 = await http.wait()
const result2 = await promise
expect(result1.res.data).toBe(result2)
expect(result2).toBe(value)
expect(result1.req).toBe(http.requests[0])
expect(result1.req).toEqual({
method: 'get',
url: '/test',
})
})
})
})

View File

@ -0,0 +1,82 @@
import {HTTPClient} from '../http/HTTPClient'
import {IRoutes} from '@rondo/common'
import {IRequest} from '../http/IRequest'
import {IResponse} from '../http/IResponse'
interface IReqRes {
req: IRequest
res: IResponse
}
export class HTTPClientMock<T extends IRoutes> extends HTTPClient<T> {
mocks: {[key: string]: any} = {}
requests: IRequest[] = []
constructor() {
super()
}
createRequestor() {
return {
request: (req: IRequest): Promise<IResponse> => {
this.requests.push(req)
return new Promise((resolve, reject) => {
const key = this.serialize(req)
if (!this.mocks.hasOwnProperty(key)) {
setImmediate(() => {
const err = new Error('No mock for request: ' + key)
reject(err)
this.notify(err)
})
return
}
const data = this.mocks[key]
const res: IResponse = {data}
setImmediate(() => {
resolve(res)
this.notify({req, res})
})
})
},
}
}
serialize(req: IRequest) {
return JSON.stringify(req, null, ' ')
}
mockAdd(req: IRequest, res: any) {
this.mocks[this.serialize(req)] = res
}
mockClear() {
this.requests = []
this.mocks = {}
}
protected waitPromise?: {
resolve: (r: IReqRes) => void
reject: (err: Error) => void
}
protected notify(r: IReqRes | Error) {
if (!this.waitPromise) {
return
}
const waitPromise = this.waitPromise
this.waitPromise = undefined
if (r instanceof Error) {
waitPromise.reject(r)
return
}
waitPromise.resolve(r)
}
async wait(): Promise<IReqRes> {
expect(this.waitPromise).toBe(undefined)
return new Promise((resolve, reject) => {
this.waitPromise = {resolve, reject}
})
}
}

View File

@ -33,7 +33,7 @@ interface IRenderParams<State> {
export class TestUtils {
render(jsx: JSX.Element) {
const component = T.renderIntoDocument(jsx) as React.Component<any>
const node = ReactDOM.findDOMNode(component)
const node = ReactDOM.findDOMNode(component) as Element
return {component, node}
}
@ -64,10 +64,10 @@ export class TestUtils {
})
const Component = params.connector.connect(params.select)
const render = () => {
const render = (additionalProps: {[key: string]: any} = {}) => {
return this.render(
<Provider store={store}>
<Component />
<Component {...additionalProps} />
</Provider>,
)
}

View File

@ -1,2 +1,3 @@
export * from './HTTPClientMock'
export * from './TestUtils'
export * from './getError'