UNPKG

chrome-devtools-frontend

Version:
211 lines (178 loc) • 10.3 kB
// Copyright 2023 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 Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Bindings from '../../models/bindings/bindings.js'; import * as TextUtils from '../../models/text_utils/text_utils.js'; import * as Workspace from '../../models/workspace/workspace.js'; import {createTarget} from '../../testing/EnvironmentHelpers.js'; import {describeWithMockConnection} from '../../testing/MockConnection.js'; import {MockProtocolBackend} from '../../testing/MockScopeChain.js'; import {getInitializedResourceTreeModel} from '../../testing/ResourceTreeHelpers.js'; import {createContentProviderUISourceCode} from '../../testing/UISourceCodeHelpers.js'; import * as Coverage from './coverage.js'; const {urlString} = Platform.DevToolsPath; const {CoverageDecorationManager} = Coverage.CoverageDecorationManager; /** Test helper that returns the "identity" line ranges for any given string */ function lineRangesForContent(content: string): TextUtils.TextRange.TextRange[] { const ranges: TextUtils.TextRange.TextRange[] = []; const text = new TextUtils.Text.Text(content); for (let i = 0; i < text.lineCount(); ++i) { const line = text.lineAt(i); ranges.push(new TextUtils.TextRange.TextRange(i, 0, i, line.length)); } return ranges; } describeWithMockConnection('CoverageDeocrationManager', () => { let target: SDK.Target.Target; let backend: MockProtocolBackend; let debuggerBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding; let workspace: Workspace.Workspace.WorkspaceImpl; let cssBinding: Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding; let coverageModel: sinon.SinonStubbedInstance<Coverage.CoverageModel.CoverageModel>; beforeEach(async () => { backend = new MockProtocolBackend(); target = createTarget(); workspace = Workspace.Workspace.WorkspaceImpl.instance({forceNew: true}); const targetManager = SDK.TargetManager.TargetManager.instance(); const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace); debuggerBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({ forceNew: true, resourceMapping, targetManager, }); Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: true, debuggerWorkspaceBinding: debuggerBinding}); cssBinding = Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance({forceNew: true, resourceMapping, targetManager}); SDK.TargetManager.TargetManager.instance().setScopeTarget(target); // Since we wanna mock 'usageForRange' we stub the whole instance. Otherwise we'd use half // a stub and half the real thing. coverageModel = sinon.createStubInstance(Coverage.CoverageModel.CoverageModel); // Wait for the resource tree model to load; otherwise, our uiSourceCodes could be asynchronously // invalidated during the test. await getInitializedResourceTreeModel(target); }); const URL = urlString`http://example.com/index.js`; describe('usageByLine (raw)', () => { it('marks lines as "unknown" coverge status if no coverage info is available', async () => { await backend.addScript(target, {url: URL, content: 'function foo(a,b){return a+b;}'}, null); const uiSourceCode = workspace.uiSourceCodeForURL(URL); assert.exists(uiSourceCode); await uiSourceCode.requestContentData(); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); const usage = await manager.usageByLine(uiSourceCode, lineRangesForContent(uiSourceCode.content())); assert.deepEqual(usage, [undefined]); }); it('marks lines as covered if coverage info says so', async () => { await backend.addScript(target, {url: URL, content: 'function foo(a,b){return a+b;}'}, null); const uiSourceCode = workspace.uiSourceCodeForURL(URL); assert.exists(uiSourceCode); await uiSourceCode.requestContentData(); coverageModel.usageForRange.returns(true); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); const usage = await manager.usageByLine(uiSourceCode, lineRangesForContent(uiSourceCode.content())); assert.deepEqual(usage, [true]); }); }); describe('usageByLine (formatted)', () => { it('marks lines as covered if coverage info says so', async () => { const scriptContent = 'function mulWithOffset(n,t,e){const f=n*t;const u=f;if(e!==undefined){const n=u+e;return n}return u}'; const script = await backend.addScript(target, {url: URL, content: scriptContent}, null); const uiSourceCode = workspace.uiSourceCodeForURL(URL); assert.exists(uiSourceCode); await uiSourceCode.requestContentData(); coverageModel.usageForRange.callsFake((contentProvider, startOffset, endOffset) => { assert.strictEqual(contentProvider, script); // Everything is covered except the body of the `if`. return endOffset <= 70 || startOffset > 90; }); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); // clang-format off // Simulate editor pretty-printing `script`. const lineRanges = [ new TextUtils.TextRange.TextRange(0, 0, 0, 30), // function mulWithOffset(n,t,e){ new TextUtils.TextRange.TextRange(0, 30, 0, 42), // const f=n*t; new TextUtils.TextRange.TextRange(0, 42, 0, 52), // const u=f; new TextUtils.TextRange.TextRange(0, 52, 0, 70), // if(e!==undefined){ new TextUtils.TextRange.TextRange(0, 70, 0, 82), // const n=u+e; new TextUtils.TextRange.TextRange(0, 82, 0, 90), // return n new TextUtils.TextRange.TextRange(0, 90, 0, 91), // } new TextUtils.TextRange.TextRange(0, 91, 0, 99), // return u new TextUtils.TextRange.TextRange(0, 99, 0, 100), // } ]; // clang-format on const usage = await manager.usageByLine(uiSourceCode, lineRanges); assert.deepEqual(usage, [true, true, true, true, false, false, false, true, true]); }); }); describe('usageByLine (sourcemap)', () => { let script: SDK.Script.Script; beforeEach(async () => { const originalContent = ` function mulWithOffset(param1, param2, offset) { const intermediate = param1 * param2; const result = intermediate; if (offset !== undefined) { const intermediate = result + offset; return intermediate; } return result; } `; const sourceMapUrl = 'file:///tmp/example.js.min.map'; // This was minified with 'terser -m -o example.min.js --source-map "includeSources;url=example.min.js.map"' v5.7.0. const sourceMapContent = JSON.stringify({ version: 3, names: ['mulWithOffset', 'param1', 'param2', 'offset', 'intermediate', 'result', 'undefined'], sources: ['example.js'], sourcesContent: [originalContent], mappings: 'AACA,SAASA,cAAcC,EAAQC,EAAQC,GACrC,MAAMC,EAAeH,EAASC,EAC9B,MAAMG,EAASD,EACf,GAAID,IAAWG,UAAW,CACxB,MAAMF,EAAeC,EAASF,EAC9B,OAAOC,CACT,CACA,OAAOC,CACT', }); const scriptContent = 'function mulWithOffset(n,t,e){const f=n*t;const u=f;if(e!==undefined){const n=u+e;return n}return u}'; script = await backend.addScript( target, {url: 'file:///tmp/bundle.js', content: scriptContent}, {url: sourceMapUrl, content: sourceMapContent}); }); it('marks lines as covered if coverage info says so', async () => { const uiSourceCode = workspace.uiSourceCodeForURL(urlString`file:///tmp/example.js`); assert.exists(uiSourceCode); await uiSourceCode.requestContentData(); coverageModel.usageForRange.callsFake((contentProvider, startOffset, endOffset) => { assert.strictEqual(contentProvider, script); // Everything is covered except the body of the `if`. return endOffset < 70 || startOffset > 90; }); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); const usage = await manager.usageByLine(uiSourceCode, lineRangesForContent(uiSourceCode.content())); assert.deepEqual(usage, [undefined, true, true, true, true, false, false, undefined, true, undefined, undefined]); }); }); it('sets the "decorationData" on all existing UISourceCodes', () => { const {uiSourceCode} = createContentProviderUISourceCode({url: URL, mimeType: 'text/javascript'}); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); assert.strictEqual(uiSourceCode.getDecorationData(Coverage.CoverageDecorationManager.decoratorType), manager); }); it('sets the "decorationData" on newly added UISourceCodes (after the manager already exists)', () => { const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); const {uiSourceCode} = createContentProviderUISourceCode({url: URL, mimeType: 'text/javascript'}); assert.strictEqual(uiSourceCode.getDecorationData(Coverage.CoverageDecorationManager.decoratorType), manager); }); it('does not update the "decorationData" on newly added UISourceCodes after being disposed', () => { const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); manager.dispose(); const {uiSourceCode} = createContentProviderUISourceCode({url: URL, mimeType: 'text/javascript'}); assert.isUndefined(uiSourceCode.getDecorationData(Coverage.CoverageDecorationManager.decoratorType)); }); describe('reset', () => { it('resets the "decorationData" on all existing UISourceCodes to "undefined"', () => { const {uiSourceCode} = createContentProviderUISourceCode({url: URL, mimeType: 'text/javascript'}); const manager = new CoverageDecorationManager(coverageModel, workspace, debuggerBinding, cssBinding); manager.reset(); assert.isUndefined(uiSourceCode.getDecorationData(Coverage.CoverageDecorationManager.decoratorType)); }); }); });