add_task(function test_Cu_import_shim_first() {
  // Load and cache with shim.

  const exports = {};
  const global = Components.utils.import(
    "resource://test/esmified-1.jsm",
    exports
  );
  Assert.equal(global.loadCount, 1);
  Assert.equal(global.obj.value, 10);
  Assert.equal(exports.loadCount, 1);
  Assert.equal(exports.obj.value, 10);
  Assert.ok(exports.obj === global.obj);

  const ns = ChromeUtils.importESModule("resource://test/esmified-1.sys.mjs");
  Assert.equal(ns.loadCount, 1);
  Assert.equal(ns.obj.value, 10);
  Assert.ok(ns.obj === global.obj);

  const exports2 = {};
  const global2 = Components.utils.import(
    "resource://test/esmified-1.jsm", exports2
  );
  Assert.equal(global2.loadCount, 1);
  Assert.equal(global2.obj.value, 10);
  Assert.equal(exports2.loadCount, 1);
  Assert.equal(exports2.obj.value, 10);
  Assert.ok(exports2.obj === global2.obj);
  Assert.ok(exports2.obj === global.obj);

  // Also test with *.js extension.
  const exports3 = {};
  const global3 = Components.utils.import(
    "resource://test/esmified-1.js", exports3
  );
  Assert.equal(global3.loadCount, 1);
  Assert.equal(global3.obj.value, 10);
  Assert.equal(exports3.loadCount, 1);
  Assert.equal(exports3.obj.value, 10);
  Assert.ok(exports3.obj === global3.obj);
  Assert.ok(exports3.obj === global.obj);

  // Also test with *.jsm.js extension.
  const exports4 = {};
  const global4 = Components.utils.import(
    "resource://test/esmified-1.js", exports4
  );
  Assert.equal(global4.loadCount, 1);
  Assert.equal(global4.obj.value, 10);
  Assert.equal(exports4.loadCount, 1);
  Assert.equal(exports4.obj.value, 10);
  Assert.ok(exports4.obj === global4.obj);
  Assert.ok(exports4.obj === global.obj);
});

add_task(function test_Cu_import_no_shim_first() {
  // Load and cache with importESModule.

  const ns = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs");
  Assert.equal(ns.loadCount, 1);
  Assert.equal(ns.obj.value, 10);

  const exports = {};
  const global = Components.utils.import(
    "resource://test/esmified-2.jsm", exports
  );
  Assert.equal(global.loadCount, 1);
  Assert.equal(global.obj.value, 10);
  Assert.equal(exports.loadCount, 1);
  Assert.equal(exports.obj.value, 10);
  Assert.ok(exports.obj === global.obj);
  Assert.ok(ns.obj === global.obj);

  const ns2 = ChromeUtils.importESModule("resource://test/esmified-2.sys.mjs");
  Assert.equal(ns2.loadCount, 1);
  Assert.equal(ns2.obj.value, 10);
});

add_task(function test_ChromeUtils_import_shim_first() {
  // Load and cache with shim.

  const exports = {};
  const global = ChromeUtils.import(
    "resource://test/esmified-3.jsm", exports
  );
  Assert.equal(global.loadCount, 1);
  Assert.equal(global.obj.value, 10);
  Assert.equal(exports.loadCount, 1);
  Assert.equal(exports.obj.value, 10);
  Assert.ok(exports.obj === global.obj);

  const ns = ChromeUtils.importESModule("resource://test/esmified-3.sys.mjs");
  Assert.equal(ns.loadCount, 1);
  Assert.equal(ns.obj.value, 10);
  Assert.ok(ns.obj === global.obj);

  const exports2 = {};
  const global2 = ChromeUtils.import(
    "resource://test/esmified-3.jsm", exports2
  );
  Assert.equal(global2.loadCount, 1);
  Assert.equal(global2.obj.value, 10);
  Assert.equal(exports2.loadCount, 1);
  Assert.equal(exports2.obj.value, 10);
  Assert.ok(exports2.obj === global2.obj);
  Assert.ok(exports2.obj === global.obj);
});

