// 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.
import 'chrome://shopping-insights-side-panel.top-chrome/app.js';
import {BrowserProxyImpl} from 'chrome://resources/cr_components/commerce/browser_proxy.js';
import type {BookmarkProductInfo, PageRemote, PriceInsightsInfo, ProductInfo} from 'chrome://resources/cr_components/commerce/shopping_service.mojom-webui.js';
import {PageCallbackRouter, PriceInsightsInfo_PriceBucket} from 'chrome://resources/cr_components/commerce/shopping_service.mojom-webui.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.js';
import {stringToMojoString16} from 'chrome://resources/js/mojo_type_util.js';
import type {PriceTrackingSection} from 'chrome://shopping-insights-side-panel.top-chrome/price_tracking_section.js';
import {assertEquals, assertTrue} from 'chrome://webui-test/chai_assert.js';
import type {MetricsTracker} from 'chrome://webui-test/metrics_test_support.js';
import {fakeMetricsPrivate} from 'chrome://webui-test/metrics_test_support.js';
import {flushTasks} from 'chrome://webui-test/polymer_test_util.js';
import {TestMock} from 'chrome://webui-test/test_mock.js';
suite('PriceTrackingSectionTest', () => {
let priceTrackingSection: PriceTrackingSection;
let callbackRouter: PageCallbackRouter;
let callbackRouterRemote: PageRemote;
const shoppingServiceApi = TestMock.fromClass(BrowserProxyImpl);
let metrics: MetricsTracker;
const productInfo: ProductInfo = {
title: 'Product Foo',
clusterTitle: 'Product Cluster Foo',
domain: 'foo.com',
imageUrl: {url: 'https://foo.com/image'},
productUrl: {url: 'https://foo.com/product'},
currentPrice: '$12',
previousPrice: '$34',
clusterId: BigInt(12345),
categoryLabels: [],
};
const priceInsights: PriceInsightsInfo = {
clusterId: BigInt(12345),
typicalLowPrice: '$100',
typicalHighPrice: '$200',
catalogAttributes: 'Unlocked, 4GB',
jackpot: {url: 'https://foo.com/jackpot'},
bucket: PriceInsightsInfo_PriceBucket.kLow,
hasMultipleCatalogs: true,
history: [{
date: '2021-01-01',
price: 100,
formattedPrice: '$100',
}],
locale: 'en-us',
currencyCode: 'usd',
};
const bookmarkProductInfo: BookmarkProductInfo = {
bookmarkId: BigInt(3),
info: productInfo,
};
function checkPriceTrackingSectionRendering(tracked: boolean) {
assertEquals(
priceTrackingSection.$.toggleTitle!.textContent,
loadTimeData.getString('trackPriceTitle'));
if (tracked) {
checkAnnotationHasText(
loadTimeData.getStringF('trackPriceSaveDescription'));
checkAnnotationHasText(
loadTimeData.getStringF('trackPriceSaveLocation', 'Parent folder'));
} else {
checkAnnotationHasText(loadTimeData.getString('trackPriceDescription'));
}
assertEquals(
priceTrackingSection.$.toggle!.getAttribute('aria-pressed')!,
tracked ? 'true' : 'false');
}
// Check whether the annotation element contains the provided text. The intent
// of this utility is to ignore extra whitespace generated by the structure in
// the HTML.
function checkAnnotationHasText(expected: string) {
const annotationText = priceTrackingSection.$.toggleAnnotation!.textContent;
assertTrue(
annotationText?.trim().length !== 0,
'Annotation text context was empty!');
assertTrue(
annotationText?.includes(expected) === true,
'Annotation section did not contain string: ' + expected +
' Actual: ' + annotationText);
}
setup(async () => {
document.body.innerHTML = window.trustedTypes!.emptyHTML;
shoppingServiceApi.reset();
callbackRouter = new PageCallbackRouter();
shoppingServiceApi.setResultFor('getCallbackRouter', callbackRouter);
shoppingServiceApi.setResultFor(
'getParentBookmarkFolderNameForCurrentUrl',
Promise.resolve({name: stringToMojoString16('Parent folder')}));
callbackRouterRemote = callbackRouter.$.bindNewPipeAndPassRemote();
BrowserProxyImpl.setInstance(shoppingServiceApi);
priceTrackingSection = document.createElement('price-tracking-section');
priceTrackingSection.productInfo = productInfo;
priceTrackingSection.priceInsightsInfo = priceInsights;
metrics = fakeMetricsPrivate();
});
[true, false].forEach((tracked) => {
test(
`PriceTracking section rendering when tracked is ${tracked}`,
async () => {
priceTrackingSection.isProductTracked = tracked;
document.body.appendChild(priceTrackingSection);
await flushTasks();
checkPriceTrackingSectionRendering(tracked);
});
test(`Toggle price tracking when tracked is ${tracked}`, async () => {
priceTrackingSection.isProductTracked = tracked;
document.body.appendChild(priceTrackingSection);
await flushTasks();
priceTrackingSection.$.toggle!.click();
const tracking = await shoppingServiceApi.whenCalled(
'setPriceTrackingStatusForCurrentUrl');
assertEquals(!tracking, tracked);
if (tracking) {
assertEquals(
1,
metrics.count(
'Commerce.PriceTracking.PriceInsightsSidePanel.Track',
PriceInsightsInfo_PriceBucket.kLow));
} else {
assertEquals(
1,
metrics.count(
'Commerce.PriceTracking.PriceInsightsSidePanel.Untrack',
PriceInsightsInfo_PriceBucket.kLow));
}
});
test(`Ignore unrealted product tracking status change`, async () => {
priceTrackingSection.isProductTracked = tracked;
document.body.appendChild(priceTrackingSection);
await flushTasks();
// Create a unrelated product.
const otherProductInfo: ProductInfo = {
title: 'Product Bar',
clusterTitle: 'Product Cluster Bar',
domain: 'bar.com',
imageUrl: {url: 'https://bar.com/image'},
productUrl: {url: 'https://bar.com/product'},
currentPrice: '$12',
previousPrice: '$34',
clusterId: BigInt(54321),
categoryLabels: [],
};
const otherBookmarkProductInfo: BookmarkProductInfo = {
bookmarkId: BigInt(4),
info: otherProductInfo,
};
if (tracked) {
callbackRouterRemote.priceUntrackedForBookmark(
otherBookmarkProductInfo);
} else {
callbackRouterRemote.priceTrackedForBookmark(otherBookmarkProductInfo);
}
await flushTasks();
checkPriceTrackingSectionRendering(tracked);
});
});
test(`Observe current product tracking status change`, async () => {
priceTrackingSection.isProductTracked = false;
document.body.appendChild(priceTrackingSection);
await flushTasks();
callbackRouterRemote.priceTrackedForBookmark(bookmarkProductInfo);
await flushTasks();
checkPriceTrackingSectionRendering(true);
callbackRouterRemote.priceUntrackedForBookmark(bookmarkProductInfo);
await flushTasks();
checkPriceTrackingSectionRendering(false);
});
test(`Trigger bookmark editor`, async () => {
priceTrackingSection.isProductTracked = true;
document.body.appendChild(priceTrackingSection);
await flushTasks();
checkPriceTrackingSectionRendering(true);
const folder = priceTrackingSection.shadowRoot!.querySelector<HTMLElement>(
'#toggleAnnotationButton');
assertTrue(!!folder);
folder.click();
await shoppingServiceApi.whenCalled('showBookmarkEditorForCurrentUrl');
assertEquals(
1,
metrics.count(
'Commerce.PriceTracking.' +
'EditedBookmarkFolderFromPriceInsightsSidePanel'));
});
test(`Render error message`, async () => {
priceTrackingSection.isProductTracked = false;
document.body.appendChild(priceTrackingSection);
await flushTasks();
callbackRouterRemote.operationFailedForBookmark(bookmarkProductInfo, true);
await flushTasks();
assertEquals(
priceTrackingSection.$.toggleTitle!.textContent,
loadTimeData.getString('trackPriceTitle'));
assertEquals(
priceTrackingSection.$.toggleAnnotation!.textContent?.trim(),
loadTimeData.getString('trackPriceError'));
assertEquals(
priceTrackingSection.$.toggle!.getAttribute('aria-pressed'), 'false');
callbackRouterRemote.operationFailedForBookmark(bookmarkProductInfo, false);
await flushTasks();
assertEquals(
priceTrackingSection.$.toggleTitle!.textContent,
loadTimeData.getString('trackPriceTitle'));
assertEquals(
priceTrackingSection.$.toggleAnnotation!.textContent?.trim(),
loadTimeData.getString('trackPriceError'));
assertEquals(
priceTrackingSection.$.toggle!.getAttribute('aria-pressed'), 'true');
});
test(`Observe product bookmark move event`, async () => {
priceTrackingSection.isProductTracked = true;
document.body.appendChild(priceTrackingSection);
await flushTasks();
checkPriceTrackingSectionRendering(true);
let expectedAnnotation =
loadTimeData.getStringF('trackPriceSaveDescription');
let expectedSaveLocationText =
loadTimeData.getStringF('trackPriceSaveLocation', 'Parent folder');
checkAnnotationHasText(expectedAnnotation);
checkAnnotationHasText(expectedSaveLocationText);
shoppingServiceApi.setResultFor(
'getParentBookmarkFolderNameForCurrentUrl',
Promise.resolve({name: stringToMojoString16('New folder')}));
callbackRouterRemote.onProductBookmarkMoved(bookmarkProductInfo);
await flushTasks();
expectedAnnotation = loadTimeData.getStringF('trackPriceSaveDescription');
expectedSaveLocationText =
loadTimeData.getStringF('trackPriceSaveLocation', 'New folder');
checkAnnotationHasText(expectedAnnotation);
checkAnnotationHasText(expectedSaveLocationText);
});
});