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.'); } }