Add full test for LoginForm
This commit is contained in:
parent
bdf0aa57be
commit
5c847b94e6
@ -1,4 +1,3 @@
|
|||||||
export * from './Button'
|
export * from './Button'
|
||||||
// export * from './Component'
|
// export * from './Component'
|
||||||
export * from './Input'
|
export * from './Input'
|
||||||
export * from './LoginForm'
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
||||||
@ -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 = []
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -4,7 +4,8 @@ import {ICredentials} from '@rondo/common'
|
|||||||
|
|
||||||
export interface ILoginFormProps {
|
export interface ILoginFormProps {
|
||||||
error?: string
|
error?: string
|
||||||
onSubmit: (credentials: ICredentials) => void
|
onSubmit: (credentials: ICredentials) => Promise<void>
|
||||||
|
onSuccess: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ILoginFormState extends ICredentials {}
|
export interface ILoginFormState extends ICredentials {}
|
||||||
@ -21,8 +22,9 @@ export class LoginForm extends React.PureComponent<
|
|||||||
password: '',
|
password: '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleSubmit = () => {
|
handleSubmit = async () => {
|
||||||
this.props.onSubmit(this.state)
|
await this.props.onSubmit(this.state)
|
||||||
|
this.props.onSuccess()
|
||||||
}
|
}
|
||||||
handleChange = (name: string, value: string) => {
|
handleChange = (name: string, value: string) => {
|
||||||
this.setState(
|
this.setState(
|
||||||
@ -32,6 +34,7 @@ export class LoginForm extends React.PureComponent<
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.handleSubmit}>
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<p className='error'>{this.props.error}</p>
|
||||||
<Input
|
<Input
|
||||||
name='username'
|
name='username'
|
||||||
type='text'
|
type='text'
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import * as Feature from './'
|
import * as Feature from './'
|
||||||
// import React from 'react'
|
import {HTTPClientMock, TestUtils} from '../test-utils'
|
||||||
import {TestUtils} from '../test-utils'
|
import {IAPIDef} from '@rondo/common'
|
||||||
|
import T from 'react-dom/test-utils'
|
||||||
|
|
||||||
const test = new TestUtils()
|
const test = new TestUtils()
|
||||||
|
|
||||||
describe('Login', () => {
|
describe('Login', () => {
|
||||||
|
|
||||||
const loginActions = new Feature.LoginActions({} as any)
|
const http = new HTTPClientMock<IAPIDef>()
|
||||||
|
const loginActions = new Feature.LoginActions(http)
|
||||||
|
|
||||||
const t = test.withProvider({
|
const t = test.withProvider({
|
||||||
reducers: {Login: Feature.Login},
|
reducers: {Login: Feature.Login},
|
||||||
@ -19,4 +21,40 @@ describe('Login', () => {
|
|||||||
t.render()
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
44
packages/client/src/test-utils/HTTPClientMock.test.ts
Normal file
44
packages/client/src/test-utils/HTTPClientMock.test.ts
Normal 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',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
82
packages/client/src/test-utils/HTTPClientMock.ts
Normal file
82
packages/client/src/test-utils/HTTPClientMock.ts
Normal 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}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -33,7 +33,7 @@ interface IRenderParams<State> {
|
|||||||
export class TestUtils {
|
export class TestUtils {
|
||||||
render(jsx: JSX.Element) {
|
render(jsx: JSX.Element) {
|
||||||
const component = T.renderIntoDocument(jsx) as React.Component<any>
|
const component = T.renderIntoDocument(jsx) as React.Component<any>
|
||||||
const node = ReactDOM.findDOMNode(component)
|
const node = ReactDOM.findDOMNode(component) as Element
|
||||||
return {component, node}
|
return {component, node}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +64,10 @@ export class TestUtils {
|
|||||||
})
|
})
|
||||||
const Component = params.connector.connect(params.select)
|
const Component = params.connector.connect(params.select)
|
||||||
|
|
||||||
const render = () => {
|
const render = (additionalProps: {[key: string]: any} = {}) => {
|
||||||
return this.render(
|
return this.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Component />
|
<Component {...additionalProps} />
|
||||||
</Provider>,
|
</Provider>,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
|
export * from './HTTPClientMock'
|
||||||
export * from './TestUtils'
|
export * from './TestUtils'
|
||||||
export * from './getError'
|
export * from './getError'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user