/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { BrowserLoader } = ChromeUtils.importESModule(
  "resource://devtools/shared/loader/browser-loader.sys.mjs"
);

const {
  getMockedModule,
  setMockedModule,
  removeMockedModule,
} = require("resource://devtools/shared/loader/browser-loader-mocks.js");
const { require: browserRequire } = BrowserLoader({
  baseURI: "resource://devtools/client/shared/",
  window,
});

// Check that modules can be mocked in the browser loader.
// Test with a custom test module under the chrome:// scheme.
function testWithChromeScheme() {
  // Full chrome URI for the test module.
  const CHROME_URI = CHROME_URL_ROOT + "test-mocked-module.js";
  const ORIGINAL_VALUE = "Original value";
  const MOCKED_VALUE_1 = "Mocked value 1";
  const MOCKED_VALUE_2 = "Mocked value 2";

  const m1 = browserRequire(CHROME_URI);
  ok(m1, "Regular module can be required");
  is(m1.methodToMock(), ORIGINAL_VALUE, "Method returns the expected value");
  is(m1.someProperty, "someProperty", "Property has the expected value");

  info("Create a simple mocked version of the test module");
  const mockedModule = {
    methodToMock: () => MOCKED_VALUE_1,
    someProperty: "somePropertyMocked",
  };
  setMockedModule(mockedModule, CHROME_URI);
  ok(!!getMockedModule(CHROME_URI), "Has an entry for the chrome URI.");

  const m2 = browserRequire(CHROME_URI);
  ok(m2, "Mocked module can be required via chrome URI");
  is(
    m2.methodToMock(),
    MOCKED_VALUE_1,
    "Mocked method returns the expected value"
  );
  is(
    m2.someProperty,
    "somePropertyMocked",
    "Mocked property has the expected value"
  );

  const { methodToMock: requiredMethod } = browserRequire(CHROME_URI);
  Assert.strictEqual(
    requiredMethod(),
    MOCKED_VALUE_1,
    "Mocked method returns the expected value when imported with destructuring"
  );

  info("Update the mocked method to return a different value");
  mockedModule.methodToMock = () => MOCKED_VALUE_2;
  is(
    requiredMethod(),
    MOCKED_VALUE_2,
    "Mocked method returns the updated value  when imported with destructuring"
  );

  info("Remove the mock for the test module");
  removeMockedModule(CHROME_URI);
  ok(!getMockedModule(CHROME_URI), "Has no entry for the chrome URI.");

  const m3 = browserRequire(CHROME_URI);
  ok(m3, "Regular module can be required after removing the mock");
  is(
    m3.methodToMock(),
    ORIGINAL_VALUE,
    "Method on module returns the expected value"
  );
}

// Similar tests as in testWithChromeScheme, but this time with a devtools module
// available under the resource:// scheme.
function testWithRegularDevtoolsModule() {
  // Testing with devtools/shared/path because it is a simple module, that can be imported
  // with destructuring. Any other module would do.
  const DEVTOOLS_MODULE_PATH = "devtools/shared/path";
  const DEVTOOLS_MODULE_URI = "resource://devtools/shared/path.js";

  const m1 = browserRequire(DEVTOOLS_MODULE_PATH);
  is(
    m1.joinURI("https://a", "b"),
    "https://a/b",
    "Original module was required"
  );

  info(
    "Set a mock for a sub-part of the path, which should not match require calls"
  );
  setMockedModule({ joinURI: () => "WRONG_PATH" }, "shared/path");

  ok(
    !getMockedModule(DEVTOOLS_MODULE_URI),
    "Has no mock entry for the full URI"
  );
  const m2 = browserRequire(DEVTOOLS_MODULE_PATH);
  is(
    m2.joinURI("https://a", "b"),
    "https://a/b",
    "Original module is still required"
  );

  info(
    "Set a mock for the complete path, which should now match require calls"
  );
  const mockedModule = {
    joinURI: () => "MOCKED VALUE",
  };
  setMockedModule(mockedModule, DEVTOOLS_MODULE_PATH);
  ok(
    !!getMockedModule(DEVTOOLS_MODULE_URI),
    "Has a mock entry for the full URI."
  );

  const m3 = browserRequire(DEVTOOLS_MODULE_PATH);
  is(
    m3.joinURI("https://a", "b"),
    "MOCKED VALUE",
    "The mocked module has been returned"
  );

  info(
    "Check that the mocked methods can be updated after a destructuring import"
  );
  const { joinURI } = browserRequire(DEVTOOLS_MODULE_PATH);
  mockedModule.joinURI = () => "UPDATED VALUE";
  is(
    joinURI("https://a", "b"),
    "UPDATED VALUE",
    "Mocked method was correctly updated"
  );

  removeMockedModule(DEVTOOLS_MODULE_PATH);
  ok(
    !getMockedModule(DEVTOOLS_MODULE_URI),
    "Has no mock entry for the full URI"
  );
  const m4 = browserRequire(DEVTOOLS_MODULE_PATH);
  is(
    m4.joinURI("https://a", "b"),
    "https://a/b",
    "Original module can be required again"
  );
}

function test() {
  testWithChromeScheme();
  testWithRegularDevtoolsModule();
  delete window.getBrowserLoaderForWindow;
  finish();
}
