blob: d66e72028ba1239d7f680f1d022aa8251ef374a5 [file] [log] [blame]
// 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 * as Host from '../core/host/host.js';
import type * as Platform from '../core/platform/platform.js';
import * as ProtocolClient from '../core/protocol_client/protocol_client.js';
import * as Root from '../core/root/root.js';
import * as SDK from '../core/sdk/sdk.js';
import type * as Protocol from '../generated/protocol.js';
import * as Bindings from '../models/bindings/bindings.js';
import * as Formatter from '../models/formatter/formatter.js';
import * as IssuesManager from '../models/issues_manager/issues_manager.js';
import * as Logs from '../models/logs/logs.js';
import * as Persistence from '../models/persistence/persistence.js';
import * as ProjectSettings from '../models/project_settings/project_settings.js';
import * as Workspace from '../models/workspace/workspace.js';
import type * as UIModule from '../ui/legacy/legacy.js';
import {deinitializeGlobalLocaleVars, initializeGlobalLocaleVars} from './LocaleHelpers.js';
import {MockCDPConnection} from './MockCDPConnection.js';
import {cleanupRuntime, setupRuntime} from './RuntimeHelpers.js';
import {cleanupSettings, setupSettings} from './SettingsHelpers.js';
// Don't import UI at this stage because it will fail without
// the environment. Instead we do the import at the end of the
// initialization phase.
// eslint-disable-next-line @typescript-eslint/naming-convention
let UI: typeof UIModule;
let uniqueTargetId = 0;
export function createTarget({
id,
name,
type = SDK.Target.Type.FRAME,
parentTarget,
subtype,
url = 'http://example.com',
connection,
targetManager = SDK.TargetManager.TargetManager.instance(),
}: {
id?: Protocol.Target.TargetID,
name?: string,
type?: SDK.Target.Type,
parentTarget?: SDK.Target.Target,
subtype?: string,
url?: string,
connection?: ProtocolClient.CDPConnection.CDPConnection,
targetManager?: SDK.TargetManager.TargetManager,
} = {}) {
if (!id) {
if (!uniqueTargetId++) {
id = 'test' as Protocol.Target.TargetID;
} else {
id = ('test' + uniqueTargetId) as Protocol.Target.TargetID;
}
}
if (!ProtocolClient.ConnectionTransport.ConnectionTransport.getFactory()) {
// We are not running with `describeWithMockConnection` so create a fresh mock connection.
// Because child targets inherit the SessionRouter/CDPConnection from the parent, we'll throw if a
// connection is passed together with a parent target as it would have no effect.
if (parentTarget && connection) {
throw new Error(
'Can\'t create child targets with it\'s own connection. Child targets share the connection with their parent.');
}
if (!connection && !parentTarget) {
connection = new MockCDPConnection([]);
}
}
return targetManager.createTarget(
id, name ?? id, type, parentTarget ? parentTarget : null, /* sessionId=*/ parentTarget ? id : undefined,
/* suspended=*/ false, connection, {targetId: id, url, subtype} as Protocol.Target.TargetInfo);
}
export {stubNoopSettings} from './SettingsHelpers.js';
export function registerActions(actions: UIModule.ActionRegistration.ActionRegistration[]): void {
for (const action of actions) {
UI.ActionRegistration.maybeRemoveActionExtension(action.actionId);
UI.ActionRegistration.registerActionExtension(action);
}
const actionRegistryInstance = UI.ActionRegistry.ActionRegistry.instance({forceNew: true});
UI.ShortcutRegistry.ShortcutRegistry.instance({forceNew: true, actionRegistry: actionRegistryInstance});
}
export function registerNoopActions(actionIds: string[]): void {
for (const actionId of actionIds) {
UI.ActionRegistration.maybeRemoveActionExtension(actionId);
UI.ActionRegistration.registerActionExtension({
actionId,
category: UI.ActionRegistration.ActionCategory.NONE,
title: () => 'mock' as Platform.UIString.LocalizedString,
});
}
const actionRegistryInstance = UI.ActionRegistry.ActionRegistry.instance({forceNew: true});
UI.ShortcutRegistry.ShortcutRegistry.instance({forceNew: true, actionRegistry: actionRegistryInstance});
}
export async function initializeGlobalVars({reset = true} = {}) {
await initializeGlobalLocaleVars();
setupSettings(reset);
setupRuntime();
// Dynamically import UI after the rest of the environment is set up, otherwise it will fail.
UI = await import('../ui/legacy/legacy.js');
UI.ZoomManager.ZoomManager.instance(
{forceNew: true, win: window, frontendHost: Host.InspectorFrontendHost.InspectorFrontendHostInstance});
// Initialize context menus.
UI.UIUtils.initializeUIUtils(document);
}
export async function deinitializeGlobalVars() {
// Remove the global SDK.
// eslint-disable-next-line @typescript-eslint/naming-convention
const globalObject = (globalThis as unknown as {SDK?: unknown, ls?: unknown});
delete globalObject.SDK;
delete globalObject.ls;
for (const target of SDK.TargetManager.TargetManager.instance().targets()) {
target.dispose('deinitializeGlobalVars');
}
// Remove instances.
deinitializeGlobalLocaleVars();
Logs.NetworkLog.NetworkLog.removeInstance();
SDK.TargetManager.TargetManager.removeInstance();
SDK.FrameManager.FrameManager.removeInstance();
Common.Settings.Settings.removeInstance();
Common.Revealer.RevealerRegistry.removeInstance();
Common.Console.Console.removeInstance();
Workspace.Workspace.WorkspaceImpl.removeInstance();
Workspace.IgnoreListManager.IgnoreListManager.removeInstance();
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.removeInstance();
Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.removeInstance();
IssuesManager.IssuesManager.IssuesManager.removeInstance();
Persistence.IsolatedFileSystemManager.IsolatedFileSystemManager.removeInstance();
ProjectSettings.ProjectSettingsModel.ProjectSettingsModel.removeInstance();
Formatter.FormatterWorkerPool.FormatterWorkerPool.removeInstance();
cleanupSettings();
// Protect against the dynamic import not having happened.
if (UI) {
UI.ZoomManager.ZoomManager.removeInstance();
UI.ViewManager.ViewManager.removeInstance();
UI.ViewManager.resetViewRegistration();
UI.Context.Context.removeInstance();
UI.InspectorView.InspectorView.removeInstance();
UI.ActionRegistry.ActionRegistry.reset();
}
cleanupRuntime();
}
export function describeWithEnvironment(title: string, fn: (this: Mocha.Suite) => void, opts: {reset: boolean} = {
reset: true,
}) {
return describe(title, function() {
before(async () => await initializeGlobalVars(opts));
fn.call(this);
after(async () => await deinitializeGlobalVars());
});
}
describeWithEnvironment.only = function(title: string, fn: (this: Mocha.Suite) => void, opts: {reset: boolean} = {
reset: true,
}) {
// eslint-disable-next-line mocha/no-exclusive-tests
return describe.only(title, function() {
before(async () => await initializeGlobalVars(opts));
fn.call(this);
after(async () => await deinitializeGlobalVars());
});
};
describeWithEnvironment.skip = function(title: string, fn: (this: Mocha.Suite) => void, _opts: {reset: boolean} = {
reset: true,
}) {
// eslint-disable-next-line @devtools/check-test-definitions
return describe.skip(title, function() {
fn.call(this);
});
};
export function createFakeSetting<T>(name: string, defaultValue: T): Common.Settings.Setting<T> {
const storage = new Common.Settings.SettingsStorage({}, undefined, 'test');
return new Common.Settings.Setting(name, defaultValue, new Common.ObjectWrapper.ObjectWrapper(), storage);
}
export function createFakeRegExpSetting(name: string, defaultValue: string): Common.Settings.RegExpSetting {
const storage = new Common.Settings.SettingsStorage({}, undefined, 'test');
return new Common.Settings.RegExpSetting(name, defaultValue, new Common.ObjectWrapper.ObjectWrapper(), storage);
}
export function setupActionRegistry() {
before(function() {
const actionRegistry = UI.ActionRegistry.ActionRegistry.instance();
UI.ShortcutRegistry.ShortcutRegistry.instance({
forceNew: true,
actionRegistry,
});
});
after(function() {
if (UI) {
UI.ShortcutRegistry.ShortcutRegistry.removeInstance();
UI.ActionRegistry.ActionRegistry.removeInstance();
}
});
}
/** This needs to be invoked within a describe block, rather than within an it() block. **/
export function expectConsoleLogs(expectedLogs: {warn?: string[], log?: string[], error?: string[]}) {
const {error, warn, log} = console;
before(() => {
if (expectedLogs.log) {
// eslint-disable-next-line no-console
console.log = (...data: unknown[]) => {
if (!expectedLogs.log?.includes(data.join(' '))) {
log(...data);
}
};
}
if (expectedLogs.warn) {
console.warn = (...data: unknown[]) => {
if (!expectedLogs.warn?.includes(data.join(' '))) {
warn(...data);
}
};
}
if (expectedLogs.error) {
console.error = (...data: unknown[]) => {
if (!expectedLogs.error?.includes(data.join(' '))) {
error(...data);
}
};
}
});
after(() => {
if (expectedLogs.log) {
// eslint-disable-next-line no-console
console.log = log;
}
if (expectedLogs.warn) {
console.warn = warn;
}
if (expectedLogs.error) {
console.error = error;
}
});
}
let originalUserAgent: string;
export function setUserAgentForTesting(): void {
originalUserAgent = window.navigator.userAgent;
Object.defineProperty(window.navigator, 'userAgent', {value: 'Chrome/unit_test', configurable: true});
}
export function restoreUserAgentForTesting(): void {
Object.defineProperty(window.navigator, 'userAgent', {value: originalUserAgent});
}
export function resetHostConfig() {
for (const key of Object.keys(Root.Runtime.hostConfig)) {
// @ts-expect-error TypeScript does not deduce the correct type
delete Root.Runtime.hostConfig[key];
}
}
/**
* Update `Root.Runtime.hostConfig` for testing.
* `Root.Runtime.hostConfig` is automatically cleaned-up between unit
* tests.
*/
export function updateHostConfig(config: Root.Runtime.HostConfig) {
Object.assign(Root.Runtime.hostConfig, config);
}