add_task(function test_ChromeUtils_import_no_shim_first() {
  // Load and cache with importESModule.

  const ns = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs");
  Assert.equal(ns.loadCount, 1);
  Assert.equal(ns.obj.value, 10);

  const exports = {};
  const global = ChromeUtils.import(
    "resource://test/esmified-4.jsm", exports
  );
  Assert.equal(global.loadCount, 1);
  Assert.equal(global.obj.value, 10);
  Assert.equal(exports.loadCount, 1);
  Assert.equal(exports.obj.value, 10);
  Assert.ok(exports.obj === global.obj);
  Assert.ok(ns.obj === global.obj);

  const ns2 = ChromeUtils.importESModule("resource://test/esmified-4.sys.mjs");
  Assert.equal(ns2.loadCount, 1);
  Assert.equal(ns2.obj.value, 10);
});

add_task(function test_ChromeUtils_import_not_exported_no_shim_JSM() {
  // `exports` properties for not-ESM-ified case.

  const exports = ChromeUtils.import(
    "resource://test/not-esmified-not-exported.jsm"
  );

  Assert.equal(exports.exportedVar, "exported var");
  Assert.equal(exports.exportedFunction(), "exported function");
  Assert.equal(exports.exportedLet, "exported let");
  Assert.equal(exports.exportedConst, "exported const");
  Assert.equal(exports.notExportedVar, undefined);
  Assert.equal(exports.notExportedFunction, undefined);
  Assert.equal(exports.notExportedLet, undefined);
  Assert.equal(exports.notExportedConst, undefined);
});

add_task(function test_ChromeUtils_import_not_exported_shim() {
  // `exports` properties for shim case.

  const exports = ChromeUtils.import(
    "resource://test/esmified-not-exported.jsm"
  );

  Assert.equal(exports.exportedVar, "exported var");
  Assert.equal(exports.exportedFunction(), "exported function");
  Assert.equal(exports.exportedLet, "exported let");
  Assert.equal(exports.exportedConst, "exported const");
  Assert.equal(exports.notExportedVar, undefined);
  Assert.equal(exports.notExportedFunction, undefined);
  Assert.equal(exports.notExportedLet, undefined);
  Assert.equal(exports.notExportedConst, undefined);
});

add_task(function test_ChromeUtils_import_not_exported_no_shim_ESM() {
  // `exports` properties for ESM-ified case.

  const exports = ChromeUtils.importESModule(
    "resource://test/esmified-not-exported.sys.mjs"
  );

  Assert.equal(exports.exportedVar, "exported var");
  Assert.equal(exports.exportedFunction(), "exported function");
  Assert.equal(exports.exportedLet, "exported let");
  Assert.equal(exports.exportedConst, "exported const");
  Assert.equal(exports.notExportedVar, undefined);
  Assert.equal(exports.notExportedFunction, undefined);
  Assert.equal(exports.notExportedLet, undefined);
  Assert.equal(exports.notExportedConst, undefined);
});

function testReadProxyOps(global, expectedNames, expectedDesc) {
  expectedNames.sort();

  // enumerate
  const names = Object.keys(global).sort();
  Assert.equal(JSON.stringify(names), JSON.stringify(expectedNames),
              `enumerate`);

  // has
  for (const name of expectedNames) {
    Assert.ok(name in global, `has for ${name}`);
  }

  // getOwnPropertyDescriptor
  for (const name of expectedNames) {
    const desc = Object.getOwnPropertyDescriptor(global, name);
    Assert.equal(desc.value, global[name]);
    Assert.equal(desc.writable, expectedDesc.writable,
                 `writable for ${name}`);
    Assert.equal(desc.enumerable, expectedDesc.enumerable,
                 `enumerable for ${name}`);
    Assert.equal(desc.configurable, expectedDesc.configurable,
                 `configurable for ${name}`);
  }
}

function testWriteProxyOps(global, expectedNames) {
  // set: no-op
  for (const name of expectedNames) {
    const before = global[name];
    global[name] = -1;
    Assert.equal(global[name], before, `value after set for ${name}`);
  }

  // delete: no-op
  for (const name of expectedNames) {
    const before = global[name];
    Assert.ok(!(delete global[name]), `delete result for ${name}`);
    Assert.equal(global[name], before, `value after delete for ${name}`);
  }
}

