Skip to content

Commit

Permalink
#289 snackbar for error handling (#290)
Browse files Browse the repository at this point in the history
* #289: Implement snackbar two ways

* #289: Fix linting

* #289: Add Snackbar to App.vue

* #289: Add snackbar unit test and an example for calling from vuex
  • Loading branch information
aswallace authored Jan 2, 2023
1 parent 6159deb commit 043180d
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 5 deletions.
10 changes: 10 additions & 0 deletions app/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
<template>
<div id="app" class="page-container">
<router-view />
<Snackbar/>
</div>
</template>

<script>
import Snackbar from '@/components/Snackbar.vue'
export default {
components: {
Snackbar
}
}
</script>

<style lang="scss">
@import 'assets/css/style.scss';
</style>
60 changes: 60 additions & 0 deletions app/src/components/Snackbar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<template>
<div>
<md-snackbar :md-position="position" :md-active.sync="show">
{{message}}
<span>
<md-button v-if="action" id="snackbarAction" class="md-primary" @click.native="callAction">Retry</md-button>
<md-button v-else id="snackbarRefresh" class="md-primary" @click.native="refresh">Refresh</md-button>
<md-button id="snackbarClose" class="md-primary" @click.native="show = false">Close</md-button>
</span>
</md-snackbar>
</div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
name: 'Snackbar',
props: {
position: {
type: String,
required: false,
default: 'center'
}
},
data () {
return {
show: false,
message: '',
action: null
}
},
computed: {
...mapGetters({
snackbar: 'getSnackbar'
})
},
methods: {
refresh () {
this.$router.go(0)
},
// If an action has been passed through vuex as a callback, call it
callAction () {
if (this.action) {
this.action()
}
}
},
watch: {
snackbar (val, oldVal) {
if (val.message) {
this.show = true
this.message = this.snackbar.message
this.action = this.snackbar.action || null
// Reset
this.$store.commit('setSnackbar', '')
}
}
}
}
</script>
4 changes: 2 additions & 2 deletions app/src/pages/explorer/curate/spreadsheet/SpreadsheetBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<div class="md-layout md-layout-responsive md-gutter">
<div class="md-layout-item md-small-hide">&nbsp;</div>
<div class="md-layout-item">
<div class="teams_container explorer_page-nav-card md-layout-item_card" @click="createDatsetIdVuex">
<div class="teams_container explorer_page-nav-card md-layout-item_card" @click="createDatasetIdVuex">
<md-icon class="explorer_page-nav-card_icon">note_add</md-icon>
<span class="explorer_page-nav-card_text">Create new</span>
<p class="md-layout-item_para md-layout-item_para_fl">
Expand Down Expand Up @@ -55,7 +55,7 @@ export default {
}
},
methods: {
...mapActions('explorer/curation', ['createDatsetIdVuex'])
...mapActions('explorer/curation', ['createDatasetIdVuex'])
}
}
</script>
10 changes: 8 additions & 2 deletions app/src/store/modules/explorer/curation/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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 })
}
})
}
}
3 changes: 3 additions & 0 deletions app/src/store/modules/misc/getters.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ export default {
},
dialogBox (state) {
return state.dialogBox
},
getSnackbar (state) {
return state.snackbar
}
}
6 changes: 5 additions & 1 deletion app/src/store/modules/misc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions app/src/store/modules/misc/mutations.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,11 @@ export default {
},
setDialogBox (state) {
state.dialogBox = !state.dialogBox
},
setSnackbar (state, { message, action = null }) {
state.snackbar = {
message,
action
}
}
}
60 changes: 60 additions & 0 deletions app/tests/unit/components/snackbar.spec.js
Original file line number Diff line number Diff line change
@@ -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()
})
})

0 comments on commit 043180d

Please sign in to comment.