/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionSettingsStore:
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
});

const { createAppInfo, promiseShutdownManager, promiseStartupManager } =
  AddonTestUtils;

AddonTestUtils.init(this);

// Allow for unsigned addons.
AddonTestUtils.overrideCertDB();

createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");

const ITEMS = {
  key1: [
    { key: "key1", value: "val1", id: "@first" },
    { key: "key1", value: "val2", id: "@second" },
    { key: "key1", value: "val3", id: "@third" },
  ],
  key2: [
    { key: "key2", value: "val1-2", id: "@first" },
    { key: "key2", value: "val2-2", id: "@second" },
    { key: "key2", value: "val3-2", id: "@third" },
  ],
};
const KEY_LIST = Object.keys(ITEMS);
const TEST_TYPE = "myType";

let callbackCount = 0;

function initialValue(key) {
  callbackCount++;
  return `key:${key}`;
}

add_task(async function test_settings_store() {
  await promiseStartupManager();

  // Create an array of test framework extension wrappers to install.
  let testExtensions = [
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@first" } },
      },
    }),
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@second" } },
      },
    }),
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@third" } },
      },
    }),
  ];

  for (let extension of testExtensions) {
    await extension.startup();
  }

  // Create an array actual Extension objects which correspond to the
  // test framework extension wrappers.
  let extensions = testExtensions.map(extension => extension.extension);

  let expectedCallbackCount = 0;

  await Assert.rejects(
    ExtensionSettingsStore.getLevelOfControl(1, TEST_TYPE, "key"),
    /The ExtensionSettingsStore was accessed before the initialize promise resolved/,
    "Accessing the SettingsStore before it is initialized throws an error."
  );

  // Initialize the SettingsStore.
  await ExtensionSettingsStore.initialize();

  // Add a setting for the second oldest extension, where it is the only setting for a key.
  for (let key of KEY_LIST) {
    let extensionIndex = 1;
    let itemToAdd = ITEMS[key][extensionIndex];
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl with no settings set for a key."
    );
    let item = await ExtensionSettingsStore.addSetting(
      extensions[extensionIndex].id,
      TEST_TYPE,
      itemToAdd.key,
      itemToAdd.value,
      initialValue
    );
    expectedCallbackCount++;
    equal(
      callbackCount,
      expectedCallbackCount,
      "initialValueCallback called the expected number of times."
    );
    deepEqual(
      item,
      itemToAdd,
      "Adding initial item for a key returns that item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      itemToAdd,
      "getSetting returns correct item with only one item in the list."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_this_extension",
      "getLevelOfControl returns correct levelOfControl with only one item in the list."
    );
    ok(
      ExtensionSettingsStore.hasSetting(
        extensions[extensionIndex].id,
        TEST_TYPE,
        key
      ),
      "hasSetting returns the correct value when an extension has a setting set."
    );
    item = await ExtensionSettingsStore.getSetting(
      TEST_TYPE,
      key,
      extensions[extensionIndex].id
    );
    deepEqual(
      item,
      itemToAdd,
      "getSetting with id returns correct item with only one item in the list."
    );
  }

  // Add a setting for the oldest extension.
  for (let key of KEY_LIST) {
    let extensionIndex = 0;
    let itemToAdd = ITEMS[key][extensionIndex];
    let item = await ExtensionSettingsStore.addSetting(
      extensions[extensionIndex].id,
      TEST_TYPE,
      itemToAdd.key,
      itemToAdd.value,
      initialValue
    );
    equal(
      callbackCount,
      expectedCallbackCount,
      "initialValueCallback called the expected number of times."
    );
    equal(
      item,
      null,
      "An older extension adding a setting for a key returns null"
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][1],
      "getSetting returns correct item with more than one item in the list."
    );
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_other_extensions",
      "getLevelOfControl returns correct levelOfControl when another extension is in control."
    );
    item = await ExtensionSettingsStore.getSetting(
      TEST_TYPE,
      key,
      extensions[extensionIndex].id
    );
    deepEqual(
      item,
      itemToAdd,
      "getSetting with id returns correct item with more than one item in the list."
    );
  }

  // Reload the settings store to emulate a browser restart.
  await ExtensionSettingsStore._reloadFile();

  // Add a setting for the newest extension.
  for (let key of KEY_LIST) {
    let extensionIndex = 2;
    let itemToAdd = ITEMS[key][extensionIndex];
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl for a more recent extension."
    );
    let item = await ExtensionSettingsStore.addSetting(
      extensions[extensionIndex].id,
      TEST_TYPE,
      itemToAdd.key,
      itemToAdd.value,
      initialValue
    );
    equal(
      callbackCount,
      expectedCallbackCount,
      "initialValueCallback called the expected number of times."
    );
    deepEqual(
      item,
      itemToAdd,
      "Adding item for most recent extension returns that item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      itemToAdd,
      "getSetting returns correct item with more than one item in the list."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_this_extension",
      "getLevelOfControl returns correct levelOfControl when this extension is in control."
    );
    item = await ExtensionSettingsStore.getSetting(
      TEST_TYPE,
      key,
      extensions[extensionIndex].id
    );
    deepEqual(
      item,
      itemToAdd,
      "getSetting with id returns correct item with more than one item in the list."
    );
  }

  for (let extension of extensions) {
    let items = await ExtensionSettingsStore.getAllForExtension(
      extension.id,
      TEST_TYPE
    );
    deepEqual(items, KEY_LIST, "getAllForExtension returns expected keys.");
  }

  // Attempting to remove a setting that has not been set should *not* throw an exception.
  let removeResult = await ExtensionSettingsStore.removeSetting(
    extensions[0].id,
    "myType",
    "unset_key"
  );
  equal(
    removeResult,
    null,
    "Removing a setting that was not previously set returns null."
  );

  // Attempting to disable a setting that has not been set should throw an exception.
  Assert.throws(
    () =>
      ExtensionSettingsStore.disable(extensions[0].id, "myType", "unset_key"),
    /Cannot alter the setting for myType:unset_key as it does not exist/,
    "disable rejects with an unset key."
  );

  // Attempting to enable a setting that has not been set should throw an exception.
  Assert.throws(
    () =>
      ExtensionSettingsStore.enable(extensions[0].id, "myType", "unset_key"),
    /Cannot alter the setting for myType:unset_key as it does not exist/,
    "enable rejects with an unset key."
  );

  let expectedKeys = KEY_LIST;
  // Disable the non-top item for a key.
  for (let key of KEY_LIST) {
    let extensionIndex = 0;
    let item = await ExtensionSettingsStore.addSetting(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key,
      "new value",
      initialValue
    );
    equal(
      callbackCount,
      expectedCallbackCount,
      "initialValueCallback called the expected number of times."
    );
    equal(item, null, "Updating non-top item for a key returns null");
    item = await ExtensionSettingsStore.disable(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(item, null, "Disabling non-top item for a key returns null.");
    let allForExtension = await ExtensionSettingsStore.getAllForExtension(
      extensions[extensionIndex].id,
      TEST_TYPE
    );
    deepEqual(
      allForExtension,
      expectedKeys,
      "getAllForExtension returns expected keys after a disable."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][2],
      "getSetting returns correct item after a disable."
    );
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_other_extensions",
      "getLevelOfControl returns correct levelOfControl after disabling of non-top item."
    );
  }

  // Re-enable the non-top item for a key.
  for (let key of KEY_LIST) {
    let extensionIndex = 0;
    let item = await ExtensionSettingsStore.enable(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(item, null, "Enabling non-top item for a key returns null.");
    let allForExtension = await ExtensionSettingsStore.getAllForExtension(
      extensions[extensionIndex].id,
      TEST_TYPE
    );
    deepEqual(
      allForExtension,
      expectedKeys,
      "getAllForExtension returns expected keys after an enable."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][2],
      "getSetting returns correct item after an enable."
    );
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_other_extensions",
      "getLevelOfControl returns correct levelOfControl after enabling of non-top item."
    );
  }

  // Remove the non-top item for a key.
  for (let key of KEY_LIST) {
    let extensionIndex = 0;
    let item = await ExtensionSettingsStore.removeSetting(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(item, null, "Removing non-top item for a key returns null.");
    expectedKeys = expectedKeys.filter(expectedKey => expectedKey != key);
    let allForExtension = await ExtensionSettingsStore.getAllForExtension(
      extensions[extensionIndex].id,
      TEST_TYPE
    );
    deepEqual(
      allForExtension,
      expectedKeys,
      "getAllForExtension returns expected keys after a removal."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][2],
      "getSetting returns correct item after a removal."
    );
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[extensionIndex].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_other_extensions",
      "getLevelOfControl returns correct levelOfControl after removal of non-top item."
    );
    ok(
      !ExtensionSettingsStore.hasSetting(
        extensions[extensionIndex].id,
        TEST_TYPE,
        key
      ),
      "hasSetting returns the correct value when an extension does not have a setting set."
    );
  }

  for (let key of KEY_LIST) {
    // Disable the top item for a key.
    let item = await ExtensionSettingsStore.disable(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      ITEMS[key][1],
      "Disabling top item for a key returns the new top item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][1],
      "getSetting returns correct item after a disable."
    );
    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after disabling of top item."
    );

    // Re-enable the top item for a key.
    item = await ExtensionSettingsStore.enable(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      ITEMS[key][2],
      "Re-enabling top item for a key returns the old top item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][2],
      "getSetting returns correct item after an enable."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after re-enabling top item."
    );

    // Remove the top item for a key.
    item = await ExtensionSettingsStore.removeSetting(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      ITEMS[key][1],
      "Removing top item for a key returns the new top item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      ITEMS[key][1],
      "getSetting returns correct item after a removal."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[2].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after removal of top item."
    );

    // Add a setting for the current top item.
    let itemToAdd = { key, value: `new-${key}`, id: "@second" };
    item = await ExtensionSettingsStore.addSetting(
      extensions[1].id,
      TEST_TYPE,
      itemToAdd.key,
      itemToAdd.value,
      initialValue
    );
    equal(
      callbackCount,
      expectedCallbackCount,
      "initialValueCallback called the expected number of times."
    );
    deepEqual(
      item,
      itemToAdd,
      "Updating top item for a key returns that item."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      itemToAdd,
      "getSetting returns correct item after updating."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after updating."
    );

    // Disable the last remaining item for a key.
    let expectedItem = { key, initialValue: initialValue(key) };
    // We're using the callback to set the expected value, so we need to increment the
    // expectedCallbackCount.
    expectedCallbackCount++;
    item = await ExtensionSettingsStore.disable(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      expectedItem,
      "Disabling last item for a key returns the initial value."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      expectedItem,
      "getSetting returns the initial value after all are disabled."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after all are disabled."
    );

    // Re-enable the last remaining item for a key.
    item = await ExtensionSettingsStore.enable(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      itemToAdd,
      "Re-enabling last item for a key returns the old value."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(
      item,
      itemToAdd,
      "getSetting returns expected value after re-enabling."
    );
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controlled_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after re-enabling."
    );

    // Remove the last remaining item for a key.
    item = await ExtensionSettingsStore.removeSetting(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    deepEqual(
      item,
      expectedItem,
      "Removing last item for a key returns the initial value."
    );
    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
    deepEqual(item, null, "getSetting returns null after all are removed.");
    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    equal(
      levelOfControl,
      "controllable_by_this_extension",
      "getLevelOfControl returns correct levelOfControl after all are removed."
    );

    // Attempting to remove a setting that has had all extensions removed should *not* throw an exception.
    removeResult = await ExtensionSettingsStore.removeSetting(
      extensions[1].id,
      TEST_TYPE,
      key
    );
    equal(
      removeResult,
      null,
      "Removing a setting that has had all extensions removed returns null."
    );
  }

  // Test adding a setting with a value in callbackArgument.
  let extensionIndex = 0;
  let testKey = "callbackArgumentKey";
  let callbackArgumentValue = Date.now();
  // Add the setting.
  let item = await ExtensionSettingsStore.addSetting(
    extensions[extensionIndex].id,
    TEST_TYPE,
    testKey,
    1,
    initialValue,
    callbackArgumentValue
  );
  expectedCallbackCount++;
  equal(
    callbackCount,
    expectedCallbackCount,
    "initialValueCallback called the expected number of times."
  );
  // Remove the setting which should return the initial value.
  let expectedItem = {
    key: testKey,
    initialValue: initialValue(callbackArgumentValue),
  };
  // We're using the callback to set the expected value, so we need to increment the
  // expectedCallbackCount.
  expectedCallbackCount++;
  item = await ExtensionSettingsStore.removeSetting(
    extensions[extensionIndex].id,
    TEST_TYPE,
    testKey
  );
  deepEqual(
    item,
    expectedItem,
    "Removing last item for a key returns the initial value."
  );
  item = await ExtensionSettingsStore.getSetting(TEST_TYPE, testKey);
  deepEqual(item, null, "getSetting returns null after all are removed.");

  item = await ExtensionSettingsStore.getSetting(TEST_TYPE, "not a key");
  equal(
    item,
    null,
    "getSetting returns a null item if the setting does not have any records."
  );
  let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(
    extensions[1].id,
    TEST_TYPE,
    "not a key"
  );
  equal(
    levelOfControl,
    "controllable_by_this_extension",
    "getLevelOfControl returns correct levelOfControl if the setting does not have any records."
  );

  for (let extension of testExtensions) {
    await extension.unload();
  }

  await promiseShutdownManager();
});

