// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from 'chai';
import sinon from 'sinon';
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) {
    const ranges = [];
    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;
    let backend;
    let debuggerBinding;
    let workspace;
    let cssBinding;
    let 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);
        const ignoreListManager = Workspace.IgnoreListManager.IgnoreListManager.instance({ forceNew: true });
        debuggerBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance({
            forceNew: true,
            resourceMapping,
            targetManager,
            ignoreListManager,
            workspace,
        });
        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;
        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));
        });
    });
});
//# sourceMappingURL=CoverageDecorationManager.test.js.map