chromium/chrome/test/data/extensions/greasemonkey_api_test.js

var localStorage;
var document;
var window;
var XMLHttpRequest;
var lastCreatedXhr;

function testGetSetValue() {
  localStorage = new LocalStorageFake();
  GM_setValue('string', 'value');
  assert('value', GM_getValue('string'));
  GM_setValue('integer', 123);
  assert(123, GM_getValue('integer'));
  GM_setValue('boolean', true);
  assert(true, GM_getValue('boolean'));

  assert(undefined, GM_getValue('notset'));
  assert('default', GM_getValue('notset', 'default'));

  // illegal values
  assertThrow(function() {
    GM_setValue('illegal value', null);
  });
  assertThrow(function() {
    GM_setValue('illegal value', 1.5);
  });
}

function testDeleteValue() {
  localStorage = new LocalStorageFake();
  GM_setValue('delete', 'value');
  assert('value', GM_getValue('delete'));
  GM_deleteValue('delete');
  assert(undefined, GM_getValue('delete'));
  GM_deleteValue('notset');
}

function testListValues() {
  localStorage = new LocalStorageFake();
  var a = GM_listValues();
  assert(0, a.length);

  GM_setValue('one', 'first');
  a = GM_listValues();
  assert(1, a.length);
  assert('one', a[0]);

  GM_setValue('two', 'second');
  a = GM_listValues();

  // Test that keys that are not namespaced can't be read.
  localStorage.setItem('three', 'third');
  assert(2, a.length);
  assert(true, a.indexOf('one') >= 0);
  assert(true, a.indexOf('two') >= 0);
}

function testGetResourceURL() {
  assertThrow(function() {
    var resource = GM_getResourceURL('urlResourceName');
  });
}

function testGetResourceText() {
  assertThrow(function() {
    var resource = GM_getResourceText('textResourceName');
  });
}

function testAddStyle() {
  var documentElement = new ElementFake('');
  var head = new ElementFake('head');
  document = new DocumentFake(documentElement, head);
  var css = 'color: #decaff;';
  GM_addStyle(css);
  assert(0, documentElement.childNodes.length);
  assert(1, head.childNodes.length);
  var style = head.childNodes[0];
  assert("style", style.tagName);
  assert("text/css", style.type);
  assert(1, style.childNodes.length);
  var textNode = style.childNodes[0];
  assert(css, textNode.text);

  document = new DocumentFake(documentElement, null);
  GM_addStyle(css);
  assert(1, documentElement.childNodes.length);
  var style = documentElement.childNodes[0];
  assert("text/css", style.type);
  assert(1, style.childNodes.length);
  textNode = style.childNodes[0];
  assert(css, textNode.text);
}

function testXmlhttpRequest() {
  XMLHttpRequest = XMLHttpRequestFake;
  var url = 'http://example.com';
  
  var onLoadCallback = function(state) {
    onLoadCallbackResponseState = state;
  };
  var onErrorCallback = function(state) {
    onErrorCallbackResponseState = state;
  };
  var onReadyStateChangeCallback = function(state) {
    onReadyStateChangeCallbackResponseState = state;
  };

  var details = {
    onload: onLoadCallback,
    onerror: onErrorCallback,
    onreadystatechange: onReadyStateChangeCallback,
    method: 'GET',
    url: url,
    overrideMimeType: 'text/html',
    headers: {
      'X-Header': 'foo'
    },
    data: 'data'
  };

  GM_xmlhttpRequest(details);
  var xhr = lastCreatedXhr;

  assert('GET', xhr.openedMethod);
  assert(url, xhr.openedUrl);
  assert('text/html', xhr.overrideMimeType);
  assert('foo', xhr.requestHeaders['X-Header']);
  assert('data', xhr.sentBody);

  xhr.responseText = 'foo';
  xhr.responseHeaders['X-Response'] = 'foo';
  xhr.status = 200;
  xhr.statusText = 'OK';

  xhr.readyState = 1;
  xhr.onreadystatechange();
  var state = onReadyStateChangeCallbackResponseState;
  assert(xhr.responseText, state.responseText);
  assert(xhr.readyState, state.readyState);
  assert('', state.responseHeaders);
  assert(0, state.status);
  assert('', state.statusText);
  assert('', state.finalUrl);

  xhr.readyState = 0;
  xhr.onerror();
  state = onErrorCallbackResponseState;
  assert(xhr.responseText, state.responseText);
  assert(xhr.readyState, state.readyState);
  assert('', state.responseHeaders);
  assert(0, state.status);
  assert('', state.statusText);
  assert('', state.finalUrl);

  xhr.readyState = 4;
  xhr.onload();
  state = onLoadCallbackResponseState;
  assert(xhr.responseText, state.responseText);
  assert(xhr.readyState, state.readyState);
  assert('X-Response: foo\r\n', state.responseHeaders);
  assert(xhr.status, state.status);
  assert(xhr.statusText, state.statusText);
  assert(url, state.finalUrl);
}