add_task(async function test_settings_store_setByUser() {
  await promiseStartupManager();

  // Create an array of test framework extension wrappers to install.
  let testExtensions = [
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@first" } },
      },
    }),
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@second" } },
      },
    }),
  ];

  let type = "some_type";
  let key = "some_key";

  for (let extension of testExtensions) {
    await extension.startup();
  }

  // Create an array actual Extension objects which correspond to the
  // test framework extension wrappers.
  let [one, two] = testExtensions.map(extension => extension.extension);
  let initialCallback = () => "initial";

  // Initialize the SettingsStore.
  await ExtensionSettingsStore.initialize();

  equal(
    null,
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting is initially null"
  );

  let item = await ExtensionSettingsStore.addSetting(
    one.id,
    type,
    key,
    "one",
    initialCallback
  );
  deepEqual(
    { key, value: "one", id: one.id },
    item,
    "addSetting returns the first set item"
  );

  item = await ExtensionSettingsStore.addSetting(
    two.id,
    type,
    key,
    "two",
    initialCallback
  );
  deepEqual(
    { key, value: "two", id: two.id },
    item,
    "addSetting returns the second set item"
  );

  // a user-set selection reverts to precedence order when new
  // extension sets the setting.
  ExtensionSettingsStore.select(
    ExtensionSettingsStore.SETTING_USER_SET,
    type,
    key
  );
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after being set by user"
  );

  let three = ExtensionTestUtils.loadExtension({
    useAddonManager: "temporary",
    manifest: {
      browser_specific_settings: { gecko: { id: "@third" } },
    },
  });
  await three.startup();

  item = await ExtensionSettingsStore.addSetting(
    three.id,
    type,
    key,
    "three",
    initialCallback
  );
  deepEqual(
    { key, value: "three", id: three.id },
    item,
    "addSetting returns the third set item"
  );
  deepEqual(
    item,
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the third set item"
  );

  ExtensionSettingsStore.select(
    ExtensionSettingsStore.SETTING_USER_SET,
    type,
    key
  );
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after being set by user"
  );

  item = ExtensionSettingsStore.select(one.id, type, key);
  deepEqual(
    { key, value: "one", id: one.id },
    item,
    "selecting an extension returns the first set item after enable"
  );

  // Disabling a selected item returns to precedence order
  ExtensionSettingsStore.disable(one.id, type, key);
  deepEqual(
    { key, value: "three", id: three.id },
    ExtensionSettingsStore.getSetting(type, key),
    "returning to precedence order sets the third set item"
  );

  // Test that disabling all then enabling one does not take over a user-set setting.
  ExtensionSettingsStore.select(
    ExtensionSettingsStore.SETTING_USER_SET,
    type,
    key
  );
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after being set by user"
  );

  ExtensionSettingsStore.disable(three.id, type, key);
  ExtensionSettingsStore.disable(two.id, type, key);
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after disabling all extensions"
  );

  ExtensionSettingsStore.enable(three.id, type, key);
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after enabling one extension"
  );

  // Ensure that calling addSetting again will not reset a user-set value when
  // the extension install date is older than the user-set date.
  item = await ExtensionSettingsStore.addSetting(
    three.id,
    type,
    key,
    "three",
    initialCallback
  );
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after calling addSetting for old addon"
  );

  item = ExtensionSettingsStore.enable(three.id, type, key);
  equal(undefined, item, "enabling the active item does not return an item");
  deepEqual(
    { key, initialValue: "initial" },
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns the initial value after enabling one extension"
  );

  ExtensionSettingsStore.removeSetting(three.id, type, key);
  ExtensionSettingsStore.removeSetting(two.id, type, key);
  ExtensionSettingsStore.removeSetting(one.id, type, key);

  equal(
    null,
    ExtensionSettingsStore.getSetting(type, key),
    "getSetting returns null after removing all settings"
  );

  for (let extension of testExtensions) {
    await extension.unload();
  }

  await promiseShutdownManager();
});

