Skip to content

Commit 9267ce6

Browse files
committed
fix(settings): correct heading order in account management sidebar
Signed-off-by: Peter Ringelmann <peter.ringelmann@nextcloud.com>
1 parent 4d5cf23 commit 9267ce6

2 files changed

Lines changed: 85 additions & 30 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { mount } from '@vue/test-utils'
7+
import { ref } from 'vue'
8+
import { describe, expect, it, vi } from 'vitest'
9+
import NcAppNavigationCaption from '@nextcloud/vue/components/NcAppNavigationCaption'
10+
11+
// The component builds a real Vuex store via useStore(); mock it so this stays
12+
// a focused component test that controls its own data.
13+
vi.mock('../store/index.js', () => ({
14+
useStore: () => ({
15+
getters: {
16+
getServerData: { isAdmin: false, isDelegatedAdmin: false },
17+
getSortedGroups: [],
18+
getSubAdminGroups: [],
19+
getSearchQuery: '',
20+
},
21+
commit: vi.fn(),
22+
dispatch: vi.fn(),
23+
}),
24+
}))
25+
26+
vi.mock('vue-router/composables', async (importActual) => ({
27+
...(await importActual<object>()),
28+
useRoute: () => ({ params: {} }),
29+
useRouter: () => ({ push: vi.fn() }),
30+
}))
31+
32+
vi.mock('../service/groups.ts', () => ({
33+
searchGroups: () => Promise.resolve([]),
34+
}))
35+
36+
vi.mock('@vueuse/core', async (importActual) => ({
37+
...(await importActual<object>()),
38+
useElementVisibility: () => ref(false),
39+
}))
40+
41+
import AppNavigationGroupList from './AppNavigationGroupList.vue'
42+
43+
describe('AppNavigationGroupList', () => {
44+
it('does not expose the group list as a heading (BITV 9.1.3.1a)', () => {
45+
const wrapper = mount(AppNavigationGroupList)
46+
47+
// The sidebar group list is navigation, not document structure. It must
48+
// not emit a heading, which would sit before the page <h1> in the DOM
49+
// and produce an out-of-order outline (h2 before h1).
50+
const caption = wrapper.findComponent(NcAppNavigationCaption)
51+
expect(caption.exists()).toBe(true)
52+
expect(caption.find('h1,h2,h3,h4,h5,h6').exists()).toBe(false)
53+
54+
// The "Groups" label is still rendered, just not as a heading.
55+
expect(caption.text()).toContain('Groups')
56+
})
57+
})

apps/settings/src/components/AppNavigationGroupList.vue

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,41 @@
55

66
<template>
77
<Fragment>
8-
<NcAppNavigationCaption
9-
:name="t('settings', 'Groups')"
10-
:disabled="loadingAddGroup"
11-
:aria-label="loadingAddGroup ? t('settings', 'Creating group…') : t('settings', 'Create group')"
12-
force-menu
13-
is-heading
14-
:open.sync="isAddGroupOpen">
15-
<template v-if="isAdminOrDelegatedAdmin" #actionsTriggerIcon>
16-
<NcLoadingIcon v-if="loadingAddGroup" />
17-
<NcIconSvgWrapper v-else :path="mdiPlus" />
18-
</template>
19-
<template v-if="isAdminOrDelegatedAdmin" #actions>
20-
<NcActionText>
21-
<template #icon>
22-
<NcIconSvgWrapper :path="mdiAccountGroupOutline" />
23-
</template>
24-
{{ t('settings', 'Create group') }}
25-
</NcActionText>
26-
<NcActionInput
27-
v-model="newGroupName"
28-
:label="t('settings', 'Group name')"
29-
data-cy-users-settings-new-group-name
30-
:label-outside="false"
31-
:disabled="loadingAddGroup"
32-
:error="hasAddGroupError"
33-
:helper-text="hasAddGroupError ? t('settings', 'Please enter a valid group name') : ''"
34-
@submit="createGroup" />
35-
</template>
36-
</NcAppNavigationCaption>
37-
388
<p id="group-list-desc" class="hidden-visually">
399
{{ t('settings', 'List of groups. This list is not fully populated for performance reasons. The groups will be loaded as you navigate or search through the list.') }}
4010
</p>
4111
<NcAppNavigationList
4212
class="account-management__group-list"
4313
aria-describedby="group-list-desc"
4414
data-cy-users-settings-navigation-groups="custom">
15+
<NcAppNavigationCaption
16+
:name="t('settings', 'Groups')"
17+
:disabled="loadingAddGroup"
18+
:aria-label="loadingAddGroup ? t('settings', 'Creating group…') : t('settings', 'Create group')"
19+
force-menu
20+
:open.sync="isAddGroupOpen">
21+
<template v-if="isAdminOrDelegatedAdmin" #actionsTriggerIcon>
22+
<NcLoadingIcon v-if="loadingAddGroup" />
23+
<NcIconSvgWrapper v-else :path="mdiPlus" />
24+
</template>
25+
<template v-if="isAdminOrDelegatedAdmin" #actions>
26+
<NcActionText>
27+
<template #icon>
28+
<NcIconSvgWrapper :path="mdiAccountGroupOutline" />
29+
</template>
30+
{{ t('settings', 'Create group') }}
31+
</NcActionText>
32+
<NcActionInput
33+
v-model="newGroupName"
34+
:label="t('settings', 'Group name')"
35+
data-cy-users-settings-new-group-name
36+
:label-outside="false"
37+
:disabled="loadingAddGroup"
38+
:error="hasAddGroupError"
39+
:helper-text="hasAddGroupError ? t('settings', 'Please enter a valid group name') : ''"
40+
@submit="createGroup" />
41+
</template>
42+
</NcAppNavigationCaption>
4543
<GroupListItem
4644
v-for="group in filteredGroups"
4745
:id="group.id"

0 commit comments

Comments
 (0)