Skip to content

Commit

Permalink
Notifies user when there is an error retrieving or saving a resource.…
Browse files Browse the repository at this point in the history
… Refactors save/load resource/resource template actions for consistency.

closes #1447
  • Loading branch information
justinlittman committed Oct 9, 2019
1 parent 0ab2837 commit abc10ae
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 215 deletions.
8 changes: 4 additions & 4 deletions __tests__/actionCreators/resourceTemplates.test.js
Expand Up @@ -14,7 +14,7 @@ describe('fetchResourceTemplate', () => {
const dispatch = jest.fn()
await fetchResourceTemplate(resourceTemplateId, dispatch)
expect(dispatch).toBeCalledWith({ type: 'RETRIEVE_RESOURCE_TEMPLATE_STARTED', payload: resourceTemplateId })
expect(dispatch).toBeCalledWith({ type: 'SET_RESOURCE_TEMPLATE', payload: templateResponse.response.body })
expect(dispatch).toBeCalledWith({ type: 'RESOURCE_TEMPLATE_LOADED', payload: templateResponse.response.body })
})
})
describe('an invalid template', () => {
Expand All @@ -26,7 +26,7 @@ describe('fetchResourceTemplate', () => {
await fetchResourceTemplate(resourceTemplateId, dispatch)
expect(dispatch).toBeCalledWith({ type: 'RETRIEVE_RESOURCE_TEMPLATE_STARTED', payload: resourceTemplateId })
expect(dispatch).toBeCalledWith({
type: 'RETRIEVE_ERROR',
type: 'RETRIEVE_RESOURCE_TEMPLATE_ERROR',
payload: {
resourceTemplateId,
reason: [
Expand Down Expand Up @@ -64,9 +64,9 @@ describe('fetchResourceTemplateSummaries', () => {
expect(server.getResourceTemplate).toHaveBeenCalledWith(noteId, 'ld4p')
expect(server.getResourceTemplate).toHaveBeenCalledWith(barcodeId, 'ld4p')

expect(dispatch).toHaveBeenCalledTimes(2)
expect(dispatch).toHaveBeenCalledTimes(4)
expect(dispatch).toBeCalledWith({
type: 'SET_RESOURCE_TEMPLATE_SUMMARY',
type: 'RESOURCE_TEMPLATE_SUMMARY_LOADED',
payload: {
key: noteId,
name: 'Note',
Expand Down
22 changes: 11 additions & 11 deletions __tests__/actionCreators/resources.test.js
Expand Up @@ -48,8 +48,8 @@ describe('update', () => {

const actions = store.getActions()
expect(actions.length).toEqual(2)
expect(actions[0]).toEqual({ type: 'UPDATE_STARTED' })
expect(actions[1]).toEqual({ type: 'UPDATE_FINISHED', payload: '5e30bd59d0186c5307065436240ba108' })
expect(actions[0]).toEqual({ type: 'SAVE_RESOURCE_STARTED' })
expect(actions[1]).toEqual({ type: 'SAVE_RESOURCE_FINISHED', payload: '5e30bd59d0186c5307065436240ba108' })
})
})

Expand Down Expand Up @@ -78,7 +78,7 @@ describe('retrieveResource', () => {

const actions = store.getActions()
expect(actions.length).toEqual(1)
expect(actions[0]).toEqual({ type: 'RETRIEVE_STARTED' })
expect(actions[0]).toEqual({ type: 'RETRIEVE_RESOURCE_STARTED' })
})
})
describe('when dispatch to existing resource returns a result', () => {
Expand Down Expand Up @@ -109,9 +109,9 @@ describe('retrieveResource', () => {
const reduxPath = ['resource', 'resourceTemplate:bf2:Note', 'http://www.w3.org/2000/01/rdf-schema#label']

expect(actions).toEqual([
{ type: 'RETRIEVE_STARTED', payload: undefined },
{ type: 'RETRIEVE_RESOURCE_STARTED', payload: undefined },
{ type: 'TOGGLE_COLLAPSE', payload: { reduxPath } },
{ type: 'SET_RESOURCE', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'RESOURCE_LOADED', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'SET_LAST_SAVE_CHECKSUM', payload: undefined },
{ type: 'SET_LAST_SAVE_CHECKSUM', payload: 'f767b63c3e1d1af6f8c136b15a31a1e0' },
{ type: 'SET_UNUSED_RDF', payload: '' },
Expand All @@ -138,9 +138,9 @@ describe('publishResource', () => {
await store.dispatch(publishResource(currentUser, group))
const actions = store.getActions()
expect(actions.length).toEqual(3)
expect(actions[0]).toEqual({ type: 'PUBLISH_STARTED' })
expect(actions[0]).toEqual({ type: 'SAVE_RESOURCE_STARTED' })
expect(actions[1]).toEqual({ type: 'SET_BASE_URL', payload: 'http://sinopia.io/repository/myGroup/myResource' })
expect(actions[2]).toEqual({ type: 'UPDATE_FINISHED', payload: '5e30bd59d0186c5307065436240ba108' })
expect(actions[2]).toEqual({ type: 'SAVE_RESOURCE_FINISHED', payload: '5e30bd59d0186c5307065436240ba108' })
})
it('dispatches actions for error path', async () => {
const store = mockStore(state)
Expand All @@ -149,8 +149,8 @@ describe('publishResource', () => {
await store.dispatch(publishResource(currentUser, group))
const actions = store.getActions()
expect(actions.length).toEqual(2)
expect(actions[0]).toEqual({ type: 'PUBLISH_STARTED' })
expect(actions[1]).toEqual({ type: 'PUBLISH_ERROR', payload: 'Unable to save resource: Error: publish error' })
expect(actions[0]).toEqual({ type: 'SAVE_RESOURCE_STARTED' })
expect(actions[1]).toEqual({ type: 'SAVE_RESOURCE_ERROR', payload: { uri: null, reason: 'Error: publish error' } })
})
})

Expand Down Expand Up @@ -186,7 +186,7 @@ describe('newResource', () => {
const actions = store.getActions()
const expectedResource = { [resourceTemplateId]: { 'http://www.w3.org/2000/01/rdf-schema#label': {} } }
expect(actions).toEqual([
{ type: 'SET_RESOURCE', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'RESOURCE_LOADED', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'SET_LAST_SAVE_CHECKSUM', payload: 'baf92a33bf689d599a41bb4563db42fc' },
{ type: 'SET_UNUSED_RDF', payload: null }])
})
Expand Down Expand Up @@ -238,7 +238,7 @@ describe('existingResource', () => {
},
}
expect(actions).toEqual([
{ type: 'SET_RESOURCE', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'RESOURCE_LOADED', payload: { resource: expectedResource, resourceTemplates: { [resourceTemplateId]: resourceTemplate } } },
{ type: 'SET_LAST_SAVE_CHECKSUM' },
{ type: 'SET_UNUSED_RDF', payload: unusedRDF }])
})
Expand Down
4 changes: 4 additions & 0 deletions __tests__/components/editor/Editor.test.js
Expand Up @@ -12,6 +12,7 @@ import { Prompt } from 'react-router'
const props = {
location: { state: { resourceTemplateId: 'resourceTemplate:bf:Note' } },
userWantsToSave: jest.fn(),
saveError: 'oops',
}

// See https://github.com/nodesecurity/eslint-plugin-security/issues/26
Expand Down Expand Up @@ -39,6 +40,9 @@ describe('<Editor />', () => {
it('includes <Prompt />', () => {
expect(wrapper.exists(Prompt)).toBe(true)
})
it('renders error', () => {
expect(wrapper.find('div.alert-danger').length).toBe(1)
})
})
describe('authenticated user', () => {
props.currentSession = { dummy: 'should be CognitoUserSession instance, but just checked for presence at present' }
Expand Down
169 changes: 138 additions & 31 deletions __tests__/reducers/index.test.js
@@ -1,8 +1,8 @@
// Copyright 2019 Stanford University see LICENSE for license

import {
createReducer, setRetrieveError, removeResource, clearServerError, updateFinished,
setLastSaveChecksum, setPublishError, setResource,
import appReducer, {
createReducer, removeResource, saveResourceFinished,
setLastSaveChecksum, setResource,
} from 'reducers/index'
import _ from 'lodash'
import { getFixtureResourceTemplate } from '../fixtureLoaderHelper'
Expand All @@ -24,14 +24,6 @@ beforeEach(() => {
}
})

describe('clearServerError', () => {
it('clears an existing error', () => {
initialState.selectorReducer.editor.serverError = 'Something is wrong'
const newState = clearServerError(initialState.selectorReducer)
expect(newState.editor.serverError).toBeUndefined()
})
})

describe('createReducer', () => {
// Make sure spies/mocks don't leak between tests
afterAll(() => {
Expand Down Expand Up @@ -80,33 +72,148 @@ describe('createReducer', () => {
})
})

describe('setPublishError', () => {
it('adds error with reason to editor state', () => {
const newState = setPublishError(initialState.selectorReducer, {
type: 'PUBLISH_ERROR',
payload: 'publishing error msg',
describe('clearRetrieveResourceError', () => {
it('clears the error', () => {
const newInitialState = { ...initialState }
newInitialState.selectorReducer.editor.retrieveResourceError = 'Ooops'

const newState = appReducer(newInitialState, {
type: 'CLEAR_RETRIEVE_RESOURCE_ERROR',
})

expect(newState.selectorReducer.editor.retrieveResourceError).toBeUndefined()
})
})

describe('clearRetrieveResourceTemplateError', () => {
it('clears the error', () => {
const newInitialState = { ...initialState }
newInitialState.selectorReducer.editor.retrieveResourceTemplateError = 'Ooops'

const newState = appReducer(newInitialState, {
type: 'CLEAR_RETRIEVE_RESOURCE_TEMPLATE_ERROR',
})

expect(newState.selectorReducer.editor.retrieveResourceTemplateError).toBeUndefined()
})
})

describe('clearSaveResourceError', () => {
it('clears the error', () => {
const newInitialState = { ...initialState }
newInitialState.selectorReducer.editor.saveResourceError = 'Ooops'

const newState = appReducer(newInitialState, {
type: 'CLEAR_SAVE_RESOURCE_ERROR',
})

expect(newState.editor.serverError).toEqual('There was a problem saving the resource: publishing error msg')
expect(newState.selectorReducer.editor.saveResourceError).toBeUndefined()
})
})

describe('setRetrieveError', () => {
it('adds error to editor state when no reason given', () => {
const newState = setRetrieveError(initialState.selectorReducer, {
type: 'RETRIEVE_ERROR',
payload: { resourceTemplateId: 'abc123' },
describe('clearSaveResourceTemplateError', () => {
it('clears the error', () => {
const newInitialState = { ...initialState }
newInitialState.selectorReducer.editor.saveResourceTemplateError = 'Ooops'

const newState = appReducer(newInitialState, {
type: 'CLEAR_SAVE_RESOURCE_TEMPLATE_ERROR',
})

expect(newState.editor.serverError).toEqual('There was a problem retrieving abc123.')
expect(newState.selectorReducer.editor.saveResourceTemplateError).toBeUndefined()
})
})

describe('setRetrieveResourceError', () => {
it('adds error with uri to editor state', () => {
const newState = appReducer(initialState, {
type: 'RETRIEVE_RESOURCE_ERROR',
payload: {
uri: 'http://abc123',
},
})
expect(newState.selectorReducer.editor.retrieveResourceError).toEqual('There was a problem retrieving http://abc123.')
})
it('adds error with uri and reason to editor state', () => {
const newState = appReducer(initialState, {
type: 'RETRIEVE_RESOURCE_ERROR',
payload: {
uri: 'http://abc123',
reason: 'Ooops',
},
})
expect(newState.selectorReducer.editor.retrieveResourceError).toEqual('There was a problem retrieving http://abc123: Ooops')
})
})

describe('setRetrieveResourceTemplateError', () => {
it('adds error with resourceTemplateId to editor state', () => {
const newState = appReducer(initialState, {
type: 'RETRIEVE_RESOURCE_TEMPLATE_ERROR',
payload: {
resourceTemplateId: 'bf2:WorkTitle',
},
})
expect(newState.selectorReducer.editor.retrieveResourceTemplateError).toEqual('There was a problem retrieving bf2:WorkTitle.')
})
it('adds error with resourceTemplateId and reason to editor state', () => {
const newState = appReducer(initialState, {
type: 'RETRIEVE_RESOURCE_TEMPLATE_ERROR',
payload: {
resourceTemplateId: 'bf2:WorkTitle',
reason: 'Ooops',
},
})
expect(newState.selectorReducer.editor.retrieveResourceTemplateError).toEqual('There was a problem retrieving bf2:WorkTitle: Ooops')
})
})

describe('setSaveResourceError', () => {
it('adds error with reason to editor state', () => {
const newState = setRetrieveError(initialState.selectorReducer, {
type: 'RETRIEVE_ERROR',
payload: { resourceTemplateId: 'abc123', reason: 'Because it is broken.' },
const newState = appReducer(initialState, {
type: 'SAVE_RESOURCE_ERROR',
payload: {
uri: null,
reason: 'publishing error msg',
},
})
expect(newState.selectorReducer.editor.saveResourceError).toEqual('There was a problem saving: publishing error msg')
})

it('adds error with reason and uri to editor state', () => {
const newState = appReducer(initialState, {
type: 'SAVE_RESOURCE_ERROR',
payload: {
uri: 'http://abc123',
reason: 'publishing error msg',
},
})

expect(newState.selectorReducer.editor.saveResourceError).toEqual('There was a problem saving http://abc123: publishing error msg')
})
})

describe('setSaveResourceTemplateError', () => {
it('adds error with resourceTemplateId to editor state', () => {
const newState = appReducer(initialState, {
type: 'SAVE_RESOURCE_TEMPLATE_ERROR',
payload: {
resourceTemplateId: 'bf2:WorkTitle',
},
})
expect(newState.selectorReducer.editor.saveResourceTemplateError).toEqual('There was a problem saving bf2:WorkTitle.')
})

it('adds error with reason and resourceTemplateId to editor state', () => {
const newState = appReducer(initialState, {
type: 'SAVE_RESOURCE_TEMPLATE_ERROR',
payload: {
resourceTemplateId: 'bf2:WorkTitle',
reason: 'publishing error msg',
},
})

expect(newState.editor.serverError).toEqual('There was a problem retrieving abc123: Because it is broken.')
expect(newState.selectorReducer.editor.saveResourceTemplateError).toEqual('There was a problem saving bf2:WorkTitle: publishing error msg')
})
})

Expand Down Expand Up @@ -186,23 +293,23 @@ describe('removeResource', () => {
})
})

describe('updateFinished', () => {
describe('saveResourceFinished', () => {
const action = { payload: 'abc123' }
it('sets last save differently each time called', () => {
expect(initialState.selectorReducer.editor.lastSave).toBeFalsy()
const newState = updateFinished(initialState.selectorReducer, action)
const newState = saveResourceFinished(initialState.selectorReducer, action)
expect(newState.editor.lastSave).toBeTruthy()

const now = Date.now()
while (now === Date.now()) {
// Wait
}

const newState2 = updateFinished(_.cloneDeep(newState), action)
const newState2 = saveResourceFinished(_.cloneDeep(newState), action)
expect(newState.editor.lastSave).not.toEqual(newState2.editor.lastSave)
})
it('sets lastSaveChecksum', () => {
const newState = updateFinished(initialState.selectorReducer, action)
const newState = saveResourceFinished(initialState.selectorReducer, action)
expect(newState.editor.lastSaveChecksum).toEqual('abc123')
})
})
Expand Down

0 comments on commit abc10ae

Please sign in to comment.