diff --git a/core/bubbles/bubble.ts b/core/bubbles/bubble.ts index 64060fe7888..20e730abb18 100644 --- a/core/bubbles/bubble.ts +++ b/core/bubbles/bubble.ts @@ -98,8 +98,8 @@ export abstract class Bubble implements IBubble, ISelectable { * when automatically positioning. * @param overriddenFocusableElement An optional replacement to the focusable * element that's represented by this bubble (as a focusable node). This - * element will have its ID and tabindex overwritten. If not provided, the - * focusable element of this node will default to the bubble's SVG root. + * element will have its ID overwritten. If not provided, the focusable + * element of this node will default to the bubble's SVG root. */ constructor( public readonly workspace: WorkspaceSvg, @@ -138,7 +138,6 @@ export abstract class Bubble implements IBubble, ISelectable { this.focusableElement = overriddenFocusableElement ?? this.svgRoot; this.focusableElement.setAttribute('id', this.id); - this.focusableElement.setAttribute('tabindex', '-1'); browserEvents.conditionalBind( this.background, diff --git a/core/comments/rendered_workspace_comment.ts b/core/comments/rendered_workspace_comment.ts index 00359b07011..3a3d57a441d 100644 --- a/core/comments/rendered_workspace_comment.ts +++ b/core/comments/rendered_workspace_comment.ts @@ -65,7 +65,6 @@ export class RenderedWorkspaceComment this.view.setEditable(this.isEditable()); this.view.getSvgRoot().setAttribute('data-id', this.id); this.view.getSvgRoot().setAttribute('id', this.id); - this.view.getSvgRoot().setAttribute('tabindex', '-1'); this.addModelUpdateBindings(); diff --git a/core/field.ts b/core/field.ts index f7e01527e5d..c4b6514785e 100644 --- a/core/field.ts +++ b/core/field.ts @@ -312,7 +312,6 @@ export abstract class Field const id = this.id_; if (!id) throw new Error('Expected ID to be defined prior to init.'); this.fieldGroup_ = dom.createSvgElement(Svg.G, { - 'tabindex': '-1', 'id': id, }); if (!this.isVisible()) { diff --git a/core/flyout_base.ts b/core/flyout_base.ts index 9f94ec30905..492d3341762 100644 --- a/core/flyout_base.ts +++ b/core/flyout_base.ts @@ -22,7 +22,6 @@ import {FlyoutItem} from './flyout_item.js'; import {FlyoutMetricsManager} from './flyout_metrics_manager.js'; import {FlyoutNavigator} from './flyout_navigator.js'; import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js'; -import {getFocusManager} from './focus_manager.js'; import {IAutoHideable} from './interfaces/i_autohideable.js'; import type {IFlyout} from './interfaces/i_flyout.js'; import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js'; @@ -308,7 +307,6 @@ export abstract class Flyout // hide/show code will set up proper visibility and size later. this.svgGroup_ = dom.createSvgElement(tagName, { 'class': 'blocklyFlyout', - 'tabindex': '0', }); this.svgGroup_.style.display = 'none'; this.svgBackground_ = dom.createSvgElement( @@ -324,8 +322,6 @@ export abstract class Flyout .getThemeManager() .subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity'); - getFocusManager().registerTree(this); - return this.svgGroup_; } @@ -407,7 +403,6 @@ export abstract class Flyout if (this.svgGroup_) { dom.removeNode(this.svgGroup_); } - getFocusManager().unregisterTree(this); } /** @@ -971,15 +966,22 @@ export abstract class Flyout return null; } - /** See IFocusableNode.getFocusableElement. */ + /** + * See IFocusableNode.getFocusableElement. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ getFocusableElement(): HTMLElement | SVGElement { - if (!this.svgGroup_) throw new Error('Flyout DOM is not yet created.'); - return this.svgGroup_; + throw new Error('Flyouts are not directly focusable.'); } - /** See IFocusableNode.getFocusableTree. */ + /** + * See IFocusableNode.getFocusableTree. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ getFocusableTree(): IFocusableTree { - return this; + throw new Error('Flyouts are not directly focusable.'); } /** See IFocusableNode.onNodeFocus. */ @@ -990,31 +992,45 @@ export abstract class Flyout /** See IFocusableNode.canBeFocused. */ canBeFocused(): boolean { - return true; + return false; } - /** See IFocusableTree.getRootFocusableNode. */ + /** + * See IFocusableNode.getRootFocusableNode. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ getRootFocusableNode(): IFocusableNode { - return this; + throw new Error('Flyouts are not directly focusable.'); } - /** See IFocusableTree.getRestoredFocusableNode. */ + /** + * See IFocusableNode.getRestoredFocusableNode. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ getRestoredFocusableNode( _previousNode: IFocusableNode | null, ): IFocusableNode | null { - return null; + throw new Error('Flyouts are not directly focusable.'); } - /** See IFocusableTree.getNestedTrees. */ + /** + * See IFocusableNode.getNestedTrees. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ getNestedTrees(): Array { - return [this.workspace_]; + throw new Error('Flyouts are not directly focusable.'); } - /** See IFocusableTree.lookUpFocusableNode. */ + /** + * See IFocusableNode.lookUpFocusableNode. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ lookUpFocusableNode(_id: string): IFocusableNode | null { - // No focusable node needs to be returned since the flyout's subtree is a - // workspace that will manage its own focusable state. - return null; + throw new Error('Flyouts are not directly focusable.'); } /** See IFocusableTree.onTreeFocus. */ @@ -1023,15 +1039,12 @@ export abstract class Flyout _previousTree: IFocusableTree | null, ): void {} - /** See IFocusableTree.onTreeBlur. */ - onTreeBlur(nextTree: IFocusableTree | null): void { - const toolbox = this.targetWorkspace.getToolbox(); - // If focus is moving to either the toolbox or the flyout's workspace, do - // not close the flyout. For anything else, do close it since the flyout is - // no longer focused. - if (toolbox && nextTree === toolbox) return; - if (nextTree === this.workspace_) return; - if (toolbox) toolbox.clearSelection(); - this.autoHide(false); + /** + * See IFocusableNode.onTreeBlur. + * + * @deprecated v12: Use the Flyout's workspace for focus operations, instead. + */ + onTreeBlur(_nextTree: IFocusableTree | null): void { + throw new Error('Flyouts are not directly focusable.'); } } diff --git a/core/flyout_button.ts b/core/flyout_button.ts index 823b57be765..c9afb8b0159 100644 --- a/core/flyout_button.ts +++ b/core/flyout_button.ts @@ -113,7 +113,7 @@ export class FlyoutButton this.id = idGenerator.getNextUniqueId(); this.svgGroup = dom.createSvgElement( Svg.G, - {'id': this.id, 'class': cssClass, 'tabindex': '-1'}, + {'id': this.id, 'class': cssClass}, this.workspace.getCanvas(), ); diff --git a/core/focus_manager.ts b/core/focus_manager.ts index 198e1f0747d..01be4813f90 100644 --- a/core/focus_manager.ts +++ b/core/focus_manager.ts @@ -17,6 +17,24 @@ import {FocusableTreeTraverser} from './utils/focusable_tree_traverser.js'; */ export type ReturnEphemeralFocus = () => void; +/** + * Represents an IFocusableTree that has been registered for focus management in + * FocusManager. + */ +class TreeRegistration { + /** + * Constructs a new TreeRegistration. + * + * @param tree The tree being registered. + * @param rootShouldBeAutoTabbable Whether the tree should have automatic + * top-level tab management. + */ + constructor( + readonly tree: IFocusableTree, + readonly rootShouldBeAutoTabbable: boolean, + ) {} +} + /** * A per-page singleton that manages Blockly focus across one or more * IFocusableTrees, and bidirectionally synchronizes this focus with the DOM. @@ -58,7 +76,7 @@ export class FocusManager { private focusedNode: IFocusableNode | null = null; private previouslyFocusedNode: IFocusableNode | null = null; - private registeredTrees: Array = []; + private registeredTrees: Array = []; private currentlyHoldsEphemeralFocus: boolean = false; private lockFocusStateChanges: boolean = false; @@ -79,7 +97,8 @@ export class FocusManager { // If the target losing or gaining focus maps to any tree, then it // should be updated. Per the contract of findFocusableNodeFor only one // tree should claim the element, so the search can be exited early. - for (const tree of this.registeredTrees) { + for (const reg of this.registeredTrees) { + const tree = reg.tree; newNode = FocusableTreeTraverser.findFocusableNodeFor(element, tree); if (newNode) break; } @@ -132,13 +151,32 @@ export class FocusManager { * This function throws if the provided tree is already currently registered * in this manager. Use isRegistered to check in cases when it can't be * certain whether the tree has been registered. + * + * The tree's registration can be customized to configure automatic tab stops. + * This specifically provides capability for the user to be able to tab + * navigate to the root of the tree but only when the tree doesn't hold active + * focus. If this functionality is disabled then the tree's root will + * automatically be made focusable (but not tabbable) when it is first focused + * in the same way as any other focusable node. + * + * @param tree The IFocusableTree to register. + * @param rootShouldBeAutoTabbable Whether the root of this tree should be + * added as a top-level page tab stop when it doesn't hold active focus. */ - registerTree(tree: IFocusableTree): void { + registerTree( + tree: IFocusableTree, + rootShouldBeAutoTabbable: boolean = false, + ): void { this.ensureManagerIsUnlocked(); if (this.isRegistered(tree)) { throw Error(`Attempted to re-register already registered tree: ${tree}.`); } - this.registeredTrees.push(tree); + this.registeredTrees.push( + new TreeRegistration(tree, rootShouldBeAutoTabbable), + ); + if (rootShouldBeAutoTabbable) { + tree.getRootFocusableNode().getFocusableElement().tabIndex = 0; + } } /** @@ -147,7 +185,15 @@ export class FocusManager { * unregisterTree. */ isRegistered(tree: IFocusableTree): boolean { - return this.registeredTrees.findIndex((reg) => reg === tree) !== -1; + return !!this.lookUpRegistration(tree); + } + + /** + * Returns the TreeRegistration for the specified tree, or null if the tree is + * not currently registered. + */ + private lookUpRegistration(tree: IFocusableTree): TreeRegistration | null { + return this.registeredTrees.find((reg) => reg.tree === tree) ?? null; } /** @@ -158,13 +204,19 @@ export class FocusManager { * * This function throws if the provided tree is not currently registered in * this manager. + * + * This function will reset the tree's root element tabindex if the tree was + * registered with automatic tab management. */ unregisterTree(tree: IFocusableTree): void { this.ensureManagerIsUnlocked(); if (!this.isRegistered(tree)) { throw Error(`Attempted to unregister not registered tree: ${tree}.`); } - const treeIndex = this.registeredTrees.findIndex((reg) => reg === tree); + const treeIndex = this.registeredTrees.findIndex( + (reg) => reg.tree === tree, + ); + const registration = this.registeredTrees[treeIndex]; this.registeredTrees.splice(treeIndex, 1); const focusedNode = FocusableTreeTraverser.findFocusedNode(tree); @@ -174,6 +226,13 @@ export class FocusManager { this.updateFocusedNode(null); } this.removeHighlight(root); + + if (registration.rootShouldBeAutoTabbable) { + tree + .getRootFocusableNode() + .getFocusableElement() + .removeAttribute('tabindex'); + } } /** @@ -240,11 +299,15 @@ export class FocusManager { * canBeFocused() method returns false), it will be ignored and any existing * focus state will remain unchanged. * + * Note that this may update the specified node's element's tabindex to ensure + * that it can be properly read out by screenreaders while focused. + * * @param focusableNode The node that should receive active focus. */ focusNode(focusableNode: IFocusableNode): void { this.ensureManagerIsUnlocked(); - if (!this.currentlyHoldsEphemeralFocus) { + const mustRestoreUpdatingNode = !this.currentlyHoldsEphemeralFocus; + if (mustRestoreUpdatingNode) { // Disable state syncing from DOM events since possible calls to focus() // below will loop a call back to focusNode(). this.isUpdatingFocusedNode = true; @@ -258,12 +321,21 @@ export class FocusManager { const prevFocusedElement = this.focusedNode?.getFocusableElement(); const hasDesyncedState = prevFocusedElement !== document.activeElement; if (this.focusedNode === focusableNode && !hasDesyncedState) { + if (mustRestoreUpdatingNode) { + // Reenable state syncing from DOM events. + this.isUpdatingFocusedNode = false; + } return; // State is unchanged. } if (!focusableNode.canBeFocused()) { // This node can't be focused. console.warn("Trying to focus a node that can't be focused."); + + if (mustRestoreUpdatingNode) { + // Reenable state syncing from DOM events. + this.isUpdatingFocusedNode = false; + } return; } @@ -312,7 +384,7 @@ export class FocusManager { this.activelyFocusNode(nodeToFocus, prevTree ?? null); } this.updateFocusedNode(nodeToFocus); - if (!this.currentlyHoldsEphemeralFocus) { + if (mustRestoreUpdatingNode) { // Reenable state syncing from DOM events. this.isUpdatingFocusedNode = false; } @@ -448,14 +520,38 @@ export class FocusManager { // node's focusable element (which *is* allowed to be invisible until the // node needs to be focused). this.lockFocusStateChanges = true; - if (node.getFocusableTree() !== prevTree) { - node.getFocusableTree().onTreeFocus(node, prevTree); + const tree = node.getFocusableTree(); + const elem = node.getFocusableElement(); + const nextTreeReg = this.lookUpRegistration(tree); + const treeIsTabManaged = nextTreeReg?.rootShouldBeAutoTabbable; + if (tree !== prevTree) { + tree.onTreeFocus(node, prevTree); + + if (treeIsTabManaged) { + // If this node's tree has its tab auto-managed, ensure that it's no + // longer tabbable now that it holds active focus. + tree.getRootFocusableNode().getFocusableElement().tabIndex = -1; + } } node.onNodeFocus(); this.lockFocusStateChanges = false; + // The tab index should be set in all cases where: + // - It doesn't overwrite an pre-set tab index for the node. + // - The node is part of a tree whose tab index is unmanaged. + // OR + // - The node is part of a managed tree but this isn't the root. Managed + // roots are ignored since they are always overwritten to have a tab index + // of -1 with active focus so that they cannot be tab navigated. + // + // Setting the tab index ensures that the node's focusable element can + // actually receive DOM focus. + if (!treeIsTabManaged || node !== tree.getRootFocusableNode()) { + if (!elem.hasAttribute('tabindex')) elem.tabIndex = -1; + } + this.setNodeToVisualActiveFocus(node); - node.getFocusableElement().focus(); + elem.focus(); } /** @@ -475,13 +571,21 @@ export class FocusManager { nextTree: IFocusableTree | null, ): void { this.lockFocusStateChanges = true; - if (node.getFocusableTree() !== nextTree) { - node.getFocusableTree().onTreeBlur(nextTree); + const tree = node.getFocusableTree(); + if (tree !== nextTree) { + tree.onTreeBlur(nextTree); + + const reg = this.lookUpRegistration(tree); + if (reg?.rootShouldBeAutoTabbable) { + // If this node's tree has its tab auto-managed, ensure that it's now + // tabbable since it no longer holds active focus. + tree.getRootFocusableNode().getFocusableElement().tabIndex = 0; + } } node.onNodeBlur(); this.lockFocusStateChanges = false; - if (node.getFocusableTree() !== nextTree) { + if (tree !== nextTree) { this.setNodeToVisualPassiveFocus(node); } } diff --git a/core/icons/icon.ts b/core/icons/icon.ts index 67547ee313e..8f8ff70fc32 100644 --- a/core/icons/icon.ts +++ b/core/icons/icon.ts @@ -59,7 +59,6 @@ export abstract class Icon implements IIcon { const svgBlock = this.sourceBlock as BlockSvg; this.svgRoot = dom.createSvgElement(Svg.G, { 'class': 'blocklyIconGroup', - 'tabindex': '-1', 'id': this.id, }); svgBlock.getSvgRoot().appendChild(this.svgRoot); diff --git a/core/interfaces/i_focusable_node.ts b/core/interfaces/i_focusable_node.ts index b21d7741a5c..00557168afa 100644 --- a/core/interfaces/i_focusable_node.ts +++ b/core/interfaces/i_focusable_node.ts @@ -19,13 +19,11 @@ export interface IFocusableNode { * - blocklyActiveFocus * - blocklyPassiveFocus * - * The returned element must also have a valid ID specified, and unique across - * the entire page. Failing to have a properly unique ID could result in - * trying to focus one node (such as via a mouse click) leading to another - * node with the same ID actually becoming focused by FocusManager. The - * returned element must also have a negative tabindex (since the focus - * manager itself will manage its tab index and a tab index must be present in - * order for the element to be focusable in the DOM). + * The returned element must also have a valid ID specified, and this ID + * should be unique across the entire page. Failing to have a properly unique + * ID could result in trying to focus one node (such as via a mouse click) + * leading to another node with the same ID actually becoming focused by + * FocusManager. * * The returned element must be visible if the node is ever focused via * FocusManager.focusNode() or FocusManager.focusTree(). It's allowed for an @@ -34,7 +32,11 @@ export interface IFocusableNode { * * It's expected the actual returned element will not change for the lifetime * of the node (that is, its properties can change but a new element should - * never be returned). + * never be returned). Also, the returned element will have its tabindex + * overwritten throughout the lifecycle of this node and FocusManager. + * + * If a node requires the ability to be focused directly without first being + * focused via FocusManager then it must set its own tab index. * * @returns The HTMLElement or SVGElement which can both receive focus and be * visually represented as actively or passively focused for this node. diff --git a/core/renderers/common/path_object.ts b/core/renderers/common/path_object.ts index 7efc6318a31..f6291b9f0fa 100644 --- a/core/renderers/common/path_object.ts +++ b/core/renderers/common/path_object.ts @@ -50,7 +50,7 @@ export class PathObject implements IPathObject { /** The primary path of the block. */ this.svgPath = dom.createSvgElement( Svg.PATH, - {'class': 'blocklyPath', 'tabindex': '-1'}, + {'class': 'blocklyPath'}, this.svgRoot, ); @@ -239,7 +239,6 @@ export class PathObject implements IPathObject { 'id': connection.id, 'class': 'blocklyHighlightedConnectionPath', 'style': 'display: none;', - 'tabindex': '-1', 'd': connectionPath, 'transform': transformation, }, diff --git a/core/toolbox/category.ts b/core/toolbox/category.ts index fc7d1aa03cf..7b0db7b3fcd 100644 --- a/core/toolbox/category.ts +++ b/core/toolbox/category.ts @@ -225,6 +225,8 @@ export class ToolboxCategory */ protected createContainer_(): HTMLDivElement { const container = document.createElement('div'); + // Ensure that the category has a tab index to ensure it receives focus when + // clicked (since clicking isn't managed by the toolbox). container.tabIndex = -1; container.id = this.getId(); const className = this.cssConfig_['container']; diff --git a/core/toolbox/separator.ts b/core/toolbox/separator.ts index 44ae358cf53..cd5ed245a04 100644 --- a/core/toolbox/separator.ts +++ b/core/toolbox/separator.ts @@ -54,6 +54,8 @@ export class ToolboxSeparator extends ToolboxItem { */ protected createDom_(): HTMLDivElement { const container = document.createElement('div'); + // Ensure that the separator has a tab index to ensure it receives focus + // when clicked (since clicking isn't managed by the toolbox). container.tabIndex = -1; container.id = this.getId(); const className = this.cssConfig_['container']; diff --git a/core/toolbox/toolbox.ts b/core/toolbox/toolbox.ts index 0fbb231dc56..57e849ce264 100644 --- a/core/toolbox/toolbox.ts +++ b/core/toolbox/toolbox.ts @@ -22,7 +22,10 @@ import '../events/events_toolbox_item_select.js'; import {EventType} from '../events/type.js'; import * as eventUtils from '../events/utils.js'; import {getFocusManager} from '../focus_manager.js'; -import type {IAutoHideable} from '../interfaces/i_autohideable.js'; +import { + isAutoHideable, + type IAutoHideable, +} from '../interfaces/i_autohideable.js'; import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_item.js'; import {isDeletable} from '../interfaces/i_deletable.js'; import type {IDraggable} from '../interfaces/i_draggable.js'; @@ -169,7 +172,7 @@ export class Toolbox ComponentManager.Capability.DRAG_TARGET, ], }); - getFocusManager().registerTree(this); + getFocusManager().registerTree(this, true); } /** @@ -200,7 +203,6 @@ export class Toolbox */ protected createContainer_(): HTMLDivElement { const toolboxContainer = document.createElement('div'); - toolboxContainer.tabIndex = 0; toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v'); dom.addClass(toolboxContainer, 'blocklyToolbox'); toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR'); @@ -1142,7 +1144,16 @@ export class Toolbox } /** See IFocusableTree.onTreeBlur. */ - onTreeBlur(_nextTree: IFocusableTree | null): void {} + onTreeBlur(nextTree: IFocusableTree | null): void { + // If navigating to anything other than the toolbox's flyout then clear the + // selection so that the toolbox's flyout can automatically close. + if (!nextTree || nextTree !== this.flyout?.getWorkspace()) { + this.clearSelection(); + if (this.flyout && isAutoHideable(this.flyout)) { + this.flyout.autoHide(false); + } + } + } } /** CSS for Toolbox. See css.js for use. */ diff --git a/core/workspace_svg.ts b/core/workspace_svg.ts index 3e8731afd4b..5d5a40ccc5f 100644 --- a/core/workspace_svg.ts +++ b/core/workspace_svg.ts @@ -762,8 +762,6 @@ export class WorkspaceSvg */ this.svgGroup_ = dom.createSvgElement(Svg.G, { 'class': 'blocklyWorkspace', - // Only the top-level workspace should be tabbable. - 'tabindex': injectionDiv ? '0' : '-1', 'id': this.id, }); if (injectionDiv) { @@ -849,7 +847,8 @@ export class WorkspaceSvg isParentWorkspace ? this.getInjectionDiv() : undefined, ); - getFocusManager().registerTree(this); + // Only the top-level and flyout workspaces should be tabbable. + getFocusManager().registerTree(this, !!this.injectionDiv || this.isFlyout); return this.svgGroup_; } @@ -2807,13 +2806,12 @@ export class WorkspaceSvg /** See IFocusableTree.onTreeBlur. */ onTreeBlur(nextTree: IFocusableTree | null): void { // If the flyout loses focus, make sure to close it unless focus is being - // lost to a different element on the page. - if (nextTree && this.isFlyout && this.targetWorkspace) { + // lost to the toolbox. + if (this.isFlyout && this.targetWorkspace) { // Only hide the flyout if the flyout's workspace is losing focus and that // focus isn't returning to the flyout itself or the toolbox. const flyout = this.targetWorkspace.getFlyout(); const toolbox = this.targetWorkspace.getToolbox(); - if (flyout && nextTree === flyout) return; if (toolbox && nextTree === toolbox) return; if (toolbox) toolbox.clearSelection(); if (flyout && isAutoHideable(flyout)) flyout.autoHide(false); diff --git a/tests/mocha/focus_manager_test.js b/tests/mocha/focus_manager_test.js index cd89d1351b2..3a1fc98a7e5 100644 --- a/tests/mocha/focus_manager_test.js +++ b/tests/mocha/focus_manager_test.js @@ -80,81 +80,86 @@ suite('FocusManager', function () { const ACTIVE_FOCUS_NODE_CSS_SELECTOR = `.${FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME}`; const PASSIVE_FOCUS_NODE_CSS_SELECTOR = `.${FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME}`; - const createFocusableTree = function (rootElementId, nestedTrees) { - return new FocusableTreeImpl( - document.getElementById(rootElementId), - nestedTrees || [], - ); - }; - const createFocusableNode = function (tree, elementId) { - return tree.addNode(document.getElementById(elementId)); - }; - setup(function () { sharedTestSetup.call(this); - this.focusManager = getFocusManager(); - this.testFocusableTree1 = createFocusableTree('testFocusableTree1'); - this.testFocusableTree1Node1 = createFocusableNode( + this.allFocusableTrees = []; + this.allFocusableNodes = []; + this.createFocusableTree = function (rootElementId, nestedTrees) { + const tree = new FocusableTreeImpl( + document.getElementById(rootElementId), + nestedTrees || [], + ); + this.allFocusableTrees.push(tree); + return tree; + }; + this.createFocusableNode = function (tree, elementId) { + const node = tree.addNode(document.getElementById(elementId)); + this.allFocusableNodes.push(node); + return node; + }; + + this.testFocusableTree1 = this.createFocusableTree('testFocusableTree1'); + this.testFocusableTree1Node1 = this.createFocusableNode( this.testFocusableTree1, 'testFocusableTree1.node1', ); - this.testFocusableTree1Node1Child1 = createFocusableNode( + this.testFocusableTree1Node1Child1 = this.createFocusableNode( this.testFocusableTree1, 'testFocusableTree1.node1.child1', ); - this.testFocusableTree1Node2 = createFocusableNode( + this.testFocusableTree1Node2 = this.createFocusableNode( this.testFocusableTree1, 'testFocusableTree1.node2', ); - this.testFocusableNestedTree4 = createFocusableTree( + this.testFocusableNestedTree4 = this.createFocusableTree( 'testFocusableNestedTree4', ); - this.testFocusableNestedTree4Node1 = createFocusableNode( + this.testFocusableNestedTree4Node1 = this.createFocusableNode( this.testFocusableNestedTree4, 'testFocusableNestedTree4.node1', ); - this.testFocusableNestedTree5 = createFocusableTree( + this.testFocusableNestedTree5 = this.createFocusableTree( 'testFocusableNestedTree5', ); - this.testFocusableNestedTree5Node1 = createFocusableNode( + this.testFocusableNestedTree5Node1 = this.createFocusableNode( this.testFocusableNestedTree5, 'testFocusableNestedTree5.node1', ); - this.testFocusableTree2 = createFocusableTree('testFocusableTree2', [ + this.testFocusableTree2 = this.createFocusableTree('testFocusableTree2', [ this.testFocusableNestedTree4, this.testFocusableNestedTree5, ]); - this.testFocusableTree2Node1 = createFocusableNode( + this.testFocusableTree2Node1 = this.createFocusableNode( this.testFocusableTree2, 'testFocusableTree2.node1', ); - this.testFocusableGroup1 = createFocusableTree('testFocusableGroup1'); - this.testFocusableGroup1Node1 = createFocusableNode( + this.testFocusableGroup1 = this.createFocusableTree('testFocusableGroup1'); + this.testFocusableGroup1Node1 = this.createFocusableNode( this.testFocusableGroup1, 'testFocusableGroup1.node1', ); - this.testFocusableGroup1Node1Child1 = createFocusableNode( + this.testFocusableGroup1Node1Child1 = this.createFocusableNode( this.testFocusableGroup1, 'testFocusableGroup1.node1.child1', ); - this.testFocusableGroup1Node2 = createFocusableNode( + this.testFocusableGroup1Node2 = this.createFocusableNode( this.testFocusableGroup1, 'testFocusableGroup1.node2', ); - this.testFocusableNestedGroup4 = createFocusableTree( + this.testFocusableNestedGroup4 = this.createFocusableTree( 'testFocusableNestedGroup4', ); - this.testFocusableNestedGroup4Node1 = createFocusableNode( + this.testFocusableNestedGroup4Node1 = this.createFocusableNode( this.testFocusableNestedGroup4, 'testFocusableNestedGroup4.node1', ); - this.testFocusableGroup2 = createFocusableTree('testFocusableGroup2', [ + this.testFocusableGroup2 = this.createFocusableTree('testFocusableGroup2', [ this.testFocusableNestedGroup4, ]); - this.testFocusableGroup2Node1 = createFocusableNode( + this.testFocusableGroup2Node1 = this.createFocusableNode( this.testFocusableGroup2, 'testFocusableGroup2.node1', ); @@ -177,6 +182,19 @@ suite('FocusManager', function () { elem.classList.remove(FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME); } + // Ensure any set tab indexes are properly reset between tests. + for (const tree of this.allFocusableTrees) { + tree + .getRootFocusableNode() + .getFocusableElement() + .removeAttribute('tabindex'); + } + for (const node of this.allFocusableNodes) { + node.getFocusableElement().removeAttribute('tabindex'); + } + this.allFocusableTrees = []; + this.allFocusableNodes = []; + // Reset the current active element. document.body.focus(); }); @@ -230,6 +248,44 @@ suite('FocusManager', function () { // The second register should not fail since the tree was previously unregistered. }); + + test('for unmanaged tree does not overwrite tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + assert.isNull(rootElem.getAttribute('tabindex')); + }); + + test('for unmanaged tree with custom tab index does not overwrite tab index', function () { + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = -1; + + this.focusManager.registerTree(this.testFocusableTree1, false); + + // The custom tab index shouldn't be overwritten for an unmanaged tree. + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('for managed tree overwrites root tab index to be tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '0'); + }); + + test('for managed tree with custom tab index overwrites root tab index to be tab navigable', function () { + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = -1; + + this.focusManager.registerTree(this.testFocusableTree1, true); + + // A custom tab index should be overwritten for a managed tree. + assert.strictEqual(rootElem.getAttribute('tabindex'), '0'); + }); }); suite('unregisterTree()', function () { @@ -259,6 +315,41 @@ suite('FocusManager', function () { errorMsgRegex, ); }); + + test('for unmanaged tree with custom tab index does not change tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = -1; + + this.focusManager.unregisterTree(this.testFocusableTree1); + + // Unregistering an unmanaged tree shouldn't change its tab index. + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('for managed tree removes tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + + this.focusManager.unregisterTree(this.testFocusableTree1); + + // Unregistering a managed tree should remove its tab index. + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + assert.isNull(rootElem.getAttribute('tabindex')); + }); + + test('for managed tree with custom tab index removes tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = -1; + + this.focusManager.unregisterTree(this.testFocusableTree1); + + // Unregistering a managed tree should remove its tab index. + assert.isNull(rootElem.getAttribute('tabindex')); + }); }); suite('isRegistered()', function () { @@ -330,6 +421,17 @@ suite('FocusManager', function () { assert.isNull(focusedNode); }); + + test('after focusing unfocusable node returns null', function () { + this.testFocusableTree1Node1.canBeFocused = () => false; + this.focusManager.registerTree(this.testFocusableTree1); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + const focusedNode = this.focusManager.getFocusedNode(); + + // Unfocusable nodes should not be focused. + assert.isNull(focusedNode); + }); }); suite('focusTree()', function () { @@ -353,6 +455,15 @@ suite('FocusManager', function () { }); }); + test('unfocused node does not have a tab index by default', function () { + const elem = this.testFocusableTree1Node1.getFocusableElement(); + + // This is slightly testing the test setup, but it acts as a precondition sanity test for the + // other tab index tests below. Important: 'getAttribute' is used here since direct access to + // 'tabIndex' can default the value returned even when the tab index isn't set. + assert.isNull(elem.getAttribute('tabindex')); + }); + suite('focusNode()', function () { test('for not registered node throws', function () { const errorMsgRegex = /Attempted to focus unregistered node.+?/; @@ -504,6 +615,210 @@ suite('FocusManager', function () { assert.strictEqual(this.testFocusableTree1.onTreeBlur.callCount, 1); }); + + test('for same node twice calls onNodeFocus once', function () { + sinon.spy(this.testFocusableTree1Node1, 'onNodeFocus'); + this.focusManager.registerTree(this.testFocusableTree1); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Call focus for the same node a second time. + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Despite two calls to focus the node should only focus once. + assert.strictEqual(this.testFocusableTree1Node1.onNodeFocus.callCount, 1); + }); + + test('for unfocusable node does not call onNodeFocus', function () { + sinon.spy(this.testFocusableTree1Node1, 'onNodeFocus'); + this.testFocusableTree1Node1.canBeFocused = () => false; + this.focusManager.registerTree(this.testFocusableTree1); + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Unfocusable nodes should not be focused, nor have their callbacks called. + assert.strictEqual(this.testFocusableTree1Node1.onNodeFocus.callCount, 0); + }); + + test('for unfocused node overwrites tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1); + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Focusing an element should overwrite its tab index. + const elem = this.testFocusableTree1Node1.getFocusableElement(); + assert.strictEqual(elem.getAttribute('tabindex'), '-1'); + }); + + test('for previously focused node keeps new tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + this.focusManager.focusNode(this.testFocusableTree1Node2); + + // The previously focused element should retain its tab index. + const elem = this.testFocusableTree1Node1.getFocusableElement(); + assert.strictEqual(elem.getAttribute('tabindex'), '-1'); + }); + + test('for node with custom tab index does not change tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1); + const elem = this.testFocusableTree1Node1.getFocusableElement(); + elem.tabIndex = 0; + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // If the node already has a tab index set then it should retain that index. + assert.strictEqual(elem.getAttribute('tabindex'), '0'); + }); + + suite('for unmanaged tree', function () { + test('focused root overwrites tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + + this.focusManager.focusNode(rootNode); + + // Focusing an unmanaged tree's root should overwrite its tab index. + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('focused root with custom tab index does not change tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = 0; + + this.focusManager.focusNode(rootNode); + + // If the node already has a tab index set then it should retain that index. + assert.strictEqual(rootElem.getAttribute('tabindex'), '0'); + }); + + test('focused node in a tree after unmanaged was focused should keep previous root unchanged', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + this.focusManager.registerTree(this.testFocusableTree2, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + this.focusManager.focusNode(this.testFocusableTree2Node1); + + // Focusing a different tree shouldn't change the root of the previous tree if it's unmanaged. + const rootElem = rootNode.getFocusableElement(); + assert.isNull(rootElem.getAttribute('tabindex')); + }); + + test('focused node in a tree after unmanaged was root focused should make previous root tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, false); + this.focusManager.registerTree(this.testFocusableTree2, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + this.focusManager.focusTree(this.testFocusableTree1); + + this.focusManager.focusNode(this.testFocusableTree2Node1); + + // The previous tree's root should be kept unchanged (since it was managed). + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + }); + + suite('for managed tree', function () { + test('for unfocused node in managed tree overwrites tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Focusing an element should overwrite its tab index. + const elem = this.testFocusableTree1Node1.getFocusableElement(); + assert.strictEqual(elem.getAttribute('tabindex'), '-1'); + }); + + test('for previously focused node in managed tree keeps new tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + this.focusManager.focusNode(this.testFocusableTree1Node2); + + // The previously focused element should retain its tab index. + const elem = this.testFocusableTree1Node1.getFocusableElement(); + assert.strictEqual(elem.getAttribute('tabindex'), '-1'); + }); + + test('focused root makes root non-tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + + this.focusManager.focusNode(rootNode); + + // Focusing the root in a managed tree should make it non-tab navigable. + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('focused root with custom tab index should overwrite tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = 0; + + this.focusManager.focusNode(rootNode); + + // Custom tab indexes are overwritten for the root in a managed tree. + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('focused node tree root makes root non-tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Focusing a node of a managed tree should make the root non-tab navigable. + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('focused node root with custom tab index should overwrite tab index', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + const rootElem = rootNode.getFocusableElement(); + rootElem.tabIndex = 0; + + this.focusManager.focusNode(this.testFocusableTree1Node1); + + // Custom tab indexes are overwritten for the root in a managed tree even when a tree's node + // is focused. + assert.strictEqual(rootElem.getAttribute('tabindex'), '-1'); + }); + + test('focused node in a tree after managed was focused should make previous root tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + this.focusManager.registerTree(this.testFocusableTree2, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + this.focusManager.focusNode(this.testFocusableTree2Node1); + + // Focusing a different tree shouldn't after a managed tree should make the managed tree tab + // navigable. + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '0'); + }); + + test('focused node in a tree after managed was root focused should make previous root tab navigable', function () { + this.focusManager.registerTree(this.testFocusableTree1, true); + this.focusManager.registerTree(this.testFocusableTree2, false); + const rootNode = this.testFocusableTree1.getRootFocusableNode(); + this.focusManager.focusTree(this.testFocusableTree1); + + this.focusManager.focusNode(this.testFocusableTree2Node1); + + // Focusing a different tree shouldn't after a managed tree should make the managed tree tab + // navigable. + const rootElem = rootNode.getFocusableElement(); + assert.strictEqual(rootElem.getAttribute('tabindex'), '0'); + }); + }); }); suite('getFocusManager()', function () { @@ -950,8 +1265,8 @@ suite('FocusManager', function () { nodeElem.textContent = 'Focusable node'; rootElem.appendChild(nodeElem); document.body.appendChild(rootElem); - const root = createFocusableTree('focusRoot'); - const node = createFocusableNode(root, 'focusNode'); + const root = this.createFocusableTree('focusRoot'); + const node = this.createFocusableNode(root, 'focusNode'); this.focusManager.registerTree(root); this.focusManager.focusNode(node); @@ -1424,6 +1739,7 @@ suite('FocusManager', function () { suite('getFocusedTree()', function () { test('registered root focus()ed no prev focus returns tree', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); @@ -1435,6 +1751,7 @@ suite('FocusManager', function () { test("registered node focus()ed no prev focus returns node's tree", function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); @@ -1446,6 +1763,8 @@ suite('FocusManager', function () { test("registered subnode focus()ed no prev focus returns node's tree", function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1.child1').tabIndex = + -1; document.getElementById('testFocusableTree1.node1.child1').focus(); @@ -1457,6 +1776,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus returns same tree', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree1.node2').focus(); @@ -1470,6 +1791,8 @@ suite('FocusManager', function () { test("registered node focus()ed after prev node focus diff tree returns new node's tree", function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1483,6 +1806,8 @@ suite('FocusManager', function () { test("registered tree root focus()ed after prev node focus diff tree returns new node's tree", function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2').focus(); @@ -1495,6 +1820,9 @@ suite('FocusManager', function () { test("non-registered node subelement focus()ed returns node's tree", function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById( + 'testFocusableTree1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableTree1.node2.unregisteredChild1') @@ -1508,12 +1836,18 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed returns null', function () { + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3').focus(); assert.isNull(this.focusManager.getFocusedTree()); }); test('non-registered tree node focus()ed returns null', function () { + document.getElementById( + 'testUnregisteredFocusableTree3.node1', + ).tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3.node1').focus(); assert.isNull(this.focusManager.getFocusedTree()); @@ -1521,6 +1855,10 @@ suite('FocusManager', function () { test('non-registered tree node focus()ed after registered node focused returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById( + 'testUnregisteredFocusableTree3.node1', + ).tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testUnregisteredFocusableTree3.node1').focus(); @@ -1530,6 +1868,7 @@ suite('FocusManager', function () { test('unfocusable element focus()ed after registered node focused returns original tree', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testUnfocusableElement').focus(); @@ -1542,6 +1881,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1551,6 +1891,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1561,6 +1902,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior focused returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableTree1.node1').focus(); @@ -1573,6 +1916,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently focused returns new tree', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1588,6 +1933,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1603,6 +1951,7 @@ suite('FocusManager', function () { test('nested tree focusTree()ed with no prev focus returns nested tree', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4').tabIndex = -1; document.getElementById('testFocusableNestedTree4').focus(); @@ -1615,6 +1964,7 @@ suite('FocusManager', function () { test('nested tree node focusNode()ed with no prev focus returns nested tree', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -1627,6 +1977,8 @@ suite('FocusManager', function () { test('nested tree node focusNode()ed after parent focused returns nested tree', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -1640,6 +1992,7 @@ suite('FocusManager', function () { suite('getFocusedNode()', function () { test('registered root focus()ed no prev focus returns root node', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); @@ -1651,6 +2004,7 @@ suite('FocusManager', function () { test('registered node focus()ed no prev focus returns node', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); @@ -1662,6 +2016,8 @@ suite('FocusManager', function () { test('registered subnode focus()ed no prev focus returns subnode', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1.child1').tabIndex = + -1; document.getElementById('testFocusableTree1.node1.child1').focus(); @@ -1673,6 +2029,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus returns new node', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree1.node2').focus(); @@ -1686,6 +2044,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree returns new node', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1699,6 +2059,8 @@ suite('FocusManager', function () { test('registered tree root focus()ed after prev node focus diff tree returns new root', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2').focus(); @@ -1711,6 +2073,9 @@ suite('FocusManager', function () { test('non-registered node subelement focus()ed returns nearest node', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById( + 'testFocusableTree1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableTree1.node2.unregisteredChild1') @@ -1724,12 +2089,18 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed returns null', function () { + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3').focus(); assert.isNull(this.focusManager.getFocusedNode()); }); test('non-registered tree node focus()ed returns null', function () { + document.getElementById( + 'testUnregisteredFocusableTree3.node1', + ).tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3.node1').focus(); assert.isNull(this.focusManager.getFocusedNode()); @@ -1737,6 +2108,10 @@ suite('FocusManager', function () { test('non-registered tree node focus()ed after registered node focused returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById( + 'testUnregisteredFocusableTree3.node1', + ).tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testUnregisteredFocusableTree3.node1').focus(); @@ -1746,6 +2121,7 @@ suite('FocusManager', function () { test('unfocusable element focus()ed after registered node focused returns original node', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testUnfocusableElement').focus(); @@ -1758,6 +2134,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1767,6 +2144,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1777,6 +2155,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior focused returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableTree1.node1').focus(); @@ -1789,6 +2169,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently focused returns new node', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1804,6 +2186,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering returns null', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -1819,6 +2204,7 @@ suite('FocusManager', function () { test('nested tree focus()ed with no prev focus returns nested root', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4').tabIndex = -1; document.getElementById('testFocusableNestedTree4').focus(); @@ -1831,6 +2217,7 @@ suite('FocusManager', function () { test('nested tree node focus()ed with no prev focus returns focused node', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -1843,6 +2230,8 @@ suite('FocusManager', function () { test('nested tree node focus()ed after parent focused returns focused node', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -1863,9 +2252,10 @@ suite('FocusManager', function () { nodeElem.textContent = 'Focusable node'; rootElem.appendChild(nodeElem); document.body.appendChild(rootElem); - const root = createFocusableTree('focusRoot'); - const node = createFocusableNode(root, 'focusNode'); + const root = this.createFocusableTree('focusRoot'); + const node = this.createFocusableNode(root, 'focusNode'); this.focusManager.registerTree(root); + document.getElementById('focusNode').tabIndex = -1; document.getElementById('focusNode').focus(); node.getFocusableElement().remove(); @@ -1873,10 +2263,44 @@ suite('FocusManager', function () { assert.notStrictEqual(this.focusManager.getFocusedNode(), node); rootElem.remove(); // Cleanup. }); + + test('after focus() after trying to focusNode() an unfocusable node updates returns focus()ed node', function () { + this.testFocusableTree1Node1.canBeFocused = () => false; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; + this.focusManager.registerTree(this.testFocusableTree1); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + document.getElementById('testFocusableTree1.node2').focus(); + + // focus()ing a new node should overwrite a failed attempt to focusNode() an unfocusable + // node. This verifies that DOM focus syncing is properly reenabled by FocusManager. + assert.strictEqual( + this.focusManager.getFocusedNode(), + this.testFocusableTree1Node2, + ); + }); + + test('after focus() after trying to focusNode() the same node twice returns focus()ed node', function () { + document.getElementById('testFocusableTree1.node2').tabIndex = -1; + this.focusManager.registerTree(this.testFocusableTree1); + // Intentionally try to focus the same node twice. + this.focusManager.focusNode(this.testFocusableTree1Node1); + this.focusManager.focusNode(this.testFocusableTree1Node1); + + document.getElementById('testFocusableTree1.node2').focus(); + + // focus()ing a new node should overwrite a failed attempt to focusNode() the same node + // twice. This verifies that DOM focus syncing is properly reenabled by FocusManager. + assert.strictEqual( + this.focusManager.getFocusedNode(), + this.testFocusableTree1Node2, + ); + }); }); suite('CSS classes', function () { test('registered root focus()ed no prev focus returns root elem has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); @@ -1895,6 +2319,7 @@ suite('FocusManager', function () { test('registered node focus()ed no prev focus node elem has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); @@ -1911,6 +2336,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus same tree old node elem has no focus property', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree1.node2').focus(); @@ -1928,6 +2355,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus same tree new node elem has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree1.node2').focus(); @@ -1946,6 +2375,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree old node elem has passive property', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1964,6 +2395,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree new node elem has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -1982,6 +2415,8 @@ suite('FocusManager', function () { test('registered tree root focus()ed after prev node focus diff tree new root has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2').focus(); @@ -2001,6 +2436,9 @@ suite('FocusManager', function () { test('non-registered node subelement focus()ed nearest node has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById( + 'testFocusableTree1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableTree1.node2.unregisteredChild1') @@ -2019,10 +2457,11 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed has no focus', function () { + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3').focus(); assert.isNull(this.focusManager.getFocusedNode()); - const rootElem = document.getElementById( 'testUnregisteredFocusableTree3', ); @@ -2037,10 +2476,13 @@ suite('FocusManager', function () { }); test('non-registered tree node focus()ed has no focus', function () { + document.getElementById( + 'testUnregisteredFocusableTree3.node1', + ).tabIndex = -1; + document.getElementById('testUnregisteredFocusableTree3.node1').focus(); assert.isNull(this.focusManager.getFocusedNode()); - const nodeElem = document.getElementById( 'testUnregisteredFocusableTree3.node1', ); @@ -2056,6 +2498,7 @@ suite('FocusManager', function () { test('unfocsable element focus()ed after registered node focused original node has active focus', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testUnfocusableElement').focus(); @@ -2086,6 +2529,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus removes focus', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -2106,6 +2550,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus removes focus', function () { this.focusManager.registerTree(this.testFocusableTree1); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -2125,6 +2570,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior removes focus from removed tree', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableTree1.node1').focus(); @@ -2157,6 +2604,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently removes focus from removed tree', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -2189,6 +2638,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering removes active indicator', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableTree1); @@ -2221,6 +2673,9 @@ suite('FocusManager', function () { test('focus() multiple nodes in same tree with switches ensure passive focus has gone', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node2').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -2243,6 +2698,9 @@ suite('FocusManager', function () { test('registered tree focus()ed other tree node passively focused tree node now has active property', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1').tabIndex = -1; document.getElementById('testFocusableTree1.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -2276,6 +2734,9 @@ suite('FocusManager', function () { test('focus on root, node in diff tree, then node in first tree; root should have focus gone', function () { this.focusManager.registerTree(this.testFocusableTree1); this.focusManager.registerTree(this.testFocusableTree2); + document.getElementById('testFocusableTree1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableTree1.node1').tabIndex = -1; document.getElementById('testFocusableTree1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -2306,6 +2767,7 @@ suite('FocusManager', function () { test('nested tree focus()ed with no prev root has active focus', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4').tabIndex = -1; document.getElementById('testFocusableNestedTree4').focus(); @@ -2325,6 +2787,7 @@ suite('FocusManager', function () { test('nested tree node focus()ed with no prev focus node has active focus', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -2343,6 +2806,8 @@ suite('FocusManager', function () { test('nested tree node focus()ed after parent focused prev has passive node has active', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -3255,6 +3720,7 @@ suite('FocusManager', function () { suite('getFocusedTree()', function () { test('registered root focus()ed no prev focus returns tree', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); @@ -3266,6 +3732,7 @@ suite('FocusManager', function () { test("registered node focus()ed no prev focus returns node's tree", function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); @@ -3277,6 +3744,8 @@ suite('FocusManager', function () { test("registered subnode focus()ed no prev focus returns node's tree", function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1.child1').tabIndex = + -1; document.getElementById('testFocusableGroup1.node1.child1').focus(); @@ -3288,6 +3757,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus returns same tree', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup1.node2').focus(); @@ -3301,6 +3772,8 @@ suite('FocusManager', function () { test("registered node focus()ed after prev node focus diff tree returns new node's tree", function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3314,6 +3787,8 @@ suite('FocusManager', function () { test("registered tree root focus()ed after prev node focus diff tree returns new node's tree", function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2').focus(); @@ -3326,6 +3801,9 @@ suite('FocusManager', function () { test("non-registered node subelement focus()ed returns node's tree", function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById( + 'testFocusableGroup1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableGroup1.node2.unregisteredChild1') @@ -3339,12 +3817,19 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed returns null', function () { + document.getElementById('testUnregisteredFocusableGroup3').tabIndex = + -1; + document.getElementById('testUnregisteredFocusableGroup3').focus(); assert.isNull(this.focusManager.getFocusedTree()); }); test('non-registered tree node focus()ed returns null', function () { + document.getElementById( + 'testUnregisteredFocusableGroup3.node1', + ).tabIndex = -1; + document .getElementById('testUnregisteredFocusableGroup3.node1') .focus(); @@ -3354,6 +3839,10 @@ suite('FocusManager', function () { test('non-registered tree node focus()ed after registered node focused returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById( + 'testUnregisteredFocusableGroup3.node1', + ).tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document @@ -3368,6 +3857,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3377,6 +3867,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3387,6 +3878,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior focused returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableGroup1.node1').focus(); @@ -3399,6 +3892,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently focused returns new tree', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3414,6 +3909,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3429,6 +3927,7 @@ suite('FocusManager', function () { test('nested tree focusTree()ed with no prev focus returns nested tree', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4').tabIndex = -1; document.getElementById('testFocusableNestedGroup4').focus(); @@ -3441,6 +3940,8 @@ suite('FocusManager', function () { test('nested tree node focusNode()ed with no prev focus returns nested tree', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -3453,6 +3954,9 @@ suite('FocusManager', function () { test('nested tree node focusNode()ed after parent focused returns nested tree', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -3466,6 +3970,7 @@ suite('FocusManager', function () { suite('getFocusedNode()', function () { test('registered root focus()ed no prev focus returns root node', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); @@ -3477,6 +3982,7 @@ suite('FocusManager', function () { test('registered node focus()ed no prev focus returns node', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); @@ -3488,6 +3994,8 @@ suite('FocusManager', function () { test('registered subnode focus()ed no prev focus returns subnode', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1.child1').tabIndex = + -1; document.getElementById('testFocusableGroup1.node1.child1').focus(); @@ -3499,6 +4007,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus returns new node', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup1.node2').focus(); @@ -3512,6 +4022,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree returns new node', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3525,6 +4037,8 @@ suite('FocusManager', function () { test('registered tree root focus()ed after prev node focus diff tree returns new root', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2').focus(); @@ -3537,6 +4051,9 @@ suite('FocusManager', function () { test('non-registered node subelement focus()ed returns nearest node', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById( + 'testFocusableGroup1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableGroup1.node2.unregisteredChild1') @@ -3550,12 +4067,19 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed returns null', function () { + document.getElementById('testUnregisteredFocusableGroup3').tabIndex = + -1; + document.getElementById('testUnregisteredFocusableGroup3').focus(); assert.isNull(this.focusManager.getFocusedNode()); }); test('non-registered tree node focus()ed returns null', function () { + document.getElementById( + 'testUnregisteredFocusableGroup3.node1', + ).tabIndex = -1; + document .getElementById('testUnregisteredFocusableGroup3.node1') .focus(); @@ -3565,6 +4089,10 @@ suite('FocusManager', function () { test('non-registered tree node focus()ed after registered node focused returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById( + 'testUnregisteredFocusableGroup3.node1', + ).tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document @@ -3576,6 +4104,7 @@ suite('FocusManager', function () { test('unfocusable element focus()ed after registered node focused returns original node', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testUnfocusableElement').focus(); @@ -3588,6 +4117,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3597,6 +4127,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3607,6 +4138,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior focused returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableGroup1.node1').focus(); @@ -3619,6 +4152,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently focused returns new node', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3634,6 +4169,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering returns null', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3649,6 +4187,7 @@ suite('FocusManager', function () { test('nested tree focus()ed with no prev focus returns nested root', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4').tabIndex = -1; document.getElementById('testFocusableNestedGroup4').focus(); @@ -3661,6 +4200,8 @@ suite('FocusManager', function () { test('nested tree node focus()ed with no prev focus returns focused node', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -3673,6 +4214,9 @@ suite('FocusManager', function () { test('nested tree node focus()ed after parent focused returns focused node', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -3682,10 +4226,44 @@ suite('FocusManager', function () { this.testFocusableNestedGroup4Node1, ); }); + + test('after focus() after trying to focusNode() an unfocusable node updates returns focus()ed node', function () { + this.testFocusableGroup1Node1.canBeFocused = () => false; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; + this.focusManager.registerTree(this.testFocusableGroup1); + this.focusManager.focusNode(this.testFocusableGroup1Node1); + + document.getElementById('testFocusableGroup1.node2').focus(); + + // focus()ing a new node should overwrite a failed attempt to focusNode() an unfocusable + // node. This verifies that DOM focus syncing is properly reenabled by FocusManager. + assert.strictEqual( + this.focusManager.getFocusedNode(), + this.testFocusableGroup1Node2, + ); + }); + + test('after focus() after trying to focusNode() the same node twice returns focus()ed node', function () { + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; + this.focusManager.registerTree(this.testFocusableGroup1); + // Intentionally try to focus the same node twice. + this.focusManager.focusNode(this.testFocusableGroup1Node1); + this.focusManager.focusNode(this.testFocusableGroup1Node1); + + document.getElementById('testFocusableGroup1.node2').focus(); + + // focus()ing a new node should overwrite a failed attempt to focusNode() the same node + // twice. This verifies that DOM focus syncing is properly reenabled by FocusManager. + assert.strictEqual( + this.focusManager.getFocusedNode(), + this.testFocusableGroup1Node2, + ); + }); }); suite('CSS classes', function () { test('registered root focus()ed no prev focus returns root elem has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); @@ -3704,6 +4282,7 @@ suite('FocusManager', function () { test('registered node focus()ed no prev focus node elem has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); @@ -3720,6 +4299,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus same tree old node elem has no focus property', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup1.node2').focus(); @@ -3738,6 +4319,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus same tree new node elem has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup1.node2').focus(); @@ -3756,6 +4339,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree old node elem has passive property', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3775,6 +4360,8 @@ suite('FocusManager', function () { test('registered node focus()ed after prev node focus diff tree new node elem has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -3793,6 +4380,8 @@ suite('FocusManager', function () { test('registered tree root focus()ed after prev node focus diff tree new root has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2').focus(); @@ -3812,6 +4401,9 @@ suite('FocusManager', function () { test('non-registered node subelement focus()ed nearest node has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById( + 'testFocusableGroup1.node2.unregisteredChild1', + ).tabIndex = -1; document .getElementById('testFocusableGroup1.node2.unregisteredChild1') @@ -3830,10 +4422,12 @@ suite('FocusManager', function () { }); test('non-registered tree focus()ed has no focus', function () { + document.getElementById('testUnregisteredFocusableGroup3').tabIndex = + -1; + document.getElementById('testUnregisteredFocusableGroup3').focus(); assert.isNull(this.focusManager.getFocusedNode()); - const rootElem = document.getElementById( 'testUnregisteredFocusableGroup3', ); @@ -3848,12 +4442,15 @@ suite('FocusManager', function () { }); test('non-registered tree node focus()ed has no focus', function () { + document.getElementById( + 'testUnregisteredFocusableGroup3.node1', + ).tabIndex = -1; + document .getElementById('testUnregisteredFocusableGroup3.node1') .focus(); assert.isNull(this.focusManager.getFocusedNode()); - const nodeElem = document.getElementById( 'testUnregisteredFocusableGroup3.node1', ); @@ -3869,6 +4466,7 @@ suite('FocusManager', function () { test('unfocusable element focus()ed after registered node focused original node has active focus', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testUnfocusableElement').focus(); @@ -3899,6 +4497,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus removes focus', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3919,6 +4518,7 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with no prev focus removes focus', function () { this.focusManager.registerTree(this.testFocusableGroup1); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -3938,6 +4538,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node prior removes focus from removed tree', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableGroup1.node1').focus(); @@ -3970,6 +4572,8 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node recently removes focus from removed tree', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -4002,6 +4606,9 @@ suite('FocusManager', function () { test('unregistered tree focus()ed with prev node after unregistering removes active indicator', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); this.focusManager.unregisterTree(this.testFocusableGroup1); @@ -4034,6 +4641,9 @@ suite('FocusManager', function () { test('focus() multiple nodes in same tree with switches ensure passive focus has gone', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node2').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -4056,6 +4666,9 @@ suite('FocusManager', function () { test('registered tree focus()ed other tree node passively focused tree node now has active property', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1').tabIndex = -1; document.getElementById('testFocusableGroup1.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -4089,6 +4702,9 @@ suite('FocusManager', function () { test('focus on root, node in diff tree, then node in first tree; root should have focus gone', function () { this.focusManager.registerTree(this.testFocusableGroup1); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup1.node1').tabIndex = -1; document.getElementById('testFocusableGroup1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -4119,6 +4735,7 @@ suite('FocusManager', function () { test('nested tree focus()ed with no prev root has active focus', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4').tabIndex = -1; document.getElementById('testFocusableNestedGroup4').focus(); @@ -4138,6 +4755,8 @@ suite('FocusManager', function () { test('nested tree node focus()ed with no prev focus node has active focus', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -4156,6 +4775,9 @@ suite('FocusManager', function () { test('nested tree node focus()ed after parent focused prev has passive node has active', function () { this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.registerTree(this.testFocusableNestedGroup4); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableNestedGroup4.node1').tabIndex = + -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableNestedGroup4.node1').focus(); @@ -4189,6 +4811,7 @@ suite('FocusManager', function () { test('Defocusing actively focused root HTML tree switches to passive highlight', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.focusTree(this.testFocusableTree2); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); @@ -4209,6 +4832,7 @@ suite('FocusManager', function () { test('Defocusing actively focused HTML tree node switches to passive highlight', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.focusNode(this.testFocusableTree2Node1); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); @@ -4229,6 +4853,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); this.focusManager.focusNode(this.testFocusableNestedTree4Node1); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); @@ -4248,6 +4873,8 @@ suite('FocusManager', function () { test('Refocusing actively focused root HTML tree restores to active highlight', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.focusTree(this.testFocusableTree2); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testFocusableTree2').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); document.getElementById('testFocusableTree2').focus(); @@ -4272,6 +4899,8 @@ suite('FocusManager', function () { test('Refocusing actively focused HTML tree node restores to active highlight', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.focusNode(this.testFocusableTree2Node1); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -4299,6 +4928,8 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableNestedTree4); this.focusManager.focusNode(this.testFocusableNestedTree4Node1); + document.getElementById('testUnregisteredFocusableTree3').tabIndex = -1; + document.getElementById('testFocusableNestedTree4.node1').tabIndex = -1; document.getElementById('testUnregisteredFocusableTree3').focus(); document.getElementById('testFocusableNestedTree4.node1').focus(); @@ -4401,6 +5032,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusTree(this.testFocusableTree2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); @@ -4501,6 +5133,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusNode(this.testFocusableTree2Node1); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); @@ -4532,6 +5165,7 @@ suite('FocusManager', function () { test('HTML DOM focus()ed then SVG focusTree()ed correctly updates getFocusedTree() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); this.focusManager.focusTree(this.testFocusableGroup2); @@ -4566,6 +5200,7 @@ suite('FocusManager', function () { test('HTML DOM focus()ed then SVG focusNode()ed correctly updates getFocusedNode() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); this.focusManager.focusNode(this.testFocusableGroup2Node1); @@ -4598,6 +5233,8 @@ suite('FocusManager', function () { test('HTML DOM focus()ed then SVG DOM focus()ed correctly updates getFocusedNode() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); document.getElementById('testFocusableGroup2.node1').focus(); @@ -4702,6 +5339,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusTree(this.testFocusableGroup2); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); @@ -4802,6 +5440,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusNode(this.testFocusableGroup2Node1); + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableTree2.node1').focus(); @@ -4833,6 +5472,7 @@ suite('FocusManager', function () { test('SVG DOM focus()ed then HTML focusTree()ed correctly updates getFocusedTree() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); this.focusManager.focusTree(this.testFocusableTree2); @@ -4867,6 +5507,7 @@ suite('FocusManager', function () { test('SVG DOM focus()ed then HTML focusNode()ed correctly updates getFocusedNode() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); this.focusManager.focusNode(this.testFocusableTree2Node1); @@ -4899,6 +5540,8 @@ suite('FocusManager', function () { test('SVG DOM focus()ed then HTML DOM focus()ed correctly updates getFocusedNode() and indicators', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; + document.getElementById('testFocusableTree2.node1').tabIndex = -1; document.getElementById('testFocusableGroup2.node1').focus(); document.getElementById('testFocusableTree2.node1').focus(); @@ -4933,6 +5576,21 @@ suite('FocusManager', function () { /* Ephemeral focus tests. */ suite('takeEphemeralFocus()', function () { + setup(function () { + // Ensure ephemeral-specific elements are focusable. + document.getElementById('nonTreeElementForEphemeralFocus').tabIndex = -1; + document.getElementById('nonTreeGroupForEphemeralFocus').tabIndex = -1; + }); + teardown(function () { + // Ensure ephemeral-specific elements have their tab indexes reset for a clean state. + document + .getElementById('nonTreeElementForEphemeralFocus') + .removeAttribute('tabindex'); + document + .getElementById('nonTreeGroupForEphemeralFocus') + .removeAttribute('tabindex'); + }); + test('with no focused node does not change states', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); @@ -5067,6 +5725,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusNode(this.testFocusableTree2Node1); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; const ephemeralElement = document.getElementById( 'nonTreeGroupForEphemeralFocus', ); @@ -5241,6 +5900,7 @@ suite('FocusManager', function () { this.focusManager.registerTree(this.testFocusableTree2); this.focusManager.registerTree(this.testFocusableGroup2); this.focusManager.focusNode(this.testFocusableTree2Node1); + document.getElementById('testFocusableGroup2.node1').tabIndex = -1; const ephemeralElement = document.getElementById( 'nonTreeGroupForEphemeralFocus', ); diff --git a/tests/mocha/index.html b/tests/mocha/index.html index 09ef8820f0e..8b1124d06a6 100644 --- a/tests/mocha/index.html +++ b/tests/mocha/index.html @@ -39,97 +39,76 @@
-
+
Focusable tree 1 -
+
Tree 1 node 1 -
+
Tree 1 node 1 child 1
+ style="margin-left: 3em"> Tree 1 node 1 child 1 child 1 (unregistered)
-
+
Tree 1 node 2
+ style="margin-left: 2em"> Tree 1 node 2 child 2 (unregistered)
-
+
Tree 1 child 1 (unregistered)
-
+
Focusable tree 2 -
+
Tree 2 node 1 -
+
Nested tree 4 -
+
Tree 4 node 1 (nested)
+ style="margin-left: 4em"> Tree 4 node 1 child 1 (unregistered)
-
+
Nested tree 5 -
+
Tree 5 node 1 (nested)
-
+
Unregistered tree 3 -
+
Tree 3 node 1 (unregistered)
Unfocusable element
-
+
- - + + Group 1 node 1 - + Tree 1 node 1 child 1 - + Group 1 node 2 - + Tree 1 node 2 child 2 (unregistered) @@ -137,27 +116,27 @@ - - + + Group 2 node 1 - - + + Group 4 node 1 (nested) - - + + Tree 3 node 1 (unregistered) - +