diff --git a/__tests__/e2e/sidebar.test.ts b/__tests__/e2e/sidebar.test.ts new file mode 100644 index 00000000..288b15c8 --- /dev/null +++ b/__tests__/e2e/sidebar.test.ts @@ -0,0 +1,36 @@ +describe('sidebar', () => { + beforeAll(async () => { + await goto('/frontmatter/multiple-levels-outline') + }) + + test('collapsible group renders a heading and a single toggle button', async () => { + const group = page.locator('.VPSidebarItem.level-0.collapsible').first() + const caret = group.locator('.caret').first() + + expect(await page.locator('.VPSidebarItem [role="button"]').count()).toBe(0) + expect(await caret.evaluate((el) => el.tagName)).toBe('BUTTON') + expect(await caret.getAttribute('aria-expanded')).toBe('true') + }) + + test('group toggles with keyboard, caret and heading', async () => { + const group = page.locator('.VPSidebarItem.level-0.collapsible').first() + const caret = group.locator('.caret').first() + const isCollapsed = () => + group.evaluate((el) => el.classList.contains('collapsed')) + + await caret.focus() + await page.keyboard.press('Enter') + expect(await isCollapsed()).toBe(true) + expect(await caret.getAttribute('aria-expanded')).toBe('false') + + await page.keyboard.press('Space') + expect(await isCollapsed()).toBe(false) + expect(await caret.getAttribute('aria-expanded')).toBe('true') + + await caret.click() + expect(await isCollapsed()).toBe(true) + + await group.locator('.text').first().click() + expect(await isCollapsed()).toBe(false) + }) +}) diff --git a/src/client/theme-default/components/VPSidebarItem.vue b/src/client/theme-default/components/VPSidebarItem.vue index 6dad9f58..fe268a63 100644 --- a/src/client/theme-default/components/VPSidebarItem.vue +++ b/src/client/theme-default/components/VPSidebarItem.vue @@ -19,19 +19,16 @@ const { toggle } = useSidebarItemControl(computed(() => props.item)) -const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`)) - const linkTag = computed(() => (isLink.value ? 'a' : 'div')) -const textTag = computed(() => { - return !hasChildren.value - ? 'p' - : props.depth + 2 === 7 - ? 'p' - : `h${props.depth + 2}` -}) +const textTag = computed(() => + hasChildren.value && props.depth < 5 ? `h${props.depth + 2}` : 'p' +) -const itemRole = computed(() => (isLink.value ? undefined : 'button')) +// a section needs a heading +const sectionTag = computed(() => + props.item.text && textTag.value !== 'p' ? 'section' : 'div' +) const classes = computed(() => [ [`level-${props.depth}`], @@ -42,31 +39,14 @@ const classes = computed(() => [ { 'has-active': hasActiveLink.value } ]) -function onItemInteraction(e: MouseEvent | Event) { - if ('key' in e && e.key !== 'Enter') { - return - } +function onItemClick() { !props.item.link && toggle() } - -function onCaretClick() { - props.item.link && toggle() -}