From cc2291cce5c5d199540334d01fcfe27207bc5962 Mon Sep 17 00:00:00 2001 From: Kristin Lee Date: Wed, 8 Jun 2022 14:17:45 -0700 Subject: [PATCH] Add Protocol Handlers section to Application Panel Adding a section for Protocol Handlers in the App Manifest pane so users can view and test their protocol handlers defined in manifest directly from the DevTools without having to go to manifest.json or having to use a separate application/interface/website to test. Explainer: https://docs.google.com/document/d/1XsKG_yqHEwWYvI2O0drMliDhF3GaLLGTkC2XmETMSrI/edit?usp=sharing Backend CL for Telemetry: https://chromium-review.googlesource.com/c/chromium/src/+/3664012 Screenshots: Protocols detected view: https://drive.google.com/file/d/1kNMHSR-q-m5LxGtDNruVbriuvULtGRXm/view?usp=sharing Protocols not detected view: https://drive.google.com/file/d/1jYtoHpYQXizNFjEKY31yh-OH7R1kK-Bu/view?usp=sharing Bug: none Change-Id: I54a4693e5e9ce10e11aa74c0cb1bffa25ee38780 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3640142 Reviewed-by: Jack Franklin Reviewed-by: Chait Pinnamaneni Commit-Queue: Kristin Lee Reviewed-by: Michael Liao --- config/gni/devtools_grd_files.gni | 2 + front_end/core/host/UserMetrics.ts | 3 +- front_end/core/i18n/locales/en-US.json | 21 ++ front_end/core/i18n/locales/en-XL.json | 21 ++ .../panels/application/AppManifestView.ts | 13 +- .../panels/application/components/BUILD.gn | 3 + .../components/ProtocolHandlersView.ts | 182 ++++++++++++++++++ .../application/components/components.ts | 2 + .../components/protocolHandlersView.css | 39 ++++ test/e2e/helpers/application-helpers.ts | 7 + .../panels/application/components/BUILD.gn | 1 + .../components/ProtocolHandlersView_test.ts | 77 ++++++++ 12 files changed, 369 insertions(+), 2 deletions(-) create mode 100644 front_end/panels/application/components/ProtocolHandlersView.ts create mode 100644 front_end/panels/application/components/protocolHandlersView.css create mode 100644 test/unittests/front_end/panels/application/components/ProtocolHandlersView_test.ts diff --git a/config/gni/devtools_grd_files.gni b/config/gni/devtools_grd_files.gni index 93da3dd9ef..1d1221debe 100644 --- a/config/gni/devtools_grd_files.gni +++ b/config/gni/devtools_grd_files.gni @@ -847,6 +847,7 @@ grd_files_debug_sources = [ "front_end/panels/application/components/InterestGroupAccessGrid.js", "front_end/panels/application/components/OriginTrialTreeView.js", "front_end/panels/application/components/PermissionsPolicySection.js", + "front_end/panels/application/components/ProtocolHandlersView.js", "front_end/panels/application/components/ReportsGrid.js", "front_end/panels/application/components/StackTrace.js", "front_end/panels/application/components/TrustTokensView.js", @@ -857,6 +858,7 @@ grd_files_debug_sources = [ "front_end/panels/application/components/originTrialTokenRows.css.js", "front_end/panels/application/components/originTrialTreeView.css.js", "front_end/panels/application/components/permissionsPolicySection.css.js", + "front_end/panels/application/components/protocolHandlersView.css.js", "front_end/panels/application/components/reportingApiGrid.css.js", "front_end/panels/application/components/stackTraceLinkButton.css.js", "front_end/panels/application/components/stackTraceRow.css.js", diff --git a/front_end/core/host/UserMetrics.ts b/front_end/core/host/UserMetrics.ts index 6e6095bd41..ca9e5fd0e2 100644 --- a/front_end/core/host/UserMetrics.ts +++ b/front_end/core/host/UserMetrics.ts @@ -345,7 +345,8 @@ export enum Action { PerfPanelTraceImported = 54, PerfPanelTraceExported = 55, StackFrameRestarted = 56, - MaxValue = 57, + CaptureTestProtocolClicked = 57, + MaxValue = 58, } /* eslint-disable @typescript-eslint/naming-convention */ diff --git a/front_end/core/i18n/locales/en-US.json b/front_end/core/i18n/locales/en-US.json index cca4541cde..c474b5008f 100644 --- a/front_end/core/i18n/locales/en-US.json +++ b/front_end/core/i18n/locales/en-US.json @@ -2426,6 +2426,9 @@ "panels/application/AppManifestView.ts | primaryManifestIconFromS": { "message": "Primary manifest icon from {PH1}" }, + "panels/application/AppManifestView.ts | protocolHandlers": { + "message": "Protocol Handlers" + }, "panels/application/AppManifestView.ts | screenshot": { "message": "Screenshot" }, @@ -3197,6 +3200,24 @@ "panels/application/components/PermissionsPolicySection.ts | showDetails": { "message": "Show details" }, + "panels/application/components/ProtocolHandlersView.ts | manifest": { + "message": "manifest" + }, + "panels/application/components/ProtocolHandlersView.ts | needHelpReadOur": { + "message": "Need help? Read {PH1}." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolDetected": { + "message": "Found valid protocol handler registration in the {PH1}. With the app installed, test the registered protocols." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolHandlerRegistrations": { + "message": "URL protocol handler registration for PWAs" + }, + "panels/application/components/ProtocolHandlersView.ts | protocolNotDetected": { + "message": "Define protocol handlers in the {PH1} to register your app as a handler for custom protocols when your app is installed." + }, + "panels/application/components/ProtocolHandlersView.ts | testProtocol": { + "message": "Test protocol" + }, "panels/application/components/ReportsGrid.ts | destination": { "message": "Destination" }, diff --git a/front_end/core/i18n/locales/en-XL.json b/front_end/core/i18n/locales/en-XL.json index 89ad5bc0d3..7a4c928121 100644 --- a/front_end/core/i18n/locales/en-XL.json +++ b/front_end/core/i18n/locales/en-XL.json @@ -2426,6 +2426,9 @@ "panels/application/AppManifestView.ts | primaryManifestIconFromS": { "message": "P̂ŕîḿâŕŷ ḿâńîf́êśt̂ íĉón̂ f́r̂óm̂ {PH1}" }, + "panels/application/AppManifestView.ts | protocolHandlers": { + "message": "P̂ŕôt́ôćôĺ Ĥán̂d́l̂ér̂ś" + }, "panels/application/AppManifestView.ts | screenshot": { "message": "Ŝćr̂éêńŝh́ôt́" }, @@ -3197,6 +3200,24 @@ "panels/application/components/PermissionsPolicySection.ts | showDetails": { "message": "Ŝh́ôẃ d̂ét̂áîĺŝ" }, + "panels/application/components/ProtocolHandlersView.ts | manifest": { + "message": "m̂án̂íf̂éŝt́" + }, + "panels/application/components/ProtocolHandlersView.ts | needHelpReadOur": { + "message": "N̂éêd́ ĥél̂ṕ? R̂éâd́ {PH1}." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolDetected": { + "message": "F̂óûńd̂ v́âĺîd́ p̂ŕôt́ôćôĺ ĥán̂d́l̂ér̂ ŕêǵîśt̂ŕât́îón̂ ín̂ t́ĥé {PH1}. Ŵít̂h́ t̂h́ê áp̂ṕ îńŝt́âĺl̂éd̂, t́êśt̂ t́ĥé r̂éĝíŝt́êŕêd́ p̂ŕôt́ôćôĺŝ." + }, + "panels/application/components/ProtocolHandlersView.ts | protocolHandlerRegistrations": { + "message": "ÛŔL̂ ṕr̂ót̂óĉól̂ h́âńd̂ĺêŕ r̂éĝíŝt́r̂át̂íôń f̂ór̂ ṔŴÁŝ" + }, + "panels/application/components/ProtocolHandlersView.ts | protocolNotDetected": { + "message": "D̂éf̂ín̂é p̂ŕôt́ôćôĺ ĥán̂d́l̂ér̂ś îń t̂h́ê {PH1} t́ô ŕêǵîśt̂ér̂ ýôúr̂ áp̂ṕ âś â h́âńd̂ĺêŕ f̂ór̂ ćûśt̂óm̂ ṕr̂ót̂óĉól̂ś ŵh́êń ŷóûŕ âṕp̂ íŝ ín̂śt̂ál̂ĺêd́." + }, + "panels/application/components/ProtocolHandlersView.ts | testProtocol": { + "message": "T̂éŝt́ p̂ŕôt́ôćôĺ" + }, "panels/application/components/ReportsGrid.ts | destination": { "message": "D̂éŝt́îńât́îón̂" }, diff --git a/front_end/panels/application/AppManifestView.ts b/front_end/panels/application/AppManifestView.ts index 310f7e2ada..3b290acf35 100644 --- a/front_end/panels/application/AppManifestView.ts +++ b/front_end/panels/application/AppManifestView.ts @@ -15,6 +15,7 @@ import * as Components from '../../ui/legacy/components/utils/utils.js'; import * as UI from '../../ui/legacy/legacy.js'; import type * as Protocol from '../../generated/protocol.js'; import * as IconButton from '../../ui/components/icon_button/icon_button.js'; +import * as ApplicationComponents from './components/components.js'; const UIStrings = { /** @@ -44,6 +45,10 @@ const UIStrings = { /** *@description Text in App Manifest View of the Application panel */ + protocolHandlers: 'Protocol Handlers', + /** + *@description Text in App Manifest View of the Application panel + */ icons: 'Icons', /** *@description Text for the name of something @@ -423,6 +428,7 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager private target?: SDK.Target.Target; private resourceTreeModel?: SDK.ResourceTreeModel.ResourceTreeModel|null; private serviceWorkerManager?: SDK.ServiceWorkerManager.ServiceWorkerManager|null; + private protocolHandlersView: ApplicationComponents.ProtocolHandlersView.ProtocolHandlersView; constructor() { super(true); @@ -448,8 +454,10 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager this.errorsSection = this.reportView.appendSection(i18nString(UIStrings.errorsAndWarnings)); this.installabilitySection = this.reportView.appendSection(i18nString(UIStrings.installability)); this.identitySection = this.reportView.appendSection(i18nString(UIStrings.identity)); - this.presentationSection = this.reportView.appendSection(i18nString(UIStrings.presentation)); + const protocolHandlersSection = this.reportView.appendSection(i18nString(UIStrings.protocolHandlers)); + this.protocolHandlersView = new ApplicationComponents.ProtocolHandlersView.ProtocolHandlersView(); + protocolHandlersSection.contentElement.append(this.protocolHandlersView); this.iconsSection = this.reportView.appendSection(i18nString(UIStrings.icons), 'report-section-icons'); this.shortcutSections = []; this.screenshotsSections = []; @@ -701,6 +709,9 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager this.newNoteUrlField.appendChild(link); } + const protocolHandlers = parsedManifest['protocol_handlers'] || []; + this.protocolHandlersView.data = {protocolHandlers, manifestLink: url}; + const icons = parsedManifest['icons'] || []; this.iconsSection.clearContent(); diff --git a/front_end/panels/application/components/BUILD.gn b/front_end/panels/application/components/BUILD.gn index f565f83ada..a095abab6d 100644 --- a/front_end/panels/application/components/BUILD.gn +++ b/front_end/panels/application/components/BUILD.gn @@ -16,6 +16,7 @@ generate_css("css_files") { "originTrialTokenRows.css", "originTrialTreeView.css", "permissionsPolicySection.css", + "protocolHandlersView.css", "reportingApiGrid.css", "stackTraceLinkButton.css", "stackTraceRow.css", @@ -33,6 +34,7 @@ devtools_module("components") { "InterestGroupAccessGrid.ts", "OriginTrialTreeView.ts", "PermissionsPolicySection.ts", + "ProtocolHandlersView.ts", "ReportsGrid.ts", "StackTrace.ts", "TrustTokensView.ts", @@ -54,6 +56,7 @@ devtools_module("components") { "../../../ui/components/expandable_list:bundle", "../../../ui/components/icon_button:bundle", "../../../ui/components/report_view:bundle", + "../../../ui/components/text_prompt:bundle", "../../../ui/components/tree_outline:bundle", "../../../ui/legacy:bundle", "../../../ui/legacy/components/source_frame:bundle", diff --git a/front_end/panels/application/components/ProtocolHandlersView.ts b/front_end/panels/application/components/ProtocolHandlersView.ts new file mode 100644 index 0000000000..a02bb63584 --- /dev/null +++ b/front_end/panels/application/components/ProtocolHandlersView.ts @@ -0,0 +1,182 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as Host from '../../../core/host/host.js'; +import * as i18n from '../../../core/i18n/i18n.js'; +import * as Platform from '../../../core/platform/platform.js'; +import * as Buttons from '../../../ui/components/buttons/buttons.js'; +import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; +import * as IconButton from '../../../ui/components/icon_button/icon_button.js'; +import * as Input from '../../../ui/components/input/input.js'; +// inspectorCommonStyles is imported for the chrome-select class that is used for the dropdown +// eslint-disable-next-line rulesdir/es_modules_import +import inspectorCommonStyles from '../../../ui/legacy/inspectorCommon.css.js'; +import * as UI from '../../../ui/legacy/legacy.js'; +import * as LitHtml from '../../../ui/lit-html/lit-html.js'; + +import protocolHandlersViewStyles from './protocolHandlersView.css.js'; + +const PROTOCOL_DOCUMENT_URL = 'https://web.dev/url-protocol-handler/'; +const UIStrings = { + /** + *@description Status message for when protocol handlers are detected in the manifest + *@example {protocolhandler/manifest.json} PH1 + */ + protocolDetected: + 'Found valid protocol handler registration in the {PH1}. With the app installed, test the registered protocols.', + /** + *@description Status message for when protocol handlers are not detected in the manifest + *@example {protocolhandler/manifest.json} PH1 + */ + protocolNotDetected: + 'Define protocol handlers in the {PH1} to register your app as a handler for custom protocols when your app is installed.', + /** + *@description Text wrapping a link pointing to more information on handling protocol handlers + *@example {https://example.com/} PH1 + */ + needHelpReadOur: 'Need help? Read {PH1}.', + /** + *@description Link text for more information on URL protocol handler registrations for PWAs + */ + protocolHandlerRegistrations: 'URL protocol handler registration for PWAs', + /** + *@description In text hyperlink to the PWA manifest + */ + manifest: 'manifest', + /** + *@description Text for test protocol button + */ + testProtocol: 'Test protocol', +}; + +const str_ = i18n.i18n.registerUIStrings('panels/application/components/ProtocolHandlersView.ts', UIStrings); +const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); + +interface HTMLSelectElementEvent extends Event { + target: HTMLSelectElement; +} + +interface HTMLInputElementEvent extends Event { + target: HTMLInputElement; +} + +export interface ProtocolHandler { + protocol: string; + url: string; +} + +export interface ProtocolHandlersData { + protocolHandlers: ProtocolHandler[]; + manifestLink: Platform.DevToolsPath.UrlString; +} + +export class ProtocolHandlersView extends HTMLElement { + static readonly litTagName = LitHtml.literal`devtools-protocol-handlers-view`; + readonly #shadow = this.attachShadow({mode: 'open'}); + #protocolHandlers: ProtocolHandler[] = []; + #manifestLink: Platform.DevToolsPath.UrlString = Platform.DevToolsPath.EmptyUrlString; + #selectedProtocolState: string = ''; + #queryInputState: string = ''; + + set data(data: ProtocolHandlersData) { + const isNewManifest = this.#manifestLink !== data.manifestLink; + this.#protocolHandlers = data.protocolHandlers; + this.#manifestLink = data.manifestLink; + if (isNewManifest) { + this.#update(); + } + } + + #update(): void { + this.#queryInputState = ''; + this.#selectedProtocolState = this.#protocolHandlers[0]?.protocol ?? ''; + this.#render(); + } + + #renderStatusMessage(): LitHtml.TemplateResult { + const manifestInTextLink = UI.XLink.XLink.create(this.#manifestLink, i18nString(UIStrings.manifest)); + const statusString = this.#protocolHandlers.length > 0 ? UIStrings.protocolDetected : UIStrings.protocolNotDetected; + const iconData: IconButton.Icon.IconData = { + iconName: this.#protocolHandlers.length > 0 ? 'ic_checkmark_16x16' : 'ic_info_black_18dp', + color: this.#protocolHandlers.length > 0 ? 'var( --color-ic-file-image)' : 'var(--color-link)', + width: '16px', + }; + return LitHtml.html` +
+ <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${iconData as IconButton.Icon.IconData}> + + ${i18n.i18n.getFormatLocalizedString(str_, statusString, { + PH1: manifestInTextLink, + })} +
+ `; + } + + #renderProtocolTest(): LitHtml.LitTemplate { + if (this.#protocolHandlers.length === 0) { + return LitHtml.nothing; + } + const protocolOptions = this.#protocolHandlers.filter(p => p.protocol) + .map(p => LitHtml.html``); + return LitHtml.html` +
+ + + <${Buttons.Button.Button.litTagName} .variant=${Buttons.Button.Variant.PRIMARY} @click=${ + this.#handleTestProtocolClick}> + ${i18nString(UIStrings.testProtocol)} + +
+ `; + } + + #handleProtocolSelect = (evt: HTMLSelectElementEvent): void => { + this.#selectedProtocolState = evt.target.value; + }; + + #handleQueryInputChange = (evt: HTMLInputElementEvent): void => { + this.#queryInputState = evt.target.value; + this.#render(); + }; + + #handleTestProtocolClick = (): void => { + const protocolURL = `${this.#selectedProtocolState}://${this.#queryInputState}` as Platform.DevToolsPath.UrlString; + Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(protocolURL); + Host.userMetrics.actionTaken(Host.UserMetrics.Action.CaptureTestProtocolClicked); + }; + + connectedCallback(): void { + this.#shadow.adoptedStyleSheets = [ + protocolHandlersViewStyles, + inspectorCommonStyles, + Input.textInputStyles, + ]; + } + + #render(): void { + const protocolDocLink = + UI.XLink.XLink.create(PROTOCOL_DOCUMENT_URL, i18nString(UIStrings.protocolHandlerRegistrations)); + // clang-format off + LitHtml.render(LitHtml.html` + ${this.#renderStatusMessage()} +
+ ${i18n.i18n.getFormatLocalizedString(str_, UIStrings.needHelpReadOur, {PH1: protocolDocLink})} +
+ ${this.#renderProtocolTest()} + `, this.#shadow, {host: this}); + // clang-format on + } +} + +ComponentHelpers.CustomElements.defineComponent('devtools-protocol-handlers-view', ProtocolHandlersView); + +declare global { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface HTMLElementTagNameMap { + 'devtools-protocol-handlers-view': ProtocolHandlersView; + } +} diff --git a/front_end/panels/application/components/components.ts b/front_end/panels/application/components/components.ts index ee780c5f07..46baa883aa 100644 --- a/front_end/panels/application/components/components.ts +++ b/front_end/panels/application/components/components.ts @@ -7,6 +7,7 @@ import * as EndpointsGrid from './EndpointsGrid.js'; import * as FrameDetailsView from './FrameDetailsView.js'; import * as InterestGroupAccessGrid from './InterestGroupAccessGrid.js'; import * as OriginTrialTreeView from './OriginTrialTreeView.js'; +import * as ProtocolHandlersView from './ProtocolHandlersView.js'; import * as ReportsGrid from './ReportsGrid.js'; import * as StackTrace from './StackTrace.js'; import * as TrustTokensView from './TrustTokensView.js'; @@ -17,6 +18,7 @@ export { FrameDetailsView, InterestGroupAccessGrid, OriginTrialTreeView, + ProtocolHandlersView, ReportsGrid, StackTrace, TrustTokensView, diff --git a/front_end/panels/application/components/protocolHandlersView.css b/front_end/panels/application/components/protocolHandlersView.css new file mode 100644 index 0000000000..46d4116db6 --- /dev/null +++ b/front_end/panels/application/components/protocolHandlersView.css @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +.devtools-link { + color: var(--color-link); + text-decoration: underline; + cursor: pointer; + padding: 2px 0; /* adjust focus ring size */ +} + +input.devtools-text-input[type="text"] { + padding: 3px 6px; + margin-left: 4px; + margin-right: 4px; + width: 250px; + height: 25px; +} + +.protocol-handlers-row { + margin: 10px 0 2px 18px; +} + +.inline-icon { + vertical-align: text-bottom; +} + +@media (forced-colors: active) { + .devtools-link:not(.devtools-link-prevent-click) { + color: linktext; + } + + .devtools-link:focus-visible { + background: Highlight; + color: HighlightText; + } +} diff --git a/test/e2e/helpers/application-helpers.ts b/test/e2e/helpers/application-helpers.ts index 569492b222..afc875f0e7 100644 --- a/test/e2e/helpers/application-helpers.ts +++ b/test/e2e/helpers/application-helpers.ts @@ -18,6 +18,13 @@ export async function navigateToServiceWorkers() { await click(SERVICE_WORKER_ROW_SELECTOR); } +export async function navigateToManifestInApplicationTab(testName: string) { + const MANIFEST_SELECTOR = '[aria-label="Manifest"]'; + const {target} = getBrowserAndPages(); + await navigateToApplicationTab(target, testName); + await click(MANIFEST_SELECTOR); +} + export async function doubleClickSourceTreeItem(selector: string) { const element = await waitFor(selector); element.evaluate(el => el.scrollIntoView(true)); diff --git a/test/unittests/front_end/panels/application/components/BUILD.gn b/test/unittests/front_end/panels/application/components/BUILD.gn index 7eaabf1a05..f799db0c82 100644 --- a/test/unittests/front_end/panels/application/components/BUILD.gn +++ b/test/unittests/front_end/panels/application/components/BUILD.gn @@ -11,6 +11,7 @@ ts_library("components") { "FrameDetailsView_test.ts", "InterestGroupAccessGrid_test.ts", "OriginTrialTreeView_test.ts", + "ProtocolHandlersView_test.ts", "ReportsGrid_test.ts", "StackTrace_test.ts", "TrustTokensView_test.ts", diff --git a/test/unittests/front_end/panels/application/components/ProtocolHandlersView_test.ts b/test/unittests/front_end/panels/application/components/ProtocolHandlersView_test.ts new file mode 100644 index 0000000000..fb97bea6f3 --- /dev/null +++ b/test/unittests/front_end/panels/application/components/ProtocolHandlersView_test.ts @@ -0,0 +1,77 @@ +// Copyright 2022 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import * as ApplicationComponents from '../../../../../../front_end/panels/application/components/components.js'; +import type * as Platform from '../../../../../../front_end/core/platform/platform.js'; +import { + assertElement, + assertShadowRoot, + getCleanTextContentFromElements, + getElementWithinComponent, + renderElementIntoDOM, +} from '../../../helpers/DOMHelpers.js'; +import {describeWithEnvironment} from '../../../helpers/EnvironmentHelpers.js'; + +const {assert} = chai; + +async function renderProtocolHandlersComponent( + manifestLink: Platform.DevToolsPath.UrlString, + protocolHandlers: ApplicationComponents.ProtocolHandlersView.ProtocolHandler[]) { + const component = new ApplicationComponents.ProtocolHandlersView.ProtocolHandlersView(); + renderElementIntoDOM(component); + component.data = {manifestLink, protocolHandlers}; + return component; +} + +describeWithEnvironment('ProtocolHandlersView', () => { + it('renders view when protocols are detected', async () => { + const protocols = [ + { + 'protocol': 'web+coffee', + 'url': './?coffee=%s', + }, + { + 'protocol': 'web+pwinter', + 'url': 'index.html?colors=%s', + }, + { + 'invalid-protocol': 'this is an invalid protocol entry for testing purposes', + }, + ]; + const manifestURL = 'https://www.example.com/index.html/manifest-protocol.json' as Platform.DevToolsPath.UrlString; + const component = await renderProtocolHandlersComponent( + manifestURL, protocols as ApplicationComponents.ProtocolHandlersView.ProtocolHandler[]); + assertShadowRoot(component.shadowRoot); + + const statusElement = component.shadowRoot.querySelector('.protocol-handlers-row.status'); + assertElement(statusElement, HTMLElement); + + // Tests if status message for when protocols are detected in the manifest is rendering + const protocolsDetectedMessage = getCleanTextContentFromElements(statusElement, 'span'); + const expectedStatusMessage = + 'Found valid protocol handler registration in the manifest. With the app installed, test the registered protocols.'; + assert.deepEqual(protocolsDetectedMessage[0], expectedStatusMessage); + + // Tests if protocols are rendering properly in dropdown + const selectElement = getElementWithinComponent(component, '.protocol-select', HTMLSelectElement); + const values = getCleanTextContentFromElements(selectElement, 'option'); + assert.deepEqual(values, ['web+coffee://', 'web+pwinter://']); + }); + + it('renders protocols not detected status message', async () => { + const protocols: ApplicationComponents.ProtocolHandlersView.ProtocolHandler[] = []; + const manifestURL = 'https://www.example.com/index.html/manifest-protocol.json' as Platform.DevToolsPath.UrlString; + const component = await renderProtocolHandlersComponent( + manifestURL, protocols as ApplicationComponents.ProtocolHandlersView.ProtocolHandler[]); + assertShadowRoot(component.shadowRoot); + + const noStatusElement = component.shadowRoot.querySelector('.protocol-handlers-row.status'); + assertElement(noStatusElement, HTMLElement); + + const protocolsNotDetectedMessage = getCleanTextContentFromElements(noStatusElement, 'span'); + const expectedStatusMessage = + 'Define protocol handlers in the manifest to register your app as a handler for custom protocols when your app is installed.'; + assert.deepEqual(protocolsNotDetectedMessage[0], expectedStatusMessage); + }); +});