chromium/chrome/test/data/extensions/api_test/accessibility_features/read_permission/test_runner.js

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Tests that the extension is able to query feature status and that the feature
 * status is as expected.
 * @param {string} featureName The boolean feature to be queried.
 * @param {boolean} expectedValue The expected value of the feature.
 */
function testFeatureIsEnabled(featureName, expectedIsEnabled) {
  chrome.accessibilityFeatures[featureName].get(
      {},
      function(result) {
        chrome.test.assertTrue(!!result);
        chrome.test.assertEq(expectedIsEnabled,
                             result.value,
                             "Unexpected value for feature " + featureName);
        chrome.test.succeed();
      });
};

/**
 * Initializes and runs tests that get feature statuses.
 * @param {Array<string>} enabledFeatures The list of features that are
 *     expected to be enabled.
 * @param {Array<string>} disabledFeatures The list of features that are
 *     expected to be disabled.
 */
function runGetterTest(enabledFeatures, disabledFeatures) {
  var tests= [];

  enabledFeatures.forEach(function(feature) {
    var test = testFeatureIsEnabled.bind(null, feature, true);
    // This is the name that will show up in the apitest framework's logging
    // output for anonymous functions.
    test.generatedName = "testIsEnabled_" + feature;

    tests.push(test);
  });

  disabledFeatures.forEach(function(feature) {
    var test = testFeatureIsEnabled.bind(null, feature, false);
    // This is the name that will show up in the apitest framework's logging
    // output for anonymous functions.
    test.generatedName = "testIsDisabled_" + feature;

    tests.push(test);
  });

  chrome.test.runTests(tests);
};

/**
 * Tests that the extension is not able to modify a feature value.
 * @param {string} feature The feature to be modified.
 * @param {boolean} value The value the feature should be set to.
 */
function testSetFeatureNotAllowed(feature, value) {
  var expectedError = 'You do not have permission to access the preference ' +
      '\'' + feature + '\'. Be sure to declare in your manifest what ' +
      'permissions you need.';

  chrome.accessibilityFeatures[feature].set(
      {value: value},
      chrome.test.callbackFail(expectedError));
}

/**
 * Initializes and runs tests that verify the extension cannot modify features.
 * In the test the extension tries to flip the feature values.
 * Note that the tests only verify that set method fails. They don't verify the
 * feature values remain unchanged. That is done by the Chrome side of the test.
 * @param {Array<string>} enabledFeatures The list of features that are
 *     enabled at the start of the test.
 * @param {Array<string>} disabledFeatures The list of features that are
 *     disabled at the start of the test.
 */
function runSetterTest(enabledFeatures, disabledFeatures) {
  var tests = [];

  enabledFeatures.forEach(function(feature) {
    var test = testSetFeatureNotAllowed.bind(null, feature, false);
    // This is the name that will show up in the apitest framework's logging
    // output for anonymous functions.
    test.generatedName = "testDisableNotAllowed_" + feature;

    tests.push(test);
  });

  disabledFeatures.forEach(function(feature) {
    var test = testSetFeatureNotAllowed.bind(null, feature, true);
    // This is the name that will show up in the apitest framework's logging
    // output for anonymous functions.
    test.generatedName = "testEnableNotAllowed_" + feature;

    tests.push(test);
  });

  chrome.test.runTests(tests);
}

/**
 * Status of the observer test Set if an observer test is running. Contains
 * two lists of feature for which an {@code onChange} event is expected to be
 * observed. One list contains features for which the event value should be true
 * ({@code toBeEnabled}), while the other one contains features for which the
 * event value is expected to be false ({@code toBeDisabled}).
 * Once an event for a feature is observed, the feature is removed from the list
 * that contains it.
 * @type {?{toBeDisabled: Array<{name: string,
 *                                listener: function(Event)}>,
 *          toBeEnabled: Array<{name: string,
 *                               listener: function(Event)}>}}
 *     For each array type: |name|: the feature name;
 *                          |listener|: {@code onChange} listener.
 */
var observerTestState = null;

