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'
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) => {
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.')
}
const promiseGenerator = getPromiseGenerator(localAuthenticatedFetch, localAuthenticatedPost, localAuthenticatedPut)(operation)
dispatch(actionSet.try())
@ -30,6 +14,44 @@ export const reduxBackedPromise = (promiseArgs, actionSet, operation = 'GET') =>
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 nextPageActionType = (BASE_NAME) => 'NEXT_' + 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.
*/
const RAW_HTTP_RESPONSE = 'DEBUG_RAW_HTTP_RESPONSE'
const JSON_RESULT = 'DEBUG_JSON_RESULT'
export const RAW_HTTP_RESPONSE = 'DEBUG_RAW_HTTP_RESPONSE'
export const JSON_RESULT = 'DEBUG_JSON_RESULT'
// response is not serializable
export const rawHttpResponse = (response) => ({

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

@ -1,6 +1,8 @@
'use strict'
import { expect } from 'chai'
import { expect, assert } from 'chai'
import * as actionHelpers from 'actions/actionHelpers'
import { GetMockDispatch, GetDispatchRecorder } from 'test/helpers/mockDispatch'
describe('ActionHelpers', function () {
describe('paginationActions', function () {
@ -16,4 +18,46 @@ describe('ActionHelpers', function () {
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 queryString from 'query-string'
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
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 createComponent from 'test/helpers/shallowRenderHelper'
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'
const setup = (props, children) => createComponent(IncidentRedirect, props, children)
@ -11,16 +11,15 @@ describe('Incident Redirect', function () {
describe('Component', function () {
describe('ComponentDidMount', function () {
it('Should attempt to fetch incident by incident id if ticketid is not known', function () {
let mockDispatchRecorder = {
action: null
}
let mockDispatchRecorder = GetDispatchRecorder()
const testProps = AddMockDispatch({
const testProps = {
ticketId: null,
incidentId: 2
})(mockDispatchRecorder)
}
const mockDispatch = GetMockDispatch(mockDispatchRecorder)
IncidentRedirectComponentDidMount(testProps)
IncidentRedirectComponentDidMount({...testProps, dispatch: mockDispatch})
expect(mockDispatchRecorder.action.type).to.equal('REQUEST_INCIDENT')
})
@ -30,12 +29,14 @@ describe('Incident Redirect', function () {
action: null
}
const testProps = AddMockDispatch({
const testProps = {
ticketId: 1,
incidentId: 2
})(mockDispatchRecorder)
}
IncidentRedirectComponentDidMount(testProps)
const mockDispatch = GetMockDispatch(mockDispatchRecorder)
IncidentRedirectComponentDidMount({...testProps, dispatch: mockDispatch})
expect(mockDispatchRecorder.action).to.be.null
})

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

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

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

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

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

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

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

@ -1,15 +1,15 @@
import { isObject } from 'util'
export const AddMockDispatch = (state) => (mockDispatchRecorder) => {
/*
Action could be:
an object
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 an object record it; a single action sets the .action property,
a second action turns .action into an array.
Subsequent actions are added to the array.
*/
/*
Action could be:
an object
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 an object record it; a single action sets the .action property,
a second action turns .action into an array.
Subsequent actions are added to the array.
*/
export const GetMockDispatch = (mockDispatchRecorder) => {
const dispatch = (action) => isObject(action)
? mockDispatchRecorder.action
? Array.isArray(mockDispatchRecorder.action)
@ -17,10 +17,9 @@ export const AddMockDispatch = (state) => (mockDispatchRecorder) => {
: mockDispatchRecorder.action = [mockDispatchRecorder.action, action]
: mockDispatchRecorder.action = action
: action(dispatch)
return {
...state,
dispatch
}
return dispatch
}
export default AddMockDispatch
export const GetDispatchRecorder = () => ({ action: null })
export default GetMockDispatch