Move dom related utils from scripts/util.js to utils/dom-util.js
Also replace existing usages and add more tests on all util methods. Change-Id: I89e0d9413153bfc115cd989ca7c66893b9709cc2
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {descendedFromClass} from '../../utils/dom-util.js';
|
||||
|
||||
export const DomUtilBehavior = {
|
||||
/**
|
||||
* Are any ancestors of the element (or the element itself) members of the
|
||||
@@ -28,13 +30,9 @@ export const DomUtilBehavior = {
|
||||
* @return {boolean}
|
||||
*/
|
||||
descendedFromClass(element, className, opt_stopElement) {
|
||||
let isDescendant = element.classList.contains(className);
|
||||
while (!isDescendant && element.parentElement &&
|
||||
(!opt_stopElement || element.parentElement !== opt_stopElement)) {
|
||||
isDescendant = element.classList.contains(className);
|
||||
element = element.parentElement;
|
||||
}
|
||||
return isDescendant;
|
||||
console.warn('DomUtilBehavior is deprecated.' +
|
||||
'Use descendedFromClass from utils directly.');
|
||||
return descendedFromClass(element, className, opt_stopElement);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-beh
|
||||
import {RESTClientBehavior} from '../../../behaviors/rest-client-behavior/rest-client-behavior.js';
|
||||
import {GrEditConstants} from '../../edit/gr-edit-constants.js';
|
||||
import {GrCountStringFormatter} from '../../shared/gr-count-string-formatter/gr-count-string-formatter.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {getComputedStyleValue} from '../../../utils/dom-util.js';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
|
||||
import {pluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
|
||||
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
|
||||
@@ -2003,7 +2003,7 @@ class GrChangeView extends mixinBehaviors( [
|
||||
_computeShowRelatedToggle() {
|
||||
// Make sure the max height has been applied, since there is now content
|
||||
// to populate.
|
||||
if (!util.getComputedStyleValue('--relation-chain-max-height', this)) {
|
||||
if (!getComputedStyleValue('--relation-chain-max-height', this)) {
|
||||
this._updateRelatedChangeMaxHeight();
|
||||
}
|
||||
// Prevents showMore from showing when click on related change, since the
|
||||
|
||||
@@ -24,7 +24,7 @@ import {dom} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {KeyboardShortcutBinder} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
|
||||
import {GrEditConstants} from '../../edit/gr-edit-constants.js';
|
||||
import {_testOnly_resetEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {getComputedStyleValue} from '../../../utils/dom-util.js';
|
||||
import {GerritNav} from '../../core/gr-navigation/gr-navigation.js';
|
||||
import {pluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
|
||||
import {_testOnly_initGerritPluginApi} from '../../shared/gr-js-api-interface/gr-gerrit.js';
|
||||
@@ -320,7 +320,7 @@ suite('gr-change-view tests', () => {
|
||||
});
|
||||
|
||||
const getCustomCssValue =
|
||||
cssParam => util.getComputedStyleValue(cssParam, element);
|
||||
cssParam => getComputedStyleValue(cssParam, element);
|
||||
|
||||
test('_handleMessageAnchorTap', () => {
|
||||
element._changeNum = '1';
|
||||
|
||||
@@ -24,7 +24,7 @@ import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-diff-selection_html.js';
|
||||
import {DomUtilBehavior} from '../../../behaviors/dom-util-behavior/dom-util-behavior.js';
|
||||
import {GrRangeNormalizer} from '../gr-diff-highlight/gr-range-normalizer.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {querySelectorAll} from '../../../utils/dom-util.js';
|
||||
|
||||
/**
|
||||
* Possible CSS classes indicating the state of selection. Dynamically added/
|
||||
@@ -203,7 +203,7 @@ class GrDiffSelection extends mixinBehaviors( [
|
||||
}
|
||||
|
||||
_getSelection() {
|
||||
const diffHosts = util.querySelectorAll(document.body, 'gr-diff');
|
||||
const diffHosts = querySelectorAll(document.body, 'gr-diff');
|
||||
if (!diffHosts.length) return window.getSelection();
|
||||
|
||||
const curDiffHost = diffHosts.find(diffHost => {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {getMockDiffResponse} from '../../../test/mocks/diff-response.js';
|
||||
import './gr-diff.js';
|
||||
import {dom, flush} from '@polymer/polymer/lib/legacy/polymer.dom.js';
|
||||
import {GrDiffBuilderImage} from '../gr-diff-builder/gr-diff-builder-image.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {getComputedStyleValue} from '../../../utils/dom-util.js';
|
||||
import {_setHiddenScroll} from '../../../scripts/hiddenscroll.js';
|
||||
import {runA11yAudit} from '../../../test/a11y-test-utils.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
@@ -82,14 +82,14 @@ suite('gr-diff tests', () => {
|
||||
element = basicFixture.instantiate();
|
||||
element.prefs = Object.assign({}, MINIMAL_PREFS, {line_wrapping: true});
|
||||
flushAsynchronousOperations();
|
||||
assert.equal(util.getComputedStyleValue('--line-limit', element), '80ch');
|
||||
assert.equal(getComputedStyleValue('--line-limit', element), '80ch');
|
||||
});
|
||||
|
||||
test('line limit without line_wrapping', () => {
|
||||
element = basicFixture.instantiate();
|
||||
element.prefs = Object.assign({}, MINIMAL_PREFS, {line_wrapping: false});
|
||||
flushAsynchronousOperations();
|
||||
assert.isNotOk(util.getComputedStyleValue('--line-limit', element));
|
||||
assert.isNotOk(getComputedStyleValue('--line-limit', element));
|
||||
});
|
||||
|
||||
suite('_get{PatchNum|IsParentComment}ByLineAndContent', () => {
|
||||
|
||||
@@ -23,7 +23,7 @@ import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {htmlTemplate} from './gr-button_html.js';
|
||||
import {TooltipBehavior} from '../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js';
|
||||
import {KeyboardShortcutBehavior} from '../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.js';
|
||||
import {util} from '../../../scripts/util.js';
|
||||
import {getEventPath} from '../../../utils/dom-util.js';
|
||||
import {appContext} from '../../../services/app-context.js';
|
||||
|
||||
/**
|
||||
@@ -113,7 +113,7 @@ class GrButton extends mixinBehaviors( [
|
||||
}
|
||||
|
||||
this.reporting.reportInteraction('button-click',
|
||||
{path: util.getEventPath(e)});
|
||||
{path: getEventPath(e)});
|
||||
}
|
||||
|
||||
_disabledChanged(disabled) {
|
||||
|
||||
@@ -15,17 +15,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function getPathFromNode(el) {
|
||||
if (!el.tagName || el.tagName === 'GR-APP'
|
||||
|| el instanceof DocumentFragment
|
||||
|| el instanceof HTMLSlotElement) {
|
||||
return '';
|
||||
}
|
||||
let path = el.tagName.toLowerCase();
|
||||
if (el.id) path += `#${el.id}`;
|
||||
if (el.className) path += `.${el.className.replace(/ /g, '.')}`;
|
||||
return path;
|
||||
}
|
||||
// TODO (dmfilippov): Each function must be exported separately. According to
|
||||
// the code style guide, a namespacing is not allowed.
|
||||
export const util = {
|
||||
@@ -76,133 +65,4 @@ export const util = {
|
||||
};
|
||||
return wrappedPromise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get computed style value.
|
||||
*
|
||||
* If ShadyCSS is provided, use ShadyCSS api.
|
||||
* If `getComputedStyleValue` is provided on the element, use it.
|
||||
* Otherwise fallback to native method (in polymer 2).
|
||||
*
|
||||
*/
|
||||
getComputedStyleValue: (name, el) => {
|
||||
let style;
|
||||
if (window.ShadyCSS) {
|
||||
style = ShadyCSS.getComputedStyleValue(el, name);
|
||||
} else if (el.getComputedStyleValue) {
|
||||
style = el.getComputedStyleValue(name);
|
||||
} else {
|
||||
style = getComputedStyle(el).getPropertyValue(name);
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
/**
|
||||
* Query selector on a dom element.
|
||||
*
|
||||
* This is shadow DOM compatible, but only works when selector is within
|
||||
* one shadow host, won't work if your selector is crossing
|
||||
* multiple shadow hosts.
|
||||
*
|
||||
*/
|
||||
querySelector: (el, selector) => {
|
||||
let nodes = [el];
|
||||
let result = null;
|
||||
while (nodes.length) {
|
||||
const node = nodes.pop();
|
||||
|
||||
// Skip if it's an invalid node.
|
||||
if (!node || !node.querySelector) continue;
|
||||
|
||||
// Try find it with native querySelector directly
|
||||
result = node.querySelector(selector);
|
||||
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add all nodes with shadowRoot and loop through
|
||||
const allShadowNodes = [...node.querySelectorAll('*')]
|
||||
.filter(child => !!child.shadowRoot)
|
||||
.map(child => child.shadowRoot);
|
||||
nodes = nodes.concat(allShadowNodes);
|
||||
|
||||
// Add shadowRoot of current node if has one
|
||||
// as its not included in node.querySelectorAll('*')
|
||||
if (node.shadowRoot) {
|
||||
nodes.push(node.shadowRoot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Query selector all dom elements matching with certain selector.
|
||||
*
|
||||
* This is shadow DOM compatible, but only works when selector is within
|
||||
* one shadow host, won't work if your selector is crossing
|
||||
* multiple shadow hosts.
|
||||
*
|
||||
* Note: this can be very expensive, only use when have to.
|
||||
*/
|
||||
querySelectorAll: (el, selector) => {
|
||||
let nodes = [el];
|
||||
const results = new Set();
|
||||
while (nodes.length) {
|
||||
const node = nodes.pop();
|
||||
|
||||
if (!node || !node.querySelectorAll) continue;
|
||||
|
||||
// Try find all from regular children
|
||||
[...node.querySelectorAll(selector)]
|
||||
.forEach(el => results.add(el));
|
||||
|
||||
// Add all nodes with shadowRoot and loop through
|
||||
const allShadowNodes = [...node.querySelectorAll('*')]
|
||||
.filter(child => !!child.shadowRoot)
|
||||
.map(child => child.shadowRoot);
|
||||
nodes = nodes.concat(allShadowNodes);
|
||||
|
||||
// Add shadowRoot of current node if has one
|
||||
// as its not included in node.querySelectorAll('*')
|
||||
if (node.shadowRoot) {
|
||||
nodes.push(node.shadowRoot);
|
||||
}
|
||||
}
|
||||
return [...results];
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the dom path of the current event.
|
||||
*
|
||||
* If the event object contains a `path` property, then use it,
|
||||
* otherwise, construct the dom path based on the event target.
|
||||
*
|
||||
* @param {!Event} e
|
||||
* @return {string}
|
||||
* @example
|
||||
*
|
||||
* domNode.onclick = e => {
|
||||
* getEventPath(e); // eg: div.class1>p#pid.class2
|
||||
* }
|
||||
*/
|
||||
getEventPath: e => {
|
||||
if (!e) return '';
|
||||
|
||||
let path = e.path;
|
||||
if (!path || !path.length) {
|
||||
path = [];
|
||||
let el = e.target;
|
||||
while (el) {
|
||||
path.push(el);
|
||||
el = el.parentNode || el.host;
|
||||
}
|
||||
}
|
||||
|
||||
return path.reduce((domPath, curEl) => {
|
||||
const pathForEl = getPathFromNode(curEl);
|
||||
if (!pathForEl) return domPath;
|
||||
return domPath ? `${pathForEl}>${domPath}` : pathForEl;
|
||||
}, '');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
@license
|
||||
Copyright (C) 2020 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
|
||||
<meta charset="utf-8">
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
|
||||
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<script src="/components/wct-browser-legacy/browser.js"></script>
|
||||
|
||||
<test-fixture id="basic">
|
||||
<template>
|
||||
<div id="test" class="a b c">
|
||||
<a class="testBtn"></a>
|
||||
</div>
|
||||
</template>
|
||||
</test-fixture>
|
||||
|
||||
<script type="module">
|
||||
import '../test/common-test-setup.js';
|
||||
import {util} from './util.js';
|
||||
suite('util tests', () => {
|
||||
suite('getEventPath', () => {
|
||||
test('empty event', () => {
|
||||
assert.equal(util.getEventPath(), '');
|
||||
assert.equal(util.getEventPath(null), '');
|
||||
assert.equal(util.getEventPath(undefined), '');
|
||||
assert.equal(util.getEventPath({}), '');
|
||||
});
|
||||
|
||||
test('event with fake path', () => {
|
||||
assert.equal(util.getEventPath({path: []}), '');
|
||||
assert.equal(util.getEventPath({path: [
|
||||
{tagName: 'dd'},
|
||||
]}), 'dd');
|
||||
});
|
||||
|
||||
test('event with fake complicated path', () => {
|
||||
assert.equal(util.getEventPath({path: [
|
||||
{tagName: 'dd', id: 'test', className: 'a b'},
|
||||
{tagName: 'DIV', id: 'test2', className: 'a b c'},
|
||||
]}), 'div#test2.a.b.c>dd#test.a.b');
|
||||
});
|
||||
|
||||
test('event with fake target', () => {
|
||||
const fakeTargetParent2 = {
|
||||
tagName: 'DIV', id: 'test2', className: 'a b c',
|
||||
};
|
||||
const fakeTargetParent1 = {
|
||||
parentNode: fakeTargetParent2,
|
||||
tagName: 'dd',
|
||||
id: 'test',
|
||||
className: 'a b',
|
||||
};
|
||||
const fakeTarget = {tagName: 'SPAN', parentNode: fakeTargetParent1};
|
||||
assert.equal(
|
||||
util.getEventPath({target: fakeTarget}),
|
||||
'div#test2.a.b.c>dd#test.a.b>span'
|
||||
);
|
||||
});
|
||||
|
||||
test('event with real click', () => {
|
||||
const element = fixture('basic');
|
||||
const aLink = element.querySelector('a');
|
||||
let path;
|
||||
aLink.onclick = e => path = util.getEventPath(e);
|
||||
MockInteractions.click(aLink);
|
||||
assert.equal(
|
||||
path,
|
||||
'html>body>test-fixture#basic>div#test.a.b.c>a.testBtn'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -245,7 +245,6 @@ const scripts = [
|
||||
'gr-group-suggestions-provider/gr-group-suggestions-provider_test.html',
|
||||
'gr-display-name-utils/gr-display-name-utils_test.html',
|
||||
'gr-email-suggestions-provider/gr-email-suggestions-provider_test.html',
|
||||
'util_test.html',
|
||||
];
|
||||
/* eslint-enable max-len */
|
||||
for (let file of scripts) {
|
||||
|
||||
178
polygerrit-ui/app/utils/dom-util.js
Normal file
178
polygerrit-ui/app/utils/dom-util.js
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function getPathFromNode(el) {
|
||||
if (!el.tagName || el.tagName === 'GR-APP'
|
||||
|| el instanceof DocumentFragment
|
||||
|| el instanceof HTMLSlotElement) {
|
||||
return '';
|
||||
}
|
||||
let path = el.tagName.toLowerCase();
|
||||
if (el.id) path += `#${el.id}`;
|
||||
if (el.className) path += `.${el.className.replace(/ /g, '.')}`;
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get computed style value.
|
||||
*
|
||||
* If ShadyCSS is provided, use ShadyCSS api.
|
||||
* If `getComputedStyleValue` is provided on the element, use it.
|
||||
* Otherwise fallback to native method (in polymer 2).
|
||||
*
|
||||
*/
|
||||
export function getComputedStyleValue(name, el) {
|
||||
let style;
|
||||
if (window.ShadyCSS) {
|
||||
style = ShadyCSS.getComputedStyleValue(el, name);
|
||||
} else if (el.getComputedStyleValue) {
|
||||
style = el.getComputedStyleValue(name);
|
||||
} else {
|
||||
style = getComputedStyle(el).getPropertyValue(name);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query selector on a dom element.
|
||||
*
|
||||
* This is shadow DOM compatible, but only works when selector is within
|
||||
* one shadow host, won't work if your selector is crossing
|
||||
* multiple shadow hosts.
|
||||
*
|
||||
*/
|
||||
export function querySelector(el, selector) {
|
||||
let nodes = [el];
|
||||
let result = null;
|
||||
while (nodes.length) {
|
||||
const node = nodes.pop();
|
||||
|
||||
// Skip if it's an invalid node.
|
||||
if (!node || !node.querySelector) continue;
|
||||
|
||||
// Try find it with native querySelector directly
|
||||
result = node.querySelector(selector);
|
||||
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add all nodes with shadowRoot and loop through
|
||||
const allShadowNodes = [...node.querySelectorAll('*')]
|
||||
.filter(child => !!child.shadowRoot)
|
||||
.map(child => child.shadowRoot);
|
||||
nodes = nodes.concat(allShadowNodes);
|
||||
|
||||
// Add shadowRoot of current node if has one
|
||||
// as its not included in node.querySelectorAll('*')
|
||||
if (node.shadowRoot) {
|
||||
nodes.push(node.shadowRoot);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query selector all dom elements matching with certain selector.
|
||||
*
|
||||
* This is shadow DOM compatible, but only works when selector is within
|
||||
* one shadow host, won't work if your selector is crossing
|
||||
* multiple shadow hosts.
|
||||
*
|
||||
* Note: this can be very expensive, only use when have to.
|
||||
*/
|
||||
export function querySelectorAll(el, selector) {
|
||||
let nodes = [el];
|
||||
const results = new Set();
|
||||
while (nodes.length) {
|
||||
const node = nodes.pop();
|
||||
|
||||
if (!node || !node.querySelectorAll) continue;
|
||||
|
||||
// Try find all from regular children
|
||||
[...node.querySelectorAll(selector)]
|
||||
.forEach(el => results.add(el));
|
||||
|
||||
// Add all nodes with shadowRoot and loop through
|
||||
const allShadowNodes = [...node.querySelectorAll('*')]
|
||||
.filter(child => !!child.shadowRoot)
|
||||
.map(child => child.shadowRoot);
|
||||
nodes = nodes.concat(allShadowNodes);
|
||||
|
||||
// Add shadowRoot of current node if has one
|
||||
// as its not included in node.querySelectorAll('*')
|
||||
if (node.shadowRoot) {
|
||||
nodes.push(node.shadowRoot);
|
||||
}
|
||||
}
|
||||
return [...results];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the dom path of the current event.
|
||||
*
|
||||
* If the event object contains a `path` property, then use it,
|
||||
* otherwise, construct the dom path based on the event target.
|
||||
*
|
||||
* @param {!Event} e
|
||||
* @return {string}
|
||||
* @example
|
||||
*
|
||||
* domNode.onclick = e => {
|
||||
* getEventPath(e); // eg: div.class1>p#pid.class2
|
||||
* }
|
||||
*/
|
||||
export function getEventPath(e) {
|
||||
if (!e) return '';
|
||||
|
||||
let path = e.path;
|
||||
if (!path || !path.length) {
|
||||
path = [];
|
||||
let el = e.target;
|
||||
while (el) {
|
||||
path.push(el);
|
||||
el = el.parentNode || el.host;
|
||||
}
|
||||
}
|
||||
|
||||
return path.reduce((domPath, curEl) => {
|
||||
const pathForEl = getPathFromNode(curEl);
|
||||
if (!pathForEl) return domPath;
|
||||
return domPath ? `${pathForEl}>${domPath}` : pathForEl;
|
||||
}, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Are any ancestors of the element (or the element itself) members of the
|
||||
* given class.
|
||||
*
|
||||
* @param {!Element} element
|
||||
* @param {string} className
|
||||
* @param {Element=} opt_stopElement If provided, stop traversing the
|
||||
* ancestry when the stop element is reached. The stop element's class
|
||||
* is not checked.
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function descendedFromClass(element, className, opt_stopElement) {
|
||||
let isDescendant = element.classList.contains(className);
|
||||
while (!isDescendant && element.parentElement &&
|
||||
(!opt_stopElement || element.parentElement !== opt_stopElement)) {
|
||||
isDescendant = element.classList.contains(className);
|
||||
element = element.parentElement;
|
||||
}
|
||||
return isDescendant;
|
||||
}
|
||||
139
polygerrit-ui/app/utils/dom-util_test.js
Normal file
139
polygerrit-ui/app/utils/dom-util_test.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import '../test/common-test-setup-karma.js';
|
||||
import {getComputedStyleValue, querySelector, querySelectorAll, descendedFromClass, getEventPath} from './dom-util.js';
|
||||
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
|
||||
import {html} from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
|
||||
class TestEle extends PolymerElement {
|
||||
static get is() {
|
||||
return 'dom-util-test-element';
|
||||
}
|
||||
|
||||
static get template() {
|
||||
return html`
|
||||
<div>
|
||||
<div class="a">
|
||||
<div class="b">
|
||||
<div class="c"></div>
|
||||
</div>
|
||||
<span class="ss"></span>
|
||||
</div>
|
||||
<span class="ss"></span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(TestEle.is, TestEle);
|
||||
|
||||
const basicFixture = fixtureFromTemplate(html`
|
||||
<div id="test" class="a b c">
|
||||
<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(null), '');
|
||||
assert.equal(getEventPath(undefined), '');
|
||||
assert.equal(getEventPath({}), '');
|
||||
});
|
||||
|
||||
test('event with fake path', () => {
|
||||
assert.equal(getEventPath({path: []}), '');
|
||||
assert.equal(getEventPath({path: [
|
||||
{tagName: 'dd'},
|
||||
]}), 'dd');
|
||||
});
|
||||
|
||||
test('event with fake complicated path', () => {
|
||||
assert.equal(getEventPath({path: [
|
||||
{tagName: 'dd', id: 'test', className: 'a b'},
|
||||
{tagName: 'DIV', id: 'test2', className: 'a b c'},
|
||||
]}), 'div#test2.a.b.c>dd#test.a.b');
|
||||
});
|
||||
|
||||
test('event with fake target', () => {
|
||||
const fakeTargetParent2 = {
|
||||
tagName: 'DIV', id: 'test2', className: 'a b c',
|
||||
};
|
||||
const fakeTargetParent1 = {
|
||||
parentNode: fakeTargetParent2,
|
||||
tagName: 'dd',
|
||||
id: 'test',
|
||||
className: 'a b',
|
||||
};
|
||||
const fakeTarget = {tagName: 'SPAN', parentNode: fakeTargetParent1};
|
||||
assert.equal(
|
||||
getEventPath({target: fakeTarget}),
|
||||
'div#test2.a.b.c>dd#test.a.b>span'
|
||||
);
|
||||
});
|
||||
|
||||
test('event with real click', () => {
|
||||
const element = basicFixture.instantiate();
|
||||
const aLink = element.querySelector('a');
|
||||
let path;
|
||||
aLink.onclick = e => path = getEventPath(e);
|
||||
MockInteractions.click(aLink);
|
||||
assert.equal(
|
||||
path,
|
||||
`html>body>test-fixture#${basicFixture.fixtureId}>` +
|
||||
'div#test.a.b.c>a.testBtn'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('querySelector and querySelectorAll', () => {
|
||||
test('query cross shadow dom', () => {
|
||||
const element = basicFixture.instantiate();
|
||||
const theFirstEl = querySelector(element, '.ss');
|
||||
const allEls = querySelectorAll(element, '.ss');
|
||||
assert.equal(allEls.length, 3);
|
||||
assert.equal(theFirstEl, allEls[0]);
|
||||
});
|
||||
});
|
||||
|
||||
suite('getComputedStyleValue', () => {
|
||||
test('color style', () => {
|
||||
const element = basicFixture.instantiate();
|
||||
const testBtn = querySelector(element, '.testBtn');
|
||||
assert.equal(
|
||||
getComputedStyleValue('color', testBtn), 'rgb(255, 0, 0)'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
suite('descendedFromClass', () => {
|
||||
test('basic tests', () => {
|
||||
const element = basicFixture.instantiate();
|
||||
const testEl = querySelector(element, 'dom-util-test-element');
|
||||
// .c is a child of .a and not vice versa.
|
||||
assert.isTrue(descendedFromClass(querySelector(testEl, '.c'), 'a'));
|
||||
assert.isFalse(descendedFromClass(querySelector(testEl, '.a'), 'c'));
|
||||
|
||||
// Stops at stop element.
|
||||
assert.isFalse(descendedFromClass(querySelector(testEl, '.c'), 'a',
|
||||
querySelector(testEl, '.b')));
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user