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);
+    });
+  });
+});