/**
 * Initializes and starts the test that observes that {@code onChange} event is
 * triggered when a feature status changes. The features are actually changed in
 * Chrome side of the test. During a single test, two success notification will
 * be sent (if the test succeeds): once when the features' change listeners have
 * been setup, and the second time when all the expected events are seen.
 * In case of the failure notification, no further notifications will be sent.
 * @param {Array<string>} initiallyEnabled
 *     The list of features that are enabled at the start of the test, and for
 *     which a {@code onChange} event with value false should be observed.
 * @param {Array<string>} initiallyDisabled
 *     The list of features that are disabled at the start of the test, and for
 *     which a {@code onChange} event with value true should be observed.
 */
function startObserverTest(initiallyEnabled, initiallyDisabled) {
  if (observerTestState) {
    chrome.test.notifyFail('Initializing observe features test before the ' +
                           'previous one finished');
    return;
  }

  observerTestState = {
    toBeDisabled: [],
    toBeEnabled: []
  };

  /**
   * Finds feature with the provided name in the.
   * @param {Array<{name: string, listener: function(Event)>} list The list in
   *     which to search.
   * @param {string} featureName The feature name for which to search.
   * @return {number} The feature's index in the list. If not found returns -1.
   */
  function findFeatureIndex(list, featureName) {
    for (var i = 0; i < list.length; ++i)
      if (list[i].name == featureName)
        return i;
    return -1;
  }

  /**
   * Initializes listener for a feature and adds it to the appropriate list in
   * {@code observerTestState}.
   * @param {string} feature The feature name.
   * @param {boolean} initiallyEnabled Whether the feature is initially enabled.
   */
  function initTestParamsForFeature(feature, initiallyEnabled) {
    var list = initiallyEnabled ? observerTestState.toBeDisabled :
                                  observerTestState.toBeEnabled;

    var listener = function(ev) {
      // Fail the test in case the new feature value is not as expected, but
      // before that, do some cleanup.
      if (initiallyEnabled == ev.value)
        clearRemainingListeners();
      chrome.test.assertEq(!initiallyEnabled, ev.value);

      var index = findFeatureIndex(list, feature);
      if (index < 0)
        clearRemaningListeners();
      chrome.test.assertTrue(index > -1);

      chrome.accessibilityFeatures[feature].onChange.removeListener(
          list[index].listener);
      list.splice(index, 1);

      if (observerTestState.toBeEnabled.length == 0 &&
          observerTestState.toBeDisabled.length == 0) {
        chrome.test.succeed();
      }
    }

    list.push({name: feature, listener: listener});
    chrome.accessibilityFeatures[feature].onChange.addListener(listener);
  }

  /**
   * If any features are still being disabled, removes their listeners.
   * This situation may happen when test fails.
   */
  function clearRemainingListeners() {
    observerTestState.toBeEnabled.forEach(function(feature) {
      chrome.accessibilityFeatures[feature.name].onChange.removeListener(
          feature.listener);
      feature.listener = null;
    });
    observerTestState.toBeDisabled.forEach(function(feature) {
      chrome.accessibilityFeatures[feature.name].onChange.removeListener(
          feature.listener);
      feature.listener = null;
    });
  }

  initiallyEnabled.forEach(function(feature) {
    initTestParamsForFeature(feature, true);
  });

  initiallyDisabled.forEach(function(feature) {
    initTestParamsForFeature(feature, false);
  });

  // Send success, so the Chrome side of the test continues. The test is not
  // actually over yet, and is expected to send another success notification
  // when all featureChanged events are seen.
  chrome.test.succeed();
}

/**
 * Mapping from test name to the function that runs tests.
 * @type {getterTest: function(Array<string>, Array<string>),
 *        setterTest: function(Array<string>, Array<string>),
 *        observerTest: function(Array<string>, Array<string>)}
 * @const
 */
var TEST_FUNCTIONS = {
  getterTest: runGetterTest,
  setterTest: runSetterTest,
  observerTest: startObserverTest,
};

/**
 * Entry point for tests. Gets test config and runs the associated test
 * function.
 */
chrome.app.runtime.onLaunched.addListener(function() {
  chrome.test.getConfig(function(config) {
    var testArgs = JSON.parse(config.customArg);
    if (!testArgs) {
      chrome.test.notifyFail("No test args");
      return;
    }
    if (!testArgs.testName) {
      chrome.test.notifyFail("No test name");
      return;
    }

    if (!TEST_FUNCTIONS[testArgs.testName]) {
      chrome.test.notifyFail("Unknown test name");
      return;
    }

    TEST_FUNCTIONS[testArgs.testName](testArgs.enabled, testArgs.disabled);
  });
});