| // Copyright 2020 The Chromium Authors |
| // 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 Protocol from '../../../generated/protocol.js'; |
| import * as IssuesManager from '../../../models/issues_manager/issues_manager.js'; |
| import { |
| renderElementIntoDOM, |
| } from '../../../testing/DOMHelpers.js'; |
| import {setupLocaleHooks} from '../../../testing/LocaleHelpers.js'; |
| import {Icon} from '../../kit/kit.js'; |
| import * as RenderCoordinator from '../render_coordinator/render_coordinator.js'; |
| |
| import * as IssueCounter from './issue_counter.js'; |
| |
| const renderIssueLinkIcon = async(data: IssueCounter.IssueLinkIcon.IssueLinkIconData): Promise<{ |
| component: IssueCounter.IssueLinkIcon.IssueLinkIcon, |
| shadowRoot: ShadowRoot, |
| }> => { |
| const component = new IssueCounter.IssueLinkIcon.IssueLinkIcon(); |
| component.data = data; |
| renderElementIntoDOM(component); |
| assert.isNotNull(component.shadowRoot); |
| await RenderCoordinator.done(); |
| return {component, shadowRoot: component.shadowRoot}; |
| }; |
| |
| export const extractElements = (shadowRoot: ShadowRoot): { |
| icon: Icon, |
| button: HTMLButtonElement, |
| } => { |
| const icon = shadowRoot.querySelector('devtools-icon'); |
| assert.instanceOf(icon, Icon); |
| const button = shadowRoot.querySelector('button'); |
| assert.instanceOf(button, HTMLButtonElement); |
| return {icon, button}; |
| }; |
| |
| interface MockIssueResolverEntry { |
| resolve: (issue: IssuesManager.Issue.Issue|null) => void; |
| promise: Promise<IssuesManager.Issue.Issue|null>; |
| } |
| |
| class MockIssueResolver { |
| #promiseMap = new Map<string, MockIssueResolverEntry>(); |
| |
| waitFor(issueId?: string) { |
| if (!issueId) { |
| if (this.#promiseMap.size !== 1) { |
| throw new Error('more than one issue being awaited, specify a issue id'); |
| } |
| issueId = this.#promiseMap.keys().next().value; |
| } |
| issueId = issueId || ''; |
| const entry = this.#promiseMap.get(issueId); |
| if (entry) { |
| return entry.promise; |
| } |
| const {resolve, promise} = Promise.withResolvers<IssuesManager.Issue.Issue|null>(); |
| this.#promiseMap.set(issueId, {resolve, promise}); |
| return promise; |
| } |
| |
| resolve(result: IssuesManager.Issue.Issue|null, issueId?: string): void { |
| if (!issueId && this.#promiseMap.size === 1) { |
| issueId = this.#promiseMap.keys().next().value; |
| } |
| issueId = issueId || result?.getIssueId() || ''; |
| const entry = this.#promiseMap.get(issueId); |
| if (!entry) { |
| throw new Error('resolve uninitialized'); |
| } |
| entry.resolve(result); |
| this.#promiseMap.delete(issueId); |
| } |
| } |
| |
| describe('IssueLinkIcon', () => { |
| setupLocaleHooks(); |
| const issueId = 'issue1' as Protocol.Audits.IssueId; |
| const mockIssue = { |
| getKind() { |
| return IssuesManager.Issue.IssueKind.PAGE_ERROR; |
| }, |
| getIssueId() { |
| return issueId; |
| }, |
| getDescription() { |
| return null; |
| }, |
| }; |
| |
| describe('with simple issues', () => { |
| const failingIssueResolver = { |
| async waitFor() { |
| throw new Error('Couldn\'t resolve'); |
| }, |
| }; |
| |
| it('renders correctly without an issue', async () => { |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issueId, |
| issueResolver: failingIssueResolver as unknown as IssuesManager.IssueResolver.IssueResolver, |
| }); |
| |
| const {icon} = extractElements(shadowRoot); |
| assert.strictEqual(icon.name, 'issue-questionmark-filled'); |
| }); |
| |
| it('renders correctly with a "page error" issue', async () => { |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issue: mockIssue as unknown as IssuesManager.Issue.Issue, |
| }); |
| |
| const {icon} = extractElements(shadowRoot); |
| assert.strictEqual(icon.name, 'issue-cross-filled'); |
| }); |
| |
| it('the style reacts to the presence of the issue', async () => { |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issue: mockIssue as unknown as IssuesManager.Issue.Issue, |
| }); |
| |
| const {button} = extractElements(shadowRoot); |
| assert.isTrue(button.classList.contains('link')); |
| }); |
| |
| it('the style reacts to the absence of an issue', async () => { |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issueId, |
| issueResolver: failingIssueResolver as unknown as IssuesManager.IssueResolver.IssueResolver, |
| }); |
| |
| const {button} = extractElements(shadowRoot); |
| assert.isFalse(button.classList.contains('link')); |
| }); |
| }); |
| |
| describe('transitions upon issue resolution', () => { |
| it('to change the style correctly', async () => { |
| const resolver = new MockIssueResolver(); |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issueId, |
| issueResolver: resolver as unknown as IssuesManager.IssueResolver.IssueResolver, |
| }); |
| |
| resolver.resolve(mockIssue as unknown as IssuesManager.Issue.Issue); |
| await RenderCoordinator.done({waitForWork: true}); |
| |
| assert.isTrue(extractElements(shadowRoot).button.classList.contains('link')); |
| }); |
| |
| it('handles multiple data assignments', async () => { |
| const {shadowRoot, component} = await renderIssueLinkIcon({ |
| issue: mockIssue as unknown as IssuesManager.Issue.Issue, |
| }); |
| |
| const mockIssue2 = { |
| getKind() { |
| return IssuesManager.Issue.IssueKind.BREAKING_CHANGE; |
| }, |
| }; |
| |
| component.data = { |
| issue: mockIssue2 as unknown as IssuesManager.Issue.Issue, |
| }; |
| await RenderCoordinator.done({waitForWork: true}); |
| |
| const {icon} = extractElements(shadowRoot); |
| assert.strictEqual(icon.name, 'issue-exclamation-filled'); |
| }); |
| }); |
| |
| describe('handles clicks correctly', () => { |
| it('if the button is clicked', async () => { |
| const revealOverride = sinon.fake(Common.Revealer.reveal); |
| const {shadowRoot} = await renderIssueLinkIcon({ |
| issue: mockIssue as unknown as IssuesManager.Issue.Issue, |
| revealOverride, |
| }); |
| |
| const {button} = extractElements(shadowRoot); |
| button.click(); |
| sinon.assert.called(revealOverride); |
| }); |
| }); |
| }); |