| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview Tests for text_click.ts. |
| */ |
| |
| import type {AnnotationsTapConsumer} from '//ios/web/annotations/resources/text_click.js'; |
| import {TextClick} from '//ios/web/annotations/resources/text_click.js'; |
| import {expectEq, FakeTaskTimer, load, TestSuite} from '//ios/web/annotations/resources/text_test_utils.js'; |
| |
| export class TestTextClick extends TestSuite { |
| // Mark: AnnotationsTapConsumer |
| |
| tappedAnnotation?: HTMLElement; |
| tappedCancel?: boolean; |
| |
| tapConsumer: AnnotationsTapConsumer = |
| (annotation: HTMLElement, cancel: boolean): void => { |
| this.tappedAnnotation = annotation; |
| this.tappedCancel = cancel; |
| }; |
| |
| // Mark: tests |
| |
| override setUp() { |
| this.tappedAnnotation = undefined; |
| this.tappedCancel = undefined; |
| } |
| |
| // Tests that `tapConsumer` is called (and `tappedCancel` is true) when no DOM |
| // mutation occurs during the bubbling of a click event. |
| testTextClickNoMutations() { |
| const decoratedHTML = '<div id="outer">' + |
| '<chrome_annotation>Hello</chrome_annotation>' + |
| '</div>'; |
| load(decoratedHTML); |
| |
| const annotation = |
| document.querySelector<HTMLElement>('chrome_annotation')!; |
| const timer = new FakeTaskTimer(); |
| const clicker = new TextClick( |
| document.documentElement, this.tapConsumer, () => undefined, timer, |
| /* mutationCheckDelay */ 50, annotation); |
| clicker.start(); |
| |
| annotation.click(); |
| |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after click:'); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 4); // -> 40ms total |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after 40ms:'); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 2); // -> 60ms total |
| expectEq(annotation, this.tappedAnnotation, 'tappedAnnotation after 60ms:'); |
| expectEq(false, this.tappedCancel, 'tappedCancel after 60ms:'); |
| |
| clicker.stop(); |
| } |
| |
| // Tests that a click event stopped with `stopImmediatePropagation` doesn't |
| // trigger the `tapConsumer`. |
| testTextClickStopped() { |
| const decoratedHTML = '<div id="outer">' + |
| '<chrome_annotation>Hello</chrome_annotation>' + |
| '</div>'; |
| load(decoratedHTML); |
| |
| const outer = document.querySelector('#outer')!; |
| outer.addEventListener('click', (event: Event) => { |
| event.stopImmediatePropagation(); |
| }); |
| const annotation = |
| document.querySelector<HTMLElement>('chrome_annotation')!; |
| const timer = new FakeTaskTimer(); |
| const clicker = new TextClick( |
| document.documentElement, this.tapConsumer, () => undefined, timer, |
| /* mutationCheckDelay */ 50, annotation); |
| clicker.start(); |
| |
| annotation.click(); |
| |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after click:'); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 10); // -> 100ms total |
| // Should not reach calling the AnnotationsTapConsumer. |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after 100ms:'); |
| expectEq(undefined, this.tappedCancel, 'tappedCancel after 100ms:'); |
| |
| clicker.stop(); |
| } |
| |
| // Tests that a click event stopped with `preventDefault` does trigger the |
| // `tapConsumer` but with `tappedCancel` set to true. |
| testTextClickPrevented() { |
| const decoratedHTML = '<div id="outer">' + |
| '<chrome_annotation>Hello</chrome_annotation>' + |
| '</div>'; |
| load(decoratedHTML); |
| |
| const outer = document.querySelector('#outer')!; |
| outer.addEventListener('click', (event: Event) => { |
| event.preventDefault(); |
| }); |
| const annotation = |
| document.querySelector<HTMLElement>('chrome_annotation')!; |
| const timer = new FakeTaskTimer(); |
| const clicker = new TextClick( |
| document.documentElement, this.tapConsumer, () => undefined, timer, |
| /* mutationCheckDelay */ 50, annotation); |
| clicker.start(); |
| |
| annotation.click(); |
| |
| // Without delay, this event should be cancelled: |
| expectEq( |
| annotation, this.tappedAnnotation, 'tappedAnnotation after 100ms:'); |
| expectEq(true, this.tappedCancel, 'tappedCancel after 100ms:'); |
| |
| clicker.stop(); |
| } |
| |
| // Tests that a click event stopped due to DOM mutation does trigger the |
| // `tapConsumer` but with `tappedCancel` set to true. |
| testTextClickWithMutationInsideTree() { |
| const decoratedHTML = '<div id="outer">' + |
| '<div id="mutate">I will mutate!' + |
| '<chrome_annotation>Hello</chrome_annotation></div>' + |
| '</div>'; |
| load(decoratedHTML); |
| |
| const annotation = |
| document.querySelector<HTMLElement>('chrome_annotation')!; |
| const timer = new FakeTaskTimer(); |
| const clicker = new TextClick( |
| document.documentElement, this.tapConsumer, () => undefined, timer, |
| /* mutationCheckDelay */ 50, annotation); |
| clicker.start(); |
| |
| annotation.click(); |
| |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after click:'); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 2); // -> 20ms total |
| document.querySelector('#mutate')!.appendChild( |
| document.createTextNode('Mutated!')); |
| clicker.updateForTesting(); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 4); // -> 60ms total |
| expectEq(annotation, this.tappedAnnotation, 'tappedAnnotation after 60ms:'); |
| expectEq(true, this.tappedCancel, 'tappedCancel after 60ms:'); |
| |
| clicker.stop(); |
| } |
| |
| // Tests that a click event is not stopped by a DOM mutation outside of the |
| // tree of the annotation. |
| testTextClickWithMutationOutsideTree() { |
| const decoratedHTML = '<div id="outer">' + |
| '<div id="mutate">I will mutate!</div>' + |
| '<chrome_annotation>Hello</chrome_annotation>' + |
| '</div>'; |
| load(decoratedHTML); |
| |
| const annotation = |
| document.querySelector<HTMLElement>('chrome_annotation')!; |
| const timer = new FakeTaskTimer(); |
| const clicker = new TextClick( |
| document.documentElement, this.tapConsumer, () => undefined, timer, |
| /* mutationCheckDelay */ 50, annotation); |
| clicker.start(); |
| |
| annotation.click(); |
| |
| expectEq(undefined, this.tappedAnnotation, 'tappedAnnotation after click:'); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 2); // -> 20ms total |
| document.querySelector('#mutate')!.appendChild( |
| document.createTextNode('Mutated!')); |
| clicker.updateForTesting(); |
| timer.moveAhead(/* ms= */ 10, /* times= */ 4); // -> 60ms total |
| expectEq(annotation, this.tappedAnnotation, 'tappedAnnotation after 60ms:'); |
| expectEq(false, this.tappedCancel, 'tappedCancel after 60ms:'); |
| |
| clicker.stop(); |
| } |
| } |