// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import { SourceMapUtil, IStrictUrl } from "../../src/debugger/sourceMap";

import assert = require("assert");
import * as path from "path";
import * as url from "url";

suite("sourceMap", function () {
    test("should convert host filesystem paths to URL-style-paths", function () {
        const sourceMap = new SourceMapUtil();
        const filePath = path.join("foo", "bar", "baz");
        const urlPath = "foo/bar/baz";
        const result = (<any>sourceMap).makeUnixStylePath(filePath);
        assert(result === urlPath, `Expected "${urlPath}", found "${result}"`);
    });

    test("should ignore sourcemap urls in JavaScript objects and strings", function () {
        const expectedUrlHref = "http://localhost:8081/index.map?platform=android&dev=true";
        const sourceMap = new SourceMapUtil();

        const testFunction = (scriptBody: string, expectedResult: string) => {
            const matchResult = (<any>sourceMap).searchSourceMapURL(scriptBody);
            assert.strictEqual(expectedResult, matchResult && matchResult[2]);
        };

        testFunction(
            'var myTest = "\\n//# sourceMappingURL=test.min.js.map\\n\\n//# sourceMappingURL=test.min.js.map\\n\\n";\n' +
                "//# sourceMappingURL=http://localhost:8081/index.map?platform=android&dev=true",
            expectedUrlHref,
        );

        testFunction(
            "  var test_js = function test_js(cdn) {\n" +
                `    return cdn === true ? '<script src="https://cdn.testjs.com/11.33.77/test.min.js"></script>' : "\\n <script>\\n` +
                '!function(t,e){\\"object\\"==typeof exports&&\\"object\\"==typeof module?module.exports=e():\\"function\\"==typeof define&&define;' +
                '\\n   //# sourceMappingURL=test.min.js.map\\n   </script>\\n";\n' +
                "};\n" +
                "//# sourceMappingURL=http://localhost:8081/index.map?platform=android&dev=true",
            expectedUrlHref,
        );
    });

    test("should resolve a valid sourcemap url", function () {
        const scriptUrl: url.Url = url.parse(
            "http://localhost:8081/index.ios.bundle?platform=ios&dev=true",
        );
        const scriptBody = "//# sourceMappingURL=/index.ios.map?platform=ios&dev=true";
        const expectedUrlHref = "http://localhost:8081/index.ios.map?platform=ios&dev=true";

        const sourceMap = new SourceMapUtil();
        const result = sourceMap.getSourceMapURL(scriptUrl, scriptBody);
        assert.strictEqual(expectedUrlHref, result && result.href);
    });

    test("should ignore inline sourcemap urls", function () {
        const scriptUrl: url.Url = url.parse(
            "http://localhost:8081/index.ios.bundle?platform=ios&dev=true",
        );
        const scriptBody =
            "//# sourceMappingURL=data:application/json;base64,eyJmb28iOiJiYXIifQ==\n" +
            "//# sourceMappingURL=/index.ios.map?platform=ios&dev=true";
        const expectedUrlHref = "http://localhost:8081/index.ios.map?platform=ios&dev=true";

        const sourceMap = new SourceMapUtil();
        const result = sourceMap.getSourceMapURL(scriptUrl, scriptBody);
        assert.strictEqual(expectedUrlHref, result && result.href);
    });

    test("should return correct sourcemap url for RN macOS", function () {
        const scriptUrl: url.Url = url.parse(
            "http://localhost:8081/index.bundle?platform=macos&dev=true",
        );
        const scriptBody =
            "//# sourceMappingURL=//localhost:8081/index.map?platform=macos&dev=true";
        const expectedUrlHref = "http://localhost:8081/index.map?platform=macos&dev=true";

        const sourceMap = new SourceMapUtil();
        const result = sourceMap.getSourceMapURL(scriptUrl, scriptBody);
        assert.strictEqual(expectedUrlHref, result && result.href);
    });

    test("should return default IStrictUrl for an invalid sourcemap url", function () {
        const scriptUrl: url.Url = url.parse(
            "http://localhost:8081/index.ios.bundle?platform=ios&dev=true",
        );
        const scriptBody = "";

        const sourceMap = new SourceMapUtil();
        const result = sourceMap.getSourceMapURL(scriptUrl, scriptBody);
        assert.deepStrictEqual(null, result);
    });

    test("should return default IStrictUrl if there are only inline sourcemap urls", function () {
        const scriptUrl: url.Url = url.parse(
            "http://localhost:8081/index.ios.bundle?platform=ios&dev=true",
        );
        const scriptBody =
            "//# sourceMappingURL=data:application/json;base64,eyJmb28iOiJiYXIifQ==\n" +
            "//# sourceMappingURL=data:application/json;base64,eyJiYXoiOiJxdXV4In0=";

        const sourceMap = new SourceMapUtil();
        const result = sourceMap.getSourceMapURL(scriptUrl, scriptBody);
        assert.deepStrictEqual(null, result);
    });

    test("should update the contents of a source map file", function () {
        const sourceMapBody: string = JSON.stringify({
            version: 3,
            sources: ["test/index.ts"],
            names: [],
            mappings: "",
            file: "test/index.js",
            sourceRoot: "../../src",
        });
        const scriptPath: string = "test/newIndex.ts";
        const sourcesRootPath: string = "new/src";
        const expectedSourceMapBody: string = JSON.stringify({
            version: 3,
            sources: ["../../test/index.ts"],
            names: [],
            mappings: "",
            file: scriptPath,
            sourceRoot: "",
        });
        const sourceMap = new SourceMapUtil();

        const result: string = sourceMap.updateSourceMapFile(
            sourceMapBody,
            scriptPath,
            sourcesRootPath,
        );
        assert.strictEqual(expectedSourceMapBody, result);
    });

    test("should update source map file path for remote packager", function () {
        const localRoot = "/home/local";
        const remoteRoot = "/home/remote";
        const sourceMapBody: string = JSON.stringify({
            version: 3,
            sources: [`${remoteRoot}/test/index.ts`],
            names: [],
            mappings: "",
            file: "test/index.js",
            sourceRoot: "../../src",
        });
        const scriptPath: string = "test/newIndex.ts";
        const sourcesRootPath: string = `${localRoot}/new/src`;
        const expectedSourceMapBody: string = JSON.stringify({
            version: 3,
            sources: [`../../test/index.ts`],
            names: [],
            mappings: "",
            file: scriptPath,
            sourceRoot: "",
        });
        const sourceMap = new SourceMapUtil();

        const result: string = sourceMap.updateSourceMapFile(
            sourceMapBody,
            scriptPath,
            sourcesRootPath,
            remoteRoot,
            localRoot,
        );
        assert.strictEqual(expectedSourceMapBody, result);
    });

    test("should normalize null section maps in indexed sourcemaps", function () {
        const sourceMapBody: string = JSON.stringify({
            version: 3,
            sections: [
                {
                    offset: { line: 0, column: 0 },
                    map: null,
                },
                {
                    offset: { line: 1, column: 0 },
                    map: {
                        version: 3,
                        sources: ["node_modules/react/index.js"],
                        names: [],
                        mappings: "",
                        file: "test/index.js",
                        sourceRoot: "",
                    },
                },
            ],
        });
        const scriptPath: string = "test/newIndex.ts";
        const sourcesRootPath: string = "new/src";
        const sourceMap = new SourceMapUtil();

        const result: string = sourceMap.updateSourceMapFile(
            sourceMapBody,
            scriptPath,
            sourcesRootPath,
        );
        const parsedResult = JSON.parse(result);

        assert.strictEqual(parsedResult.file, scriptPath);
        assert.strictEqual(parsedResult.sourceRoot, "");
        assert.strictEqual(parsedResult.sections, undefined);
        assert.deepStrictEqual(parsedResult.sources, ["../../node_modules/react/index.js"]);
    });

    test("should update scripts with source mapping urls", function () {
        const scriptBody: string = "//# sourceMappingURL=/index.ios.map?platform=ios&dev=true";
        const sourceMappingUrl: IStrictUrl = <IStrictUrl>url.parse("/index.android.map");
        const expectedScriptBody = "//# sourceMappingURL=index.android.map";
        const sourceMap = new SourceMapUtil();

        const result = sourceMap.updateScriptPaths(scriptBody, sourceMappingUrl);
        assert.strictEqual(expectedScriptBody, result);
    });

    test("should not update scripts without source mapping urls", function () {
        const scriptBody: string = "var path = require('path');";
        const sourceMappingUrl: IStrictUrl = <IStrictUrl>url.parse("/index.android.map");
        const sourceMap = new SourceMapUtil();

        const result = sourceMap.updateScriptPaths(scriptBody, sourceMappingUrl);
        assert.strictEqual(scriptBody, result);
    });

    test("should update absolute source path to relative unix style path", function () {
        const sourcePath: string = "foo/bar";
        const sourcesRootPath: string = "baz/fuzz";
        const expectedPath: string = "../../foo/bar";
        const sourceMap = new SourceMapUtil();

        const result = (<any>sourceMap).updateSourceMapPath(sourcePath, sourcesRootPath);
        assert.strictEqual(expectedPath, result);
    });

    test("should get only the latest sourceMappingURL", function () {
        const scriptBody: string = `//# sourceMappingURL=abort.controller.js.map
//# sourceMappingURL=index.map
//# sourceURL=http://localhost:8081/index.bundle?platform=android&dev=true&minify=false`;
        const expectedScriptBody = `index.map`;
        const sourceMap = new SourceMapUtil();

        const result = sourceMap.getSourceMapRelativeUrl(scriptBody);
        assert.strictEqual(expectedScriptBody, result);
    });

    test("should remove sourceURL from the bundle script body correctly", function () {
        const scriptBody: string = `var sourceURL = '//# sourceURL=' + (hasOwnProperty.call(options, 'sourceURL') ? (options.sourceURL + '').replace(/[\\r\\n]/g, ' ') : 'lodash.templateSources[' + ++templateCounter + ']') + '\\n';
//# sourceMappingURL=index.map
//# sourceURL=http://localhost:8081/index.bundle?platform=android&dev=true&minify=false`;
        const expectedScriptBody = `var sourceURL = '//# sourceURL=' + (hasOwnProperty.call(options, 'sourceURL') ? (options.sourceURL + '').replace(/[\\r\\n]/g, ' ') : 'lodash.templateSources[' + ++templateCounter + ']') + '\\n';
//# sourceMappingURL=index.map\n`;
        const sourceMap = new SourceMapUtil();

        const result = sourceMap.removeSourceURL(scriptBody);
        assert.strictEqual(expectedScriptBody, result);
    });

    test("should remove sourceURL if it is before sourceMappingURL", function () {
        const scriptBody: string = `var sourceURL = '//# sourceURL=' + (hasOwnProperty.call(options, 'sourceURL') ? (options.sourceURL + '').replace(/[\\r\\n]/g, ' ') : 'lodash.templateSources[' + ++templateCounter + ']') + '\\n';
//# sourceURL=http://localhost:8081/index.bundle?platform=android&dev=true&minify=false
//# sourceMappingURL=index.map`;
        const expectedScriptBody = `var sourceURL = '//# sourceURL=' + (hasOwnProperty.call(options, 'sourceURL') ? (options.sourceURL + '').replace(/[\\r\\n]/g, ' ') : 'lodash.templateSources[' + ++templateCounter + ']') + '\\n';

//# sourceMappingURL=index.map`;
        const sourceMap = new SourceMapUtil();

        const result = sourceMap.removeSourceURL(scriptBody);
        assert.strictEqual(expectedScriptBody, result);
    });
});
