| /** |
| * @license |
| * Copyright 2020 Google LLC |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| import '../test/common-test-setup'; |
| import { |
| descendedFromClass, |
| eventMatchesShortcut, |
| getComputedStyleValue, |
| getEventPath, |
| Key, |
| Modifier, |
| querySelectorAll, |
| shouldSuppress, |
| strToClassName, |
| } from './dom-util'; |
| import {mockPromise, pressKey, queryAndAssert} from '../test/test-utils'; |
| import {fixture, assert} from '@open-wc/testing'; |
| import {LitElement, html} from 'lit'; |
| import {customElement} from 'lit/decorators.js'; |
| |
| /** |
| * You might think that instead of passing in the callback with assertions as a |
| * parameter that you could as well just `await keyEventOn()` and *then* run |
| * your assertions. But at that point the event is not "hot" anymore, so most |
| * likely you want to assert stuff about the event within the callback |
| * parameter. |
| */ |
| function keyEventOn( |
| el: HTMLElement, |
| callback: (e: KeyboardEvent) => void, |
| key = 'k' |
| ): Promise<KeyboardEvent> { |
| const promise = mockPromise<KeyboardEvent>(); |
| el.addEventListener('keydown', (e: KeyboardEvent) => { |
| callback(e); |
| promise.resolve(e); |
| }); |
| pressKey(el, key); |
| return promise; |
| } |
| |
| @customElement('dom-util-test-element') |
| export class TestElement extends LitElement { |
| override render() { |
| return html` |
| <div> |
| <div class="a"> |
| <div class="b"> |
| <div class="c"></div> |
| </div> |
| <span class="ss"></span> |
| </div> |
| <span class="ss"></span> |
| </div> |
| `; |
| } |
| } |
| |
| async function createFixture() { |
| return await fixture<HTMLElement>(html` |
| <div id="test" class="a b c d"> |
| <a class="testBtn" style="color:red;"></a> |
| <dom-util-test-element></dom-util-test-element> |
| <span class="ss"></span> |
| </div> |
| `); |
| } |
| |
| suite('dom-util tests', () => { |
| suite('getEventPath', () => { |
| test('empty event', () => { |
| assert.equal(getEventPath(), ''); |
| assert.equal(getEventPath(undefined), ''); |
| assert.equal(getEventPath(new MouseEvent('click')), ''); |
| }); |
| |
| test('event with fake path', () => { |
| assert.equal(getEventPath(new MouseEvent('click')), ''); |
| const dd = document.createElement('dd'); |
| assert.equal( |
| getEventPath({...new MouseEvent('click'), composedPath: () => [dd]}), |
| 'dd' |
| ); |
| }); |
| |
| test('event with fake complicated path', () => { |
| const dd = document.createElement('dd'); |
| dd.setAttribute('id', 'test'); |
| dd.className = 'a b'; |
| const divNode = document.createElement('DIV'); |
| divNode.id = 'test2'; |
| divNode.className = 'a b c'; |
| assert.equal( |
| getEventPath({ |
| ...new MouseEvent('click'), |
| composedPath: () => [dd, divNode], |
| }), |
| 'div#test2.a.b.c>dd#test.a.b' |
| ); |
| }); |
| |
| test('event with fake target', () => { |
| const fakeTargetParent1 = document.createElement('dd'); |
| fakeTargetParent1.setAttribute('id', 'test'); |
| fakeTargetParent1.className = 'a b'; |
| const fakeTargetParent2 = document.createElement('DIV'); |
| fakeTargetParent2.id = 'test2'; |
| fakeTargetParent2.className = 'a b c'; |
| fakeTargetParent2.appendChild(fakeTargetParent1); |
| const fakeTarget = document.createElement('SPAN'); |
| fakeTargetParent1.appendChild(fakeTarget); |
| assert.equal( |
| getEventPath({ |
| ...new MouseEvent('click'), |
| composedPath: () => [], |
| target: fakeTarget, |
| }), |
| 'div#test2.a.b.c>dd#test.a.b>span' |
| ); |
| }); |
| |
| test('event with real click', async () => { |
| const element = await createFixture(); |
| const aLink = queryAndAssert<HTMLAnchorElement>(element, 'a'); |
| let path; |
| aLink.addEventListener('click', (e: Event) => { |
| path = getEventPath(e as MouseEvent); |
| }); |
| aLink.click(); |
| assert.equal(path, 'html>body>div>div#test.a.b.c.d>a.testBtn'); |
| }); |
| }); |
| |
| suite('querySelector and querySelectorAll', () => { |
| test('query cross shadow dom', async () => { |
| const element = await createFixture(); |
| const theFirstEl = queryAndAssert(element, '.ss'); |
| const allEls = querySelectorAll(element, '.ss'); |
| assert.equal(allEls.length, 3); |
| assert.equal(theFirstEl, allEls[0]); |
| }); |
| }); |
| |
| suite('getComputedStyleValue', () => { |
| test('color style', async () => { |
| const element = await createFixture(); |
| const testBtn = queryAndAssert(element, '.testBtn'); |
| assert.equal(getComputedStyleValue('color', testBtn), 'rgb(255, 0, 0)'); |
| }); |
| }); |
| |
| suite('descendedFromClass', () => { |
| test('descends from itself', async () => { |
| const element = await createFixture(); |
| const testEl = queryAndAssert(element, 'dom-util-test-element'); |
| assert.isTrue(descendedFromClass(queryAndAssert(testEl, '.c'), 'c')); |
| assert.isTrue(descendedFromClass(queryAndAssert(testEl, '.b'), 'b')); |
| assert.isTrue(descendedFromClass(queryAndAssert(testEl, '.a'), 'a')); |
| }); |
| |
| test('.c in .b in .a', async () => { |
| const element = await createFixture(); |
| const testEl = queryAndAssert(element, 'dom-util-test-element'); |
| const a = queryAndAssert(testEl, '.a'); |
| const b = queryAndAssert(testEl, '.b'); |
| const c = queryAndAssert(testEl, '.c'); |
| assert.isTrue(descendedFromClass(a, 'a')); |
| assert.isTrue(descendedFromClass(b, 'a')); |
| assert.isTrue(descendedFromClass(c, 'a')); |
| assert.isFalse(descendedFromClass(a, 'b')); |
| assert.isTrue(descendedFromClass(b, 'b')); |
| assert.isTrue(descendedFromClass(c, 'b')); |
| assert.isFalse(descendedFromClass(a, 'c')); |
| assert.isFalse(descendedFromClass(b, 'c')); |
| assert.isTrue(descendedFromClass(c, 'c')); |
| }); |
| |
| test('stops at shadow root', async () => { |
| const element = await createFixture(); |
| const testEl = queryAndAssert(element, 'dom-util-test-element'); |
| const a = queryAndAssert(testEl, '.a'); |
| // div.d is a parent of testEl, but `descendedFromClass` does not cross |
| // the shadow root boundary of <dom-util-test-element>. So div.a inside |
| // the shadow root is not considered to descend from div.d outside of it. |
| assert.isFalse(descendedFromClass(a, 'd')); |
| }); |
| |
| test('stops at stop element', async () => { |
| const element = await createFixture(); |
| const testEl = queryAndAssert(element, 'dom-util-test-element'); |
| assert.isFalse( |
| descendedFromClass( |
| queryAndAssert(testEl, '.c'), |
| 'a', |
| queryAndAssert(testEl, '.b') |
| ) |
| ); |
| }); |
| }); |
| |
| suite('strToClassName', () => { |
| test('basic tests', () => { |
| assert.equal(strToClassName(''), 'generated_'); |
| assert.equal(strToClassName('11'), 'generated_11'); |
| assert.equal(strToClassName('0.123'), 'generated_0_123'); |
| assert.equal(strToClassName('0.123', 'prefix_'), 'prefix_0_123'); |
| assert.equal(strToClassName('0>123', 'prefix_'), 'prefix_0_123'); |
| assert.equal(strToClassName('0<123', 'prefix_'), 'prefix_0_123'); |
| assert.equal(strToClassName('0+1+23', 'prefix_'), 'prefix_0_1_23'); |
| }); |
| }); |
| |
| suite('eventMatchesShortcut', () => { |
| test('basic tests', () => { |
| const a = new KeyboardEvent('keydown', {key: 'a'}); |
| const b = new KeyboardEvent('keydown', {key: 'B'}); |
| assert.isTrue(eventMatchesShortcut(a, {key: 'a'})); |
| assert.isFalse(eventMatchesShortcut(a, {key: 'B'})); |
| assert.isFalse(eventMatchesShortcut(b, {key: 'a'})); |
| assert.isTrue(eventMatchesShortcut(b, {key: 'B'})); |
| }); |
| |
| test('check modifiers for a', () => { |
| const e = new KeyboardEvent('keydown', {key: 'a'}); |
| const s = {key: 'a'}; |
| assert.isTrue(eventMatchesShortcut(e, s)); |
| |
| const eAlt = new KeyboardEvent('keydown', {key: 'a', altKey: true}); |
| const sAlt = {key: 'a', modifiers: [Modifier.ALT_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eAlt, s)); |
| assert.isFalse(eventMatchesShortcut(e, sAlt)); |
| const eCtrl = new KeyboardEvent('keydown', {key: 'a', ctrlKey: true}); |
| const sCtrl = {key: 'a', modifiers: [Modifier.CTRL_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eCtrl, s)); |
| assert.isFalse(eventMatchesShortcut(e, sCtrl)); |
| const eMeta = new KeyboardEvent('keydown', {key: 'a', metaKey: true}); |
| const sMeta = {key: 'a', modifiers: [Modifier.META_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eMeta, s)); |
| assert.isFalse(eventMatchesShortcut(e, sMeta)); |
| |
| // Do NOT check SHIFT for alphanum keys. |
| const eShift = new KeyboardEvent('keydown', {key: 'a', shiftKey: true}); |
| const sShift = {key: 'a', modifiers: [Modifier.SHIFT_KEY]}; |
| assert.isTrue(eventMatchesShortcut(eShift, s)); |
| assert.isTrue(eventMatchesShortcut(e, sShift)); |
| }); |
| |
| test('check modifiers for Enter', () => { |
| const e = new KeyboardEvent('keydown', {key: 'Enter'}); |
| const s = {key: 'Enter'}; |
| assert.isTrue(eventMatchesShortcut(e, s)); |
| |
| const eAlt = new KeyboardEvent('keydown', {key: 'Enter', altKey: true}); |
| const sAlt = {key: 'Enter', modifiers: [Modifier.ALT_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eAlt, s)); |
| assert.isFalse(eventMatchesShortcut(e, sAlt)); |
| const eCtrl = new KeyboardEvent('keydown', {key: 'Enter', ctrlKey: true}); |
| const sCtrl = {key: 'Enter', modifiers: [Modifier.CTRL_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eCtrl, s)); |
| assert.isFalse(eventMatchesShortcut(e, sCtrl)); |
| const eMeta = new KeyboardEvent('keydown', {key: 'Enter', metaKey: true}); |
| const sMeta = {key: 'Enter', modifiers: [Modifier.META_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eMeta, s)); |
| assert.isFalse(eventMatchesShortcut(e, sMeta)); |
| const eShift = new KeyboardEvent('keydown', { |
| key: 'Enter', |
| shiftKey: true, |
| }); |
| const sShift = {key: 'Enter', modifiers: [Modifier.SHIFT_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eShift, s)); |
| assert.isFalse(eventMatchesShortcut(e, sShift)); |
| }); |
| |
| test('check modifiers for [', () => { |
| const e = new KeyboardEvent('keydown', {key: '['}); |
| const s = {key: '['}; |
| assert.isTrue(eventMatchesShortcut(e, s)); |
| |
| const eCtrl = new KeyboardEvent('keydown', {key: '[', ctrlKey: true}); |
| const sCtrl = {key: '[', modifiers: [Modifier.CTRL_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eCtrl, s)); |
| assert.isFalse(eventMatchesShortcut(e, sCtrl)); |
| const eMeta = new KeyboardEvent('keydown', {key: '[', metaKey: true}); |
| const sMeta = {key: '[', modifiers: [Modifier.META_KEY]}; |
| assert.isFalse(eventMatchesShortcut(eMeta, s)); |
| assert.isFalse(eventMatchesShortcut(e, sMeta)); |
| |
| // Do NOT check SHIFT and ALT for special chars like [. |
| const eAlt = new KeyboardEvent('keydown', {key: '[', altKey: true}); |
| const sAlt = {key: '[', modifiers: [Modifier.ALT_KEY]}; |
| assert.isTrue(eventMatchesShortcut(eAlt, s)); |
| assert.isTrue(eventMatchesShortcut(e, sAlt)); |
| const eShift = new KeyboardEvent('keydown', { |
| key: '[', |
| shiftKey: true, |
| }); |
| const sShift = {key: '[', modifiers: [Modifier.SHIFT_KEY]}; |
| assert.isTrue(eventMatchesShortcut(eShift, s)); |
| assert.isTrue(eventMatchesShortcut(e, sShift)); |
| }); |
| }); |
| |
| suite('shouldSuppress', () => { |
| test('do not suppress shortcut event from <div>', async () => { |
| await keyEventOn(document.createElement('div'), e => { |
| assert.isFalse(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('suppress shortcut event from <div contenteditable>', async () => { |
| const el = document.createElement('div'); |
| el.setAttribute('contenteditable', ''); |
| await keyEventOn(el, e => { |
| assert.isTrue(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('suppress shortcut event from <input>', async () => { |
| await keyEventOn(document.createElement('input'), e => { |
| assert.isTrue(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('suppress shortcut event from <textarea>', async () => { |
| await keyEventOn(document.createElement('textarea'), e => { |
| assert.isTrue(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('suppress shortcut event from <gr-textarea>', async () => { |
| await keyEventOn(document.createElement('gr-textarea'), e => { |
| assert.isTrue(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('do not suppress shortcut event from checkbox <input>', async () => { |
| const inputEl = document.createElement('input'); |
| inputEl.setAttribute('type', 'checkbox'); |
| await keyEventOn(inputEl, e => { |
| assert.isFalse(shouldSuppress(e)); |
| }); |
| }); |
| |
| test('suppress "enter" shortcut event from <gr-button>', async () => { |
| await keyEventOn( |
| document.createElement('gr-button'), |
| e => assert.isTrue(shouldSuppress(e)), |
| Key.ENTER |
| ); |
| }); |
| |
| test('suppress "enter" shortcut event from <a>', async () => { |
| await keyEventOn(document.createElement('a'), e => { |
| assert.isFalse(shouldSuppress(e)); |
| }); |
| await keyEventOn( |
| document.createElement('a'), |
| e => assert.isTrue(shouldSuppress(e)), |
| Key.ENTER |
| ); |
| }); |
| }); |
| }); |