add_task(async function test_settings_store_add_disabled() {
  await promiseStartupManager();

  let id = "@add-on-disable";
  let extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "temporary",
    manifest: {
      browser_specific_settings: { gecko: { id } },
    },
  });

  await extension.startup();
  await ExtensionSettingsStore.initialize();

  await ExtensionSettingsStore.addSetting(
    id,
    "foo",
    "bar",
    "set",
    () => "not set"
  );

  let item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item.id, id, "The add-on is in control");
  equal(item.value, "set", "The value is set");

  ExtensionSettingsStore.disable(id, "foo", "bar");
  item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item.id, undefined, "The add-on is not in control");
  equal(item.initialValue, "not set", "The value is not set");

  await ExtensionSettingsStore.addSetting(
    id,
    "foo",
    "bar",
    "set",
    () => "not set"
  );
  item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item.id, id, "The add-on is in control");
  equal(item.value, "set", "The value is set");

  await extension.unload();

  await promiseShutdownManager();
});

add_task(async function test_settings_uninstall_remove() {
  await promiseStartupManager();

  let id = "@add-on-uninstall";
  let extension = ExtensionTestUtils.loadExtension({
    useAddonManager: "temporary",
    manifest: {
      browser_specific_settings: { gecko: { id } },
    },
  });

  await extension.startup();
  await ExtensionSettingsStore.initialize();

  await ExtensionSettingsStore.addSetting(
    id,
    "foo",
    "bar",
    "set",
    () => "not set"
  );

  let item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item.id, id, "The add-on is in control");
  equal(item.value, "set", "The value is set");

  await extension.unload();

  await promiseShutdownManager();

  item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item, null, "The add-on setting was removed");
});

