Added more tests, attempted to add coverage yaml config for categoriz… (#93)

* Added more tests, attempted to add coverage yaml config for categorization of coverage

* formatting improvements, cyclomatic complexity reduction on testableReduxBackedPromise

* throw Error rather than string, some adjustment to validateActionSet
This commit is contained in:
pdimitratos 2018-03-02 15:33:05 -08:00 коммит произвёл GitHub
Родитель 8504e3e396
Коммит 86437c9f65
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 243 добавлений и 59 удалений

34
.codecov.yml Normal file
Просмотреть файл

@ -0,0 +1,34 @@
coverage:
precision: 2
round: down
range: "70...100"
status:
project:
default: on
patch:
default: on
changes:
default: off
comment:
layout: "header, reach, diff, flags, files, footer"
behavior: default
require_changes: no
require_base: no
require_head: yes
flags:
actions:
paths:
- src/actions/
reducers:
paths:
- src/actions/
services:
paths:
- src/services/
components:
paths:
- src/components/
- src/containers/

Просмотреть файл

@ -1,27 +1,11 @@
import { authenticatedFetch, authenticatedPost, authenticatedPut } from 'services/authenticatedFetch' import { authenticatedFetch, authenticatedPost, authenticatedPut } from 'services/authenticatedFetch'
const needOnActionSet = (prop) => `Need "${prop}" function on actionSet!` export const testableReduxBackedPromise = (localAuthenticatedFetch, localAuthenticatedPost, localAuthenticatedPut) =>
(promiseArgs, actionSet, operation = 'GET') =>
(dispatch) => {
validateActionSet(actionSet)
export const reduxBackedPromise = (promiseArgs, actionSet, operation = 'GET') => (dispatch) => { const promiseGenerator = getPromiseGenerator(localAuthenticatedFetch, localAuthenticatedPost, localAuthenticatedPut)(operation)
if (!actionSet.try) { throw needOnActionSet('try') }
if (!actionSet.succeed) { throw needOnActionSet('succeed') }
if (!actionSet.fail) { throw needOnActionSet('fail') }
let promiseGenerator
switch (operation.toUpperCase()) {
case 'PUT':
promiseGenerator = authenticatedPut
break
case 'POST':
promiseGenerator = authenticatedPost
break
default:
promiseGenerator = authenticatedFetch
break
}
if (!promiseGenerator) {
throw new Error('promiseGenerator not initialized. This should not be possible. Consider rolling back.')
}
dispatch(actionSet.try()) dispatch(actionSet.try())
@ -30,6 +14,44 @@ export const reduxBackedPromise = (promiseArgs, actionSet, operation = 'GET') =>
error => dispatch(actionSet.fail(error))) error => dispatch(actionSet.fail(error)))
} }
export const reduxBackedPromise = testableReduxBackedPromise(authenticatedFetch, authenticatedPost, authenticatedPut)
const needOnActionSet = (prop) => `Need "${prop}" function on actionSet!`
/*
Expect actionSet to have shape:
{
try: () => tryAction,
succeed: (returnedObject) => succeedAction,
fail: (failureReason) => failureAction
}
actions can be Thunk actions ((dispatch) => action) or plain action objects
*/
export const validateActionSet = (actionSet) => {
['try', 'succeed', 'fail'].map(
a => {
if (!actionSet[a] || typeof actionSet[a] !== 'function') {
throw new Error(needOnActionSet(a))
}
}
)
}
export const getPromiseGenerator = (localAuthenticatedFetch, localAuthenticatedPost, localAuthenticatedPut) =>
(operation) => {
switch (operation.toUpperCase()) {
case 'TESTERROR':
break // Never intended to happen in a deployed instance, just here for testing
case 'PUT':
return localAuthenticatedPut
case 'POST':
return localAuthenticatedPost
default:
return localAuthenticatedFetch
}
throw new Error('promiseGenerator not initialized. This should not be possible. Consider rolling back.')
}
const goToPageActionType = (BASE_NAME) => 'GOTO_' + BASE_NAME + '_PAGE' const goToPageActionType = (BASE_NAME) => 'GOTO_' + BASE_NAME + '_PAGE'
const nextPageActionType = (BASE_NAME) => 'NEXT_' + BASE_NAME + '_PAGE' const nextPageActionType = (BASE_NAME) => 'NEXT_' + BASE_NAME + '_PAGE'
const prevPageActionType = (BASE_NAME) => 'PREV_' + BASE_NAME + '_PAGE' const prevPageActionType = (BASE_NAME) => 'PREV_' + BASE_NAME + '_PAGE'