function testRegisterMenuCommand() {
  assertThrow(function() {
    GM_registerMenuCommand('name', function() {}, 'a', '', 'a');
  });
}

function testOpenInTab() {
  var mockWindow = {
    open: function(url, name, opt_options) {
      this.openedUrl = url;
      this.openedName = name;
      this.openedOptions = opt_options;
    }
  };
  window = mockWindow;
  var url = 'http://example.com';
  GM_openInTab(url);
  assert(mockWindow.openedUrl, url);
}

function testLog() {
  var mockWindow = {
    console: {
      message: null,
      log: function(message) {
        this.message = message;
      }
    }
  };
  window = mockWindow;
  var message = 'hello world';
  GM_log(message);
  assert(message, mockWindow.console.message);
}

function LocalStorageFake() {
  this.map_ = {};
  this.keys_ = [];
}
LocalStorageFake.prototype = {
  length: 0,
  key: function(index) {
    if (index >= this.length) {
      throw new Error('INDEX_SIZE_ERR');
    }
    return this.keys_[index];
  },
  getItem: function(key) {
    if (key in this.map_) {
      return this.map_[key];
    }
    return null;
  },
  setItem: function(key, data) {
    this.map_[key] = data;
    this.updateKeys_();
  },
  removeItem: function(key) {
    delete this.map_[key];
    this.updateKeys_();
  },
  clear: function() {
    this.map_ = {};
    this.updateKeys_();
  },
  updateKeys_: function() {
    var keys = [];
    for (var key in this.map_) {
      keys.push(key);
    }
    this.keys_ = keys;
    this.length = keys.length;
  }
}

function DocumentFake(documentElement, head) {
  this.documentElement = documentElement;
  this.head_ = head;
}
DocumentFake.prototype = {
  getElementsByTagName: function(tagName) {
    if (tagName == 'head' && this.head_) {
      return [this.head_];
    }
    return [];
  },
  createElement: function(tagName) {
    return new ElementFake(tagName);
  },
  createTextNode: function(text) {
    return new TextNodeFake(text);
  }
}

function ElementFake(tagName) {
  this.tagName = tagName;
  this.childNodes = [];
}
ElementFake.prototype = {
  appendChild: function(e) {
    this.childNodes.push(e);
    return e;
  }
}

function TextNodeFake(text) {
  this.text = text;
}

function XMLHttpRequestFake() {
  lastCreatedXhr = this;
  this.onload = null;
  this.onerror = null;
  this.onreadystatechange = null;
  this.openedMethod = null;
  this.openededUrl = null;
  this.overriddenMimeType = null;
  this.requestHeaders = {};
  this.sentBody = null;
  this.responseText = null;
  this.readyState = null;
  this.responseHeaders = {};
  this.status = null;
  this.statusText = null;
}
XMLHttpRequestFake.prototype = {
  open: function(method, url) {
    this.openedMethod = method;
    this.openedUrl = url;
  },
  overrideMimeType: function(mimeType) {
    this.overrideMimeType = mimeType;
  },
  setRequestHeader: function(header, value) {
    this.requestHeaders[header] = value;
  },
  send: function(opt_body) {
    this.sentBody = opt_body;
  },
  getAllResponseHeaders: function() {
    var s = '';
    for (var header in this.responseHeaders) {
      // The delimiter used in Webkit's XMLHttpRequest is \r\n, however
      // the new Chrome networking code (and Firefox) uses \n, so watch
      // out for this!
      s += header + ': ' + this.responseHeaders[header] + '\r\n';
    }
    return s;
  }
}

function assert(expected, actual) {
  if (expected !== actual) {
    throw new Error('Assert failed: "' + expected + '" !== "' + actual + '"');
  }
}

function fail() {
  throw new Error('Fail');
}

function assertThrow(f) {
  var threw = false;
  try {
    f();
  } catch(e) {
    threw = true;
  }
  if (!threw) {
    throw new Error('Assert failed, expression did not throw.');
  }
}