Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/static_src/elements/framework/mr-dropdown/mr-dropdown.test.js b/static_src/elements/framework/mr-dropdown/mr-dropdown.test.js
new file mode 100644
index 0000000..51f8ce9
--- /dev/null
+++ b/static_src/elements/framework/mr-dropdown/mr-dropdown.test.js
@@ -0,0 +1,276 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {assert} from 'chai';
+import {MrDropdown, SCREENREADER_ATTRIBUTE_ERROR} from './mr-dropdown.js';
+import sinon from 'sinon';
+
+let element;
+let randomButton;
+
+describe('mr-dropdown', () => {
+ beforeEach(() => {
+ element = document.createElement('mr-dropdown');
+ document.body.appendChild(element);
+ element.label = 'new dropdown';
+
+ randomButton = document.createElement('button');
+ document.body.appendChild(randomButton);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(element);
+ document.body.removeChild(randomButton);
+ });
+
+ it('initializes', () => {
+ assert.instanceOf(element, MrDropdown);
+ });
+
+ it('warns users about accessibility when no label or text', async () => {
+ element.label = 'ok';
+ sinon.spy(console, 'error');
+
+ await element.updateComplete;
+ sinon.assert.notCalled(console.error);
+
+ element.label = undefined;
+
+ await element.updateComplete;
+ sinon.assert.calledWith(console.error, SCREENREADER_ATTRIBUTE_ERROR);
+
+ console.error.restore();
+ });
+
+ it('toggle changes opened state', () => {
+ element.open();
+ assert.isTrue(element.opened);
+
+ element.close();
+ assert.isFalse(element.opened);
+
+ element.toggle();
+ assert.isTrue(element.opened);
+
+ element.toggle();
+ assert.isFalse(element.opened);
+
+ element.toggle();
+ element.toggle();
+ assert.isFalse(element.opened);
+ });
+
+ it('clicking outside element closes menu', () => {
+ element.open();
+ assert.isTrue(element.opened);
+
+ randomButton.click();
+
+ assert.isFalse(element.opened);
+ });
+
+ it('escape while focusing the anchor closes menu', async () => {
+ element.open();
+ await element.updateComplete;
+
+ assert.isTrue(element.opened);
+
+ const anchor = element.shadowRoot.querySelector('.anchor');
+ anchor.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
+
+ assert.isFalse(element.opened);
+ });
+
+ it('other key while focusing the anchor does not close menu', async () => {
+ element.open();
+ await element.updateComplete;
+
+ assert.isTrue(element.opened);
+
+ const anchor = element.shadowRoot.querySelector('.anchor');
+ anchor.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter'}));
+
+ assert.isTrue(element.opened);
+ });
+
+ it('escape while focusing an item closes the menu', async () => {
+ element.items = [{text: 'An item'}];
+ element.open();
+ await element.updateComplete;
+
+ assert.isTrue(element.opened);
+
+ const item = element.shadowRoot.querySelector('.menu-item');
+ item.dispatchEvent(new KeyboardEvent('keydown', {key: 'Escape'}));
+
+ assert.isFalse(element.opened);
+ });
+
+ it('icon hidden when undefined', async () => {
+ element.items = [
+ {text: 'test'},
+ ];
+
+ await element.updateComplete;
+
+ const icon = element.shadowRoot.querySelector(
+ '.menu-item > .material-icons');
+
+ assert.isTrue(icon.hidden);
+ });
+
+ it('icon shown when defined, even as empty string', async () => {
+ element.items = [
+ {text: 'test', icon: ''},
+ ];
+
+ await element.updateComplete;
+
+ const icon = element.shadowRoot.querySelector(
+ '.menu-item > .material-icons');
+
+ assert.isFalse(icon.hidden);
+ assert.equal(icon.textContent.trim(), '');
+ });
+
+ it('icon shown when set to material icon', async () => {
+ element.items = [
+ {text: 'test', icon: 'check'},
+ ];
+
+ await element.updateComplete;
+
+ const icon = element.shadowRoot.querySelector(
+ '.menu-item > .material-icons');
+
+ assert.isFalse(icon.hidden);
+ assert.equal(icon.textContent.trim(), 'check');
+ });
+
+ it('items with handlers are handled', async () => {
+ const handler1 = sinon.spy();
+ const handler2 = sinon.spy();
+ const handler3 = sinon.spy();
+
+ element.items = [
+ {
+ url: '#',
+ text: 'blah',
+ handler: handler1,
+ },
+ {
+ url: '#',
+ text: 'rutabaga noop',
+ handler: handler2,
+ },
+ {
+ url: '#',
+ text: 'click me please',
+ handler: handler3,
+ },
+ ];
+
+ element.open();
+
+ await element.updateComplete;
+
+ element.clickItem(0);
+
+ assert.isTrue(handler1.calledOnce);
+ assert.isFalse(handler2.called);
+ assert.isFalse(handler3.called);
+
+ element.clickItem(2);
+
+ assert.isTrue(handler1.calledOnce);
+ assert.isFalse(handler2.called);
+ assert.isTrue(handler3.calledOnce);
+ });
+
+ describe('nested dropdown menus', () => {
+ beforeEach(() => {
+ element.items = [
+ {
+ text: 'test',
+ items: [
+ {text: 'item 1'},
+ {text: 'item 2'},
+ {text: 'item 3'},
+ ],
+ },
+ ];
+
+ element.open();
+ });
+
+ it('nested dropdown menu renders', async () => {
+ await element.updateComplete;
+
+ const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
+
+ assert.equal(nestedDropdown.text, 'test');
+ assert.deepEqual(nestedDropdown.items, [
+ {text: 'item 1'},
+ {text: 'item 2'},
+ {text: 'item 3'},
+ ]);
+ });
+
+ it('clicking nested item with handler calls handler', async () => {
+ const handler = sinon.stub();
+ element.items = [{
+ text: 'test',
+ items: [
+ {text: 'item 1'},
+ {
+ text: 'item with handler',
+ handler,
+ },
+ ],
+ }];
+
+ await element.updateComplete;
+
+ const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
+
+ nestedDropdown.open();
+ await element.updateComplete;
+
+ // Clicking an unrelated nested item shouldn't call the handler.
+ nestedDropdown.clickItem(0);
+ // Nor should clicking the parent item call the handler.
+ element.clickItem(0);
+ sinon.assert.notCalled(handler);
+
+ element.open();
+ nestedDropdown.open();
+ await element.updateComplete;
+
+ nestedDropdown.clickItem(1);
+ sinon.assert.calledOnce(handler);
+ });
+
+ it('clicking nested dropdown menu toggles nested menu', async () => {
+ await element.updateComplete;
+
+ const nestedDropdown = element.shadowRoot.querySelector('mr-dropdown');
+ const nestedAnchor = nestedDropdown.shadowRoot.querySelector('.anchor');
+
+ assert.isTrue(element.opened);
+ assert.isFalse(nestedDropdown.opened);
+
+ nestedAnchor.click();
+ await element.updateComplete;
+
+ assert.isTrue(element.opened);
+ assert.isTrue(nestedDropdown.opened);
+
+ nestedAnchor.click();
+ await element.updateComplete;
+
+ assert.isTrue(element.opened);
+ assert.isFalse(nestedDropdown.opened);
+ });
+ });
+});