Просмотреть файл

@ -3,8 +3,8 @@ These actions have no associated reducer, but they provide additional context fo
without changing state directly. They're intended to be used with Redux dev tools. without changing state directly. They're intended to be used with Redux dev tools.
*/ */
const RAW_HTTP_RESPONSE = 'DEBUG_RAW_HTTP_RESPONSE' export const RAW_HTTP_RESPONSE = 'DEBUG_RAW_HTTP_RESPONSE'
const JSON_RESULT = 'DEBUG_JSON_RESULT' export const JSON_RESULT = 'DEBUG_JSON_RESULT'
// response is not serializable // response is not serializable
export const rawHttpResponse = (response) => ({ export const rawHttpResponse = (response) => ({

Просмотреть файл

@ -1,6 +1,8 @@
'use strict' 'use strict'
import { expect } from 'chai' import { expect, assert } from 'chai'
import * as actionHelpers from 'actions/actionHelpers' import * as actionHelpers from 'actions/actionHelpers'
import { GetMockDispatch, GetDispatchRecorder } from 'test/helpers/mockDispatch'
describe('ActionHelpers', function () { describe('ActionHelpers', function () {
describe('paginationActions', function () { describe('paginationActions', function () {
@ -16,4 +18,46 @@ describe('ActionHelpers', function () {
expect(result.types.FILTER).to.equal('FILTER_TEST') expect(result.types.FILTER).to.equal('FILTER_TEST')
}) })
}) })
describe('validateActionSet', function () {
it('Should throw "Need "try" function on actionSet!" when no try or try is not a function', function () {
assert.throws(
() => actionHelpers.validateActionSet({}),
'Need "try" function on actionSet'
)
assert.throws(
() => actionHelpers.validateActionSet({ try: true }),
'Need "try" function on actionSet'
)
})
it('Should throw "Need "succeed" function on actionSet!" when no succeed or succeed is not a function', function () {
assert.throws(
() => actionHelpers.validateActionSet({ try: () => null }),
'Need "succeed" function on actionSet'
)
assert.throws(
() => actionHelpers.validateActionSet({ try: () => null, succeed: true }),
'Need "succeed" function on actionSet'
)
})
it('Should throw "Need "fail" function on actionSet!" when no fail or fail is not a function', function () {
assert.throws(
() => actionHelpers.validateActionSet({ try: () => null, succeed: () => null }),
'Need "fail" function on actionSet'
)
assert.throws(
() => actionHelpers.validateActionSet({ try: () => null, succeed: () => null, fail: true }),
'Need "fail" function on actionSet'
)
})
})
describe('testableReduxBackedPromise', function () {
const mockSuccess = {
json: 'successJson',
response: 'successResponse'
}
const mockFailure = 'failureError'
const successDummy = Promise.resolve(mockSuccess)
})
}) })

Просмотреть файл

@ -0,0 +1,20 @@
'use strict'
import { expect } from 'chai'
import * as expandSectionActions from 'actions/expandSectionActions'
describe('Expand Section Actions', function () {
describe('toggleCollapse', function () {
context('when passed an elementName, the returned object', function () {
const expectedElementName = 'testElement'
const result = expandSectionActions.toggleCollapse(expectedElementName)
it('Should have type TOGGLE_COLLAPSE', function (){
expect(result.type).to.equal(expandSectionActions.TOGGLE_COLLAPSE)
})
it('Should have property elementName with value equal to the passed in elementName', function (){
expect(result.elementName == expectedElementName).to.be.true
})
})
})
})

Просмотреть файл

@ -2,7 +2,6 @@
import { expect } from 'chai' import { expect } from 'chai'
import queryString from 'query-string' import queryString from 'query-string'
import * as filterActions from 'actions/filterActions' import * as filterActions from 'actions/filterActions'
import AddMockDispatch from 'test/helpers/mockDispatch'
// Istanbul for test coverage, provides us with a new command nyc-mocha, which runs the tests and gives a reporter // Istanbul for test coverage, provides us with a new command nyc-mocha, which runs the tests and gives a reporter
describe('FilterActions', function () { describe('FilterActions', function () {

Просмотреть файл

@ -0,0 +1,69 @@
'use strict'
import { expect } from 'chai'
import * as debugActions from 'actions/debugActions'
describe('Debug Actions', function () {
describe('rawHttpResponse', function() {
const mockResponse = {
bodyUsed: 'expectedBodyUsed',
ok: 'expectedOk',
redirected: 'expectedRedirected',
status: 'expectedStatus',
statusText: 'expectedStatusText',
type: 'expectedResponseType',
url: 'expectedUrl',
mockUnusedField: '!!!expect undefined, not this!!!'
}
const mockResult = debugActions.rawHttpResponse(mockResponse)
const nullResult = debugActions.rawHttpResponse(null)
const undefinedResult = debugActions.rawHttpResponse(undefined)
context('When passed a null or undefined response object', function () {
it('Should return an object with a null response', function () {
expect(nullResult.response).to.be.null
expect(undefinedResult.response).to.be.null
})
})
context('When passed a valid response object', function () {
it('Should copy a specific subset of the object property values to the result.response object', function () {
expect(mockResult.response.bodyUsed).to.equal(mockResponse.bodyUsed)
expect(mockResult.response.ok).to.equal(mockResponse.ok)
expect(mockResult.response.redirected).to.equal(mockResponse.redirected)
expect(mockResult.response.status).to.equal(mockResponse.status)
expect(mockResult.response.statusText).to.equal(mockResponse.statusText)
expect(mockResult.response.type).to.equal(mockResponse.type)
expect(mockResult.response.url).to.equal(mockResponse.url)
expect(mockResult.response.mockUnusedField).to.be.undefined
})
it('Should not return the passed in object as its response property', function () {
expect(mockResult.response == mockResponse).to.be.false
})
})
context('Whenever the function is called', function () {
it('Should return an object with a type property equal to RAW_HTTP_RESPONSE', function () {
expect(nullResult.type).to.equal(debugActions.RAW_HTTP_RESPONSE)
expect(undefinedResult.type).to.equal(debugActions.RAW_HTTP_RESPONSE)
expect(mockResult.type).to.equal(debugActions.RAW_HTTP_RESPONSE)
})
})
})
describe('jsonResult', function () {
const mock = {}
const result = debugActions.jsonResult(mock)
context('The returned object', function () {
it('Should have type debugActions.JSON_RESULT', function () {
expect(result.type).to.equal(debugActions.JSON_RESULT)
})
it('Should have json with value the same as the passed in object', function () {
expect(result.json == mock).to.be.true
})
})
})
})

Просмотреть файл

@ -2,7 +2,7 @@
import { expect } from 'chai' import { expect } from 'chai'
import createComponent from 'test/helpers/shallowRenderHelper' import createComponent from 'test/helpers/shallowRenderHelper'
import { IncidentRedirect, mapStateToProps, IncidentRedirectComponentDidMount } from 'components/Incident/IncidentRedirect' import { IncidentRedirect, mapStateToProps, IncidentRedirectComponentDidMount } from 'components/Incident/IncidentRedirect'
import AddMockDispatch from 'test/helpers/mockDispatch' import { GetMockDispatch, GetDispatchRecorder } from 'test/helpers/mockDispatch'
import { Redirect } from 'react-router' import { Redirect } from 'react-router'
const setup = (props, children) => createComponent(IncidentRedirect, props, children) const setup = (props, children) => createComponent(IncidentRedirect, props, children)
@ -11,16 +11,15 @@ describe('Incident Redirect', function () {
describe('Component', function () { describe('Component', function () {
describe('ComponentDidMount', function () { describe('ComponentDidMount', function () {
it('Should attempt to fetch incident by incident id if ticketid is not known', function () { it('Should attempt to fetch incident by incident id if ticketid is not known', function () {
let mockDispatchRecorder = { let mockDispatchRecorder = GetDispatchRecorder()
action: null
}
const testProps = AddMockDispatch({ const testProps = {
ticketId: null, ticketId: null,
incidentId: 2 incidentId: 2
})(mockDispatchRecorder) }
const mockDispatch = GetMockDispatch(mockDispatchRecorder)
IncidentRedirectComponentDidMount(testProps) IncidentRedirectComponentDidMount({...testProps, dispatch: mockDispatch})
expect(mockDispatchRecorder.action.type).to.equal('REQUEST_INCIDENT') expect(mockDispatchRecorder.action.type).to.equal('REQUEST_INCIDENT')
}) })
@ -30,12 +29,14 @@ describe('Incident Redirect', function () {
action: null action: null
} }
const testProps = AddMockDispatch({ const testProps = {
ticketId: 1, ticketId: 1,
incidentId: 2 incidentId: 2
})(mockDispatchRecorder) }
IncidentRedirectComponentDidMount(testProps) const mockDispatch = GetMockDispatch(mockDispatchRecorder)
IncidentRedirectComponentDidMount({...testProps, dispatch: mockDispatch})
expect(mockDispatchRecorder.action).to.be.null expect(mockDispatchRecorder.action).to.be.null
}) })

Просмотреть файл

@ -9,7 +9,6 @@ import AutoComplete from 'material-ui/AutoComplete'
import createComponent from 'test/helpers/shallowRenderHelper' import createComponent from 'test/helpers/shallowRenderHelper'
import AutoCompleteMenu, { onNewRequest } from 'components/elements/AutoCompleteMenu' import AutoCompleteMenu, { onNewRequest } from 'components/elements/AutoCompleteMenu'
import AddMockDispatch from 'test/helpers/mockDispatch'
const mockProps = { const mockProps = {

Просмотреть файл

@ -4,22 +4,20 @@ import React from 'react'
import createComponent from 'test/helpers/shallowRenderHelper' import createComponent from 'test/helpers/shallowRenderHelper'
import { DisplayRetryButton } from 'components/elements/Buttons' import { DisplayRetryButton } from 'components/elements/Buttons'
import FlatButtonStyled from 'components/elements/FlatButtonStyled' import FlatButtonStyled from 'components/elements/FlatButtonStyled'
import AddMockDispatch from 'test/helpers/mockDispatch' import { GetMockDispatch, GetDispatchRecorder } from 'test/helpers/mockDispatch'
const setup = (props, children) => createComponent(DisplayRetryButton, props, children) const setup = (props, children) => createComponent(DisplayRetryButton, props, children)
const dummyState = AddMockDispatch({ const dummyState = {
actionForRetry: { actionForRetry: {
type: 'ActionForRetry' type: 'ActionForRetry'
} }
}) }
describe('DisplayRetryButton', function () { describe('DisplayRetryButton', function () {
beforeEach(() => { beforeEach(() => {
this.mockDispatchRecorder = { this.mockDispatchRecorder = GetDispatchRecorder()
action: null this.singleState = setup({ ...dummyState, dispatch: GetMockDispatch(this.mockDispatchRecorder)}, null)
}
this.singleState = setup(dummyState(this.mockDispatchRecorder), null)
}) })
it('Should render a FlatButtonStyled with a Retry label', () => { it('Should render a FlatButtonStyled with a Retry label', () => {

Просмотреть файл

@ -4,7 +4,6 @@ import React from 'react'
import Chip from 'material-ui/Chip' import Chip from 'material-ui/Chip'
import createComponent from 'test/helpers/shallowRenderHelper' import createComponent from 'test/helpers/shallowRenderHelper'
import { FilterChips, mapStateToProps, renderChip, hydrateChip } from 'components/elements/FilterChips' import { FilterChips, mapStateToProps, renderChip, hydrateChip } from 'components/elements/FilterChips'
import AddMockDispatch from 'test/helpers/mockDispatch'
describe('FilterChips', function () { describe('FilterChips', function () {
describe('mapStateToProps', function () { describe('mapStateToProps', function () {

Просмотреть файл

@ -1,15 +1,15 @@
import { isObject } from 'util' import { isObject } from 'util'
export const AddMockDispatch = (state) => (mockDispatchRecorder) => { /*
/* Action could be:
Action could be: an object
an object a function that takes dispatch as an argument (thunk middleware action)
a function that takes dispatch as an argument (thunk middleware action) If the action is a function, call it passing in dispatch.
If the action is a function, call it passing in dispatch. If the action is an object record it; a single action sets the .action property,
If the action is an object record it; a single action sets the .action property, a second action turns .action into an array.
a second action turns .action into an array. Subsequent actions are added to the array.
Subsequent actions are added to the array. */
*/ export const GetMockDispatch = (mockDispatchRecorder) => {
const dispatch = (action) => isObject(action) const dispatch = (action) => isObject(action)
? mockDispatchRecorder.action ? mockDispatchRecorder.action
? Array.isArray(mockDispatchRecorder.action) ? Array.isArray(mockDispatchRecorder.action)
@ -17,10 +17,9 @@ export const AddMockDispatch = (state) => (mockDispatchRecorder) => {
: mockDispatchRecorder.action = [mockDispatchRecorder.action, action] : mockDispatchRecorder.action = [mockDispatchRecorder.action, action]
: mockDispatchRecorder.action = action : mockDispatchRecorder.action = action
: action(dispatch) : action(dispatch)
return { return dispatch
...state,
dispatch
}
} }
export default AddMockDispatch export const GetDispatchRecorder = () => ({ action: null })
export default GetMockDispatch