Files
blockly/tests/mocha/focus_manager_test.js
Ben Henning 0e16b0405a fix: Auto close drop-down divs on lost focus (reapply) (#9213)
## The basics

- [x] I [validated my changes](https://developers.google.com/blockly/guides/contribute/core#making_and_verifying_a_change)

## The details
### Resolves

Fixes https://github.com/google/blockly-keyboard-experimentation/issues/563

### Proposed Changes

This introduces support in `FocusManager` to receive feedback on when an ephemerally focused element entirely loses focus (that is, neither it nor any of its descendants have focus).

This also introduces a behavior change for drop-down divs using the previously mentioned functionality to automatically close themselves when they lose focus for any reason (e.g. clicking outside of the div or tab navigating away from it).

Finally, and **importantly**, this adds a case where ephemeral focus does _not_ automatically return to the previously focused node: when focus is lost to the ephemerally focused element's tree and isn't instead put on another focused node.

### Reason for Changes

Ultimately, focus is probably the best proxy for cases when a drop-down div ought to no longer be open. However, tracking focus only within the scope of the drop-down div utility is rather difficult since a lot of the same problems that `FocusManager` handles also occur here (with regards to both descendants and outside elements receiving focus). It made more sense to expand `FocusManager`'s ephemeral focus support:
- It was easier to implement this `FocusManager` and in a way that's much more robust (since it's leveraging existing event handlers).
- Using `FocusManager` trivialized the solution for drop-down divs.
- There could be other use cases where custom ephemeral focus uses might benefit from knowing when they lose focus.

This new support is enabled by default for all drop-down divs, but can be disabled by callers if they wish to revert to the previous behavior of not auto-closing.

The change for whether to restore ephemeral focus was needed to fix a drawback that arises from the automatic returning of ephemeral focus introduced in this PR: when a user clicks out of an open drop-down menu it will restore focus back to the node that held focus prior to taking ephemeral focus (since it properly hides the drop-down div and restores focus). This creates awkward behavior issues for both mouse and keyboard users:
- For mouse: trying to open a drop-down outside of Blockly will automatically close the drop-down when the Blockly drop-down finishes closing (since focus is stolen back away from the thing the user clicked on).
- For keyboard: tab navigating out of Blockly tries to force focus back to Blockly.

**New in v2 of this PR**: Commit 0363d67c18 is the main one that prevents #9203 from being reintroduced by ensuring that widget div only clears its contents after ephemeral focus has returned. This was missed in the first audit since it wasn't clear that this line, in particular, can cause a div with focus to be removed and thus focus lost: dfd565957b/core/widgetdiv.ts (L156)

### Test Coverage

New tests have been added for both the drop-down div and `FocusManager` components, and have been verified as failing without the new behaviors in place.

There may be other edge cases worth testing for `FocusManager` in particular, but the tests introduced in this PR seem to cover the most important cases.

**New in v2 of this PR**: A test was added to validate that widget div now clears its contents only after ephemeral focus has returned to avoid the desyncing scenario that led to #9203. This test has been verified to fail without the fix. There are also a few new tests being added in the keyboard navigation plugin repository that also validate this behavior at a higher level (see https://github.com/google/blockly-keyboard-experimentation/pull/649).

Demonstration of the new behavior:

[Screen recording 2025-07-01 6.28.37 PM.webm](https://github.com/user-attachments/assets/7af29fed-1ba1-4828-a6cd-65bb94509e72)

### Documentation

No new documentation changes seem needed beyond the code documentation updates.

### Additional Information

It's also possible to change the automatic restoration behavior to be conditional instead of always assuming focus shouldn't be reset if focus leaves the ephemeral element, but that's probably a better change if there's an actual user issue discovered with this approach.

This was originally introduced in #9175 but it was reverted in #9204 due to #9203.
2025-07-07 15:52:38 -07:00

6147 lines
240 KiB
JavaScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {
FocusManager,
getFocusManager,
} from '../../build/src/core/focus_manager.js';
import {assert} from '../../node_modules/chai/chai.js';
import {
sharedTestSetup,
sharedTestTeardown,
} from './test_helpers/setup_teardown.js';
class FocusableNodeImpl {
constructor(element, tree) {
this.element = element;
this.tree = tree;
}
getFocusableElement() {
return this.element;
}
getFocusableTree() {
return this.tree;
}
onNodeFocus() {}
onNodeBlur() {}
canBeFocused() {
return true;
}
}
class FocusableTreeImpl {
constructor(rootElement, nestedTrees) {
this.nestedTrees = nestedTrees;
this.idToNodeMap = {};
this.rootNode = this.addNode(rootElement);
this.fallbackNode = null;
}
addNode(element) {
const node = new FocusableNodeImpl(element, this);
this.idToNodeMap[element.id] = node;
return node;
}
removeNode(node) {
delete this.idToNodeMap[node.getFocusableElement().id];
}
getRootFocusableNode() {
return this.rootNode;
}
getRestoredFocusableNode() {
return this.fallbackNode;
}
getNestedTrees() {
return this.nestedTrees;
}
lookUpFocusableNode(id) {
return this.idToNodeMap[id];
}
onTreeFocus() {}
onTreeBlur() {}
}
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}`;
setup(function () {
sharedTestSetup.call(this);
this.focusManager = getFocusManager();
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 = this.createFocusableNode(
this.testFocusableTree1,
'testFocusableTree1.node1.child1',
);
this.testFocusableTree1Node2 = this.createFocusableNode(
this.testFocusableTree1,
'testFocusableTree1.node2',
);
this.testFocusableNestedTree4 = this.createFocusableTree(
'testFocusableNestedTree4',
);
this.testFocusableNestedTree4Node1 = this.createFocusableNode(
this.testFocusableNestedTree4,
'testFocusableNestedTree4.node1',
);
this.testFocusableNestedTree5 = this.createFocusableTree(
'testFocusableNestedTree5',
);
this.testFocusableNestedTree5Node1 = this.createFocusableNode(
this.testFocusableNestedTree5,
'testFocusableNestedTree5.node1',
);
this.testFocusableTree2 = this.createFocusableTree('testFocusableTree2', [
this.testFocusableNestedTree4,
this.testFocusableNestedTree5,
]);
this.testFocusableTree2Node1 = this.createFocusableNode(
this.testFocusableTree2,
'testFocusableTree2.node1',
);
this.testFocusableGroup1 = this.createFocusableTree('testFocusableGroup1');
this.testFocusableGroup1Node1 = this.createFocusableNode(
this.testFocusableGroup1,
'testFocusableGroup1.node1',
);
this.testFocusableGroup1Node1Child1 = this.createFocusableNode(
this.testFocusableGroup1,
'testFocusableGroup1.node1.child1',
);
this.testFocusableGroup1Node2 = this.createFocusableNode(
this.testFocusableGroup1,
'testFocusableGroup1.node2',
);
this.testFocusableNestedGroup4 = this.createFocusableTree(
'testFocusableNestedGroup4',
);
this.testFocusableNestedGroup4Node1 = this.createFocusableNode(
this.testFocusableNestedGroup4,
'testFocusableNestedGroup4.node1',
);
this.testFocusableGroup2 = this.createFocusableTree('testFocusableGroup2', [
this.testFocusableNestedGroup4,
]);
this.testFocusableGroup2Node1 = this.createFocusableNode(
this.testFocusableGroup2,
'testFocusableGroup2.node1',
);
});
teardown(function () {
sharedTestTeardown.call(this);
// Ensure all node CSS styles are reset so that state isn't leaked between tests.
const activeElems = document.querySelectorAll(
ACTIVE_FOCUS_NODE_CSS_SELECTOR,
);
const passiveElems = document.querySelectorAll(
PASSIVE_FOCUS_NODE_CSS_SELECTOR,
);
for (const elem of activeElems) {
elem.classList.remove(FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME);
}
for (const elem of passiveElems) {
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();
});
assert.includesClass = function (classList, className) {
assert.isTrue(
classList.contains(className),
'Expected class list to include: ' + className,
);
};
assert.notIncludesClass = function (classList, className) {
assert.isFalse(
classList.contains(className),
'Expected class list to not include: ' + className,
);
};
/* Basic lifecycle tests. */
suite('registerTree()', function () {
test('once does not throw', function () {
this.focusManager.registerTree(this.testFocusableTree1);
// The test should pass due to no exception being thrown.
});
test('twice for same tree throws error', function () {
this.focusManager.registerTree(this.testFocusableTree1);
const errorMsgRegex =
/Attempted to re-register already registered tree.+?/;
assert.throws(
() => this.focusManager.registerTree(this.testFocusableTree1),
errorMsgRegex,
);
});
test('twice with different trees does not throw', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableGroup1);
// The test shouldn't throw since two different trees were registered.
});
test('register after an unregister does not throw', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree1);
// The second register should not fail since the tree was previously unregistered.
});
test('for tree with missing ID throws error', function () {
const rootNode = this.testFocusableTree1.getRootFocusableNode();
const rootElem = rootNode.getFocusableElement();
const oldId = rootElem.id;
rootElem.removeAttribute('id');
const errorMsgRegex =
/Attempting to register a tree with a root element that has an invalid ID.+?/;
assert.throws(
() => this.focusManager.registerTree(this.testFocusableTree1),
errorMsgRegex,
);
// Restore the ID for other tests.
rootElem.id = oldId;
});
test('for tree with null ID throws error', function () {
const rootNode = this.testFocusableTree1.getRootFocusableNode();
const rootElem = rootNode.getFocusableElement();
const oldId = rootElem.id;
rootElem.setAttribute('id', null);
const errorMsgRegex =
/Attempting to register a tree with a root element that has an invalid ID.+?/;
assert.throws(
() => this.focusManager.registerTree(this.testFocusableTree1),
errorMsgRegex,
);
// Restore the ID for other tests.
rootElem.id = oldId;
});
test('for tree with empty throws error', function () {
const rootNode = this.testFocusableTree1.getRootFocusableNode();
const rootElem = rootNode.getFocusableElement();
const oldId = rootElem.id;
rootElem.setAttribute('id', '');
const errorMsgRegex =
/Attempting to register a tree with a root element that has an invalid ID.+?/;
assert.throws(
() => this.focusManager.registerTree(this.testFocusableTree1),
errorMsgRegex,
);
// Restore the ID for other tests.
rootElem.id = oldId;
});
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 () {
test('for not yet registered tree throws', function () {
const errorMsgRegex = /Attempted to unregister not registered tree.+?/;
assert.throws(
() => this.focusManager.unregisterTree(this.testFocusableTree1),
errorMsgRegex,
);
});
test('for registered tree does not throw', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Unregistering a registered tree should not fail.
});
test('twice for registered tree throws', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
const errorMsgRegex = /Attempted to unregister not registered tree.+?/;
assert.throws(
() => this.focusManager.unregisterTree(this.testFocusableTree1),
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 () {
test('for not registered tree returns false', function () {
const isRegistered = this.focusManager.isRegistered(
this.testFocusableTree1,
);
assert.isFalse(isRegistered);
});
test('for registered tree returns true', function () {
this.focusManager.registerTree(this.testFocusableTree1);
const isRegistered = this.focusManager.isRegistered(
this.testFocusableTree1,
);
assert.isTrue(isRegistered);
});
test('for unregistered tree returns false', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
const isRegistered = this.focusManager.isRegistered(
this.testFocusableTree1,
);
assert.isFalse(isRegistered);
});
test('for re-registered tree returns true', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree1);
const isRegistered = this.focusManager.isRegistered(
this.testFocusableTree1,
);
assert.isTrue(isRegistered);
});
test('for unregistered tree with other registered tree returns false', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
const isRegistered = this.focusManager.isRegistered(
this.testFocusableTree1,
);
assert.isFalse(isRegistered);
});
});
suite('getFocusedTree()', function () {
test('by default returns null', function () {
const focusedTree = this.focusManager.getFocusedTree();
assert.isNull(focusedTree);
});
});
suite('getFocusedNode()', function () {
test('by default returns null', function () {
const focusedNode = this.focusManager.getFocusedNode();
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 () {
test('for not registered tree throws', function () {
const errorMsgRegex = /Attempted to focus unregistered tree.+?/;
assert.throws(
() => this.focusManager.focusTree(this.testFocusableTree1),
errorMsgRegex,
);
});
test('for unregistered tree throws', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
const errorMsgRegex = /Attempted to focus unregistered tree.+?/;
assert.throws(
() => this.focusManager.focusTree(this.testFocusableTree1),
errorMsgRegex,
);
});
});
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.+?/;
assert.throws(
() => this.focusManager.focusNode(this.testFocusableTree1Node1),
errorMsgRegex,
);
});
test('for unregistered node throws', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
const errorMsgRegex = /Attempted to focus unregistered node.+?/;
assert.throws(
() => this.focusManager.focusNode(this.testFocusableTree1Node1),
errorMsgRegex,
);
});
test('focuses element', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.strictEqual(document.activeElement, nodeElem);
});
test('fires focusin event', function () {
let focusCount = 0;
const focusListener = () => focusCount++;
document.addEventListener('focusin', focusListener);
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
document.removeEventListener('focusin', focusListener);
// There should be exactly 1 focus event fired from focusNode().
assert.strictEqual(focusCount, 1);
});
test('for orphaned node returns tree root by default', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.testFocusableTree1.removeNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
// Focusing an invalid node should fall back to the tree root when it has no restoration
// fallback node.
const currentNode = this.focusManager.getFocusedNode();
const treeRoot = this.testFocusableTree1.getRootFocusableNode();
assert.strictEqual(currentNode, treeRoot);
});
test('for orphaned node returns specified fallback node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.testFocusableTree1.fallbackNode = this.testFocusableTree1Node2;
this.testFocusableTree1.removeNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
// Focusing an invalid node should fall back to the restored fallback.
const currentNode = this.focusManager.getFocusedNode();
assert.strictEqual(currentNode, this.testFocusableTree1Node2);
});
test('restores focus when element quietly loses focus', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
// Remove the FocusManager's listeners to simulate not receiving a focus
// event when focus is lost. This can happen in Firefox and Safari when an
// element is removed and then re-added to the DOM. This is a contrived
// setup to achieve the same outcome on all browsers. For context, see:
// https://github.com/google/blockly-keyboard-experimentation/issues/87.
for (const registeredListener of this.globalDocumentEventListeners) {
const eventType = registeredListener.type;
const eventListener = registeredListener.listener;
document.removeEventListener(eventType, eventListener);
}
document.body.focus();
this.focusManager.focusNode(this.testFocusableTree1Node1);
const currentNode = this.focusManager.getFocusedNode();
const currentElem = currentNode?.getFocusableElement();
assert.strictEqual(currentNode, this.testFocusableTree1Node1);
assert.strictEqual(document.activeElement, currentElem);
});
test('restores focus when element and new node focused', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
// Remove the FocusManager's listeners to simulate not receiving a focus
// event when focus is lost. This can happen in Firefox and Safari when an
// element is removed and then re-added to the DOM. This is a contrived
// setup to achieve the same outcome on all browsers. For context, see:
// https://github.com/google/blockly-keyboard-experimentation/issues/87.
for (const registeredListener of this.globalDocumentEventListeners) {
const eventType = registeredListener.type;
const eventListener = registeredListener.listener;
document.removeEventListener(eventType, eventListener);
}
document.body.focus();
this.focusManager.focusNode(this.testFocusableTree1Node2);
const currentNode = this.focusManager.getFocusedNode();
const currentElem = currentNode?.getFocusableElement();
assert.strictEqual(currentNode, this.testFocusableTree1Node2);
assert.strictEqual(document.activeElement, currentElem);
});
test('for unfocused node calls onNodeFocus once', function () {
sinon.spy(this.testFocusableTree1Node1, 'onNodeFocus');
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
assert.strictEqual(this.testFocusableTree1Node1.onNodeFocus.callCount, 1);
});
test('for previously focused node calls onNodeBlur once', function () {
sinon.spy(this.testFocusableTree1Node1, 'onNodeBlur');
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
assert.strictEqual(this.testFocusableTree1Node1.onNodeBlur.callCount, 1);
});
test('for unfocused tree calls onTreeFocus once', function () {
sinon.spy(this.testFocusableTree1, 'onTreeFocus');
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
assert.strictEqual(this.testFocusableTree1.onTreeFocus.callCount, 1);
});
test('for previously focused tree calls onTreeBlur once', function () {
sinon.spy(this.testFocusableTree1, 'onTreeBlur');
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
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 () {
test('returns non-null manager', function () {
const manager = getFocusManager();
assert.isNotNull(manager);
});
test('returns the exact same instance in subsequent calls', function () {
const manager1 = getFocusManager();
const manager2 = getFocusManager();
assert.strictEqual(manager2, manager1);
});
});
/* Focus tests for HTML trees. */
suite('focus*() switching in HTML tree', function () {
suite('getFocusedTree()', function () {
test('registered tree focusTree()ed no prev focus returns tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test('registered tree focusTree()ed prev node focused returns tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test('registered tree focusTree()ed diff tree prev focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
test('registered tree focusTree()ed diff tree node prev focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
test('registered root focusNode()ed no prev focus returns tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(
this.testFocusableTree1.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test("registered node focusNode()ed no prev focus returns node's tree", function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test("registered subnode focusNode()ed no prev focus returns node's tree", function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1Child1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test('registered node focusNode()ed after prev node focus returns same tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
test("registered node focusNode()ed after prev node focus diff tree returns new node's tree", function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
test("registered tree root focusNode()ed after prev node focus diff tree returns new node's tree", function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(
this.testFocusableTree2.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
test('unregistered tree focusTree()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with prev node prior focused returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with prev node recently focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
test('nested tree focusTree()ed with no prev focus returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusTree(this.testFocusableNestedTree4);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
test('nested tree node focusNode()ed with no prev focus returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
test('nested tree node focusNode()ed after parent focused returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
});
suite('getFocusedNode()', function () {
test('registered tree focusTree()ed no prev focus returns root node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1.getRootFocusableNode(),
);
});
test('registered tree focusTree()ed prev node focused returns original node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree1);
// The original node retains focus since the tree already holds focus (per focusTree's
// contract).
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1,
);
});
test('registered tree focusTree()ed diff tree prev focused returns new root node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2.getRootFocusableNode(),
);
});
test('registered tree focusTree()ed diff tree node prev focused returns new root node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2.getRootFocusableNode(),
);
});
test('registered root focusNode()ed no prev focus returns root node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(
this.testFocusableTree1.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1.getRootFocusableNode(),
);
});
test('registered node focusNode()ed no prev focus returns node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1,
);
});
test('registered subnode focusNode()ed no prev focus returns subnode', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1Child1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1Child1,
);
});
test('registered node focusNode()ed after prev node focus returns new node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node2,
);
});
test('registered node focusNode()ed after prev node focus diff tree returns new node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
});
test('registered tree root focusNode()ed after prev node focus diff tree returns new root', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(
this.testFocusableTree2.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2.getRootFocusableNode(),
);
});
test('unregistered tree focusTree()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with prev node prior focused returns null', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with prev node recently focused returns new node', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
});
test('nested tree focusTree()ed with no prev focus returns nested root', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusTree(this.testFocusableNestedTree4);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4.getRootFocusableNode(),
);
});
test('nested tree node focusNode()ed with no prev focus returns focused node', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4Node1,
);
});
test('nested tree node focusNode()ed after parent focused returns focused node', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4Node1,
);
});
test('deletion after focusNode() returns null', function () {
const rootElem = document.createElement('div');
const nodeElem = document.createElement('div');
rootElem.setAttribute('id', 'focusRoot');
rootElem.setAttribute('tabindex', '-1');
nodeElem.setAttribute('id', 'focusNode');
nodeElem.setAttribute('tabindex', '-1');
nodeElem.textContent = 'Focusable node';
rootElem.appendChild(nodeElem);
document.body.appendChild(rootElem);
const root = this.createFocusableTree('focusRoot');
const node = this.createFocusableNode(root, 'focusNode');
this.focusManager.registerTree(root);
this.focusManager.focusNode(node);
node.getFocusableElement().remove();
assert.notStrictEqual(this.focusManager.getFocusedNode(), node);
rootElem.remove(); // Cleanup.
});
});
suite('CSS classes', function () {
test('registered tree focusTree()ed no prev focus root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed prev node focused original elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree1);
// The original node retains active focus since the tree already holds focus (per
// focusTree's contract).
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed diff tree prev focused new root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree2);
const rootElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed diff tree node prev focused new root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusTree(this.testFocusableTree2);
const rootElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered root focusNode()ed no prev focus returns root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(
this.testFocusableTree1.getRootFocusableNode(),
);
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed no prev focus node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus same tree old node elem has no focus property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
const prevNodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus same tree new node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
const newNodeElem = this.testFocusableTree1Node2.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus diff tree old node elem has passive property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const prevNodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus diff tree new node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const newNodeElem = this.testFocusableTree2Node1.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree root focusNode()ed after prev node focus diff tree new root has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(
this.testFocusableTree2.getRootFocusableNode(),
);
const rootElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusTree()ed with no prev focus removes focus', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators.
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with no prev focus removes focus', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators.
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with prev node prior removes focus from removed tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators. However, the old node
// should still have passive indication.
const otherNodeElem =
this.testFocusableTree2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with prev node recently removes focus from removed tree', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators. However, the new node
// should still have active indication.
const otherNodeElem =
this.testFocusableTree2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('focusNode() multiple nodes in same tree with switches ensure passive focus has gone', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableTree1Node2);
// When switching back to the first tree, ensure the original passive node is no longer
// passive now that the new node is active.
const node1 = this.testFocusableTree1Node1.getFocusableElement();
const node2 = this.testFocusableTree1Node2.getFocusableElement();
assert.notIncludesClass(
node1.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
node2.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed other tree node passively focused tree node now has active property', function () {
this.focusManager.registerTree(this.testFocusableTree1);
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableTree1Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusTree(this.testFocusableTree1);
// The original node in the tree should be moved from passive to active focus per the
// contract of focusTree). Also, the root of the tree should have no focus indication.
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
this.focusManager.focusTree(this.testFocusableTree1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableTree1Node1);
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree focusTree()ed with no prev root has active focus', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusTree(this.testFocusableNestedTree4);
const rootElem = this.testFocusableNestedTree4
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree node focusNode()ed with no prev focus node has active focus', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
const nodeElem =
this.testFocusableNestedTree4Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree node focusNode()ed after parent focused prev has passive node has active', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableNestedTree4);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableNestedTree4Node1);
const prevNodeElem = this.testFocusableTree2Node1.getFocusableElement();
const currNodeElem =
this.testFocusableNestedTree4Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
currNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
});
suite('DOM focus() switching in HTML tree', 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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
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')
.focus();
// The tree of the unregistered child element should take focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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());
});
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();
assert.isNull(this.focusManager.getFocusedTree());
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree1,
);
});
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);
assert.isNull(this.focusManager.getFocusedTree());
});
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);
assert.isNull(this.focusManager.getFocusedTree());
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedTree());
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
});
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);
document.getElementById('testFocusableTree1.node1').focus();
// Attempting to focus a now removed tree should result in nothing being
// focused since the removed tree can have DOM focus, but that focus is
// ignored by FocusManager.
assert.isNull(this.focusManager.getFocusedTree());
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
});
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1.getRootFocusableNode(),
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1Child1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node2,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2.getRootFocusableNode(),
);
});
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')
.focus();
// The nearest node of the unregistered child element should take focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node2,
);
});
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());
});
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();
assert.isNull(this.focusManager.getFocusedNode());
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree1Node1,
);
});
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);
assert.isNull(this.focusManager.getFocusedNode());
});
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);
assert.isNull(this.focusManager.getFocusedNode());
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedNode());
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
});
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);
document.getElementById('testFocusableTree1.node1').focus();
// Attempting to focus a now removed tree should result in nothing being
// focused since the removed tree can have DOM focus, but that focus is
// ignored by FocusManager.
assert.isNull(this.focusManager.getFocusedNode());
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4.getRootFocusableNode(),
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4Node1,
);
});
test('deletion after focus() returns null', function () {
const rootElem = document.createElement('div');
const nodeElem = document.createElement('div');
rootElem.setAttribute('id', 'focusRoot');
rootElem.setAttribute('tabindex', '-1');
nodeElem.setAttribute('id', 'focusNode');
nodeElem.setAttribute('tabindex', '-1');
nodeElem.textContent = 'Focusable node';
rootElem.appendChild(nodeElem);
document.body.appendChild(rootElem);
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();
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();
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const newNodeElem = this.testFocusableTree1Node2.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const newNodeElem = this.testFocusableTree2Node1.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const rootElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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')
.focus();
// The nearest node of the unregistered child element should be actively focused.
const nodeElem = this.testFocusableTree1Node2.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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',
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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',
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
// The original node should be unchanged, and the unregistered node should not have any
// focus indicators.
const nodeElem = document.getElementById('testFocusableTree1.node1');
const attemptedNewNodeElem = document.getElementById(
'testUnfocusableElement',
);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
attemptedNewNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
attemptedNewNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
// Since the tree was unregistered it no longer has focus indicators.
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
// Since the tree was unregistered it no longer has focus indicators.
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators. However, the old node
// should still have passive indication.
const otherNodeElem =
this.testFocusableTree2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
this.focusManager.unregisterTree(this.testFocusableTree1);
// Since the tree was unregistered it no longer has focus indicators. However, the new node
// should still have active indication.
const otherNodeElem =
this.testFocusableTree2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
document.getElementById('testFocusableTree1.node1').focus();
// Attempting to focus a now removed tree should remove active.
const otherNodeElem =
this.testFocusableTree2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableTree1Node1.getFocusableElement();
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableTree1.node2').focus();
// When switching back to the first tree, ensure the original passive node is no longer
// passive now that the new node is active.
const node1 = this.testFocusableTree1Node1.getFocusableElement();
const node2 = this.testFocusableTree1Node2.getFocusableElement();
assert.notIncludesClass(
node1.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
node2.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableTree1').focus();
// Directly refocusing a tree's root should have functional parity with focusTree(). That
// means the tree's previous node should now have active focus again and its root should
// have no focus indication.
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableTree1.node1').focus();
const nodeElem = this.testFocusableTree1Node1.getFocusableElement();
const rootElem = this.testFocusableTree1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const rootElem = this.testFocusableNestedTree4
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem =
this.testFocusableNestedTree4Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem = this.testFocusableTree2Node1.getFocusableElement();
const currNodeElem =
this.testFocusableNestedTree4Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
currNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
});
/* Focus tests for SVG trees. */
suite('focus*() switching in SVG tree', function () {
suite('getFocusedTree()', function () {
test('registered tree focusTree()ed no prev focus returns tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test('registered tree focusTree()ed prev node focused returns tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test('registered tree focusTree()ed diff tree prev focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
test('registered tree focusTree()ed diff tree node prev focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
test('registered root focusNode()ed no prev focus returns tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(
this.testFocusableGroup1.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test("registered node focusNode()ed no prev focus returns node's tree", function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test("registered subnode focusNode()ed no prev focus returns node's tree", function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1Child1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test('registered node focusNode()ed after prev node focus returns same tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
test("registered node focusNode()ed after prev node focus diff tree returns new node's tree", function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
test("registered tree root focusNode()ed after prev node focus diff tree returns new node's tree", function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(
this.testFocusableGroup2.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
test('unregistered tree focusTree()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with prev node prior focused returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedTree());
});
test('unregistered tree focusNode()ed with prev node recently focused returns new tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
test('nested tree focusTree()ed with no prev focus returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusTree(this.testFocusableNestedGroup4);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
test('nested tree node focusNode()ed with no prev focus returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
test('nested tree node focusNode()ed after parent focused returns nested tree', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
});
suite('getFocusedNode()', function () {
test('registered tree focusTree()ed no prev focus returns root node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1.getRootFocusableNode(),
);
});
test('registered tree focusTree()ed prev node focused returns original node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup1);
// The original node retains focus since the tree already holds focus (per focusTree's
// contract).
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1,
);
});
test('registered tree focusTree()ed diff tree prev focused returns new root node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2.getRootFocusableNode(),
);
});
test('registered tree focusTree()ed diff tree node prev focused returns new root node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2.getRootFocusableNode(),
);
});
test('registered root focusNode()ed no prev focus returns root node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(
this.testFocusableGroup1.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1.getRootFocusableNode(),
);
});
test('registered node focusNode()ed no prev focus returns node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1,
);
});
test('registered subnode focusNode()ed no prev focus returns subnode', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1Child1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1Child1,
);
});
test('registered node focusNode()ed after prev node focus returns new node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node2);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node2,
);
});
test('registered node focusNode()ed after prev node focus diff tree returns new node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
});
test('registered tree root focusNode()ed after prev node focus diff tree returns new root', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(
this.testFocusableGroup2.getRootFocusableNode(),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2.getRootFocusableNode(),
);
});
test('unregistered tree focusTree()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with no prev focus returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with prev node prior focused returns null', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedNode());
});
test('unregistered tree focusNode()ed with prev node recently focused returns new node', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
});
test('nested tree focusTree()ed with no prev focus returns nested root', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusTree(this.testFocusableNestedGroup4);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedGroup4.getRootFocusableNode(),
);
});
test('nested tree node focusNode()ed with no prev focus returns focused node', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedGroup4Node1,
);
});
test('nested tree node focusNode()ed after parent focused returns focused node', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedGroup4Node1,
);
});
});
suite('CSS classes', function () {
test('registered tree focusTree()ed no prev focus root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed prev node focused original elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup1);
// The original node retains active focus since the tree already holds focus (per
// focusTree's contract).
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed diff tree prev focused new root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup2);
const rootElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed diff tree node prev focused new root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusTree(this.testFocusableGroup2);
const rootElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered root focusNode()ed no prev focus returns root elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(
this.testFocusableGroup1.getRootFocusableNode(),
);
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed no prev focus node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus same tree old node elem has no focus property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node2);
const prevNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus same tree new node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node2);
const newNodeElem = this.testFocusableGroup1Node2.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus diff tree old node elem has passive property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
const prevNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered node focusNode()ed after prev node focus diff tree new node elem has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
const newNodeElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree root focusNode()ed after prev node focus diff tree new root has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(
this.testFocusableGroup2.getRootFocusableNode(),
);
const rootElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusTree()ed with no prev focus removes focus', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators.
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with no prev focus removes focus', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators.
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with prev node prior removes focus from removed tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators. However, the old node
// should still have passive indication.
const otherNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('unregistered tree focusNode()ed with prev node recently removes focus from removed tree', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators. However, the new node
// should still have active indication.
const otherNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('focusNode() multiple nodes in same tree with switches ensure passive focus has gone', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node2);
// When switching back to the first tree, ensure the original passive node is no longer
// passive now that the new node is active.
const node1 = this.testFocusableGroup1Node1.getFocusableElement();
const node2 = this.testFocusableGroup1Node2.getFocusableElement();
assert.notIncludesClass(
node1.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
node2.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('registered tree focusTree()ed other tree node passively focused tree node now has active property', function () {
this.focusManager.registerTree(this.testFocusableGroup1);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusTree(this.testFocusableGroup1);
// The original node in the tree should be moved from passive to active focus per the
// contract of focusTree). Also, the root of the tree should have no focus indication.
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
this.focusManager.focusTree(this.testFocusableGroup1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableGroup1Node1);
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree focusTree()ed with no prev root has active focus', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusTree(this.testFocusableNestedGroup4);
const rootElem = this.testFocusableNestedGroup4
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree node focusNode()ed with no prev focus node has active focus', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
const nodeElem =
this.testFocusableNestedGroup4Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('nested tree node focusNode()ed after parent focused prev has passive node has active', function () {
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.registerTree(this.testFocusableNestedGroup4);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableNestedGroup4Node1);
const prevNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const currNodeElem =
this.testFocusableNestedGroup4Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
currNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
});
suite('DOM focus() switching in SVG tree', 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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
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')
.focus();
// The tree of the unregistered child element should take focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup1,
);
});
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();
assert.isNull(this.focusManager.getFocusedTree());
});
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
.getElementById('testUnregisteredFocusableGroup3.node1')
.focus();
// Attempting to focus a now removed tree should result in nothing being
// focused since the removed tree can have DOM focus, but that focus is
// ignored by FocusManager.
assert.isNull(this.focusManager.getFocusedTree());
});
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);
assert.isNull(this.focusManager.getFocusedTree());
});
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);
assert.isNull(this.focusManager.getFocusedTree());
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedTree());
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
});
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);
document.getElementById('testFocusableGroup1.node1').focus();
// Attempting to focus a now removed tree should result in nothing being
// focused since the removed tree can have DOM focus, but that focus is
// ignored by FocusManager.
assert.isNull(this.focusManager.getFocusedTree());
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedGroup4,
);
});
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1.getRootFocusableNode(),
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1Child1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node2,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2.getRootFocusableNode(),
);
});
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')
.focus();
// The nearest node of the unregistered child element should take focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node2,
);
});
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();
assert.isNull(this.focusManager.getFocusedNode());
});
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
.getElementById('testUnregisteredFocusableGroup3.node1')
.focus();
assert.isNull(this.focusManager.getFocusedNode());
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup1Node1,
);
});
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);
assert.isNull(this.focusManager.getFocusedNode());
});
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);
assert.isNull(this.focusManager.getFocusedNode());
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the more recent tree was removed, there's no tree currently focused.
assert.isNull(this.focusManager.getFocusedNode());
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the most recent tree still exists, it still has focus.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
});
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);
document.getElementById('testFocusableGroup1.node1').focus();
// Attempting to focus a now removed tree should result in nothing being
// focused since the removed tree can have DOM focus, but that focus is
// ignored by FocusManager.
assert.isNull(this.focusManager.getFocusedNode());
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedGroup4.getRootFocusableNode(),
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedGroup4Node1,
);
});
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();
assert.strictEqual(
this.focusManager.getFocusedNode(),
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();
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const newNodeElem = this.testFocusableGroup1Node2.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const newNodeElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.includesClass(
newNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
newNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const rootElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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')
.focus();
// The nearest node of the unregistered child element should be actively focused.
const nodeElem = this.testFocusableGroup1Node2.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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',
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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',
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
// The original node should be unchanged, and the unregistered node should not have any
// focus indicators.
const nodeElem = document.getElementById('testFocusableGroup1.node1');
const attemptedNewNodeElem = document.getElementById(
'testUnfocusableElement',
);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
attemptedNewNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
attemptedNewNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
// Since the tree was unregistered it no longer has focus indicators.
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
// Since the tree was unregistered it no longer has focus indicators.
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators. However, the old node
// should still have passive indication.
const otherNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
this.focusManager.unregisterTree(this.testFocusableGroup1);
// Since the tree was unregistered it no longer has focus indicators. However, the new node
// should still have active indication.
const otherNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
document.getElementById('testFocusableGroup1.node1').focus();
// Attempting to focus a now removed tree should remove active.
const otherNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const removedNodeElem =
this.testFocusableGroup1Node1.getFocusableElement();
assert.notIncludesClass(
otherNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
otherNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
removedNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableGroup1.node2').focus();
// When switching back to the first tree, ensure the original passive node is no longer
// passive now that the new node is active.
const node1 = this.testFocusableGroup1Node1.getFocusableElement();
const node2 = this.testFocusableGroup1Node2.getFocusableElement();
assert.notIncludesClass(
node1.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
node2.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableGroup1').focus();
// Directly refocusing a tree's root should have functional parity with focusTree(). That
// means the tree's previous node should now have active focus again and its root should
// have no focus indication.
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
document.getElementById('testFocusableGroup1.node1').focus();
const nodeElem = this.testFocusableGroup1Node1.getFocusableElement();
const rootElem = this.testFocusableGroup1
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const rootElem = this.testFocusableNestedGroup4
.getRootFocusableNode()
.getFocusableElement();
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem =
this.testFocusableNestedGroup4Node1.getFocusableElement();
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevNodeElem =
this.testFocusableGroup2Node1.getFocusableElement();
const currNodeElem =
this.testFocusableNestedGroup4Node1.getFocusableElement();
assert.notIncludesClass(
prevNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
currNodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currNodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
});
/* High-level focus/defocusing tests. */
suite('Defocusing and refocusing', 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();
const rootNode = this.testFocusableTree2.getRootFocusableNode();
const rootElem = rootNode.getFocusableElement();
assert.isNull(this.focusManager.getFocusedTree());
assert.isNull(this.focusManager.getFocusedNode());
assert.includesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem = this.testFocusableTree2Node1.getFocusableElement();
assert.isNull(this.focusManager.getFocusedTree());
assert.isNull(this.focusManager.getFocusedNode());
assert.includesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('Defocusing actively focused HTML subtree node switches to passive highlight', 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();
const nodeElem = this.testFocusableNestedTree4Node1.getFocusableElement();
assert.isNull(this.focusManager.getFocusedTree());
assert.isNull(this.focusManager.getFocusedNode());
assert.includesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const rootNode = this.testFocusableTree2.getRootFocusableNode();
const rootElem = rootNode.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
assert.strictEqual(this.focusManager.getFocusedNode(), rootNode);
assert.notIncludesClass(
rootElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const nodeElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('Refocusing actively focused HTML subtree node restores to active highlight', 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();
const nodeElem = this.testFocusableNestedTree4Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableNestedTree4,
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableNestedTree4Node1,
);
assert.notIncludesClass(
nodeElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
/* Combined HTML/SVG tree focus tests. */
suite('HTML/SVG focus tree switching', function () {
suite('Focus HTML tree then SVG tree', function () {
test('HTML focusTree()ed then SVG focusTree()ed correctly updates getFocusedTree() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableTree2);
this.focusManager.focusTree(this.testFocusableGroup2);
const prevElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('HTML focusTree()ed then SVG focusNode()ed correctly updates getFocusedNode() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableTree2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
const prevElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('HTML focusTree()ed then SVG DOM focus()ed correctly updates getFocusedNode() and indicators', 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();
const prevElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('HTML focusNode()ed then SVG focusTree()ed correctly updates getFocusedTree() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusTree(this.testFocusableGroup2);
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('HTML focusNode()ed then SVG focusNode()ed correctly updates getFocusedNode() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('HTML focusNode()ed then SVG DOM focus()ed correctly updates getFocusedNode() and indicators', 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();
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevElem = this.testFocusableTree2Node1.getFocusableElement();
const currElem = this.testFocusableGroup2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
suite('Focus SVG tree then HTML tree', function () {
test('SVG focusTree()ed then HTML focusTree()ed correctly updates getFocusedTree() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableTree2);
const prevElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('SVG focusTree()ed then HTML focusNode()ed correctly updates getFocusedNode() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const prevElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('SVG focusTree()ed then HTML DOM focus()ed correctly updates getFocusedNode() and indicators', 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();
const prevElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('SVG focusNode()ed then HTML focusTree()ed correctly updates getFocusedTree() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusTree(this.testFocusableTree2);
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('SVG focusNode()ed then HTML focusNode()ed correctly updates getFocusedNode() and indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('SVG focusNode()ed then HTML DOM focus()ed correctly updates getFocusedNode() and indicators', 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();
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2
.getRootFocusableNode()
.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableTree2,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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);
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
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();
const prevElem = this.testFocusableGroup2Node1.getFocusableElement();
const currElem = this.testFocusableTree2Node1.getFocusableElement();
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(document.activeElement, currElem);
assert.includesClass(
currElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
currElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.notIncludesClass(
prevElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.includesClass(
prevElem.classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
});
});
/* 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);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
// Taking focus without an existing node having focus should change no focus indicators.
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
const passiveElems = Array.from(
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
assert.isEmpty(passiveElems);
});
test('with focused node changes focused node to passive', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
// Taking focus without an existing node having focus should change no focus indicators.
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
const passiveElems = Array.from(
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
assert.strictEqual(passiveElems.length, 1);
assert.includesClass(
this.testFocusableTree2Node1.getFocusableElement().classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
});
test('focuses provided HTML element', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
assert.strictEqual(document.activeElement, ephemeralElement);
});
test('focuses provided SVG element', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
assert.strictEqual(document.activeElement, ephemeralElement);
});
test('twice for without finishing previous throws error', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralGroupElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const ephemeralDivElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralGroupElement);
const errorMsgRegex =
/Attempted to take ephemeral focus when it's already held+?/;
assert.throws(
() => this.focusManager.takeEphemeralFocus(ephemeralDivElement),
errorMsgRegex,
);
});
test('then focusTree() changes getFocusedTree() but not active state', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableTree2);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
this.focusManager.focusTree(this.testFocusableGroup2);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
});
test('then focusNode() changes getFocusedNode() but not active state', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
});
test('then DOM refocus changes getFocusedNode() but not active state', 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',
);
this.focusManager.takeEphemeralFocus(ephemeralElement);
// Force focus to change via the DOM.
document.getElementById('testFocusableGroup2.node1').focus();
// The focus() state change will affect getFocusedNode() but it will not cause the node to now
// be active.
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
});
test('then finish ephemeral callback with no node does not change indicators', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
finishFocusCallback();
// Finishing ephemeral focus without a previously focused node should not change indicators.
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
const passiveElems = Array.from(
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
assert.isEmpty(passiveElems);
});
test('again after finishing previous empheral focus should focus new element', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralGroupElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const ephemeralDivElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const finishFocusCallback = this.focusManager.takeEphemeralFocus(
ephemeralGroupElement,
);
finishFocusCallback();
this.focusManager.takeEphemeralFocus(ephemeralDivElement);
// An exception should not be thrown and the new element should receive focus.
assert.strictEqual(document.activeElement, ephemeralDivElement);
});
test('calling ephemeral callback twice throws error', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
finishFocusCallback();
const errorMsgRegex =
/Attempted to finish ephemeral focus twice for element+?/;
assert.throws(() => finishFocusCallback(), errorMsgRegex);
});
test('then finish ephemeral callback should restore focused node', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
finishFocusCallback();
// The original focused node should be restored.
const nodeElem = this.testFocusableTree2Node1.getFocusableElement();
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(activeElems.length, 1);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, nodeElem);
});
test('then focusTree() and finish ephemeral callback correctly sets new active state', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusTree(this.testFocusableTree2);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
this.focusManager.focusTree(this.testFocusableGroup2);
finishFocusCallback();
// The tree's root should now be the active element since focus changed between the start and
// end of the ephemeral flow.
const rootElem = this.testFocusableGroup2
.getRootFocusableNode()
.getFocusableElement();
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.strictEqual(
this.focusManager.getFocusedTree(),
this.testFocusableGroup2,
);
assert.strictEqual(activeElems.length, 1);
assert.includesClass(
rootElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, rootElem);
});
test('then focusNode() and finish ephemeral callback correctly sets new active state', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
this.focusManager.focusNode(this.testFocusableGroup2Node1);
finishFocusCallback();
// The tree's root should now be the active element since focus changed between the start and
// end of the ephemeral flow.
const nodeElem = this.testFocusableGroup2Node1.getFocusableElement();
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(activeElems.length, 1);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, nodeElem);
});
test('then DOM focus change and finish ephemeral callback correctly sets new active state', 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',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
document.getElementById('testFocusableGroup2.node1').focus();
finishFocusCallback();
// The tree's root should now be the active element since focus changed between the start and
// end of the ephemeral flow.
const nodeElem = this.testFocusableGroup2Node1.getFocusableElement();
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableGroup2Node1,
);
assert.strictEqual(activeElems.length, 1);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, nodeElem);
});
test('with focus change callback initially calls focus change callback with initial state', function () {
const callback = sinon.fake();
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
assert.strictEqual(callback.callCount, 1);
assert.isTrue(callback.firstCall.calledWithExactly(true));
});
test('with focus change callback finishes ephemeral does not calls focus change callback again', function () {
const callback = sinon.fake();
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const finishFocusCallback = this.focusManager.takeEphemeralFocus(
ephemeralElement,
callback,
);
callback.resetHistory();
finishFocusCallback();
assert.isFalse(callback.called);
});
test('with focus change callback set focus to ephemeral child does not call focus change callback again', function () {
const callback = sinon.fake();
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const ephemeralElementChild = document.getElementById(
'nonTreeElementForEphemeralFocus.child1',
);
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
callback.resetHistory();
ephemeralElementChild.focus();
// Focusing a child element shouldn't invoke the callback since the
// ephemeral element's tree still holds focus.
assert.isFalse(callback.called);
});
test('with focus change callback set focus to non-ephemeral element calls focus change callback', function () {
const callback = sinon.fake();
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const ephemeralElement2 = document.getElementById(
'nonTreeElementForEphemeralFocus2',
);
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
ephemeralElement2.focus();
// There should be a second call that indicates focus was lost.
assert.strictEqual(callback.callCount, 2);
assert.isTrue(callback.secondCall.calledWithExactly(false));
});
test('with focus change callback set focus to non-ephemeral element then back calls focus change callback again', function () {
const callback = sinon.fake();
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
const ephemeralElement = document.getElementById(
'nonTreeElementForEphemeralFocus',
);
const ephemeralElementChild = document.getElementById(
'nonTreeElementForEphemeralFocus.child1',
);
const ephemeralElement2 = document.getElementById(
'nonTreeElementForEphemeralFocus2',
);
this.focusManager.takeEphemeralFocus(ephemeralElement, callback);
ephemeralElement2.focus();
ephemeralElementChild.focus();
// The latest call should be returning focus.
assert.strictEqual(callback.callCount, 3);
assert.isTrue(callback.thirdCall.calledWithExactly(true));
});
test('with focus change callback set focus to non-ephemeral element with auto return finishes ephemeral', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const ephemeralElement2 = document.getElementById(
'nonTreeElementForEphemeralFocus2',
);
const finishFocusCallback = this.focusManager.takeEphemeralFocus(
ephemeralElement,
(hasFocus) => {
if (!hasFocus) finishFocusCallback();
},
);
// Force focus away, triggering the callback's automatic returning logic.
ephemeralElement2.focus();
// The original focused node should be restored.
const nodeElem = this.testFocusableTree2Node1.getFocusableElement();
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.strictEqual(
this.focusManager.getFocusedNode(),
this.testFocusableTree2Node1,
);
assert.strictEqual(activeElems.length, 1);
assert.includesClass(
nodeElem.classList,
FocusManager.ACTIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, nodeElem);
});
test('with focus on non-ephemeral element ephemeral ended does not restore to focused node', function () {
this.focusManager.registerTree(this.testFocusableTree2);
this.focusManager.registerTree(this.testFocusableGroup2);
this.focusManager.focusNode(this.testFocusableTree2Node1);
const ephemeralElement = document.getElementById(
'nonTreeGroupForEphemeralFocus',
);
const ephemeralElement2 = document.getElementById(
'nonTreeElementForEphemeralFocus2',
);
const finishFocusCallback =
this.focusManager.takeEphemeralFocus(ephemeralElement);
// Force focus away, triggering the callback's automatic returning logic.
ephemeralElement2.focus();
finishFocusCallback();
// The original node should not be focused since the ephemeral element
// lost its own DOM focus while ephemeral focus was active. Instead, the
// newly active element should still hold focus.
const activeElems = Array.from(
document.querySelectorAll(ACTIVE_FOCUS_NODE_CSS_SELECTOR),
);
const passiveElems = Array.from(
document.querySelectorAll(PASSIVE_FOCUS_NODE_CSS_SELECTOR),
);
assert.isEmpty(activeElems);
assert.strictEqual(passiveElems.length, 1);
assert.includesClass(
this.testFocusableTree2Node1.getFocusableElement().classList,
FocusManager.PASSIVE_FOCUS_NODE_CSS_CLASS_NAME,
);
assert.strictEqual(document.activeElement, ephemeralElement2);
assert.isFalse(this.focusManager.ephemeralFocusTaken());
});
});
});