| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Do not depend on the Chai Assertion Library in this file. Some consumers of |
| // the following test utils are not configured to use Chai. |
| |
| /** |
| * Observes an HTML attribute and fires a promise when it matches a given |
| * value. |
| */ |
| export function whenAttributeIs( |
| target: HTMLElement, attributeName: string, |
| attributeValue: any): Promise<void> { |
| function isDone(): boolean { |
| return target.getAttribute(attributeName) === attributeValue; |
| } |
| |
| return isDone() ? Promise.resolve() : new Promise(function(resolve) { |
| new MutationObserver(function(mutations, observer) { |
| for (const mutation of mutations) { |
| if (mutation.type === 'attributes' && |
| mutation.attributeName === attributeName && isDone()) { |
| observer.disconnect(); |
| resolve(); |
| return; |
| } |
| } |
| }) |
| .observe( |
| target, {attributes: true, childList: false, characterData: false}); |
| }); |
| } |
| |
| /** |
| * Observes an HTML element and fires a promise when the check function is |
| * satisfied. |
| */ |
| export function whenCheck( |
| target: HTMLElement, check: () => boolean): Promise<void> { |
| return check() ? |
| Promise.resolve() : |
| new Promise(resolve => new MutationObserver((_list, observer) => { |
| if (check()) { |
| observer.disconnect(); |
| resolve(); |
| } |
| }).observe(target, { |
| attributes: true, |
| childList: true, |
| subtree: true, |
| })); |
| } |
| |
| /** |
| * Converts an event occurrence to a promise. |
| * @return A promise firing once the event occurs. |
| */ |
| export function eventToPromise( |
| eventType: string, target: Element|EventTarget|Window): Promise<any> { |
| return new Promise(function(resolve, _reject) { |
| target.addEventListener(eventType, function f(e) { |
| target.removeEventListener(eventType, f); |
| resolve(e); |
| }); |
| }); |
| } |
| |
| /** |
| * Returns whether or not the element specified is visible. |
| */ |
| export function isVisible(element: Element|null): boolean { |
| const rect = element ? element.getBoundingClientRect() : null; |
| return (!!rect && rect.width * rect.height > 0); |
| } |
| |
| /** |
| * Searches the DOM of the parentEl element for a child matching the provided |
| * selector then checks the visibility of the child. |
| */ |
| export function isChildVisible(parentEl: Element, selector: string, |
| checkLightDom?: boolean): boolean { |
| const element = checkLightDom ? parentEl.querySelector(selector) : |
| parentEl.shadowRoot!.querySelector(selector); |
| return isVisible(element); |
| } |
| |
| /** |
| * Queries |selector| on |element|'s shadow root and returns the resulting |
| * element if there is any. |
| */ |
| export function $$<E extends HTMLElement = HTMLElement>( |
| element: HTMLElement, selector: string): E|null; |
| export function $$(element: HTMLElement, selector: string) { |
| return element.shadowRoot!.querySelector(selector); |
| } |
| |
| /** |
| * Returns whether the |element|'s style |property| matches the expected value. |
| */ |
| export function hasStyle( |
| element: Element, property: string, expected: string): boolean { |
| return expected === element.computedStyleMap().get(property)?.toString(); |
| } |
| |
| |
| /** |
| * Helper method to locally add a breakpoint to a test and automatically pause |
| * execution once it is hit, allowing further debugging via the DevTools, like |
| * stepping through the code. |
| * |
| * Note: DO NOT commit such calls into the repository. |
| * |
| * Specifically in order to use it, follow the steps below: |
| * |
| * 1) Add an `await launchDebugger();` statement right before the line of |
| * interest. |
| * 2) [Optional] Change test() to test.only() to only run the relevant test |
| * case. |
| * 3) Launch the test with all of the following command line flags |
| * --enable-pixel-output-in-tests |
| * --ui-test-action-timeout=1000000 |
| * --auto-open-devtools-for-tabs |
| * --gtest_filter=MyTest.Foo |
| * |
| * Once the test launches, you should see a DevTools window, with the code |
| * paused at the 'debugger;' statement below. |
| * 4) Click the "Step out" icon in the DevTools Sources panel to step out of |
| * this helper function and into the actual test code of interest. Continue |
| * debugging as needed. |
| * |
| * Note that the timeout is necessary because 'debugger;' statements are a |
| * no-op if DevTools is not open, and opening DevTools with the |
| * --auto-open-devtools-for-tabs flag takes a bit of time. The default value |
| * seems to work for most tests, but can be overridden with a larger value if |
| * more time is needed in your environment. |
| */ |
| export async function launchDebugger(timeout: number = 2000) { |
| await new Promise<void>(res => { |
| window.setTimeout(() => res(), timeout); |
| }); |
| /* eslint-disable-next-line no-debugger */ |
| debugger; |
| } |
| |
| /** |
| * When dealing with CrLitElement instances, prefer this over using the |
| * `updateComplete` Promise, since it guarantees that microtasks queued by all |
| * other Lit elements have executed, as well as any updates that would be |
| * triggered by those updates, and so on. |
| */ |
| export function microtasksFinished(): Promise<void> { |
| return new Promise(resolve => setTimeout(() => resolve(), 0)); |
| } |