diff options
-rw-r--r-- | src/components/AppHeader/AppHeader.vue | 56 | ||||
-rw-r--r-- | src/components/AppNavigation/AppNavigation.vue | 2 | ||||
-rw-r--r-- | src/locales/en-US.json | 2 | ||||
-rw-r--r-- | tests/unit/AppHeader.spec.js | 84 |
4 files changed, 85 insertions, 59 deletions
diff --git a/src/components/AppHeader/AppHeader.vue b/src/components/AppHeader/AppHeader.vue index 08c82565..114d6c9d 100644 --- a/src/components/AppHeader/AppHeader.vue +++ b/src/components/AppHeader/AppHeader.vue @@ -1,19 +1,23 @@ <template> <div> - <a class="link-skip-nav btn btn-light" href="#main-content"> - {{ $t('appHeader.skipToContent') }} - </a> <header id="page-header"> - <b-navbar variant="dark" type="dark"> + <a role="link" class="link-skip-nav btn btn-light" href="#main-content"> + {{ $t('appHeader.skipToContent') }} + </a> + + <b-navbar + variant="dark" + type="dark" + :aria-label="$t('appHeader.applicationHeader')" + > <!-- Left aligned nav items --> <b-button + id="app-header-trigger" class="nav-trigger" aria-hidden="true" title="Open navigation" type="button" variant="link" - :aria-expanded="isNavigationOpen" - :class="{ 'nav-open': isNavigationOpen }" @click="toggleNavigation" > <icon-close v-if="isNavigationOpen" /> @@ -24,24 +28,27 @@ </b-navbar-nav> <!-- Right aligned nav items --> <b-navbar-nav class="ml-auto"> - <b-nav> - <b-nav-item> - {{ $t('appHeader.health') }} - <status-icon :status="healthStatusIcon" /> - </b-nav-item> - <b-nav-item> - {{ $t('appHeader.power') }} - <status-icon :status="hostStatusIcon" /> - </b-nav-item> - <b-nav-item @click="refresh"> + <b-nav-item> + {{ $t('appHeader.health') }} + <status-icon :status="healthStatusIcon" /> + </b-nav-item> + <b-nav-item> + {{ $t('appHeader.power') }} + <status-icon :status="hostStatusIcon" /> + </b-nav-item> + <!-- Using LI elements instead of b-nav-item to support semantic button elements --> + <li class="nav-item"> + <b-button id="app-header-refresh" variant="link" @click="refresh"> {{ $t('appHeader.refresh') }} <icon-renew /> - </b-nav-item> - <b-nav-item @click="logout"> + </b-button> + </li> + <li> + <b-button id="app-header-logout" variant="link" @click="logout"> {{ $t('appHeader.logOut') }} <icon-avatar /> - </b-nav-item> - </b-nav> + </b-button> + </li> </b-navbar-nav> </b-navbar> </header> @@ -138,10 +145,13 @@ export default { } .navbar-dark { .navbar-text, - .nav-link { + .nav-link, + .btn-link { color: $white !important; + fill: currentColor; } } + .nav-item { fill: $light; } @@ -150,6 +160,10 @@ export default { padding: 0; height: $header-height; overflow: hidden; + + .btn-link { + padding: $spacer / 2; + } } .navbar-nav { diff --git a/src/components/AppNavigation/AppNavigation.vue b/src/components/AppNavigation/AppNavigation.vue index f2f049b3..94076de3 100644 --- a/src/components/AppNavigation/AppNavigation.vue +++ b/src/components/AppNavigation/AppNavigation.vue @@ -1,7 +1,7 @@ <template> <div> <div class="nav-container" :class="{ open: isNavigationOpen }"> - <nav ref="nav"> + <nav ref="nav" :aria-label="$t('appNavigation.primaryNavigation')"> <b-nav vertical> <b-nav-item to="/"> <icon-overview /> diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 22bb5142..b243f539 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -36,6 +36,7 @@ } }, "appHeader": { + "applicationHeader": "Application header", "bmcSystemManagement": "BMC System Management", "health": "Health", "logOut": "Log out", @@ -56,6 +57,7 @@ "managePowerUsage": "@:appPageTitle.managePowerUsage", "networkSettings": "@:appPageTitle.networkSettings", "overview": "@:appPageTitle.overview", + "primaryNavigation": "Primary navigation", "rebootBmc": "@:appPageTitle.rebootBmc", "sensors": "@:appPageTitle.sensors", "serverLed": "@:appPageTitle.serverLed", diff --git a/tests/unit/AppHeader.spec.js b/tests/unit/AppHeader.spec.js index 6dea960b..52e45437 100644 --- a/tests/unit/AppHeader.spec.js +++ b/tests/unit/AppHeader.spec.js @@ -1,62 +1,72 @@ -import { mount } from '@vue/test-utils'; +import { shallowMount, createLocalVue, createWrapper } from '@vue/test-utils'; import Vue from 'vue'; +import Vuex from 'vuex'; import AppHeader from '@/components/AppHeader'; -import $store from '@/store'; -import { BootstrapVue } from 'bootstrap-vue'; + +// Silencing warnings about undefined Bootsrap-vue components +Vue.config.silent = true; +const localVue = createLocalVue(); +localVue.use(Vuex); describe('AppHeader.vue', () => { - let wrapper; - let spy; - Vue.use(BootstrapVue); + const actions = { + 'global/getHostStatus': sinon.spy(), + 'eventLog/getEventLogData': sinon.spy() + }; - wrapper = mount(AppHeader, { + const store = new Vuex.Store({ actions }); + const wrapper = shallowMount(AppHeader, { + store, + localVue, mocks: { - $t: key => key, - $store + $t: key => key } }); + // Reset spy for each test. Otherwise mutiple actions + // are dispatched in each test beforeEach(() => { - spy = sinon.spy($store.dispatch); + store.dispatch = sinon.spy(); }); - describe('Component exists', () => { - it('should check if AppHeader exists', async () => { - expect(wrapper.exists()); + describe('UI', () => { + it('should check if AppHeader exists', () => { + expect(wrapper.exists()).to.be.true; }); - }); - describe('AppHeader methods', () => { - it('should call getHostInfo and dispatch global/getHostStatus', async () => { - wrapper.vm.getHostInfo(); - spy('global/getHostStatus'); - expect(spy).to.have.been.calledWith('global/getHostStatus'); + it('should check if the skip navigation link exists', () => { + expect(wrapper.get('.link-skip-nav').exists()).to.be.true; }); - it('should call getEvents and dispatch eventLog/getEventLogData', async () => { - wrapper.vm.getEvents(); - spy('eventLog/getEventLogData'); - expect(spy).to.have.been.calledWith('eventLog/getEventLogData'); + it('refresh button click should emit refresh event', async () => { + wrapper.get('#app-header-refresh').trigger('click'); + await wrapper.vm.$nextTick(); + expect(wrapper.emitted().refresh).to.exist; }); - it('should call refresh and emit refresh', async () => { - spy = sinon.spy(wrapper.vm.$emit); - wrapper.vm.refresh(); - spy('refresh'); - expect(spy).to.have.been.calledWith('refresh'); + it('nav-trigger button click should emit toggle:navigation event', async () => { + const rootWrapper = createWrapper(wrapper.vm.$root); + wrapper.get('#app-header-trigger').trigger('click'); + await wrapper.vm.$nextTick(); + expect(rootWrapper.emitted()['toggle:navigation']).to.exist; }); - it('should call logout and dispatch authentication/logout', async () => { - wrapper.vm.logout(); - spy('authentication/logout'); - expect(spy).to.have.been.calledWith('authentication/logout'); + it('logout button should dispatch authentication/logout', async () => { + wrapper.get('#app-header-logout').trigger('click'); + await wrapper.vm.$nextTick(); + expect(store.dispatch).calledWith('authentication/logout'); }); + }); - it('should call toggleNavigation and dispatch toggle:navigation', async () => { - spy = sinon.spy(wrapper.vm.$root.$emit); - wrapper.vm.toggleNavigation(); - spy('toggle:navigation'); - expect(spy).to.have.been.calledWith('toggle:navigation'); + describe('Methods', () => { + it('getHostInfo should dispatch global/getHostStatus', () => { + wrapper.vm.getHostInfo(); + expect(store.dispatch).calledWith('global/getHostStatus'); + }); + + it('getEvents should dispatch eventLog/getEventLogData', () => { + wrapper.vm.getEvents(); + expect(store.dispatch).calledWith('eventLog/getEventLogData'); }); }); }); |