Bug 1453010 - Part 4: Add test for locking highlighting. r=gl
authorDaisuke Akatsuka <dakatsuka@mozilla.com>
Sat, 28 Apr 2018 10:48:23 +0900
changeset 416196 cd392b05865ea0059a8134e57a8db6e6cdf71a8b
parent 416195 a1666927ab16389f635786c38ad16a91acef5494
child 416197 d7008cb7d66a3a7984873d1b6b9201d76f31a0f1
push id33918
push usernerli@mozilla.com
push dateSun, 29 Apr 2018 09:47:13 +0000
treeherdermozilla-central@afbec7f03bd8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgl
bugs1453010
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1453010 - Part 4: Add test for locking highlighting. r=gl This patch depends on following PR. https://github.com/devtools-html/devtools-core/pull/1028 MozReview-Commit-ID: 5IAWzZ3YTyg
devtools/client/inspector/animation/components/AnimationTarget.js
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/animation/test/browser_animation_animation-target.js
devtools/client/inspector/animation/test/browser_animation_animation-target_highlight.js
devtools/client/inspector/animation/test/head.js
--- a/devtools/client/inspector/animation/components/AnimationTarget.js
+++ b/devtools/client/inspector/animation/components/AnimationTarget.js
@@ -49,17 +49,17 @@ class AnimationTarget extends Component 
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     return this.state.nodeFront !== nextState.nodeFront ||
            this.props.highlightedNode !== nextState.highlightedNode;
   }
 
   async updateNodeFront(animation) {
-    const { emitEventForTest, getNodeFromActor } = this.props;
+    const { getNodeFromActor } = this.props;
 
     // Try and get it from the playerFront directly.
     let nodeFront = animation.animationTargetNodeFront;
 
     // Next, get it from the walkerActor if it wasn't found.
     if (!nodeFront) {
       try {
         nodeFront = await getNodeFromActor(animation.actorID);
@@ -68,21 +68,21 @@ class AnimationTarget extends Component 
         // attributed to the panel having been destroyed in the meantime, this
         // error needs to be logged and render needs to stop.
         console.error(e);
         return;
       }
     }
 
     this.setState({ nodeFront });
-    emitEventForTest("animation-target-rendered");
   }
 
   render() {
     const {
+      emitEventForTest,
       onHideBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       highlightedNode,
       setHighlightedNode,
       setSelectedNode,
     } = this.props;
 
     const { nodeFront } = this.state;
@@ -90,16 +90,18 @@ class AnimationTarget extends Component 
     if (!nodeFront) {
       return dom.div(
         {
           className: "animation-target"
         }
       );
     }
 
+    emitEventForTest("animation-target-rendered");
+
     const isHighlighted = nodeFront.actorID === highlightedNode;
 
     return dom.div(
       {
         className: "animation-target" +
                    (isHighlighted ? " highlighting" : ""),
       },
       Rep(
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -19,16 +19,17 @@ support-files =
 [browser_animation_animated-property-list.js]
 [browser_animation_animated-property-list_unchanged-items.js]
 [browser_animation_animated-property-name.js]
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
 [browser_animation_animation-detail_visibility.js]
 [browser_animation_animation-list.js]
 [browser_animation_animation-target.js]
+[browser_animation_animation-target_highlight.js]
 [browser_animation_animation-timeline-tick.js]
 [browser_animation_current-time-label.js]
 [browser_animation_current-time-scrubber.js]
 [browser_animation_empty_on_invalid_nodes.js]
 [browser_animation_inspector_exists.js]
 [browser_animation_keyframes-graph_computed-value-path.js]
 [browser_animation_keyframes-graph_computed-value-path_easing-hint.js]
 [browser_animation_keyframes-graph_keyframe-marker.js]
--- a/devtools/client/inspector/animation/test/browser_animation_animation-target.js
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-target.js
@@ -2,34 +2,42 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for following AnimationTarget component works.
 // * element existance
 // * number of elements
 // * content of element
+// * select an animated node by clicking on inspect node
+// * title of inspect icon
 
 add_task(async function() {
   await addTab(URL_ROOT + "doc_simple_animation.html");
   await removeAnimatedElementsExcept([".animated", ".long"]);
-  const { animationInspector, inspector, panel } = await openAnimationInspector();
+  const { animationInspector, panel } = await openAnimationInspector();
 
   info("Checking the animation target elements existance");
   const animationItemEls = panel.querySelectorAll(".animation-list .animation-item");
   is(animationItemEls.length, animationInspector.state.animations.length,
      "Number of animation target element should be same to number of animations " +
      "that displays");
 
   for (const animationItemEl of animationItemEls) {
     const animationTargetEl = animationItemEl.querySelector(".animation-target");
     ok(animationTargetEl,
       "The animation target element should be in each animation item element");
   }
 
+  info("Checking the selecting an animated node by clicking the target node");
+  await clickOnTargetNode(animationInspector, panel, 0);
+  is(panel.querySelectorAll(".animation-target").length, 1,
+    "The length of animations should be 1");
+
   info("Checking the content of animation target");
-  await selectNodeAndWaitForAnimations(".animated", inspector);
   const animationTargetEl =
     panel.querySelector(".animation-list .animation-item .animation-target");
   is(animationTargetEl.textContent, "div.ball.animated",
     "The target element's content is correct");
   ok(animationTargetEl.querySelector(".objectBox"), "objectBox is in the page exists");
+  ok(animationTargetEl.querySelector(".open-inspector").title,
+     INSPECTOR_L10N.getStr("inspector.nodePreview.highlightNodeLabel"));
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/animation/test/browser_animation_animation-target_highlight.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for following highlighting related.
+// * highlight when mouse over on a target node
+// * unhighlight when mouse out from the above element
+// * lock highlighting when click on the inspect icon in animation target component
+// * add 'highlighting' class to animation target component during locking
+// * unlock highlighting when click on the above icon
+// * lock highlighting when click on the other inspect icon
+// * if the locked node has multi animations,
+//   the class will add to those animation target as well
+
+add_task(async function() {
+  await addTab(URL_ROOT + "doc_simple_animation.html");
+  await removeAnimatedElementsExcept([".animated", ".multi"]);
+  const { animationInspector, panel, toolbox } = await openAnimationInspector();
+
+  info("Check highlighting when mouse over on a target node");
+  let onHighlight = toolbox.once("node-highlight");
+  mouseOverOnTargetNode(animationInspector, panel, 0);
+  let nodeFront = await onHighlight;
+  assertNodeFront(nodeFront, "DIV", "ball animated");
+
+  info("Check unhighlighting when mouse out on a target node");
+  let onUnhighlight = toolbox.once("node-unhighlight");
+  mouseOutOnTargetNode(animationInspector, panel, 0);
+  await onUnhighlight;
+  ok(true, "Unhighlighted the targe node");
+
+  info("Check node is highlighted when the inspect icon is clicked");
+  onHighlight = toolbox.once("node-highlight");
+  await clickOnInspectIcon(animationInspector, panel, 0);
+  nodeFront = await onHighlight;
+  assertNodeFront(nodeFront, "DIV", "ball animated");
+  ok(panel.querySelectorAll(".animation-target")[0].classList.contains("highlighting"),
+    "The highlighted animation target element should have 'highlighting' class");
+
+  info("Check if the animation target is still highlighted on mouse out");
+  mouseOutOnTargetNode(animationInspector, panel, 0);
+  await wait(500);
+  ok(panel.querySelectorAll(".animation-target")[0].classList.contains("highlighting"),
+    "The highlighted element still should have 'highlighting' class");
+
+  info("Highlighting another animation target");
+  onHighlight = toolbox.once("node-highlight");
+  await clickOnInspectIcon(animationInspector, panel, 1);
+  nodeFront = await onHighlight;
+  assertNodeFront(nodeFront, "DIV", "ball multi");
+
+  info("Check the highlighted state of the animation targets");
+  const animationTargetEls = panel.querySelectorAll(".animation-target");
+  ok(!animationTargetEls[0].classList.contains("highlighting"),
+    "The animation target[0] should not have 'highlighting' class");
+  ok(animationTargetEls[1].classList.contains("highlighting"),
+    "The animation target[1] should have 'highlighting' class");
+  ok(animationTargetEls[2].classList.contains("highlighting"),
+    "The animation target[2] should have 'highlighting' class");
+});
+
+function assertNodeFront(nodeFront, tagName, classValue) {
+  is(nodeFront.tagName, "DIV",
+     "The highlighted node has the correct tagName");
+  is(nodeFront.attributes[0].name, "class",
+     "The highlighted node has the correct attributes");
+  is(nodeFront.attributes[0].value, classValue,
+     "The highlighted node has the correct class");
+}
--- a/devtools/client/inspector/animation/test/head.js
+++ b/devtools/client/inspector/animation/test/head.js
@@ -93,97 +93,98 @@ addTab = async function(url) {
 const removeAnimatedElementsExcept = async function(selectors) {
   return executeInContent("Test:RemoveAnimatedElementsExcept", { selectors });
 };
 
 /**
  * Click on an animation in the timeline to select it.
  *
  * @param {AnimationInspector} animationInspector.
- * @param {AnimationsPanel} panel
- *        The panel instance.
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} index
  *        The index of the animation to click on.
  */
 const clickOnAnimation = async function(animationInspector, panel, index) {
   info("Click on animation " + index + " in the timeline");
   const summaryGraphEl = panel.querySelectorAll(".animation-summary-graph")[index];
   await clickOnSummaryGraph(animationInspector, panel, summaryGraphEl);
 };
 
 /**
  * Click on an animation by given selector of node which is target element of animation.
  *
  * @param {AnimationInspector} animationInspector.
- * @param {AnimationsPanel} panel
- *        The panel instance.
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {String} selector
  *        Selector of node which is target element of animation.
  */
 const clickOnAnimationByTargetSelector = async function(animationInspector,
                                                         panel, selector) {
   info(`Click on animation whose selector of target element is '${ selector }'`);
   const animationItemEl = findAnimationItemElementsByTargetSelector(panel, selector);
   const summaryGraphEl = animationItemEl.querySelector(".animation-summary-graph");
   await clickOnSummaryGraph(animationInspector, panel, summaryGraphEl);
 };
 
 /**
  * Click on close button for animation detail pane.
  *
- * @param {AnimationsPanel} panel
- *        The panel instance.
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 const clickOnDetailCloseButton = function(panel) {
   info("Click on close button for animation detail pane");
   const buttonEl = panel.querySelector(".animation-detail-close-button");
   const bounds = buttonEl.getBoundingClientRect();
   const x = bounds.width / 2;
   const y = bounds.height / 2;
   EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
 };
 
 /**
  * Click on pause/resume button.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
- *        The panel instance.
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 const clickOnPauseResumeButton = async function(animationInspector, panel) {
   info("Click on pause/resume button");
   const buttonEl = panel.querySelector(".pause-resume-button");
   const bounds = buttonEl.getBoundingClientRect();
   const x = bounds.width / 2;
   const y = bounds.height / 2;
   EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
  * Click on rewind button.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
- *        The panel instance.
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 const clickOnRewindButton = async function(animationInspector, panel) {
   info("Click on rewind button");
   const buttonEl = panel.querySelector(".rewind-button");
   const bounds = buttonEl.getBoundingClientRect();
   const x = bounds.width / 2;
   const y = bounds.height / 2;
   EventUtils.synthesizeMouse(buttonEl, x, y, {}, buttonEl.ownerGlobal);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
  * Click on the scrubber controller pane to update the animation current time.
  *
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} mouseDownPosition
  *        rate on scrubber controller pane.
  *        This method calculates
  *        `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
  *        as the clientX of MouseEvent.
  */
 const clickOnCurrentTimeScrubberController = async function(animationInspector,
                                                             panel,
@@ -194,20 +195,40 @@ const clickOnCurrentTimeScrubberControll
   const mousedonwX = bounds.width * mouseDownPosition;
 
   info(`Click ${ mousedonwX } on scrubber controller`);
   EventUtils.synthesizeMouse(controllerEl, mousedonwX, 0, {}, controllerEl.ownerGlobal);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
+ * Click on the inspect icon for the given AnimationTargetComponent.
+ *
+ * @param {AnimationInspector} animationInspector.
+ * @param {DOMElement} panel
+ *        #animation-container element.
+ * @param {Number} index
+ *        The index of the AnimationTargetComponent to click on.
+ */
+const clickOnInspectIcon = async function(animationInspector, panel, index) {
+  info(`Click on an inspect icon in animation target component[${ index }]`);
+  const iconEl =
+    panel.querySelectorAll(".animation-target .objectBox .open-inspector")[index];
+  iconEl.scrollIntoView(false);
+  EventUtils.synthesizeMouseAtCenter(iconEl, {}, iconEl.ownerGlobal);
+  // We wait just one time, because the components are updated synchronously.
+  await animationInspector.once("animation-target-rendered");
+};
+
+/**
  * Click on playback rate selector to select given rate.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} rate
  */
 const clickOnPlaybackRateSelector = async function(animationInspector, panel, rate) {
   info(`Click on playback rate selector to select ${rate}`);
   const selectEl = panel.querySelector(".playback-rate-selector");
   const optionEl = [...selectEl.options].filter(o => Number(o.value) === rate)[0];
 
   if (!optionEl) {
@@ -221,35 +242,56 @@ const clickOnPlaybackRateSelector = asyn
   EventUtils.synthesizeMouseAtCenter(optionEl, { type: "mouseup" }, win);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
  * Click on given summary graph element.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Element} summaryGraphEl
  */
 const clickOnSummaryGraph = async function(animationInspector, panel, summaryGraphEl) {
   // Disable pointer-events of the scrubber in order to avoid to click accidently.
   const scrubberEl = panel.querySelector(".current-time-scrubber");
   scrubberEl.style.pointerEvents = "none";
   // Scroll to show the timeBlock since the element may be out of displayed area.
   summaryGraphEl.scrollIntoView(false);
   EventUtils.synthesizeMouseAtCenter(summaryGraphEl, {}, summaryGraphEl.ownerGlobal);
   await waitForAnimationDetail(animationInspector);
   // Restore the scrubber style.
   scrubberEl.style.pointerEvents = "unset";
 };
 
 /**
+ * Click on the target node for the given AnimationTargetComponent index.
+ *
+ * @param {AnimationInspector} animationInspector.
+ * @param {DOMElement} panel
+ *        #animation-container element.
+ * @param {Number} index
+ *        The index of the AnimationTargetComponent to click on.
+ */
+const clickOnTargetNode = async function(animationInspector, panel, index) {
+  info(`Click on a target node in animation target component[${ index }]`);
+  const targetEl = panel.querySelectorAll(".animation-target .objectBox")[index];
+  targetEl.scrollIntoView(false);
+  const onHighlight = animationInspector.inspector.toolbox.once("node-highlight");
+  EventUtils.synthesizeMouseAtCenter(targetEl, {}, targetEl.ownerGlobal);
+  await waitForRendering(animationInspector);
+  await onHighlight;
+};
+
+/**
  * Drag on the scrubber to update the animation current time.
  *
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} mouseDownPosition
  *        rate on scrubber controller pane.
  *        This method calculates
  *        `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
  *        as the clientX of MouseEvent.
  * @param {Number} mouseMovePosition
  *        Dispatch mousemove event with mouseMovePosition after mousedown.
  *        Calculation for clinetX is same to above.
@@ -275,17 +317,18 @@ const dragOnCurrentTimeScrubber = async 
   EventUtils.synthesizeMouse(controllerEl, mousemoveX, mouseYPixel,
                              { type: "mouseup" }, controllerEl.ownerGlobal);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
  * Drag on the scrubber controller pane to update the animation current time.
  *
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} mouseDownPosition
  *        rate on scrubber controller pane.
  *        This method calculates
  *        `mouseDownPosition * offsetWidth + offsetLeft of scrubber controller pane`
  *        as the clientX of MouseEvent.
  * @param {Number} mouseMovePosition
  *        Dispatch mousemove event with mouseMovePosition after mousedown.
  *        Calculation for clinetX is same to above.
@@ -310,17 +353,18 @@ const dragOnCurrentTimeScrubberControlle
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
  * Get current animation duration and rate of
  * clickOrDragOnCurrentTimeScrubberController in given pixels.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} pixels
  * @return {Object}
  *         {
  *           duration,
  *           rate,
  *         }
  */
 const getDurationAndRate = function(animationInspector, panel, pixels) {
@@ -328,16 +372,48 @@ const getDurationAndRate = function(anim
   const bounds = controllerEl.getBoundingClientRect();
   const duration =
     animationInspector.state.timeScale.getDuration() / bounds.width * pixels;
   const rate = 1 / bounds.width * pixels;
   return { duration, rate };
 };
 
 /**
+ * Mouse over the target node for the given AnimationTargetComponent index.
+ *
+ * @param {AnimationInspector} animationInspector.
+ * @param {DOMElement} panel
+ *        #animation-container element.
+ * @param {Number} index
+ *        The index of the AnimationTargetComponent to click on.
+ */
+const mouseOverOnTargetNode = function(animationInspector, panel, index) {
+  info(`Mouse over on a target node in animation target component[${ index }]`);
+  const el = panel.querySelectorAll(".animation-target .objectBox")[index];
+  el.scrollIntoView(false);
+  EventUtils.synthesizeMouse(el, 10, 5, { type: "mouseover" }, el.ownerGlobal);
+};
+
+/**
+ * Mouse out of the target node for the given AnimationTargetComponent index.
+ *
+ * @param {AnimationInspector} animationInspector.
+ * @param {DOMElement} panel
+ *        #animation-container element.
+ * @param {Number} index
+ *        The index of the AnimationTargetComponent to click on.
+ */
+const mouseOutOnTargetNode = function(animationInspector, panel, index) {
+  info(`Mouse out on a target node in animation target component[${ index }]`);
+  const el = panel.querySelectorAll(".animation-target .objectBox")[index];
+  el.scrollIntoView(false);
+  EventUtils.synthesizeMouse(el, -1, -1, { type: "mouseout" }, el.ownerGlobal);
+};
+
+/**
  * Select animation inspector in sidebar and toolbar.
  *
  * @param {InspectorPanel} inspector
  */
 const selectAnimationInspector = async function(inspector) {
   await inspector.toolbox.selectTool("inspector");
   const onUpdated = inspector.once("inspector-updated");
   inspector.sidebar.select("newanimationinspector");
@@ -367,17 +443,18 @@ const selectNodeAndWaitForAnimations = a
   await onUpdated;
   await waitForRendering(inspector.animationinspector);
 };
 
 /**
  * Send keyboard event of space to given panel.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 const sendSpaceKeyEvent = async function(animationInspector, panel) {
   panel.focus();
   EventUtils.sendKey("SPACE", panel.ownerGlobal);
   await waitForSummaryAndDetail(animationInspector);
 };
 
 /**
@@ -494,40 +571,43 @@ const waitForSummaryAndDetail = async fu
     waitForAnimationDetail(animationInspector),
   ]);
 };
 
 /**
  * Check whether current time of all animations and UI are given specified time.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {Number} time
  */
 function assertAnimationsCurrentTime(animationInspector, time) {
   const isTimeEqual =
     animationInspector.state.animations.every(({state}) => state.currentTime === time);
   ok(isTimeEqual, `Current time of animations should be ${ time }`);
 }
 
 /**
  * Check whether the animations are pausing.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 function assertAnimationsPausing(animationInspector, panel) {
   assertAnimationsPausingOrRunning(animationInspector, panel, true);
 }
 
 /**
  * Check whether the animations are pausing/running.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  * @param {boolean} shouldPause
  */
 function assertAnimationsPausingOrRunning(animationInspector, panel, shouldPause) {
   const hasRunningAnimation =
     animationInspector.state.animations.some(({state}) => state.playState === "running");
 
   if (shouldPause) {
     is(hasRunningAnimation, false, "All animations should be paused");
@@ -535,17 +615,18 @@ function assertAnimationsPausingOrRunnin
     is(hasRunningAnimation, true, "Animations should be running at least one");
   }
 }
 
 /**
  * Check whether the animations are running.
  *
  * @param {AnimationInspector} animationInspector
- * @param {AnimationsPanel} panel
+ * @param {DOMElement} panel
+ *        #animation-container element.
  */
 function assertAnimationsRunning(animationInspector, panel) {
   assertAnimationsPausingOrRunning(animationInspector, panel, false);
 }
 
 /**
  * Check the <stop> element in the given linearGradientEl for the correct offset
  * and color attributes.
@@ -628,19 +709,22 @@ function isPassingThrough(pathSegList, x
   return false;
 }
 
 /**
  * Return animation item element by target node selector.
  * This function compares betweem animation-target textContent and given selector.
  * Then returns matched first item.
  *
- * @param {Element} panel - root element of animation inspector.
- * @param {String} selector - selector of tested element.
- * @return {Element} animation item element.
+ * @param {DOMElement} panel
+ *        #animation-container element.
+ * @param {String} selector
+ *        Selector of tested element.
+ * @return {DOMElement}
+ *        Animation item element.
  */
 function findAnimationItemElementsByTargetSelector(panel, selector) {
   const attrNameEls = panel.querySelectorAll(".animation-target .attrName");
   const regexp = new RegExp(`\\${ selector }(\\.|$)`, "gi");
 
   for (const attrNameEl of attrNameEls) {
     if (regexp.exec(attrNameEl.textContent)) {
       return attrNameEl.closest(".animation-item");