diff options
-rw-r--r-- | apps/theming/src/components/AppOrderSelectorElement.vue | 59 | ||||
-rw-r--r-- | cypress/e2e/theming/navigation-bar-settings.cy.ts | 41 |
2 files changed, 96 insertions, 4 deletions
diff --git a/apps/theming/src/components/AppOrderSelectorElement.vue b/apps/theming/src/components/AppOrderSelectorElement.vue index ee795b6272a..73da7579d59 100644 --- a/apps/theming/src/components/AppOrderSelectorElement.vue +++ b/apps/theming/src/components/AppOrderSelectorElement.vue @@ -23,20 +23,22 @@ <div class="order-selector-element__actions"> <NcButton v-show="!isFirst && !app.default" + ref="buttonUp" :aria-label="t('settings', 'Move up')" data-cy-app-order-button="up" type="tertiary-no-background" - @click="$emit('move:up')"> + @click="moveUp"> <template #icon> <IconArrowUp :size="20" /> </template> </NcButton> <div v-show="isFirst || !!app.default" aria-hidden="true" class="order-selector-element__placeholder" /> <NcButton v-show="!isLast && !app.default" + ref="buttonDown" :aria-label="t('settings', 'Move down')" data-cy-app-order-button="down" type="tertiary-no-background" - @click="$emit('move:down')"> + @click="moveDown"> <template #icon> <IconArrowDown :size="20" /> </template> @@ -47,8 +49,10 @@ </template> <script lang="ts"> +import type { PropType } from 'vue' + import { translate as t } from '@nextcloud/l10n' -import { PropType, defineComponent } from 'vue' +import { defineComponent, nextTick, onUpdated, ref } from 'vue' import IconArrowDown from 'vue-material-design-icons/ArrowDown.vue' import IconArrowUp from 'vue-material-design-icons/ArrowUp.vue' @@ -86,8 +90,55 @@ export default defineComponent({ 'move:up': () => true, 'move:down': () => true, }, - setup() { + setup(props, { emit }) { + const buttonUp = ref() + const buttonDown = ref() + + /** + * Used to decide if we need to trigger focus() an a button on update + */ + let needsFocus = 0 + + /** + * Handle move up, ensure focus is kept on the button + */ + const moveUp = () => { + emit('move:up') + needsFocus = 1 // request focus on buttonUp + } + + /** + * Handle move down, ensure focus is kept on the button + */ + const moveDown = () => { + emit('move:down') + needsFocus = -1 // request focus on buttonDown + } + + /** + * onUpdated hook is used to reset the focus on the last used button (if requested) + * If the button is now visible anymore (because this element is the first/last) then the opposite button is focussed + */ + onUpdated(() => { + if (needsFocus !== 0) { + // focus requested + if ((needsFocus === 1 || props.isLast) && !props.isFirst) { + // either requested to btn up and it is not the first, or it was requested to btn down but it is the last + nextTick(() => buttonUp.value.$el.focus()) + } else { + nextTick(() => buttonDown.value.$el.focus()) + } + } + needsFocus = 0 + }) + return { + buttonUp, + buttonDown, + + moveUp, + moveDown, + t, } }, diff --git a/cypress/e2e/theming/navigation-bar-settings.cy.ts b/cypress/e2e/theming/navigation-bar-settings.cy.ts index 50c48d5ac6d..a5657ee5a15 100644 --- a/cypress/e2e/theming/navigation-bar-settings.cy.ts +++ b/cypress/e2e/theming/navigation-bar-settings.cy.ts @@ -210,3 +210,44 @@ describe('User theming set app order with default app', () => { }) }) }) + +describe('User theming app order list accessibility', () => { + let user: User + + before(() => { + cy.resetAdminTheming() + // Create random user for this test + cy.createRandomUser().then(($user) => { + user = $user + cy.login($user) + }) + }) + + after(() => { + cy.deleteUser(user) + }) + + it('See the app order settings', () => { + cy.visit('/settings/user/theming') + cy.get('[data-cy-app-order]').scrollIntoView() + cy.get('[data-cy-app-order] [data-cy-app-order-element]').should('have.length', 2) + }) + + it('click the first button', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('be.visible').click() + }) + + it('see the same app kept the focus', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('not.have.focus') + cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('have.focus') + }) + + it('click the last button', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('be.visible').click() + }) + + it('see the same app kept the focus', () => { + cy.get('[data-cy-app-order] [data-cy-app-order-element]:first-of-type [data-cy-app-order-button="down"]').should('have.focus') + cy.get('[data-cy-app-order] [data-cy-app-order-element]:last-of-type [data-cy-app-order-button="up"]').should('not.have.focus') + }) +}) |