| /** |
| * @license |
| * Copyright 2021 The Go Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file. |
| */ |
| |
| import { parse } from '../../markdown'; |
| import { TreeNavController } from './tree'; |
| |
| const observe = jest.fn(); |
| window.IntersectionObserver = jest.fn(() => ({ |
| observe, |
| })) as any; |
| |
| let treeEl: HTMLElement; |
| let a1: HTMLElement; |
| let a21: HTMLElement; |
| let a22: HTMLElement; |
| let a23: HTMLElement; |
| let a31: HTMLElement; |
| let a41: HTMLElement; |
| let a42: HTMLElement; |
| |
| beforeEach(async () => { |
| document.body.innerHTML = await parse(__dirname + '/outline.md'); |
| treeEl = document.querySelector('.js-tree') as HTMLElement; |
| new TreeNavController(treeEl); |
| a1 = a('#one'); |
| a21 = a('#two-one'); |
| a22 = a('#two-two'); |
| a23 = a('#two-three'); |
| a31 = a('#three-one'); |
| a41 = a('#four-one'); |
| a42 = a('#four-two'); |
| a1.focus(); |
| }); |
| |
| afterEach(() => { |
| document.body.innerHTML = ''; |
| }); |
| |
| it('creates tree nav from ul', () => { |
| expect(treeEl).toMatchSnapshot(); |
| }); |
| |
| it('adds role=group attribute to ul elements', () => { |
| for (const ul of treeEl.querySelectorAll('ul')) { |
| expect(attr(ul, 'role')).toBe('group'); |
| } |
| }); |
| |
| it('adds role=none attribute to li elements', () => { |
| for (const ul of treeEl.querySelectorAll('li')) { |
| expect(attr(ul, 'role')).toBe('none'); |
| } |
| }); |
| |
| it('adds role=treeitem attribute to a elements', () => { |
| for (const ul of treeEl.querySelectorAll('a')) { |
| expect(attr(ul, 'role')).toBe('treeitem'); |
| } |
| }); |
| |
| it('adds aria-expanded role to tree items with children', () => { |
| expect(attr(a1, 'aria-expanded')).toBe('false'); |
| expect(attr(a22, 'aria-expanded')).toBe('false'); |
| expect(attr(a31, 'aria-expanded')).toBe('false'); |
| }); |
| |
| it('does not add aria-expanded role to tree items with children', () => { |
| expect(attr(a21, 'aria-expanded')).toBeNull(); |
| expect(attr(a41, 'aria-expanded')).toBeNull(); |
| expect(attr(a42, 'aria-expanded')).toBeNull(); |
| }); |
| |
| it('adds aria-level to tree items', () => { |
| expect(attr(a1, 'aria-level')).toBe('1'); |
| expect(attr(a21, 'aria-level')).toBe('2'); |
| expect(attr(a22, 'aria-level')).toBe('2'); |
| expect(attr(a31, 'aria-level')).toBe('3'); |
| expect(attr(a41, 'aria-level')).toBe('4'); |
| expect(attr(a42, 'aria-level')).toBe('4'); |
| }); |
| |
| it('focuses tree item on click', () => { |
| a1.click(); |
| expect(attr(a1, 'aria-expanded')).toBe('true'); |
| expect(attr(a1, 'aria-selected')).toBe('true'); |
| }); |
| |
| it('closes unfocused branches', () => { |
| a1.click(); |
| expect(attr(a1, 'aria-expanded')).toBe('true'); |
| expect(attr(a1, 'aria-selected')).toBe('true'); |
| a22.click(); |
| expect(attr(a22, 'aria-expanded')).toBe('true'); |
| expect(attr(a22, 'aria-selected')).toBe('true'); |
| a21.click(); |
| expect(attr(a22, 'aria-expanded')).toBe('false'); |
| expect(attr(a22, 'aria-selected')).toBe('false'); |
| }); |
| |
| it('navigates treeitems with the keyboard', () => { |
| keydown(a1, 'ArrowRight'); // expand a1 |
| expect(attr(a1, 'aria-expanded')).toBe('true'); |
| |
| keydown(a1, 'ArrowRight'); // focus a21 |
| expect(attr(a1, 'tabindex')).toBe('-1'); |
| expect(attr(a21, 'tabindex')).toBe('0'); |
| |
| keydown(a21, 'ArrowDown'); // focus a22 |
| expect(attr(a21, 'tabindex')).toBe('-1'); |
| expect(attr(a22, 'tabindex')).toBe('0'); |
| |
| keydown(a22, 'ArrowRight'); // expand a22 |
| expect(attr(a22, 'aria-expanded')).toBe('true'); |
| |
| keydown(a22, 'ArrowRight'); // focus a31 |
| expect(attr(a22, 'tabindex')).toBe('-1'); |
| expect(attr(a31, 'tabindex')).toBe('0'); |
| |
| keydown(a31, 'ArrowUp'); // focus a22 |
| expect(attr(a31, 'tabindex')).toBe('-1'); |
| expect(attr(a22, 'tabindex')).toBe('0'); |
| |
| keydown(a22, 'ArrowUp'); // focus a21 |
| expect(attr(a22, 'tabindex')).toBe('-1'); |
| expect(attr(a21, 'tabindex')).toBe('0'); |
| |
| keydown(a21, 'ArrowLeft'); // focus a1 |
| expect(attr(a21, 'tabindex')).toBe('-1'); |
| expect(attr(a1, 'tabindex')).toBe('0'); |
| |
| keydown(a1, 'End'); // focus a31 |
| expect(attr(a1, 'tabindex')).toBe('-1'); |
| expect(attr(a23, 'tabindex')).toBe('0'); |
| |
| keydown(a23, 'Home'); // focus a1 |
| expect(attr(a31, 'tabindex')).toBe('-1'); |
| expect(attr(a1, 'tabindex')).toBe('0'); |
| }); |
| |
| it('expands sibling items with * key', () => { |
| keydown(a1, 'ArrowRight'); |
| keydown(a1, 'ArrowDown'); |
| keydown(a21, '*'); |
| expect(attr(a22, 'aria-expanded')).toBe('true'); |
| expect(attr(a23, 'aria-expanded')).toBe('true'); |
| }); |
| |
| function a(href: string): HTMLElement { |
| return document.querySelector(`[href="${href}"]`); |
| } |
| |
| function attr(el: Element, name: string): string { |
| return el.getAttribute(name); |
| } |
| |
| function keydown(el: Element, key: string): void { |
| el.dispatchEvent(new KeyboardEvent('keydown', { key })); |
| } |