add_task(async function test_exceptions() {
  await ExtensionSettingsStore.initialize();

  await Assert.rejects(
    ExtensionSettingsStore.addSetting(
      1,
      TEST_TYPE,
      "key_not_a_function",
      "val1",
      "not a function"
    ),
    /initialValueCallback must be a function/,
    "addSetting rejects with a callback that is not a function."
  );
});

add_task(async function test_get_all_settings() {
  await promiseStartupManager();

  let testExtensions = [
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@first" } },
      },
    }),
    ExtensionTestUtils.loadExtension({
      useAddonManager: "temporary",
      manifest: {
        browser_specific_settings: { gecko: { id: "@second" } },
      },
    }),
  ];

  for (let extension of testExtensions) {
    await extension.startup();
  }

  await ExtensionSettingsStore.initialize();

  let items = ExtensionSettingsStore.getAllSettings("foo", "bar");
  equal(items.length, 0, "There are no addons controlling this setting yet");

  await ExtensionSettingsStore.addSetting(
    "@first",
    "foo",
    "bar",
    "set",
    () => "not set"
  );

  items = ExtensionSettingsStore.getAllSettings("foo", "bar");
  equal(items.length, 1, "The add-on setting has 1 addon trying to control it");

  await ExtensionSettingsStore.addSetting(
    "@second",
    "foo",
    "bar",
    "setting",
    () => "not set"
  );

  let item = ExtensionSettingsStore.getSetting("foo", "bar");
  equal(item.id, "@second", "The second add-on is in control");
  equal(item.value, "setting", "The second value is set");

  items = ExtensionSettingsStore.getAllSettings("foo", "bar");
  equal(
    items.length,
    2,
    "The add-on setting has 2 addons trying to control it"
  );

  await ExtensionSettingsStore.removeSetting("@first", "foo", "bar");

  items = ExtensionSettingsStore.getAllSettings("foo", "bar");
  equal(items.length, 1, "There is only 1 addon controlling this setting");

  await ExtensionSettingsStore.removeSetting("@second", "foo", "bar");

  items = ExtensionSettingsStore.getAllSettings("foo", "bar");
  equal(
    items.length,
    0,
    "There is no longer any addon controlling this setting"
  );

  for (let extension of testExtensions) {
    await extension.unload();
  }

  await promiseShutdownManager();
});
