// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome-untrusted://lens/selection_overlay.js';
import type {RectF} from '//resources/mojo/ui/gfx/geometry/mojom/geometry.mojom-webui.js';
import {BrowserProxyImpl} from 'chrome-untrusted://lens/browser_proxy.js';
import {CenterRotatedBox_CoordinateType} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
import type {CenterRotatedBox} from 'chrome-untrusted://lens/geometry.mojom-webui.js';
import type {LensPageRemote} from 'chrome-untrusted://lens/lens.mojom-webui.js';
import type {OverlayObject} from 'chrome-untrusted://lens/overlay_object.mojom-webui.js';
import {ScreenshotBitmapBrowserProxyImpl} from 'chrome-untrusted://lens/screenshot_bitmap_browser_proxy.js';
import type {SelectionOverlayElement} from 'chrome-untrusted://lens/selection_overlay.js';
import {loadTimeData} from 'chrome-untrusted://resources/js/load_time_data.js';
import {assertDeepEquals, assertEquals, assertFalse, assertNotEquals, assertStringContains, assertTrue} from 'chrome-untrusted://webui-test/chai_assert.js';
import {flushTasks, waitAfterNextRender} from 'chrome-untrusted://webui-test/polymer_test_util.js';
import {isVisible} from 'chrome-untrusted://webui-test/test_util.js';
import {fakeScreenshotBitmap, waitForScreenshotRendered} from '../utils/image_utils.js';
import {assertBoxesWithinThreshold, createObject} from '../utils/object_utils.js';
import {getImageBoundingRect, simulateClick, simulateDrag} from '../utils/selection_utils.js';
import {createLine, createParagraph, createText, createTranslatedLine, createTranslatedParagraph, createWord} from '../utils/text_utils.js';
import {TestLensOverlayBrowserProxy} from './test_overlay_browser_proxy.js';
suite('SelectionOverlay', function() {
let testBrowserProxy: TestLensOverlayBrowserProxy;
let selectionOverlayElement: SelectionOverlayElement;
let callbackRouterRemote: LensPageRemote;
let objects: OverlayObject[];
setup(async () => {
// Resetting the HTML needs to be the first thing we do in setup to
// guarantee that any singleton instances don't change while any UI is still
// attached to the DOM.
document.body.innerHTML = window.trustedTypes!.emptyHTML;
testBrowserProxy = new TestLensOverlayBrowserProxy();
callbackRouterRemote =
testBrowserProxy.callbackRouter.$.bindNewPipeAndPassRemote();
BrowserProxyImpl.setInstance(testBrowserProxy);
// Turn off the shimmer. Since the shimmer is resource intensive, turn off
// to prevent from causing issues in the tests.
loadTimeData.overrideValues({'enableShimmer': false});
selectionOverlayElement = document.createElement('lens-selection-overlay');
document.body.appendChild(selectionOverlayElement);
// Since the size of the Selection Overlay is based on the screenshot which
// is not loaded in the test, we need to force the overlay to take up the
// viewport.
selectionOverlayElement.$.selectionOverlay.style.width = '100%';
selectionOverlayElement.$.selectionOverlay.style.height = '100%';
// The first frame triggers our resize handler. Wait another frame for us
// the changes made by our resize handler to take effect.
await waitAfterNextRender(selectionOverlayElement);
return waitAfterNextRender(selectionOverlayElement);
});
// Normalizes the given values to the size of selection overlay.
function normalizedBox(box: RectF): RectF {
const boundingRect = selectionOverlayElement.getBoundingClientRect();
return {
x: box.x / boundingRect.width,
y: box.y / boundingRect.height,
width: box.width / boundingRect.width,
height: box.height / boundingRect.height,
};
}
function addWords() {
const text = createText([
createParagraph([
createLine([
createWord(
'hello', normalizedBox({x: 20, y: 20, width: 30, height: 10})),
createWord(
'there', normalizedBox({x: 50, y: 20, width: 50, height: 10})),
createWord(
'test', normalizedBox({x: 80, y: 20, width: 30, height: 10})),
]),
]),
]);
callbackRouterRemote.textReceived(text);
return flushTasks();
}
function addWordsWithTranslations() {
const text = createText([
createParagraph(
[
createLine([
createWord(
'hello',
normalizedBox({x: 20, y: 20, width: 30, height: 10})),
createWord(
'there',
normalizedBox({x: 50, y: 20, width: 50, height: 10})),
createWord(
'test', normalizedBox({x: 80, y: 20, width: 30, height: 10})),
]),
],
createTranslatedParagraph([createTranslatedLine(
[
createWord(
'wow',
normalizedBox({x: 20, y: 20, width: 30, height: 10})),
createWord(
'a', normalizedBox({x: 50, y: 20, width: 50, height: 10})),
createWord(
'translation',
normalizedBox({x: 80, y: 20, width: 30, height: 10})),
],
/*translation=*/ 'wow a translation',
/*textHexColor=*/ '#ffffff',
/*backgroundHexColor=*/ '#000000')])),
]);
callbackRouterRemote.textReceived(text);
return flushTasks();
}
function dispatchTranslateStateEvent(
target: Element, translateModeEnabled: boolean, targetLanguage: string) {
target.dispatchEvent(new CustomEvent('translate-mode-state-changed', {
detail: {translateModeEnabled, targetLanguage},
bubbles: true,
composed: true,
}));
}
function addObjects() {
objects =
[
{x: 80, y: 20, width: 25, height: 10},
{x: 70, y: 35, width: 20, height: 10},
]
.map(
(rect, i) => createObject(
i.toString(), normalizedBox(rect), /*isMaskClick=*/ false));
callbackRouterRemote.objectsReceived(objects);
return flushTasks();
}
async function verifyRegionRequest(
expectedRegion: CenterRotatedBox, expectedIsClick: boolean) {
await testBrowserProxy.handler.whenCalled('issueLensRegionRequest');
const requestRegion =
testBrowserProxy.handler.getArgs('issueLensRegionRequest')[0][0];
const isClick =
testBrowserProxy.handler.getArgs('issueLensRegionRequest')[0][1];
assertBoxesWithinThreshold(expectedRegion, requestRegion);
assertEquals(expectedIsClick, isClick);
}
async function waitForScreenshotResize(): Promise<void> {
// The first render triggers the ResizeObserver. The second runs the
// requestAnimationFrame callback queued by the ResizeObserver.
await waitAfterNextRender(selectionOverlayElement);
await waitAfterNextRender(selectionOverlayElement);
}
// <if expr="not chromeos_lacros">
test(
'verify that starting a drag on a word does not trigger region search',
async () => {
await addWords();
// Drag that starts on a word but finishes on empty space.
const wordEl = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting()[0]!;
await simulateDrag(
selectionOverlayElement, {
x: wordEl.getBoundingClientRect().left + 15,
y: wordEl.getBoundingClientRect().top + 5,
},
{x: 0, y: 0});
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTextSelectionRequest');
assertDeepEquals('hello', textQuery);
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
});
// </if>
test(
`verify that starting a drag off a word and continuing onto a word triggers region search`,
async () => {
await addWords();
// Drag that starts off a word but finishes on a word.
const wordEl = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting()[0]!;
const dragEnd = {
x: wordEl.getBoundingClientRect().left + 5,
y: wordEl.getBoundingClientRect().top + 5,
};
await simulateDrag(selectionOverlayElement, {x: 0, y: 0}, dragEnd);
const expectedRect: CenterRotatedBox = {
box: normalizedBox({
x: dragEnd.x / 2,
y: dragEnd.y / 2,
width: dragEnd.x,
height: dragEnd.y,
}),
rotation: 0,
coordinateType: CenterRotatedBox_CoordinateType.kNormalized,
};
verifyRegionRequest(expectedRect, /*expectedIsClick=*/ false);
assertEquals(
0,
testBrowserProxy.handler.getCallCount('issueTextSelectionRequest'));
});
test(
'verify region search canvas resizes when selection overlay resizes',
async () => {
selectionOverlayElement.style.display = 'block';
selectionOverlayElement.style.width = '50px';
selectionOverlayElement.style.height = '50px';
// Resize observer does not trigger with flushTasks(), so we need to use
// waitAfterNextRender() instead.
await waitForScreenshotResize();
assertEquals(
50,
selectionOverlayElement.$.regionSelectionLayer.$
.regionSelectionCanvas.width);
assertEquals(
50,
selectionOverlayElement.$.regionSelectionLayer.$
.regionSelectionCanvas.height);
selectionOverlayElement.style.width = '200px';
selectionOverlayElement.style.height = '200px';
await waitForScreenshotResize();
assertEquals(
200,
selectionOverlayElement.$.regionSelectionLayer.$
.regionSelectionCanvas.width);
assertEquals(
200,
selectionOverlayElement.$.regionSelectionLayer.$
.regionSelectionCanvas.height);
});
test(
'verify object selection canvas resizes when selection overlay resizes',
async () => {
selectionOverlayElement.style.display = 'block';
selectionOverlayElement.style.width = '50px';
selectionOverlayElement.style.height = '50px';
// Resize observer does not trigger with flushTasks(), so we need to use
// waitAfterNextRender() instead.
await waitForScreenshotResize();
assertEquals(
50,
selectionOverlayElement.$.objectSelectionLayer.$
.objectSelectionCanvas.width);
assertEquals(
50,
selectionOverlayElement.$.objectSelectionLayer.$
.objectSelectionCanvas.height);
selectionOverlayElement.style.width = '200px';
selectionOverlayElement.style.height = '200px';
await waitForScreenshotResize();
assertEquals(
200,
selectionOverlayElement.$.objectSelectionLayer.$
.objectSelectionCanvas.width);
assertEquals(
200,
selectionOverlayElement.$.objectSelectionLayer.$
.objectSelectionCanvas.height);
});
test(
`verify that text respond to taps, even when an object is underneath`,
async () => {
await Promise.all([addWords(), addObjects()]);
await simulateClick(selectionOverlayElement, {x: 80, y: 20});
const textQuery =
await testBrowserProxy.handler.whenCalled('issueTextSelectionRequest');
assertDeepEquals('test', textQuery);
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
});
test(
`verify that dragging performs region search, even when an object overlaps`,
async () => {
await addObjects();
// Drag that starts and ends inside the bounding box of an object.
const objectEl = selectionOverlayElement.$.objectSelectionLayer
.getObjectNodesForTesting()[1]!;
const objectElBoundingBox = objectEl.getBoundingClientRect();
const dragStart = {
x: objectElBoundingBox.left + 1,
y: objectElBoundingBox.top + 1,
};
const dragEnd = {
x: objectElBoundingBox.right - 1,
y: objectElBoundingBox.bottom - 1,
};
await simulateDrag(selectionOverlayElement, dragStart, dragEnd);
const expectedRect: CenterRotatedBox = {
box: normalizedBox({
x: (dragStart.x + dragEnd.x) / 2,
y: (dragStart.y + dragEnd.y) / 2,
width: dragEnd.x - dragStart.x,
height: dragEnd.y - dragStart.y,
}),
rotation: 0,
coordinateType: CenterRotatedBox_CoordinateType.kNormalized,
};
verifyRegionRequest(expectedRect, /*expectedIsClick=*/ false);
});
// <if expr="not chromeos_lacros">
test(
'verify that region search over text triggers detected text context menu',
async () => {
await addWords();
await simulateDrag(
selectionOverlayElement, {x: 51, y: 10}, {x: 80, y: 40});
assertEquals(
1, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
assertEquals(
0,
testBrowserProxy.handler.getCallCount('issueTextSelectionRequest'));
assertTrue(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
testBrowserProxy.handler.reset();
selectionOverlayElement.handleSelectTextForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTextSelectionRequest');
assertDeepEquals('there test', textQuery);
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
});
test(
`verify that adding text after region selection triggers detected text context menu`,
async () => {
callbackRouterRemote.setPostRegionSelection({
box: normalizedBox({x: 65, y: 25, width: 30, height: 30}),
rotation: 0.0,
coordinateType: 1,
});
await addWords();
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
assertEquals(
0,
testBrowserProxy.handler.getCallCount('issueTextSelectionRequest'));
assertTrue(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
testBrowserProxy.handler.reset();
selectionOverlayElement.handleSelectTextForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTextSelectionRequest');
assertDeepEquals('there test', textQuery);
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
});
test(
'verify that select text in detected text context menu works',
async () => {
await addWords();
await simulateDrag(
selectionOverlayElement, {x: 51, y: 10}, {x: 80, y: 40});
selectionOverlayElement.handleSelectTextForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTextSelectionRequest');
assertDeepEquals('there test', textQuery);
assertEquals(
1, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
assertFalse(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
});
test(
'verify that translate in detected text context menu works', async () => {
await addWords();
await simulateDrag(
selectionOverlayElement, {x: 51, y: 10}, {x: 80, y: 40});
selectionOverlayElement.handleTranslateDetectedTextForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTranslateSelectionRequest');
assertDeepEquals('there test', textQuery);
assertEquals(
1, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
assertFalse(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
assertFalse(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
});
// </if>
test('verify that region search triggers post selection', async () => {
await simulateDrag(
selectionOverlayElement, {x: 50, y: 25}, {x: 300, y: 200});
const postSelectionStyles =
selectionOverlayElement.$.postSelectionRenderer.style;
const parentBoundingRect = selectionOverlayElement.getBoundingClientRect();
const expectedHeight = 175 / parentBoundingRect.height * 100;
const expectedWidth = 250 / parentBoundingRect.width * 100;
const expectedTop = 25 / parentBoundingRect.height * 100;
const expectedLeft = 50 / parentBoundingRect.width * 100;
// Only look at first 5 digits to account for rounding errors.
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-height'),
expectedHeight.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-width'),
expectedWidth.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-top'),
expectedTop.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-left'),
expectedLeft.toString().substring(0, 6));
});
test('verify that tapping an object triggers post selection', async () => {
await addObjects();
const objectEl = selectionOverlayElement.$.objectSelectionLayer
.getObjectNodesForTesting()[1]!;
const objectBoundingBox = objectEl.getBoundingClientRect();
await simulateClick(
selectionOverlayElement,
{x: objectBoundingBox.left + 2, y: objectBoundingBox.top + 2});
const postSelectionStyles =
selectionOverlayElement.$.postSelectionRenderer.style;
const parentBoundingRect = selectionOverlayElement.getBoundingClientRect();
// Based on box coordinates of {x: 70, y: 35, width: 20, height: 10},
const expectedHeight = 10 / parentBoundingRect.height * 100;
const expectedWidth = 20 / parentBoundingRect.width * 100;
const expectedTop = 30 / parentBoundingRect.height * 100;
const expectedLeft = 60 / parentBoundingRect.width * 100;
// Only look at first 5 digits to account for rounding errors.
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-height'),
expectedHeight.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-width'),
expectedWidth.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-top'),
expectedTop.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-left'),
expectedLeft.toString().substring(0, 6));
});
test('verify that resizing renders image with padding', async () => {
// Resize to be the same size as screenshot.
selectionOverlayElement.style.display = 'block';
selectionOverlayElement.style.width = '100px';
selectionOverlayElement.style.height = '100px';
// ScreenshotBitmapBrowserProxy assumes only one screenshot will be sent. We
// need to reset it to allow a new screenshot to be fetched.
ScreenshotBitmapBrowserProxyImpl.setInstance(
new ScreenshotBitmapBrowserProxyImpl());
selectionOverlayElement.fetchNewScreenshotForTesting();
// Send a fake screenshot of size 100x100.
testBrowserProxy.page.screenshotDataReceived(
fakeScreenshotBitmap(100, 100));
await waitForScreenshotRendered(selectionOverlayElement);
await waitForScreenshotResize();
// Resize to smaller than the screenshot and verify margins.
selectionOverlayElement.style.width = '90px';
selectionOverlayElement.style.height = '90px';
await waitForScreenshotResize();
// Size should now be 90px - 48px margin.
let imageSize =
selectionOverlayElement.$.backgroundImageCanvas.getBoundingClientRect();
assertEquals(42, imageSize.width);
assertEquals(42, imageSize.height);
// Resize back to same size as screenshot and verify no margins.
selectionOverlayElement.style.width = '100px';
selectionOverlayElement.style.height = '100px';
await waitForScreenshotResize();
// Size should now be back to fullscreen.
imageSize =
selectionOverlayElement.$.backgroundImageCanvas.getBoundingClientRect();
assertEquals(100, imageSize.width);
assertEquals(100, imageSize.height);
// Increase the device pixel ratio and resize. Since 100 (screenshot size) /
// 1.5 is 66.666667, this will also test rounding errors in our
// calculations.
selectionOverlayElement.style.width = '67px';
selectionOverlayElement.style.height = '67px';
window.devicePixelRatio = 1.5;
await waitForScreenshotResize();
// Size should now be back to fullscreen.
imageSize =
selectionOverlayElement.$.backgroundImageCanvas.getBoundingClientRect();
assertEquals(67, imageSize.width);
assertEquals(67, imageSize.height);
});
test('verify that you can drag text over post selection', async () => {
// Add the words
await addWords();
// Add the post selection over the words.
await simulateDrag(selectionOverlayElement, {x: 150, y: 150}, {x: 5, y: 5});
testBrowserProxy.handler.reset();
// Drag that starts on a word and post selection.
const wordEl = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting()[1]!;
const wordElBoundingBox = wordEl.getBoundingClientRect();
await simulateDrag(
selectionOverlayElement, {
x: wordElBoundingBox.left + (wordElBoundingBox.width / 3),
y: wordElBoundingBox.top + (wordElBoundingBox.height / 2),
},
{
x: wordElBoundingBox.right,
y: wordElBoundingBox.bottom,
});
const textQuery =
await testBrowserProxy.handler.whenCalled('issueTextSelectionRequest');
assertDeepEquals('there test', textQuery);
assertEquals(
0, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
});
test('verify that copy in selected text context menu works', async () => {
// Add the words
await addWords();
testBrowserProxy.handler.reset();
// Drag that starts on a word.
const wordEl = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting()[1]!;
const wordElBoundingBox = wordEl.getBoundingClientRect();
await simulateDrag(
selectionOverlayElement, {
x: wordElBoundingBox.left + (wordElBoundingBox.width / 3),
y: wordElBoundingBox.top + (wordElBoundingBox.height / 2),
},
{
x: wordElBoundingBox.right,
y: wordElBoundingBox.bottom,
});
assertFalse(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
assertTrue(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
selectionOverlayElement.handleCopyForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled('copyText');
assertDeepEquals('there test', textQuery);
// Verify context menu hides when an option is selected.
assertFalse(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
// Verify context menu is restored when a selected word is right-clicked.
await simulateClick(
selectionOverlayElement, {
x: wordElBoundingBox.left + (wordElBoundingBox.width / 2),
y: wordElBoundingBox.top + (wordElBoundingBox.height / 2),
},
/* button = */ 2);
assertTrue(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
});
test(
'verify that translate in selected text context menu works', async () => {
// Add the words
await addWords();
testBrowserProxy.handler.reset();
// Drag that starts on a word.
const wordEl = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting()[1]!;
const wordElBoundingBox = wordEl.getBoundingClientRect();
await simulateDrag(
selectionOverlayElement, {
x: wordElBoundingBox.left + (wordElBoundingBox.width / 3),
y: wordElBoundingBox.top + (wordElBoundingBox.height / 2),
},
{
x: wordElBoundingBox.right,
y: wordElBoundingBox.bottom,
});
assertFalse(
selectionOverlayElement.getShowDetectedTextContextMenuForTesting());
assertTrue(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
selectionOverlayElement.handleTranslateForTesting();
const textQuery = await testBrowserProxy.handler.whenCalled(
'issueTranslateSelectionRequest');
assertDeepEquals('there test', textQuery);
// Verify context menu hides when an option is selected.
assertFalse(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
// Verify context menu is restored when a selected word is
// right-clicked.
await simulateClick(
selectionOverlayElement, {
x: wordElBoundingBox.left + (wordElBoundingBox.width / 2),
y: wordElBoundingBox.top + (wordElBoundingBox.height / 2),
},
/* button = */ 2);
assertTrue(
selectionOverlayElement.getShowSelectedTextContextMenuForTesting());
});
test(
'verify that dragging on post selection over an object does not tap that object',
async () => {
// Add the objects
await addObjects();
// Add the post selection over the words.
await simulateDrag(
selectionOverlayElement, {x: 150, y: 150}, {x: 5, y: 5});
testBrowserProxy.handler.reset();
// Store the previous post seleciton dimensions.
let postSelectionStyles =
selectionOverlayElement.$.postSelectionRenderer.style;
const oldLeft =
parseInt(postSelectionStyles.getPropertyValue('--selection-top'));
const oldTop =
parseInt(postSelectionStyles.getPropertyValue('--selection-left'));
// Drag that starts on an object
const objectEl = selectionOverlayElement.$.objectSelectionLayer
.getObjectNodesForTesting()[1]!;
const objectBoundingBox = objectEl.getBoundingClientRect();
await simulateDrag(
selectionOverlayElement, {
x: objectBoundingBox.left + (objectBoundingBox.width / 2),
y: objectBoundingBox.top + (objectBoundingBox.height / 2),
},
{
x: 100,
y: 100,
});
// Should only be called once from post selection adjustment and not
// object tap.
assertEquals(
1, testBrowserProxy.handler.getCallCount('issueLensRegionRequest'));
// Get most recent styles
postSelectionStyles =
selectionOverlayElement.$.postSelectionRenderer.style;
assertNotEquals(
oldLeft,
parseInt(postSelectionStyles.getPropertyValue('--selection-left')));
assertNotEquals(
oldTop,
parseInt(postSelectionStyles.getPropertyValue('--selection-top')));
});
test(
`verify that only objects respond to taps, even when post selection overlaps`,
async () => {
// Add the objects
await addObjects();
// Add the post selection over the words.
await simulateDrag(
selectionOverlayElement, {x: 150, y: 150}, {x: 5, y: 5});
testBrowserProxy.handler.reset();
// Click on an object behind post selection
const objectEl = selectionOverlayElement.$.objectSelectionLayer
.getObjectNodesForTesting()[1]!;
const objectBoundingBox = objectEl.getBoundingClientRect();
await simulateClick(selectionOverlayElement, {
x: objectBoundingBox.left + (objectBoundingBox.width / 2),
y: objectBoundingBox.top + (objectBoundingBox.height / 2),
});
// Should only be called once from post selection adjustment and not
// object tap.
assertEquals(
1, testBrowserProxy.handler.getCallCount('issueLensObjectRequest'));
// Verify tap triggered new post selection
const postSelectionStyles =
selectionOverlayElement.$.postSelectionRenderer.style;
const parentBoundingRect =
selectionOverlayElement.getBoundingClientRect();
// Based on box coordinates of {x: 70, y: 35, width: 20, height: 10},
const expectedHeight = 10 / parentBoundingRect.height * 100;
const expectedWidth = 20 / parentBoundingRect.width * 100;
const expectedTop = 30 / parentBoundingRect.height * 100;
const expectedLeft = 60 / parentBoundingRect.width * 100;
// Only look at first 5 digits to account for rounding errors.
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-height'),
expectedHeight.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-width'),
expectedWidth.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-top'),
expectedTop.toString().substring(0, 6));
assertStringContains(
postSelectionStyles.getPropertyValue('--selection-left'),
expectedLeft.toString().substring(0, 6));
});
test(
`verify that post selection corners are draggable over text and objects`,
async () => {
await Promise.all([addWords(), addObjects()]);
// Add the post selection to have top left corner overlap with text
// and objects
await simulateDrag(
selectionOverlayElement, {x: 10, y: 150}, {x: 80, y: 20});
testBrowserProxy.handler.reset();
// Start drag on word corner over word and object
await simulateDrag(
selectionOverlayElement, {x: 85, y: 25}, {x: 100, y: 50});
// No text request was triggered
assertEquals(
0,
testBrowserProxy.handler.getCallCount('issueTextSelectionRequest'));
// Lens request for the new region
const expectedRect: CenterRotatedBox = {
box: normalizedBox({
x: 55,
y: 100,
width: 90,
height: 100,
}),
rotation: 0,
coordinateType: CenterRotatedBox_CoordinateType.kNormalized,
};
verifyRegionRequest(expectedRect, /*expectedIsClick=*/ false);
});
test('verify that completing a drag calls closeSearchBubble', async () => {
const imageBounds = getImageBoundingRect(selectionOverlayElement);
const startPointInsideOverlay = {
x: imageBounds.left + 10,
y: imageBounds.top + 10,
};
const endPointAboveOverlay = {
x: imageBounds.left + 100,
y: imageBounds.top - 30,
};
await simulateDrag(
selectionOverlayElement, startPointInsideOverlay, endPointAboveOverlay);
await testBrowserProxy.handler.whenCalled('closeSearchBubble');
assertEquals(1, testBrowserProxy.handler.getCallCount('closeSearchBubble'));
});
test(`verify that a tap calls closeSearchBubble`, async () => {
const imageBounds = getImageBoundingRect(selectionOverlayElement);
await simulateClick(
selectionOverlayElement,
{x: imageBounds.left + 10, y: imageBounds.top + 10});
await testBrowserProxy.handler.whenCalled('closeSearchBubble');
assertEquals(1, testBrowserProxy.handler.getCallCount('closeSearchBubble'));
});
test(
'verify that completing a drag calls closePreselectionBubble',
async () => {
const imageBounds = getImageBoundingRect(selectionOverlayElement);
const startPointInsideOverlay = {
x: imageBounds.left + 10,
y: imageBounds.top + 10,
};
const endPointAboveOverlay = {
x: imageBounds.left + 100,
y: imageBounds.top - 30,
};
await simulateDrag(
selectionOverlayElement, startPointInsideOverlay,
endPointAboveOverlay);
await testBrowserProxy.handler.whenCalled('closePreselectionBubble');
assertEquals(
1,
testBrowserProxy.handler.getCallCount('closePreselectionBubble'));
});
test(`verify that a tap calls closePreselectionBubble`, async () => {
const imageBounds = getImageBoundingRect(selectionOverlayElement);
await simulateClick(
selectionOverlayElement,
{x: imageBounds.left + 10, y: imageBounds.top + 10});
await testBrowserProxy.handler.whenCalled('closePreselectionBubble');
assertEquals(
1, testBrowserProxy.handler.getCallCount('closePreselectionBubble'));
});
test(
`verify that translate text does not render if translate mode disabled`,
async () => {
await addWordsWithTranslations();
// Make sure only non-translated word divs are present and visible.
const wordElements = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting();
assertTrue(wordElements.length > 0);
for (const word of wordElements) {
assertTrue(isVisible(word));
}
const translatedWordElements =
selectionOverlayElement.$.textSelectionLayer
.getTranslatedWordNodesForTesting();
assertTrue(translatedWordElements.length > 0);
for (const word of translatedWordElements) {
assertFalse(isVisible(word));
}
});
test(
`verify that translate text does render if translate mode enabled`,
async () => {
await addWordsWithTranslations();
dispatchTranslateStateEvent(
selectionOverlayElement.$.textSelectionLayer, true, 'es');
await waitAfterNextRender(selectionOverlayElement);
// Make sure only non-translated word divs are visible.
const wordElements = selectionOverlayElement.$.textSelectionLayer
.getWordNodesForTesting();
assertTrue(wordElements.length > 0);
for (const word of wordElements) {
assertFalse(isVisible(word));
}
const translatedWordElements =
selectionOverlayElement.$.textSelectionLayer
.getTranslatedWordNodesForTesting();
assertTrue(translatedWordElements.length > 0);
for (const word of translatedWordElements) {
assertTrue(isVisible(word));
}
});
});