Skip to content

Commit

Permalink
feat(treeview): handle aria-expanded true on load
Browse files Browse the repository at this point in the history
See #268
  • Loading branch information
NickDJM authored May 8, 2024
1 parent cbc4b68 commit a01b53c
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 8 deletions.
24 changes: 22 additions & 2 deletions docs/api/treeview-toggle.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ The constructor will call [BaseMenuToggle's constructor](./base-menu-toggle#cons

## Initialize

The initialize method is inherited from the [BaseMenuToggle](./base-menu-toggle#initialize) class. There are no customizations for the TreeviewToggle class.
Initializes the menu toggle.

```js
TreeviewToggle.initialize();
```

The first steps are to ensure that the toggle and controlled menu have IDs using the [setIds](./base-menu-toggle#method--setIds) method, and to set the ARIA attributes on the toggle and controlled menu using the [setAriaAttributes](#method--setAriaAttributes) method.

Then the [open](./base-menu-toggle#method--open) or [collapse](./base-menu-toggle#method--collapse) method is called based on the state of the toggle's aria-expanded attribute.

## Properties

Expand All @@ -49,4 +57,16 @@ Getters and setters are inherited from the [BaseMenuToggle](./base-menu-toggle#g

## Methods

Methods are inherited from the [BaseMenuToggle](./base-menu-toggle#methods) class. There are no custom methods for the TreeviewToggle class.
Methods are inherited from the [BaseMenuToggle](./base-menu-toggle#methods) class. The following methods are unique to or overwritten in the TreeviewToggle class.

### _setAriaAttributes <badge type="warning" text="protected" /> {#method--setAriaAttributes}

Sets the ARIA attributes on the toggle and controlled menu.

```js
TreeviewToggle._setAriaAttributes();
```

The first steps are to ensure that the toggle has `aria-haspopup` set to "true", `aria-expanded` set to "false" if it's not already set explicitly to "true", and if the toggle element is not a `<button>`, set the `role` to "button".

Then using the toggle and menu's IDs, the menu's `aria-labelledby` is set to the toggle's ID, and the toggle's `aria-controls` is set to the menu's ID.
64 changes: 64 additions & 0 deletions src/treeviewToggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* global Treeview */

import BaseMenuToggle from "./_baseMenuToggle.js";
import { isTag } from "./validate.js";

/**
* A link or button that controls the visibility of a Treeview.
Expand Down Expand Up @@ -37,6 +38,69 @@ class TreeviewToggle extends BaseMenuToggle {
this.initialize();
}
}

/**
* Initializes the menu toggle.
*
* The first steps are to ensure that the toggle and controlled menu have IDs
* using the setIds method, and to set the ARIA attributes on the toggle
* and controlled menu using the setAriaAttributes method.
*
* Then the open or collapse method is called based on the state of the
* toggle's aria-expanded attribute.
*/
initialize() {
// Ensure both toggle and menu have IDs.
this._setIds();

// Set ARIA attributes.
this._setAriaAttributes();

// Open the menu if aria-expanded is true, otherwise collapse it.
if (this.dom.toggle.getAttribute("aria-expanded") === "true") {
this.open();
} else {
this._collapse(false);
}
}

/**
* Sets the ARIA attributes on the toggle and controlled menu.
*
* The first steps are to ensure that the toggle has `aria-haspopup`
* set to "true", `aria-expanded` set to "false" if it's not already
* set explicitly to "true", and if the toggle element is not a `<button>`,
* set the `role` to "button".
*
* Then using the toggle and menu's IDs, the menu's `aria-labelledby` is set to
* the toggle's ID, and the toggle's `aria-controls` is set to the menu's ID.
*
* @protected
*/
_setAriaAttributes() {
// Set up proper aria attributes.
this.dom.toggle.setAttribute("aria-haspopup", "true");

// If the toggle element doesn't have aria-expanded set to true, set it to false.
if (this.dom.toggle.getAttribute("aria-expanded") !== "true") {
this.dom.toggle.setAttribute("aria-expanded", "false");
}

// If the toggle element is a button, there's no need to add a role.
if (!isTag("button", { toggle: this.dom.toggle })) {
this.dom.toggle.setAttribute("role", "button");
}

// Set up proper aria label and control.
this.elements.controlledMenu.dom.menu.setAttribute(
"aria-labelledby",
this.dom.toggle.id
);
this.dom.toggle.setAttribute(
"aria-controls",
this.elements.controlledMenu.dom.menu.id
);
}
}

export default TreeviewToggle;
164 changes: 158 additions & 6 deletions tests/menus/TreeviewToggle/protected.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@
* Tests for protected methods in the TreeviewToggle class.
*/

import { describe, it, expect } from "vitest";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { twoLevel } from "../../../demo/menus.js";
import Treeview from "../../../src/treeview.js";
import TreeviewToggle from "../../../src/treeviewToggle.js";
import BaseMenuToggle from "../../../src/_baseMenuToggle.js";

beforeEach(() => {
document.body.innerHTML = twoLevel;
});

afterEach(() => {
document.body.innerHTML = "";
});

// Test TreeviewToggle protected methods.
describe("TreeviewToggle protected methods", () => {
// Test TreeviewToggle _expand().
Expand Down Expand Up @@ -38,13 +48,155 @@ describe("TreeviewToggle protected methods", () => {
});
});

// Test TreeviewToggle _setAriaAttributes().
describe("_setAriaAttributes", () => {
// Test that TreeviewToggle implements the BaseMenuToggle _setAriaAttributes.
it("should implement the BaseMenuToggle _setAriaAttributes", () => {
expect(TreeviewToggle.prototype._setAriaAttributes).toBe(
BaseMenuToggle.prototype._setAriaAttributes
// Test that _setAriaAttributes sets the toggle's aria-haspopup attribute to true.
it("should set the toggle's aria-haspopup attribute to true", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.removeAttribute("aria-haspopup");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's aria-haspopup attribute.
expect(menuToggle.dom.toggle.getAttribute("aria-haspopup")).toBe("true");
});

// Test that _setAriaAttributes sets the toggle's aria-expanded attribute to false if it is not set.
it("should set the toggle's aria-expanded attribute to false if it is not set", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.removeAttribute("aria-expanded");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's aria-expanded attribute.
expect(menuToggle.dom.toggle.getAttribute("aria-expanded")).toBe("false");
});

// Test that _setAriaAttributes does not change the toggle's aria-expanded attribute if it is already set to true.
it("should not change the toggle's aria-expanded attribute if it is already set to true", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.setAttribute("aria-expanded", "true");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's aria-expanded attribute.
expect(menuToggle.dom.toggle.getAttribute("aria-expanded")).toBe("true");
});

// Test that _setAriaAttributes sets the toggle's aria-expanded role to false if it is set to anything other than true.
it("should set the toggle's aria-expanded role to false if it is set to anything other than true", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.setAttribute("aria-expanded", "invalid");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's aria-expanded attribute.
expect(menuToggle.dom.toggle.getAttribute("aria-expanded")).toBe("false");
});

// Test that _setAriaAttributes sets the toggle's role attribute to button if the toggle is not a button.
// @todo Make a test for when the toggle _is_ a button. This will require a new menu template.
it("should set the toggle's role attribute to button if the toggle is not a button", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.removeAttribute("role");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's role attribute.
expect(menuToggle.dom.toggle.getAttribute("role")).toBe("button");
});

// Test that _setAriaAttributes sets the toggle's aria-controls attribute to the controlled menu's id.
it("should set the toggle's aria-controls attribute to the controlled menu's id", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.removeAttribute("aria-controls");

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the toggle's aria-controls attribute.
expect(menuToggle.dom.toggle.getAttribute("aria-controls")).toBe(
menuToggle.elements.controlledMenu.dom.menu.id
);
});

// Test that _setAriaAttributes sets the controlled menu's aria-labelledby attribute to the toggle's id.
it("should set the controlled menu's aria-labelledby attribute to the toggle's id", () => {
// Create a new Treeview instance for testing.
const menu = new Treeview({
menuElement: document.querySelector("ul"),
submenuItemSelector: "li.dropdown",
containerElement: document.querySelector("nav"),
controllerElement: document.querySelector("button"),
});

const menuToggle = menu.elements.submenuToggles[0];
menuToggle.dom.toggle.removeAttribute("id");
menuToggle.elements.controlledMenu.dom.menu.removeAttribute(
"aria-labelledby"
);

// Call _setAriaAttributes.
menuToggle._setAriaAttributes();

// Test the controlled menu's aria-labelledby attribute.
expect(
menuToggle.elements.controlledMenu.dom.menu.getAttribute(
"aria-labelledby"
)
).toBe(menuToggle.dom.toggle.id);
});
});
});

0 comments on commit a01b53c

Please sign in to comment.