diff --git a/app/src/App.vue b/app/src/App.vue index a4159366..ee6d1cb7 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -1,9 +1,19 @@ + + diff --git a/app/src/components/Snackbar.vue b/app/src/components/Snackbar.vue new file mode 100644 index 00000000..1b85c16a --- /dev/null +++ b/app/src/components/Snackbar.vue @@ -0,0 +1,60 @@ + + + diff --git a/app/src/pages/explorer/curate/spreadsheet/SpreadsheetBase.vue b/app/src/pages/explorer/curate/spreadsheet/SpreadsheetBase.vue index 603357a8..21940cba 100644 --- a/app/src/pages/explorer/curate/spreadsheet/SpreadsheetBase.vue +++ b/app/src/pages/explorer/curate/spreadsheet/SpreadsheetBase.vue @@ -8,7 +8,7 @@
 
-
+
note_add Create new

@@ -55,7 +55,7 @@ export default { } }, methods: { - ...mapActions('explorer/curation', ['createDatsetIdVuex']) + ...mapActions('explorer/curation', ['createDatasetIdVuex']) } } diff --git a/app/src/store/modules/explorer/curation/actions.js b/app/src/store/modules/explorer/curation/actions.js index 5d8ded60..af094e73 100644 --- a/app/src/store/modules/explorer/curation/actions.js +++ b/app/src/store/modules/explorer/curation/actions.js @@ -3,7 +3,7 @@ import router from '@/router' import apollo from '@/modules/gql/apolloClient' export default { - async createDatsetIdVuex ({ commit }) { + async createDatasetIdVuex ({ commit, dispatch }) { await apollo.mutate({ mutation: CREATE_DATASET_ID_MUTATION }).then((result) => { @@ -15,7 +15,13 @@ export default { const datasetId = error.message.split('-')[1]?.split(' ')[1] commit('setDatasetId', datasetId) router.push({ name: 'CurateSpreadsheet', params: { datasetId } }) - } else return error + } else { + // Show error in snackbar and pass current function as callback + commit('setSnackbar', { + message: error.message, + action: () => { dispatch('createDatasetIdVuex') } + }, { root: true }) + } }) } } diff --git a/app/src/store/modules/misc/getters.js b/app/src/store/modules/misc/getters.js index ea0b3b32..06209ede 100644 --- a/app/src/store/modules/misc/getters.js +++ b/app/src/store/modules/misc/getters.js @@ -4,5 +4,8 @@ export default { }, dialogBox (state) { return state.dialogBox + }, + getSnackbar (state) { + return state.snackbar } } diff --git a/app/src/store/modules/misc/index.js b/app/src/store/modules/misc/index.js index 3e165af1..94e2f2d4 100644 --- a/app/src/store/modules/misc/index.js +++ b/app/src/store/modules/misc/index.js @@ -12,7 +12,11 @@ export default { name: 'MaterialsMine', subtitle: 'An open source repository for nanocomposite data (NanoMine), and mechanical metamaterials data (MetaMine)' }, - dialogBox: false + dialogBox: false, + snackbar: { + message: '', + action: null + } } }, mutations, diff --git a/app/src/store/modules/misc/mutations.js b/app/src/store/modules/misc/mutations.js index 4c8cf003..b10de594 100644 --- a/app/src/store/modules/misc/mutations.js +++ b/app/src/store/modules/misc/mutations.js @@ -4,5 +4,11 @@ export default { }, setDialogBox (state) { state.dialogBox = !state.dialogBox + }, + setSnackbar (state, { message, action = null }) { + state.snackbar = { + message, + action + } } } diff --git a/app/tests/unit/components/snackbar.spec.js b/app/tests/unit/components/snackbar.spec.js new file mode 100644 index 00000000..85dde962 --- /dev/null +++ b/app/tests/unit/components/snackbar.spec.js @@ -0,0 +1,60 @@ +import createWrapper from '../../jest/script/wrapper' +import { enableAutoDestroy } from '@vue/test-utils' +import Snackbar from '@/components/Snackbar.vue' + +describe('@/components/Snackbar.vue', () => { + const testMessage = 'Test snack message' + let wrapper + beforeEach(() => { + wrapper = createWrapper(Snackbar, { }, false) + wrapper.setData({ show: true }) + }) + + enableAutoDestroy(afterEach) + + it('exists', () => { + expect.assertions(1) + expect(wrapper.exists()).toBe(true) + }) + + it('contains a close button', async () => { + expect.assertions(4) + const close = wrapper.find('#snackbarClose') + expect(close.exists()).toBe(true) + expect(close.text()).toContain('Close') + expect(wrapper.vm.show).toBe(true) + await close.trigger('click') + expect(wrapper.vm.show).toBe(false) + }) + + it('contains a refresh button by default', async () => { + expect.assertions(2) + const refresh = wrapper.find('#snackbarRefresh') + expect(refresh.exists()).toBe(true) + expect(refresh.text()).toContain('Refresh') + }) + + it('renders snackbar message from Vuex', async () => { + expect.assertions(1) + await wrapper.vm.$store.commit('setSnackbar', { + message: testMessage + }) + wrapper.vm.$nextTick() + expect(wrapper.text()).toContain(testMessage) + }) + + it('changes available action when passed from Vuex', async () => { + expect.assertions(3) + const mockFn = jest.fn() + await wrapper.vm.$store.commit('setSnackbar', { + message: testMessage, + action: mockFn + }) + wrapper.vm.$nextTick() + const action = wrapper.find('#snackbarAction') + expect(action.exists()).toBe(true) + expect(action.text()).toContain('Retry') + await action.trigger('click') + expect(mockFn).toHaveBeenCalled() + }) +})