add_task(function test_Cu_import_not_exported_no_shim_JSM() {
  // `exports` and `global` properties for not-ESM-ified case.
  // Not-exported variables should be visible in `global`.

  const exports = {};
  const global = Components.utils.import(
    "resource://test/not-esmified-not-exported.jsm",
    exports
  );

  Assert.equal(global.exportedVar, "exported var");
  Assert.equal(global.exportedFunction(), "exported function");
  Assert.equal(global.exportedLet, "exported let");
  Assert.equal(global.exportedConst, "exported const");
  Assert.equal(global.notExportedVar, "not exported var");
  Assert.equal(global.notExportedFunction(), "not exported function");
  Assert.equal(global.notExportedLet, "not exported let");
  Assert.equal(global.notExportedConst, "not exported const");

  const expectedNames = [
    "EXPORTED_SYMBOLS",
    "exportedVar",
    "exportedFunction",
    "exportedLet",
    "exportedConst",
    "notExportedVar",
    "notExportedFunction",
    "notExportedLet",
    "notExportedConst",
  ];

  testReadProxyOps(global, expectedNames, {
    writable: false,
    enumerable: true,
    configurable: false,
  });
  testWriteProxyOps(global, expectedNames);

  Assert.equal(exports.exportedVar, "exported var");
  Assert.equal(exports.exportedFunction(), "exported function");
  Assert.equal(exports.exportedLet, "exported let");
  Assert.equal(exports.exportedConst, "exported const");
  Assert.equal(exports.notExportedVar, undefined);
  Assert.equal(exports.notExportedFunction, undefined);
  Assert.equal(exports.notExportedLet, undefined);
  Assert.equal(exports.notExportedConst, undefined);
});

add_task(function test_Cu_import_not_exported_shim() {
  // `exports` and `global` properties for shim case.
  // Not-exported variables should be visible in global.

  const exports = {};
  const global = Components.utils.import(
    "resource://test/esmified-not-exported.jsm",
    exports
  );

  Assert.equal(global.exportedVar, "exported var");
  Assert.equal(global.exportedFunction(), "exported function");
  Assert.equal(global.exportedLet, "exported let");
  Assert.equal(global.exportedConst, "exported const");

  Assert.equal(global.notExportedVar, "not exported var");
  Assert.equal(global.notExportedFunction(), "not exported function");
  Assert.equal(global.notExportedLet, "not exported let");
  Assert.equal(global.notExportedConst, "not exported const");

  const expectedNames = [
    "exportedVar",
    "exportedFunction",
    "exportedLet",
    "exportedConst",
    "notExportedVar",
    "notExportedFunction",
    "notExportedLet",
    "notExportedConst",
  ];

  testReadProxyOps(global, expectedNames, {
    writable: false,
    enumerable: true,
    configurable: false,
  });
  testWriteProxyOps(global, expectedNames);

  Assert.equal(exports.exportedVar, "exported var");
  Assert.equal(exports.exportedFunction(), "exported function");
  Assert.equal(exports.exportedLet, "exported let");
  Assert.equal(exports.exportedConst, "exported const");
  Assert.equal(exports.notExportedVar, undefined);
  Assert.equal(exports.notExportedFunction, undefined);
  Assert.equal(exports.notExportedLet, undefined);
  Assert.equal(exports.notExportedConst, undefined);

  const desc = Object.getOwnPropertyDescriptor(global, "*namespace*");
  Assert.ok(!desc, `*namespace* special binding should not be exposed`);
  Assert.equal("*namespace*" in global, false,
               `*namespace* special binding should not be exposed`);
  Assert.equal(global["*namespace*"], undefined,
               `*namespace* special binding should not be exposed`);
});

add_task(function test_Cu_isModuleLoaded_shim() {
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false);

  Cu.import("resource://test/esmified-5.jsm", {});

  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm"), true);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.js"), true);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.jsm.js"), true);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.jsm"), true);

  // This is false because Cu.isModuleLoaded does not support ESM directly
  // (bug 1768819)
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-5.sys.mjs"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-5.sys.mjs"), false);
});

add_task(function test_Cu_isModuleLoaded_no_shim() {
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false);

  ChromeUtils.importESModule("resource://test/esmified-6.sys.mjs");

  // Regardless of whether the ESM is loaded by shim or not,
  // query that accesses the ESM-ified module returns the existence of
  // ESM.
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm"), true);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.js"), true);
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.jsm.js"), true);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm"), true);

  // This is false because shim always use *.jsm.
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.js"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.jsm.js"), false);

  // This is false because Cu.isModuleLoaded does not support ESM directly
  // (bug 1768819)
  Assert.equal(Cu.isModuleLoaded("resource://test/esmified-6.sys.mjs"), false);
  Assert.equal(Cu.loadedModules.includes("resource://test/esmified-6.sys.mjs"), false);
});
