Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions core/src/components/AccountMenu/AccountMenuProfileEntry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
{{ name }}
</template>
<template v-if="canCreateAppToken" #extra-actions>
<NcButton variant="secondary" @click="handleQrCodeClick">
<NcButton
:aria-label="t('core', 'Show QR code for mobile app login')"
variant="secondary"
@click="handleQrCodeClick">
<template #icon>
<IconQrcodeScan :size="20" />
</template>
Expand All @@ -36,6 +39,7 @@ import axios from '@nextcloud/axios'
import { getCapabilities } from '@nextcloud/capabilities'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { addPasswordConfirmationInterceptors, PwdConfirmationMode } from '@nextcloud/password-confirmation'
import { generateUrl } from '@nextcloud/router'
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
Expand Down Expand Up @@ -88,8 +92,9 @@ export default defineComponent({
setup() {
return {
canCreateAppToken,
profileEnabled,
displayName: getCurrentUser()!.displayName,
profileEnabled,
t,
}
},

Expand Down
132 changes: 132 additions & 0 deletions core/src/tests/components/AccountMenuProfileEntry.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const capabilities = vi.hoisted(() => ({
getCapabilities: vi.fn(),
}))
vi.mock('@nextcloud/capabilities', () => capabilities)

vi.mock('@nextcloud/auth', () => ({
getCurrentUser: () => ({ uid: 'user', displayName: 'User' }),
}))

vi.mock('@nextcloud/axios', () => ({ default: { post: vi.fn() } }))

vi.mock('@nextcloud/event-bus', () => ({
subscribe: vi.fn(),
unsubscribe: vi.fn(),
}))

vi.mock('@nextcloud/initial-state', () => ({
loadState: vi.fn((_app: string, key: string, fallback: unknown) => {
if (key === 'profileEnabled') {
return { profileEnabled: false }
}
return fallback
}),
}))

vi.mock('@nextcloud/l10n', () => ({
getLanguage: () => 'en',
t: (_app: string, text: string) => text,
}))

vi.mock('@nextcloud/password-confirmation', () => ({
addPasswordConfirmationInterceptors: vi.fn(),
PwdConfirmationMode: {
Strict: 0,
},
}))

vi.mock('@nextcloud/router', () => ({
generateUrl: (path: string) => path,
}))

vi.mock('@nextcloud/vue/components/NcButton', () => ({
default: {
name: 'NcButton',
inheritAttrs: false,
render(h) {
return h('button', {
attrs: this.$attrs,
on: {
click: (event: MouseEvent) => this.$emit('click', event),
},
}, this.$slots.icon)
},
},
}))

vi.mock('@nextcloud/vue/components/NcListItem', () => ({
default: {
name: 'NcListItem',
render(h) {
return h('li', [
this.$slots.subname,
this.$slots['extra-actions'],
this.$slots.indicator,
])
},
},
}))

vi.mock('@nextcloud/vue/components/NcLoadingIcon', () => ({
default: {
name: 'NcLoadingIcon',
render(h) {
return h('span')
},
},
}))

vi.mock('@nextcloud/vue/functions/dialog', () => ({
spawnDialog: vi.fn(),
}))

vi.mock('../../components/AccountMenu/AccountQRLoginDialog.vue', () => ({
default: {
name: 'AccountQRLoginDialog',
render(h) {
return h('div')
},
},
}))

vi.mock('vue-material-design-icons/QrcodeScan.vue', () => ({
default: {
name: 'IconQrcodeScan',
render(h) {
return h('span')
},
},
}))

describe('core: AccountMenuProfileEntry', () => {
beforeEach(() => {
vi.resetModules()
capabilities.getCapabilities.mockReturnValue({
core: {
'can-create-app-token': true,
},
})
})

it('labels the QR code button for assistive technologies', async () => {
const AccountMenuProfileEntry = (await import('../../components/AccountMenu/AccountMenuProfileEntry.vue')).default
const wrapper = mount(AccountMenuProfileEntry, {
propsData: {
id: 'profile',
name: 'Profile',
href: '/settings/user',
active: false,
},
})

expect(wrapper.get('button').attributes('aria-label')).toBe('Show QR code for mobile app login')
})
})
7 changes: 7 additions & 0 deletions core/src/tests/components/AppMenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ async function openPopover(wrapper: ReturnType<typeof mount>) {
}

describe('core: AppMenu', () => {
it('labels the app menu trigger buttons for assistive technologies', () => {
const wrapper = mount(AppMenu, { attachTo: document.body })

expect(wrapper.get('.app-menu__waffle').attributes('aria-label')).toBe('Open apps menu')
expect(wrapper.get('.app-menu__current-app').attributes('aria-label')).toBe('Open apps menu, currently in Files')
})

it('renders one AppItem per app in the list, plus the "App store" tile for non-admins', async () => {
const wrapper = mount(AppMenu, { attachTo: document.body })
await openPopover(wrapper)
Expand Down
Loading