UNPKG

chrome-devtools-frontend

Version:
299 lines (250 loc) • 10.6 kB
// Copyright 2020 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 Common from '../../../core/common/common.js'; import type * as Platform from '../../../core/platform/platform.js'; import type * as SDK from '../../../core/sdk/sdk.js'; import type * as Protocol from '../../../generated/protocol.js'; import type * as Logs from '../../../models/logs/logs.js'; import * as NetworkForward from '../../../panels/network/forward/forward.js'; import {renderElementIntoDOM} from '../../../testing/DOMHelpers.js'; import {describeWithEnvironment} from '../../../testing/EnvironmentHelpers.js'; import * as UI from '../../legacy/legacy.js'; import * as IconButton from '../icon_button/icon_button.js'; import * as RenderCoordinator from '../render_coordinator/render_coordinator.js'; import * as RequestLinkIcon from './request_link_icon.js'; const renderRequestLinkIcon = async(data: RequestLinkIcon.RequestLinkIcon.RequestLinkIconData): Promise<{ component: RequestLinkIcon.RequestLinkIcon.RequestLinkIcon, shadowRoot: ShadowRoot, }> => { const component = new RequestLinkIcon.RequestLinkIcon.RequestLinkIcon(); component.data = data; renderElementIntoDOM(component); assert.isNotNull(component.shadowRoot); await RenderCoordinator.done(); return {component, shadowRoot: component.shadowRoot}; }; export const extractElements = (shadowRoot: ShadowRoot): { icon: IconButton.Icon.Icon, button: HTMLButtonElement, label: HTMLSpanElement|null, } => { const icon = shadowRoot.querySelector('devtools-icon'); assert.instanceOf(icon, IconButton.Icon.Icon); const button = shadowRoot.querySelector('button'); assert.instanceOf(button, HTMLButtonElement); const label = shadowRoot.querySelector('button > span'); if (label !== null) { assert.instanceOf(label, HTMLSpanElement); } return {icon, button, label}; }; interface MockRequestResolverEntry { resolve: (request: SDK.NetworkRequest.NetworkRequest|null) => void; promise: Promise<SDK.NetworkRequest.NetworkRequest|null>; } class MockRequestResolver { #promiseMap = new Map<string, MockRequestResolverEntry>(); waitFor(requestId?: string) { if (!requestId) { if (this.#promiseMap.size !== 1) { throw new Error('more than one request being awaited, specify a request id'); } requestId = this.#promiseMap.keys().next().value; } requestId = requestId || ''; const entry = this.#promiseMap.get(requestId); if (entry) { return entry.promise; } const {resolve, promise} = Promise.withResolvers<SDK.NetworkRequest.NetworkRequest|null>(); this.#promiseMap.set(requestId, {resolve, promise}); return promise; } resolve(result: SDK.NetworkRequest.NetworkRequest|null, requestId?: string): void { if (!requestId && this.#promiseMap.size === 1) { requestId = this.#promiseMap.keys().next().value; } requestId = requestId || result?.requestId() || ''; const entry = this.#promiseMap.get(requestId); if (!entry) { throw new Error('resolve uninitialized'); } entry.resolve(result); this.#promiseMap.delete(requestId); } clear() { for (const {resolve} of this.#promiseMap.values()) { resolve(null); } } } describeWithEnvironment('RequestLinkIcon', () => { const requestId1 = 'r1' as Protocol.Network.RequestId; const requestId2 = 'r2' as Protocol.Network.RequestId; describe('with simple requests', () => { const mockRequest = { url() { return 'http://foo.bar/baz'; }, }; const mockRequestWithTrailingSlash = { url() { return 'http://foo.bar/baz/'; }, }; const failingRequestResolver = { async waitFor() { throw new Error('Couldn\'t resolve'); }, }; it('renders correctly without a request', async () => { const {shadowRoot} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId1}, requestResolver: failingRequestResolver as unknown as Logs.RequestResolver.RequestResolver, }); const {button, icon, label} = extractElements(shadowRoot); assert.isFalse(button.classList.contains('link')); assert.strictEqual(icon.name, 'arrow-up-down-circle'); assert.isNull(label, 'Didn\'t expect a label'); }); it('renders correctly with a request', async () => { const {shadowRoot} = await renderRequestLinkIcon({ request: mockRequest as unknown as SDK.NetworkRequest.NetworkRequest, }); const {button, icon, label} = extractElements(shadowRoot); assert.isTrue(button.classList.contains('link')); assert.strictEqual(icon.name, 'arrow-up-down-circle'); assert.isNull(label, 'Didn\'t expect a label'); }); it('renders the request label correctly without a trailing slash', async () => { const {shadowRoot} = await renderRequestLinkIcon({ request: mockRequest as unknown as SDK.NetworkRequest.NetworkRequest, displayURL: true, }); const {label} = extractElements(shadowRoot); assert.strictEqual(label?.textContent, 'baz'); }); it('renders the request label correctly with a trailing slash', async () => { const {shadowRoot} = await renderRequestLinkIcon({ request: mockRequestWithTrailingSlash as unknown as SDK.NetworkRequest.NetworkRequest, displayURL: true, }); const {label} = extractElements(shadowRoot); assert.strictEqual(label?.textContent, 'baz/'); }); it('renders the request label correctly without a request', async () => { const {shadowRoot} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId1, url: 'https://alpha.beta/gamma'}, requestResolver: failingRequestResolver as unknown as Logs.RequestResolver.RequestResolver, displayURL: true, }); const {label} = extractElements(shadowRoot); assert.strictEqual(label?.textContent, 'gamma'); }); it('renders alternative text for URL', async () => { const {shadowRoot} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId1, url: 'https://alpha.beta/gamma'}, requestResolver: failingRequestResolver as unknown as Logs.RequestResolver.RequestResolver, displayURL: true, urlToDisplay: 'https://alpha.beta/gamma', }); const {label} = extractElements(shadowRoot); assert.strictEqual(label?.textContent, 'https://alpha.beta/gamma'); }); }); describe('transitions upon request resolution', () => { const mockRequest = { url() { return 'http://foo.bar/baz'; }, }; it('to change the style correctly', async () => { const resolver = new MockRequestResolver(); const {shadowRoot} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId1, url: 'https://alpha.beta/gamma'}, requestResolver: resolver as unknown as Logs.RequestResolver.RequestResolver, }); assert.isFalse(extractElements(shadowRoot).button.classList.contains('link')); resolver.resolve(mockRequest as unknown as SDK.NetworkRequest.NetworkRequest); await RenderCoordinator.done({waitForWork: true}); assert.isTrue(extractElements(shadowRoot).button.classList.contains('link')); }); it('to set the label correctly', async () => { const resolver = new MockRequestResolver(); const {shadowRoot} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId1, url: 'https://alpha.beta/gamma'}, requestResolver: resolver as unknown as Logs.RequestResolver.RequestResolver, displayURL: true, }); assert.strictEqual(extractElements(shadowRoot).label?.textContent, 'gamma'); resolver.resolve(mockRequest as unknown as SDK.NetworkRequest.NetworkRequest); await RenderCoordinator.done({waitForWork: true}); assert.strictEqual(extractElements(shadowRoot).label?.textContent, 'baz'); }); it('handles multiple data assignments', async () => { const resolver = new MockRequestResolver(); const {shadowRoot, component} = await renderRequestLinkIcon({ affectedRequest: {requestId: requestId2, url: 'https://alpha.beta/gamma'}, requestResolver: resolver as unknown as Logs.RequestResolver.RequestResolver, displayURL: true, }); assert.strictEqual(extractElements(shadowRoot).label?.textContent, 'gamma'); const mockRequest2 = { url() { return 'http://foo.bar/baz'; }, requestId() { return requestId1; }, }; component.data = { affectedRequest: {requestId: requestId1, url: 'https://alpha.beta/gamma'}, requestResolver: resolver as unknown as Logs.RequestResolver.RequestResolver, displayURL: true, }; resolver.resolve(mockRequest2 as unknown as SDK.NetworkRequest.NetworkRequest); await RenderCoordinator.done({waitForWork: true}); assert.strictEqual(extractElements(shadowRoot).label?.textContent, 'baz'); resolver.clear(); }); }); describe('handles clicks correctly', () => { const mockRequest = { url() { return 'http://foo.bar/baz'; }, }; before(() => { UI.ViewManager.resetViewRegistration(); UI.ViewManager.registerViewExtension({ // @ts-expect-error location: 'mock-location', id: 'network', title: () => 'Network' as Platform.UIString.LocalizedString, commandPrompt: () => 'Network' as Platform.UIString.LocalizedString, persistence: UI.ViewManager.ViewPersistence.CLOSEABLE, async loadView() { return new UI.Widget.Widget(); }, }); UI.ViewManager.ViewManager.instance({forceNew: true}); }); after(() => { UI.ViewManager.maybeRemoveViewExtension('network'); }); it('if the button is clicked', async () => { const revealOverride = sinon.fake(Common.Revealer.reveal); const {shadowRoot} = await renderRequestLinkIcon({ request: mockRequest as unknown as SDK.NetworkRequest.NetworkRequest, displayURL: true, revealOverride, }); const {button} = extractElements(shadowRoot); button.click(); sinon.assert.called(revealOverride); assert.isTrue(revealOverride.calledOnceWith( sinon.match({tab: NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT}))); }); }); });