├── docs ├── .gitignore ├── requirements.txt ├── index.rst ├── overview.rst ├── types.rst ├── make.bat ├── Makefile ├── conf.py └── api.rst ├── .gitignore ├── bower.json ├── xmlrpc.jquery.json ├── tests ├── index.html ├── min.html ├── tests.binary.js ├── tests.request.js ├── tests.document.js ├── tests.encoding.js ├── tests.decoding.js ├── qunit.css └── qunit.js ├── LICENCE ├── README.md ├── jquery.xmlrpc.min.js └── jquery.xmlrpc.js /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==1.1.3 2 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-xmlrpc", 3 | "version": "0.4.4", 4 | "main": ["jquery.xmlrpc.js", "jquery.xmlrpc.min.js"], 5 | "dependencies": { 6 | "jquery": ">1.10.2" 7 | }, 8 | "devDependencies": { 9 | "jquery-mockjax": "~1.5.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to jQuery XML-RPC's documentation! 2 | ========================================== 3 | 4 | This is a small library that sits on top of jQuery for communicating with 5 | XML-RPC services - without worrying about the horrible bloat of XML-RPC. Using 6 | this library, you can pass JSON parameters to the library, and receive 7 | responses in JSON. Encoding the JSON document is handled for you, intelligently 8 | mapping types between the two languages. 9 | 10 | Contents: 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | overview 16 | types 17 | api 18 | -------------------------------------------------------------------------------- /xmlrpc.jquery.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xmlrpc", 3 | "version": "0.4.4", 4 | "title": "XML-RPC over AJAX", 5 | "description": "Interact with remote XML-RPC services using AJAX", 6 | "author": { 7 | "name": "Tim Heap", 8 | "email": "heap.tim@gmail.com", 9 | "url": "http://timheap.me" 10 | }, 11 | "licenses": [ 12 | {"url": "http://unlicense.org/"} 13 | ], 14 | "dependencies": { 15 | "jquery": ">1.8.0" 16 | }, 17 | "keywords": [ 18 | "xmlrpc", "xml-rpc", 19 | "ajax", 20 | "xml", 21 | "remote-service" 22 | ], 23 | "homepage": "https://github.com/timheap/jquery-xmlrpc", 24 | "docs": "http://jquery-xml-rpc.readthedocs.org/" 25 | } 26 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery XML-RPC tests 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/min.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | jQuery XML-RPC minified tests 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /tests/tests.binary.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true jquery:true */ 2 | /*globals deepEqual equal ok assert test module */ 3 | (function() { 4 | "use strict"; 5 | module("Binary"); 6 | 7 | /** 8 | * Make an array buffer out of an array of ints 9 | */ 10 | var makeArrayBuffer = function(uint8s) { 11 | var ab = new ArrayBuffer(uint8s.length); 12 | var uint8 = new Uint8Array(ab); 13 | 14 | uint8s.forEach(function(val, i) { 15 | uint8[i] = val; 16 | }); 17 | 18 | return ab; 19 | }; 20 | 21 | /** 22 | * A wrapper around `ok` that acts like `equal`, but works on ArrayBuffers 23 | * Call like: 24 | * 25 | * abEqual(testArrayBuffer, expectedArrayBuffer, message); 26 | */ 27 | var abEqual = function(ab1, ab2) { 28 | var args = [].slice.call(arguments, 2); 29 | 30 | var allGood = (function() { 31 | 32 | var uint81 = new Uint8Array(ab1); 33 | var uint82 = new Uint8Array(ab2); 34 | 35 | if (uint81.length != uint82.length) return false; 36 | 37 | for (var i = 0; i < uint81.length; i++) { 38 | if (uint81[i] !== uint82[i]) return false; 39 | } 40 | 41 | return true; 42 | })(); 43 | 44 | args.unshift(allGood); 45 | 46 | ok.apply(null, args); 47 | }; 48 | 49 | /** 50 | * Naive range() function. Only supports forward steps 51 | */ 52 | var range = function(start, stop) { 53 | var acc = []; 54 | for (var i = start; i < stop; i++) { 55 | acc.push(i); 56 | } 57 | return acc; 58 | }; 59 | 60 | test("base64 encoding", function() { 61 | equal($.xmlrpc.binary.toBase64(makeArrayBuffer([1, 2, 3, 4, 5])), "AQIDBAU=", 62 | "ArrayBuffer encoded to base64 string"); 63 | }); 64 | 65 | test("base64 decoding", function() { 66 | abEqual($.xmlrpc.binary.fromBase64("AQIDBAU="), makeArrayBuffer([1, 2, 3, 4, 5]), 67 | "Base64 string decoded into ArrayBuffer"); 68 | }); 69 | 70 | test("base64 round trip", function() { 71 | var max = 20, low, high; 72 | for (low = 0; low < max; low++) { 73 | for (high = low + 1; high < max; high++) { 74 | var ab = makeArrayBuffer(range(low, high)); 75 | abEqual(ab, $.xmlrpc.binary.fromBase64($.xmlrpc.binary.toBase64(ab)), 76 | "Range from " + low + " to " + high + " encodes and decodes"); 77 | } 78 | } 79 | }); 80 | })(); 81 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | .. _installing: 2 | 3 | ========== 4 | Installing 5 | ========== 6 | 7 | Simply include the jQuery library, and this library in your page: 8 | 9 | .. code-block:: html 10 | 11 | 12 | 13 | 14 | This was built upon jQuery 1.8.1. It will probably work with old versions, and 15 | will probably continue to work with new versions. 16 | 17 | .. _using: 18 | 19 | ===== 20 | Using 21 | ===== 22 | 23 | The ``jQuery.xmlrpc`` function is the main work-horse of this library. 24 | Call it like so: 25 | 26 | .. code-block:: javascript 27 | 28 | $.xmlrpc({ 29 | url: '/RPC2', 30 | methodName: 'foo', 31 | params: ['bar', 1, 4.6, true, [1, 2, 3], {name: 'value'}], 32 | success: function(response, status, jqXHR) { }, 33 | error: function(jqXHR, status, error) { } 34 | }); 35 | 36 | It takes all of the same arguments as ``jQuery.ajax``, 37 | so refer there for more documentation. 38 | The two new keys added are: 39 | 40 | ``methodName`` 41 | This is method put in the ```` element from XML-RPC. It should be a 42 | string. The XML-RPC service you are communicating with will determine valid 43 | method names you can call. 44 | 45 | ``params`` 46 | An array of parameters to send. Specify an empty array, or do not supply this 47 | key at all if you do not want to send any parameters. 48 | 49 | See the docs section on [Encoding and Decoding XML-RPC Documents][encoding] for 50 | more information. 51 | 52 | Getting data back 53 | ----------------- 54 | 55 | When the XML-RPC call returns, 56 | the contents of the ```` element are parsed into JSON and 57 | supplied to the ``success`` callback of the AJAX call as the first parameter, 58 | much like a JSON request. 59 | 60 | Handling errors 61 | --------------- 62 | 63 | If any HTTP errors occur during transport, 64 | the normal jQuery AJAX error handling will be used. 65 | If the XML-RPC service successfully replies, 66 | but replies with a ```` response, 67 | an ``$.xmlrpc.XmlRpcFault`` is thrown. 68 | This error will be sent as the third parameter to the ``error`` callback 69 | of the AJAX call, as with other errors. 70 | -------------------------------------------------------------------------------- /tests/tests.request.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true */ 2 | /*globals $ deepEqual equal ok assert test module throws */ 3 | (function() { 4 | "use strict"; 5 | module("Request"); 6 | 7 | /** 8 | * Serialize a Document to a string easily 9 | */ 10 | var s = (function() { 11 | var serializer = new XMLSerializer(); 12 | return function($node) { 13 | return serializer.serializeToString($node); 14 | }; 15 | })(); 16 | 17 | var d = (function() { 18 | var parser = new DOMParser(); 19 | return function(xml) { 20 | return parser.parseFromString(xml, "text/xml"); 21 | }; 22 | })(); 23 | 24 | $.mockjax({ 25 | url: '/mock/xmlrpc/hello', 26 | contentType: 'text/xml', 27 | responseTime: 1, 28 | responseText: ( 29 | '' + 30 | '' + 31 | '' + 32 | '4' + 33 | 'World!' + 34 | '' + 35 | '' 36 | ) 37 | }); 38 | 39 | $.mockjax({ 40 | url: '/mock/xmlrpc/empty', 41 | contentType: 'text/xml', 42 | responseTime: 1, 43 | responseText: ( 44 | '' + 45 | '' + 46 | '' + 47 | '' + 48 | '' 49 | ) 50 | }); 51 | 52 | asyncTest("Make a simple request", function() { 53 | expect(2); 54 | $.xmlrpc("/mock/xmlrpc/hello", { 55 | 'methodName': 'test-method', 56 | 'params': [1, "Hello"], 57 | 'success': function(data, status) { 58 | deepEqual(this.data, 59 | "test-method" + 60 | "1" + 61 | "Hello" + 62 | "", 63 | "Request body was encoded correctly!"); 64 | deepEqual(data, [4, 'World!'], 65 | "Response body was decoded correctly!"); 66 | start(); 67 | }, 68 | 'error': function() { 69 | ok(false, arguments); 70 | } 71 | }); 72 | }); 73 | 74 | asyncTest("Make a request with no params", function() { 75 | expect(2); 76 | $.xmlrpc("/mock/xmlrpc/empty", { 77 | 'methodName': 'test-method', 78 | 'success': function(data, request) { 79 | deepEqual(this.data, 80 | "test-method" + 81 | "", 82 | "Request body was encoded correctly!"); 83 | deepEqual(data, [], 84 | "Response body was decoded correctly!"); 85 | start(); 86 | }, 87 | 'error': function() { 88 | ok(false, arguments); 89 | } 90 | }); 91 | }); 92 | 93 | })(); 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery XML-RPC library 2 | ====================== 3 | 4 | This is a small library that sits on top of jQuery for communicating with 5 | XML-RPC services - without worrying about the horrible bloat of XML-RPC. Using 6 | this library, you can pass JSON parameters to the library, and receive 7 | responses in JSON. Encoding the JSON document is handled for you, intelligently 8 | mapping types between the two languages. 9 | 10 | Installing 11 | ---------- 12 | 13 | Simply include the jQuery library, and this library in your page: 14 | 15 | ```html 16 | 17 | 18 | ``` 19 | 20 | This was built upon jQuery 1.8.1. It will probably work with old versions, and 21 | will probably continue to work with new versions. 22 | 23 | Using 24 | ----- 25 | 26 | The `jQuery.xmlrpc` function is the main work-horse of this library. Call it 27 | like so: 28 | 29 | ```javascript 30 | $.xmlrpc({ 31 | url: '/RPC2', 32 | methodName: 'foo', 33 | params: ['bar', 1, 4.6, true, [1, 2, 3], {name: 'value'}], 34 | success: function(response, status, jqXHR) { /* ... */ }, 35 | error: function(jqXHR, status, error) { /* ... */ } 36 | }); 37 | ``` 38 | 39 | It takes all of the same arguments as `jQuery.ajax`, so refer there for more 40 | documentation. The two new keys added are: 41 | 42 | ### `methodName` 43 | 44 | This is method put in the `` element from XML-RPC. It should be a 45 | string. The XML-RPC service you are communicating with will determine valid 46 | method names you can call. 47 | 48 | ### `params` 49 | 50 | An array of parameters to send. Specify an empty array, or do not supply this 51 | key at all if you do not want to send any parameters. 52 | 53 | See the docs section on [Encoding and Decoding XML-RPC Documents][encoding] for 54 | more information. 55 | 56 | ### Getting data back 57 | 58 | When the XML-RPC call returns, the contents of the `` element are 59 | parsed into JSON and supplied to the `success` callback of the AJAX call as the 60 | first parameter, much like a JSON request. 61 | 62 | ### Handling errors 63 | 64 | If any HTTP errors occur during transport, the normal jQuery AJAX error 65 | handling will be used. If the XML-RPC service successfully replies, but replies 66 | with a `` response, an `$.xmlrpc.XmlRpcFault` is thrown. This error will 67 | be sent as the third parameter to the `error` callback of the AJAX call, as 68 | with other errors. 69 | 70 | Documentation 71 | ------------- 72 | 73 | [The full documentation can be found on Read The Docs][docs]. 74 | 75 | [docs]: http://jquery-xml-rpc.readthedocs.org/ "Documentation" 76 | [encoding]: http://jquery-xml-rpc.readthedocs.org/en/latest/types.html#encoding-and-decoding-xml-rpc-documents 77 | "Encoding and Decoding XML-RPC Documents" 78 | -------------------------------------------------------------------------------- /docs/types.rst: -------------------------------------------------------------------------------- 1 | .. _types: 2 | 3 | ===== 4 | Types 5 | ===== 6 | 7 | JSON and XML-RPC are two unrelated markup languages, so converting between the 8 | types requires a small understanding of both languages. Luckily, most of the 9 | types have a direct mapping between the two languages. 10 | 11 | Encoding and Decoding XML-RPC Documents 12 | --------------------------------------- 13 | 14 | Use the following table to see how XML-RPC types are mapped to JavaScript 15 | types: 16 | 17 | +-------------------------+-----------------+ 18 | | XML-RPC | JavaScript | 19 | +=========================+=================+ 20 | | ```` | ``null`` | 21 | +-------------------------+-----------------+ 22 | | ```` | ``Array`` | 23 | +-------------------------+-----------------+ 24 | | ```` | ``Object`` | 25 | +-------------------------+-----------------+ 26 | | ```` | ``String`` | 27 | +-------------------------+-----------------+ 28 | | ```` | ``Boolean`` | 29 | +-------------------------+-----------------+ 30 | | ```` | ``Number`` | 31 | +-------------------------+ | 32 | | ```` | | 33 | +-------------------------+ | 34 | | ```` | | 35 | +-------------------------+ | 36 | | ```` | | 37 | +-------------------------+-----------------+ 38 | | ```` | ``Number`` | 39 | +-------------------------+-----------------+ 40 | | ```` | ``Date`` | 41 | +-------------------------+-----------------+ 42 | | ```` | ``ArrayBuffer`` | 43 | +-------------------------+-----------------+ 44 | 45 | .. note:: JavaScript does not have separate types for integers and floats, it simply 46 | has ``Number``. As such, it is impossible to tell if ``4`` really means 47 | ``4`` or ``4``. If this is an issue for you, read on. 48 | 49 | Forcing types 50 | ~~~~~~~~~~~~~ 51 | 52 | Some times, the automatic type guessing going from JSON to XML-RPC may not work 53 | for you. The most common source of this problem is in encoding numbers. The 54 | library may sometimes encode a ``Number`` as a ```` instead of a ````, as 55 | there is no reliable way of determining what was actually desired. 56 | 57 | To force a type, wrap the value in a call to ``$.xmlrpc.force``. The types are 58 | named after their XML-RPC equivalents, as mentioned in the above table. 59 | 60 | 61 | To force a floating point JavaScript ``Number`` 62 | to be encoded as an ```` 63 | and sent as a parameter, use the following: 64 | 65 | .. code-block:: javascript 66 | 67 | var forcedValue = $.xmlrpc.force('i8', 4.5) 68 | 69 | $.xmlrpc({ 70 | url: '/RPC2', 71 | methodName: 'foo', 72 | params: [forcedValue] 73 | }); 74 | 75 | Adding and Extending Types 76 | -------------------------- 77 | 78 | You can add your own types to XML-RPC by adding a member to ``$.xmlrpc.types``, 79 | combined with the ``$.xmlrpc.makeType`` function. See 80 | :ref:`xmlrpc-makeType` for more information 81 | -------------------------------------------------------------------------------- /tests/tests.document.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true jquery:true */ 2 | /*globals deepEqual equal ok assert test module throws */ 3 | (function() { 4 | "use strict"; 5 | module("Document"); 6 | 7 | /** 8 | * Serialize a Document to a string easily 9 | */ 10 | var s = (function() { 11 | var serializer = new XMLSerializer(); 12 | return function($node) { 13 | return serializer.serializeToString($node); 14 | }; 15 | })(); 16 | 17 | var d = (function() { 18 | var parser = new DOMParser(); 19 | return function(xml) { 20 | return parser.parseFromString(xml, "text/xml"); 21 | }; 22 | })(); 23 | 24 | test("Creating a document", function() { 25 | equal(s($.xmlrpc.document('method', [4, 'foo'])), 26 | '' + 27 | 'method' + 28 | '' + 29 | '4' + 30 | 'foo' + 31 | '' + 32 | '', 33 | 'Can create a simple document'); 34 | 35 | equal(s($.xmlrpc.document('empty', [])), 36 | '' + 37 | 'empty' + 38 | '' + 39 | '', 40 | 'Can create an empty document'); 41 | }); 42 | 43 | test("Decoding a document", function() { 44 | 45 | deepEqual( 46 | $.xmlrpc.parseDocument(d( 47 | '' + 48 | '' + 49 | '' + 50 | '4' + 51 | 'Hello' + 52 | '' + 53 | '')), 54 | [4, "Hello"], 55 | "Can parse a simple response"); 56 | 57 | deepEqual( 58 | $.xmlrpc.parseDocument(d( 59 | '' + 60 | '' + 61 | '' + 62 | '')), 63 | [], 64 | "Can parse an empty response"); 65 | 66 | deepEqual( 67 | $.xmlrpc.parseDocument(d( 68 | '' + 69 | '' + 70 | '' + 71 | '4' + 72 | '' + 73 | '1' + 74 | '' + 75 | '2' + 76 | '' + 77 | '' + 78 | '' + 79 | '')), 80 | [4, [1, [2]]], 81 | "Can parse a complex response"); 82 | 83 | }); 84 | 85 | test("Handling errors", 5, function() { 86 | throws( 87 | function() { 88 | $.xmlrpc.parseDocument(d( 89 | '' + 90 | '' + 91 | '' + 92 | '' + 93 | '' + 94 | 'faultCode' + 95 | '4' + 96 | '' + 97 | '' + 98 | 'faultString' + 99 | 'Too many parameters.' + 100 | '' + 101 | '' + 102 | '' + 103 | '' 104 | )); 105 | }, 106 | $.xmlrpc.XmlRpcFault, 107 | "Parsing a fault response throws an error"); 108 | 109 | try { 110 | $.xmlrpc.parseDocument(d( 111 | '' + 112 | '' + 113 | '' + 114 | '' + 115 | '' + 116 | '' + 117 | 'faultCode' + 118 | '4' + 119 | '' + 120 | '' + 121 | 'faultString' + 122 | 'Error message' + 123 | '' + 124 | '' + 125 | '' + 126 | '' + 127 | '' 128 | )); 129 | ok(false, "Should have thrown an error"); 130 | } catch (e) { 131 | equal(e.code, 4, "Error code is present"); 132 | equal(e.type, 4, "Error type is present"); 133 | 134 | equal(e.msg, "Error message", "Error message is present"); 135 | equal(e.message, "Error message", "Error message is present"); 136 | } 137 | 138 | }); 139 | 140 | })(); 141 | -------------------------------------------------------------------------------- /tests/tests.encoding.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true jquery:true */ 2 | /*globals deepEqual equal ok assert test module */ 3 | (function() { 4 | "use strict"; 5 | module("Encoding"); 6 | 7 | /** 8 | * Serialize an Element to a string easily 9 | */ 10 | var s = (function() { 11 | var serializer = new XMLSerializer(); 12 | return function($node) { 13 | return serializer.serializeToString($node[0]); 14 | }; 15 | })(); 16 | 17 | /** 18 | * A wrapper around test functions that makes an $xml function for testing 19 | * with. Kinda like a Python decorator 20 | */ 21 | var need$xml = function(fn) { 22 | return function() { 23 | var doc = document.implementation.createDocument(null, null, null); 24 | var $xml = function(name) { 25 | return $(doc.createElement(name)); 26 | }; 27 | 28 | var args = [].slice.call(arguments); 29 | args.unshift($xml); 30 | 31 | return fn.apply(this, args); 32 | }; 33 | }; 34 | 35 | test("JavaScript primitive value encoding", need$xml(function($xml) { 36 | var types = $.xmlrpc.types; 37 | 38 | deepEqual(types.boolean.encode(true, $xml), $xml('boolean').text('1'), 39 | "Boolean true encodes to 1"); 40 | 41 | deepEqual(types.boolean.encode(false, $xml), $xml('boolean').text('0'), 42 | "Boolean true encodes to 0"); 43 | 44 | 45 | deepEqual(types.int.encode(3, $xml), $xml('int').text('3'), 46 | "Integer 3 encodes to 3"); 47 | deepEqual(types.i8.encode(4, $xml), $xml('i8').text('4'), 48 | "Integer 3 encodes to 4"); 49 | deepEqual(types.double.encode(5.5, $xml), $xml('double').text('5.5'), 50 | "Double 5.5 encodes to 5.5"); 51 | 52 | deepEqual(types.nil.encode(null, $xml), $xml('nil'), 53 | "Null encodes to "); 54 | deepEqual(types.nil.encode("hello", $xml), $xml('nil'), 55 | "Null encodes to when supplied a non-null value"); 56 | 57 | deepEqual(types.string.encode("Hello, World!", $xml), $xml('string').text("Hello, World!"), 58 | "String encodes to ..."); 59 | deepEqual(types.string.encode("", $xml), $xml('string').text(""), 60 | "Empty String encodes to "); 61 | 62 | var timestamp = 1350943077107; 63 | var datestring = "2012-10-22T21:57:57Z"; 64 | var date = new Date(); 65 | date.setTime(timestamp); 66 | deepEqual(types['datetime.iso8601'].encode(date, $xml), $xml('dateTime.iso8601').text(datestring), 67 | "Date encodes to ..."); 68 | 69 | })); 70 | 71 | test("Array encoding", need$xml(function($xml) { 72 | var types = $.xmlrpc.types; 73 | 74 | equal(s(types.array.encode([4, "Hello"], $xml)), 75 | '4Hello', 76 | "Simple array encodes"); 77 | 78 | // If not all browsers encode this to , this will fail. 79 | equal(s(types.array.encode([], $xml)), 80 | '', 81 | "Empty array encodes"); 82 | 83 | equal(s(types.array.encode([1, [2]], $xml)), 84 | '' + 85 | '1' + 86 | '' + 87 | '2' + 88 | '' + 89 | '', 90 | "Array containing array encodes"); 91 | })); 92 | 93 | test("Guessing types", need$xml(function($xml) { 94 | ok($.xmlrpc.toXmlRpc(4, $xml).is('int'), 95 | "Number 4 guessed to be "); 96 | 97 | ok($.xmlrpc.toXmlRpc(4.5, $xml).is('double'), 98 | "Number 4.5 guessed to be "); 99 | 100 | ok($.xmlrpc.toXmlRpc(true, $xml).is('boolean'), 101 | "Boolean guessed to be "); 102 | 103 | ok($.xmlrpc.toXmlRpc(null, $xml).is('nil'), 104 | "null guessed to be "); 105 | 106 | ok($.xmlrpc.toXmlRpc(undefined, $xml).is('nil'), 107 | "undefined guessed to be "); 108 | 109 | ok($.xmlrpc.toXmlRpc("Hello", $xml).is('string'), 110 | "String guessed to be "); 111 | 112 | ok($.xmlrpc.toXmlRpc(new Date(), $xml).is('dateTime\\.iso8601'), 113 | "Date guessed to be "); 114 | 115 | ok($.xmlrpc.toXmlRpc({foo: 'bar'}, $xml).is('struct'), 116 | "Object guessed to be "); 117 | 118 | ok($.xmlrpc.toXmlRpc([], $xml).is('array'), 119 | "Array guessed to be "); 120 | 121 | ok($.xmlrpc.toXmlRpc(new ArrayBuffer(), $xml).is('base64'), 122 | "ArrayBuffer guessed to be "); 123 | })); 124 | 125 | })(); 126 | -------------------------------------------------------------------------------- /jquery.xmlrpc.min.js: -------------------------------------------------------------------------------- 1 | !function(e){"use strict";var t=function(){Error.apply(this,arguments)};t.prototype=new Error,t.prototype.type="XML-RPC fault";var n=e.xmlrpc=function(t,i){2===arguments.length?i.url=t:(i=t,t=i.url),i.dataType="xml json",i.type="POST",i.contentType="text/xml",i.converters={"xml json":n.parseDocument};var r=n.document(i.methodName,i.params||[]);return i.data="XMLSerializer"in window?(new window.XMLSerializer).serializeToString(r):r.xml,e.ajax(i)};n.createXMLDocument=function(){if(document.implementation&&"createDocument"in document.implementation)return document.implementation.createDocument(null,null,null);var e,t,n=["MSXML6.DomDocument","MSXML3.DomDocument","MSXML2.DomDocument","MSXML.DomDocument","Microsoft.XmlDom"];for(e=0,t=n.length;t>e;e++)try{return new ActiveXObject(n[e])}catch(i){}},n.document=function(t,i){var r=n.createXMLDocument(),a=function(t){return e(r.createElement(t))},o=a("methodName").text(t),s=a("params").append(e.map(i,function(e){var t=a("value").append(n.toXmlRpc(e,a));return a("param").append(t)})),u=a("methodCall").append(o,s);return r.appendChild(u.get(0)),r};var i=function(e){return e===parseInt(e,10)&&!isNaN(e)};n.toXmlRpc=function(t,n){if(t instanceof r)return t.toXmlRpc(n);var a=e.xmlrpc.types,o=e.type(t);switch(o){case"undefined":case"null":return a.nil.encode(t,n);case"date":return a["datetime.iso8601"].encode(t,n);case"object":return t instanceof ArrayBuffer?a.base64.encode(t,n):a.struct.encode(t,n);case"number":return i(t)?a["int"].encode(t,n):a["double"].encode(t,n);case"array":case"boolean":case"string":return a[o].encode(t,n);default:throw new Error("Unknown type",t)}},n.parseDocument=function(i){var r=e(i),a=r.children("methodresponse"),o=a.find("> fault");if(0===o.length){var s=a.find("> params > param > value > *"),u=s.toArray().map(n.parseNode);return u}var l=n.parseNode(o.find("> value > *").get(0)),c=new t(l.faultString);throw c.msg=c.message=l.faultString,c.type=c.code=l.faultCode,c},n.parseNode=function(e){if(void 0===e)return null;var t=e.nodeName.toLowerCase();if(t in n.types)return n.types[t].decode(e);throw new Error("Unknown type "+t)},n.parseValue=function(t){var i=e(t).children()[0];return i?n.parseNode(i):e(t).text()};var r=function(){};e.xmlrpc.types={},n.makeType=function(t,i,a,o){var s;if(s=function(e){this.value=e},s.prototype=new r,s.prototype.tagName=t,i){var u=a,l=o;a=function(e,t){var n=u(e);return t(s.tagName).text(n)},o=function(t){return l(e(t).text(),t)}}s.prototype.toXmlRpc=function(e){return s.encode(this.value,e)},s.tagName=t,s.encode=a,s.decode=o,n.types[t.toLowerCase()]=s};var a=function(e){return""+Math.floor(e)},o=function(e){return parseInt(e,10)};n.makeType("int",!0,a,o),n.makeType("i4",!0,a,o),n.makeType("i8",!0,a,o),n.makeType("i16",!0,a,o),n.makeType("i32",!0,a,o),n.makeType("double",!0,String,function(e){return parseFloat(e,10)}),n.makeType("string",!0,String,String),n.makeType("boolean",!0,function(e){return e?"1":"0"},function(e){return"1"===e});var s=function(e){return 10>e?"0"+e:e};n.makeType("dateTime.iso8601",!0,function(e){return[e.getUTCFullYear(),"-",s(e.getUTCMonth()+1),"-",s(e.getUTCDate()),"T",s(e.getUTCHours()),":",s(e.getUTCMinutes()),":",s(e.getUTCSeconds()),"Z"].join("")},function(e){return new Date(e)}),n.binary=function(){var e="=",t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".split(""),n=t.reduce(function(e,t,n){return e[t]=n,e},{});return{toBase64:function(n){for(var i,r=[],a=new Uint8Array(n),o=0;o>18)%64]),r.push(t[(i>>12)%64]),r.push(t[(i>>6)%64]),r.push(t[(i>>0)%64]);for(var s=3-(n.byteLength%3||3);s--;)r[r.length-s-1]=e;return r.join("")},fromBase64:function(t){t=t.replace(/\s/g, '');var i=t.length,r=3*(i/4);t.charAt(i-1)===e&&r--,t.charAt(i-2)===e&&r--;for(var a,o=new ArrayBuffer(r),s=new Uint8Array(o),u=0,l=0;i>u;u+=4,l+=3)a=(n[t[u+0]]<<18)+(n[t[u+1]]<<12)+(n[t[u+2]]<<6)+(n[t[u+3]]<<0),s[l+0]=(a>>16)%256,s[l+1]=(a>>8)%256,s[l+2]=(a>>0)%256;return o}}}(),n.makeType("base64",!0,function(e){return n.binary.toBase64(e)},function(e){return n.binary.fromBase64(e)}),n.makeType("nil",!1,function(e,t){return t("nil")},function(){return null}),n.makeType("struct",!1,function(t,i){var r=i("struct");return e.each(t,function(e,t){var a=i("name").text(e),o=i("value").append(n.toXmlRpc(t,i));r.append(i("member").append(a,o))}),r},function(t){return e(t).find("> member").toArray().reduce(function(t,i){var r=e(i),a=r.find("> name").text(),o=n.parseValue(r.find("> value"));return t[a]=o,t},{})}),n.makeType("array",!1,function(t,i){var r=i("array"),a=i("data");return e.each(t,function(e,t){a.append(i("value").append(n.toXmlRpc(t,i)))}),r.append(a),r},function(t){return e(t).find("> data > value").toArray().map(n.parseValue)}),n.force=function(e,t){return new n.types[e](t)}}(jQuery); 2 | -------------------------------------------------------------------------------- /tests/tests.decoding.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true jquery:true */ 2 | /*globals deepEqual equal ok assert test module */ 3 | (function() { 4 | "use strict"; 5 | module("Decoding"); 6 | 7 | /** 8 | * Generate an element from a string, and return just that element 9 | */ 10 | function el(xml) { 11 | return $(xml)[0]; 12 | } 13 | 14 | test("JavaScript primitive value decoding", function($xml) { 15 | 16 | equal($.xmlrpc.parseNode(el('1')), true, 17 | ' true node decodes'); 18 | 19 | equal($.xmlrpc.parseNode(el('0')), false, 20 | ' false node decodes'); 21 | 22 | 23 | equal($.xmlrpc.parseNode(el('4')), 4, 24 | ' node decodes'); 25 | equal($.xmlrpc.parseNode(el('5')), 5, 26 | ' node decodes'); 27 | equal($.xmlrpc.parseNode(el('6')), 6, 28 | ' node decodes'); 29 | equal($.xmlrpc.parseNode(el('7')), 7, 30 | ' node decodes'); 31 | equal($.xmlrpc.parseNode(el('8.9')), 8.9, 32 | ' node decodes'); 33 | 34 | equal($.xmlrpc.parseNode(el('')), null, 35 | ' node decodes'); 36 | equal($.xmlrpc.parseNode(el('Hello')), null, 37 | ' node decodes, even when not empty'); 38 | 39 | equal($.xmlrpc.parseNode(el('Hello')), "Hello", 40 | ' node decodes'); 41 | 42 | equal($.xmlrpc.parseNode(el('')), "", 43 | 'Empty node decodes'); 44 | 45 | var timestamp = 1350943077000; 46 | var datestring = "2012-10-22T21:57:57Z"; 47 | var dateNode = el('' + datestring + ''); 48 | equal($.xmlrpc.parseNode(dateNode).getTime(), timestamp, 49 | " node decodes"); 50 | }); 51 | 52 | test("Array decoding", function($xml) { 53 | deepEqual($.xmlrpc.parseNode(el('4Hello')), 54 | [4, "Hello"], 55 | "Simple array decodes"); 56 | 57 | deepEqual($.xmlrpc.parseNode(el('')), 58 | [], 59 | "Empty array decodes"); 60 | 61 | deepEqual( 62 | $.xmlrpc.parseNode(el( 63 | '' + 64 | '1' + 65 | '' + 66 | '2' + 67 | '' + 68 | '' 69 | )), 70 | [1, [2]], 71 | "Array containing array encodes"); 72 | 73 | // Childless value nodes should be treated like string nodes 74 | deepEqual( 75 | $.xmlrpc.parseNode(el( 76 | '' + 77 | 'String node' + 78 | '' + 79 | 'Raw value string' + 80 | '' 81 | )), 82 | ["String node", "", "Raw value string"], 83 | "Array containing childless nodes parses correctly"); 84 | }); 85 | 86 | test("Struct decoding", function($xml) { 87 | deepEqual( 88 | $.xmlrpc.parseNode(el( 89 | '' + 90 | '' + 91 | 'foo' + 92 | '4' + 93 | '' + 94 | '' + 95 | 'bar' + 96 | 'Hello' + 97 | '' + 98 | '')), 99 | {foo: 4, bar: "Hello"}, 100 | "Simple decodes"); 101 | 102 | deepEqual($.xmlrpc.parseNode(el('')), {}, 103 | "Empty decodes"); 104 | 105 | deepEqual( 106 | $.xmlrpc.parseNode(el( 107 | '' + 108 | '' + 109 | 'foo' + 110 | '4' + 111 | '' + 112 | '' + 113 | 'bar' + 114 | '' + 115 | '' + 116 | 'baz' + 117 | '5' + 118 | '' + 119 | '' + 120 | '' + 121 | '')), 122 | {foo: 4, bar: {baz: 5}}, 123 | "struct containing struct decodes"); 124 | 125 | deepEqual( 126 | $.xmlrpc.parseNode(el( 127 | '' + 128 | '' + 129 | 'stringNode' + 130 | 'String node' + 131 | '' + 132 | '' + 133 | 'emptyValue' + 134 | '' + 135 | '' + 136 | '' + 137 | 'rawStringValue' + 138 | 'Raw string value' + 139 | '' + 140 | '')), 141 | { 142 | stringNode: "String node", 143 | emptyValue: "", 144 | rawStringValue: "Raw string value" 145 | }, 146 | "Struct with childless nodes parses correctly"); 147 | }); 148 | 149 | })(); 150 | -------------------------------------------------------------------------------- /tests/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests ol { 115 | margin-top: 0.5em; 116 | padding: 0.5em; 117 | 118 | background-color: #fff; 119 | 120 | border-radius: 5px; 121 | -moz-border-radius: 5px; 122 | -webkit-border-radius: 5px; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | padding: 5px; 166 | background-color: #fff; 167 | border-bottom: none; 168 | list-style-position: inside; 169 | } 170 | 171 | /*** Passing Styles */ 172 | 173 | #qunit-tests li li.pass { 174 | color: #3c510c; 175 | background-color: #fff; 176 | border-left: 10px solid #C6E746; 177 | } 178 | 179 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 180 | #qunit-tests .pass .test-name { color: #366097; } 181 | 182 | #qunit-tests .pass .test-actual, 183 | #qunit-tests .pass .test-expected { color: #999999; } 184 | 185 | #qunit-banner.qunit-pass { background-color: #C6E746; } 186 | 187 | /*** Failing Styles */ 188 | 189 | #qunit-tests li li.fail { 190 | color: #710909; 191 | background-color: #fff; 192 | border-left: 10px solid #EE5757; 193 | white-space: pre; 194 | } 195 | 196 | #qunit-tests > li:last-child { 197 | border-radius: 0 0 5px 5px; 198 | -moz-border-radius: 0 0 5px 5px; 199 | -webkit-border-bottom-right-radius: 5px; 200 | -webkit-border-bottom-left-radius: 5px; 201 | } 202 | 203 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 204 | #qunit-tests .fail .test-name, 205 | #qunit-tests .fail .module-name { color: #000000; } 206 | 207 | #qunit-tests .fail .test-actual { color: #EE5757; } 208 | #qunit-tests .fail .test-expected { color: green; } 209 | 210 | #qunit-banner.qunit-fail { background-color: #EE5757; } 211 | 212 | 213 | /** Result */ 214 | 215 | #qunit-testresult { 216 | padding: 0.5em 0.5em 0.5em 2.5em; 217 | 218 | color: #2b81af; 219 | background-color: #D2E0E6; 220 | 221 | border-bottom: 1px solid white; 222 | } 223 | #qunit-testresult .module-name { 224 | font-weight: bold; 225 | } 226 | 227 | /** Fixture */ 228 | 229 | #qunit-fixture { 230 | position: absolute; 231 | top: -10000px; 232 | left: -10000px; 233 | width: 1000px; 234 | height: 1000px; 235 | } 236 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\jQueryXML-RPC.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\jQueryXML-RPC.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/jQueryXML-RPC.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/jQueryXML-RPC.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/jQueryXML-RPC" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/jQueryXML-RPC" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # jQuery XML-RPC documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Oct 22 19:51:25 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = [] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'jQuery XML-RPC' 44 | copyright = u'2012, Tim Heap' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.1' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.1.0' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | 90 | # -- Options for HTML output --------------------------------------------------- 91 | 92 | # The theme to use for HTML and HTML Help pages. See the documentation for 93 | # a list of builtin themes. 94 | html_theme = 'default' 95 | 96 | # Theme options are theme-specific and customize the look and feel of a theme 97 | # further. For a list of options available for each theme, see the 98 | # documentation. 99 | #html_theme_options = {} 100 | 101 | # Add any paths that contain custom themes here, relative to this directory. 102 | #html_theme_path = [] 103 | 104 | # The name for this set of Sphinx documents. If None, it defaults to 105 | # " v documentation". 106 | #html_title = None 107 | 108 | # A shorter title for the navigation bar. Default is the same as html_title. 109 | #html_short_title = None 110 | 111 | # The name of an image file (relative to this directory) to place at the top 112 | # of the sidebar. 113 | #html_logo = None 114 | 115 | # The name of an image file (within the static path) to use as favicon of the 116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 117 | # pixels large. 118 | #html_favicon = None 119 | 120 | # Add any paths that contain custom static files (such as style sheets) here, 121 | # relative to this directory. They are copied after the builtin static files, 122 | # so a file named "default.css" will overwrite the builtin "default.css". 123 | html_static_path = ['_static'] 124 | 125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 126 | # using the given strftime format. 127 | #html_last_updated_fmt = '%b %d, %Y' 128 | 129 | # If true, SmartyPants will be used to convert quotes and dashes to 130 | # typographically correct entities. 131 | #html_use_smartypants = True 132 | 133 | # Custom sidebar templates, maps document names to template names. 134 | #html_sidebars = {} 135 | 136 | # Additional templates that should be rendered to pages, maps page names to 137 | # template names. 138 | #html_additional_pages = {} 139 | 140 | # If false, no module index is generated. 141 | #html_domain_indices = True 142 | 143 | # If false, no index is generated. 144 | #html_use_index = True 145 | 146 | # If true, the index is split into individual pages for each letter. 147 | #html_split_index = False 148 | 149 | # If true, links to the reST sources are added to the pages. 150 | #html_show_sourcelink = True 151 | 152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 153 | #html_show_sphinx = True 154 | 155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 156 | #html_show_copyright = True 157 | 158 | # If true, an OpenSearch description file will be output, and all pages will 159 | # contain a tag referring to it. The value of this option must be the 160 | # base URL from which the finished HTML is served. 161 | #html_use_opensearch = '' 162 | 163 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 164 | #html_file_suffix = None 165 | 166 | # Output file base name for HTML help builder. 167 | htmlhelp_basename = 'jQueryXML-RPCdoc' 168 | 169 | 170 | # -- Options for LaTeX output -------------------------------------------------- 171 | 172 | latex_elements = { 173 | # The paper size ('letterpaper' or 'a4paper'). 174 | #'papersize': 'letterpaper', 175 | 176 | # The font size ('10pt', '11pt' or '12pt'). 177 | #'pointsize': '10pt', 178 | 179 | # Additional stuff for the LaTeX preamble. 180 | #'preamble': '', 181 | } 182 | 183 | # Grouping the document tree into LaTeX files. List of tuples 184 | # (source start file, target name, title, author, documentclass [howto/manual]). 185 | latex_documents = [ 186 | ('index', 'jQueryXML-RPC.tex', u'jQuery XML-RPC Documentation', 187 | u'Tim Heap', 'manual'), 188 | ] 189 | 190 | # The name of an image file (relative to this directory) to place at the top of 191 | # the title page. 192 | #latex_logo = None 193 | 194 | # For "manual" documents, if this is true, then toplevel headings are parts, 195 | # not chapters. 196 | #latex_use_parts = False 197 | 198 | # If true, show page references after internal links. 199 | #latex_show_pagerefs = False 200 | 201 | # If true, show URL addresses after external links. 202 | #latex_show_urls = False 203 | 204 | # Documents to append as an appendix to all manuals. 205 | #latex_appendices = [] 206 | 207 | # If false, no module index is generated. 208 | #latex_domain_indices = True 209 | 210 | 211 | # -- Options for manual page output -------------------------------------------- 212 | 213 | # One entry per manual page. List of tuples 214 | # (source start file, name, description, authors, manual section). 215 | man_pages = [ 216 | ('index', 'jqueryxml-rpc', u'jQuery XML-RPC Documentation', 217 | [u'Tim Heap'], 1) 218 | ] 219 | 220 | # If true, show URL addresses after external links. 221 | #man_show_urls = False 222 | 223 | 224 | # -- Options for Texinfo output ------------------------------------------------ 225 | 226 | # Grouping the document tree into Texinfo files. List of tuples 227 | # (source start file, target name, title, author, 228 | # dir menu entry, description, category) 229 | texinfo_documents = [ 230 | ('index', 'jQueryXML-RPC', u'jQuery XML-RPC Documentation', 231 | u'Tim Heap', 'jQueryXML-RPC', 'One line description of project.', 232 | 'Miscellaneous'), 233 | ] 234 | 235 | # Documents to append as an appendix to all manuals. 236 | #texinfo_appendices = [] 237 | 238 | # If false, no module index is generated. 239 | #texinfo_domain_indices = True 240 | 241 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 242 | #texinfo_show_urls = 'footnote' 243 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | === 4 | API 5 | === 6 | 7 | The following API is exposed in case you need to extend this library. 8 | You should not have to use this API in everyday use of the library. 9 | 10 | .. _xmlrpc: 11 | 12 | $.xmlrpc() 13 | ---------- 14 | 15 | Call a remote procedure. 16 | This is a small wrapper around `jQuery.ajax() `_ 17 | so see the documentation for that for more information. 18 | It takes the following arguments: 19 | 20 | ``url`` 21 | The URL of the service to call. Optional. 22 | If not specified, this is pulled from the options dict 23 | 24 | ``options`` 25 | Options for the request. 26 | Most options are passed straight through to ``jQuery.ajax()``, 27 | with the exception of two keys. 28 | 29 | The ``methodName`` key must always be supplied, and must be a string. 30 | It is used as the ```` for the call 31 | 32 | The ``params`` key can be used to send parameters. 33 | This must be an array of values. 34 | Leave this blank, or supply and empty array to send no parameters. 35 | 36 | See :ref:`types` for more information on how JavaScript types are translated to XML-RPC types 37 | 38 | Getting data back 39 | ~~~~~~~~~~~~~~~~~ 40 | 41 | When the XML-RPC call returns, 42 | the contents of the ```` element are parsed into JSON and 43 | supplied to the ``success`` callback of the AJAX call as the first parameter, 44 | much like a JSON request. 45 | 46 | Handling errors 47 | ~~~~~~~~~~~~~~~ 48 | 49 | If any HTTP errors occur during transport, 50 | the normal jQuery AJAX error handling will be used. 51 | If the XML-RPC service successfully replies, 52 | but replies with a ```` response, 53 | an ``$.xmlrpc.XmlRpcFault`` is thrown. 54 | This error will be sent as the third parameter to the ``error`` callback 55 | of the AJAX call, as with other errors. 56 | 57 | .. _xmlrpc-document: 58 | 59 | $.xmlrpc.document() 60 | ------------------- 61 | 62 | Make an XML-RPC document from a method name and a set of parameters. 63 | It takes the following arguments: 64 | 65 | ``methodName`` 66 | This is method put in the ```` element from XML-RPC. It should be a 67 | string. The XML-RPC service you are communicating with will determine valid 68 | method names you can call. 69 | 70 | ``params`` 71 | An array of parameters to send. 72 | Specify an empty array if you do not want to send any parameters. 73 | 74 | Example 75 | ~~~~~~~ 76 | 77 | The JavaScript call: 78 | 79 | .. code-block:: javascript 80 | :linenos: 81 | 82 | $.xmlrpc.document('foo', ['bar, true, [1, 2, 3]]); 83 | 84 | produces the following XML document (with out the whitespace): 85 | 86 | .. code-block:: xml 87 | 88 | 89 | foo 90 | 91 | 92 | bar 93 | 94 | 95 | 1 96 | 97 | 98 | 99 | 1 100 | 2 101 | 3 102 | 103 | 104 | 105 | 106 | 107 | .. _xmlrpc-toXmlRpc: 108 | 109 | $.xmlrpc.toXmlRpc() 110 | ------------------- 111 | 112 | Take a value, and encode it as an XML-RPC node. 113 | Because the XML nodes must be created by the XML documents own ``createElement``, 114 | this can not be used outside of a call to ``$.xmlrpc.document``. 115 | It takes the following arguments: 116 | 117 | ``value`` 118 | The value to encode 119 | 120 | ``$xml`` 121 | A helper function to create an XML node on the document. 122 | It is then returned, wrapped by ``jQuery``. 123 | 124 | .. _xmlrpc-parseDocument: 125 | 126 | $.xmlrpc.parseDocument() 127 | ------------------------ 128 | 129 | Parse an XML-RPC document, and return its contents. 130 | If the document represents an XML-RPC fault, 131 | an ``$.xmlrpc.XmlRpcFault`` is thrown. 132 | It takes the following arguments: 133 | 134 | Example 135 | ~~~~~~~ 136 | 137 | The following XML document: 138 | 139 | .. code-block:: xml 140 | 141 | 142 | 143 | 144 | 145 | foo 146 | 147 | 148 | 3 149 | 150 | 151 | 152 | 153 | foo 154 | 1 155 | 156 | 157 | bar 158 | 2 159 | 160 | 161 | 162 | 163 | 164 | 165 | parsed by: 166 | 167 | .. code-block:: javascript 168 | 169 | $.xmlrpc.parseDocument(doc); 170 | 171 | would result in the JSON document: 172 | 173 | .. code-block:: javascript 174 | 175 | [ 176 | 'foo', 177 | 3, 178 | { 179 | foo: 1, 180 | bar: 2 181 | } 182 | ] 183 | 184 | .. _xmlrpc-parseNode: 185 | 186 | $.xmlrpc.parseNode() 187 | -------------------- 188 | 189 | Take a single XML element, and return the JSON equivalent of it. 190 | It takes one argument: 191 | 192 | ``node`` 193 | The XML node to decode. 194 | It should be be one of the types registered with 195 | :ref:`xmlrpc-makeType`. 196 | If the type can not be found, and error is thrown. 197 | 198 | Example 199 | ~~~~~~~ 200 | 201 | The XML element: 202 | 203 | .. code-block:: xml 204 | 205 | 206 | 207 | foo 208 | 1 209 | 210 | 211 | bar 212 | 2 213 | 214 | 215 | 216 | would be parsed by calling: 217 | 218 | .. code-block:: javascript 219 | 220 | $.xmlrpc.parseNode(node) 221 | 222 | resulting in the JSON: 223 | 224 | .. code-block:: javascript 225 | 226 | { 227 | foo: 1, 228 | bar: 2 229 | } 230 | 231 | .. _xmlrpc-makeType: 232 | 233 | $.xmlrpc.makeType() 234 | ------------------- 235 | 236 | Add a XML-RPC type to the library. 237 | The library will then know how to decode elements of this type when they are returned. 238 | It takes the following arguments: 239 | 240 | ``tag`` 241 | The name of the XML-RPC element this represents. 242 | Example: ``'boolean'`` 243 | 244 | ``simple`` 245 | If the element is a simple type or not. 246 | All standard elements except ```` and ```` are simple types. 247 | The encoding a decoding functions of simple types are simplified, 248 | as they just deal with the text content of the elements. 249 | 250 | ``encode`` 251 | Take a JavaScript value, and encode it to an XML-RPC element. 252 | Receives the value to be encoded, 253 | and a helper function used to create XML nodes on the correct document - 254 | This helper MUST be used to create XML nodes for child elements. 255 | 256 | Simple types need only return the text of the node, 257 | creating the node is handled for you. 258 | 259 | ``decode`` 260 | Take an XML element, and decode it to a JavaScript representation. 261 | 262 | Simple types receive the text of the node instead of the node itself. 263 | 264 | Example 265 | ~~~~~~~ 266 | 267 | A simple boolean node: 268 | 269 | .. code-block:: javascript 270 | 271 | // Boolean type. True == '1', False == '0' 272 | $.xmlrpc.makeType('boolean', true, function(value) { 273 | return value ? '1' : '0'; 274 | }, function(text) { 275 | return text == '1'; 276 | }); 277 | 278 | A complex, custom element: 279 | 280 | .. code-block:: javascript 281 | 282 | /** 283 | * Convert 284 | * {foo: 1, bar: "hello"} 285 | * into 286 | * 1hello 287 | * Note the call to `$.xmlrpc.toXmlRpc`` to recursively encode the `bar` element. 288 | */ 289 | $.xmlrpc.makeType('custom', false, function(value, $xml) { 290 | return $xml('custom').append([ 291 | $xml('foo').text($.xmlrpc.toXmlRpc(value.foo, $xml)), 292 | $xml('bar').text($.xmlrpc.toXmlRpc(value.foo, $xml)) 293 | ]); 294 | }, function(node) { 295 | return { 296 | foo: parseInt($(node).find('> foo').text()), 297 | bar: fromXmlRpc($(node).find('> bar > *').get(0)), 298 | } 299 | }); 300 | 301 | .. _xmlrpc-force: 302 | 303 | $.xmlrpc.force() 304 | ---------------- 305 | 306 | Force a value to be encoded as a certain type in XML-RPC. 307 | It takes the following arguments: 308 | 309 | ``type`` 310 | The type to force the value to. One of the XML-RPC types named in the 311 | [types documentation][types], or one of the custom types added with 312 | ``$.xmlrpc.makeType``. 313 | 314 | ``value`` 315 | Any value that will be encoded as the type. 316 | 317 | Example 318 | ~~~~~~~ 319 | 320 | Force a float to be encoded as an i8, to send as a parameter: 321 | 322 | .. code-block:: javascript 323 | 324 | var forcedValue = $.xmlrpc.force('i8', 4.5) 325 | 326 | $.xmlrpc({ 327 | url: '/RPC2', 328 | methodName: 'foo', 329 | params: [forcedValue] 330 | }); 331 | -------------------------------------------------------------------------------- /jquery.xmlrpc.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true */ 2 | /*global jQuery */ 3 | (function($) { 4 | "use strict"; 5 | 6 | var XmlRpcFault = function() { 7 | Error.apply(this, arguments); 8 | }; 9 | XmlRpcFault.prototype = new Error(); 10 | XmlRpcFault.prototype.type = 'XML-RPC fault'; 11 | 12 | var xmlrpc = $.xmlrpc = function(url, settings) { 13 | 14 | if (arguments.length === 2) { 15 | settings.url = url; 16 | } else { 17 | settings = url; 18 | url = settings.url; 19 | } 20 | 21 | settings.dataType = 'xml json'; 22 | settings.type = 'POST'; 23 | settings.contentType = 'text/xml'; 24 | settings.converters = {'xml json': xmlrpc.parseDocument}; 25 | 26 | var xmlDoc = xmlrpc.document(settings.methodName, settings.params || []); 27 | 28 | if ("XMLSerializer" in window) { 29 | settings.data = new window.XMLSerializer().serializeToString(xmlDoc); 30 | } else { 31 | // IE does not have XMLSerializer 32 | settings.data = xmlDoc.xml; 33 | } 34 | 35 | return $.ajax(settings); 36 | }; 37 | 38 | /** 39 | * Make an XML document node. 40 | */ 41 | xmlrpc.createXMLDocument = function () { 42 | 43 | if (document.implementation && "createDocument" in document.implementation) { 44 | // Most browsers support createDocument 45 | return document.implementation.createDocument(null, null, null); 46 | 47 | } else { 48 | // IE uses ActiveXObject instead of the above. 49 | var i, length, activeX = [ 50 | "MSXML6.DomDocument", "MSXML3.DomDocument", 51 | "MSXML2.DomDocument", "MSXML.DomDocument", "Microsoft.XmlDom" 52 | ]; 53 | for (i = 0, length = activeX.length; i < length; i++) { 54 | try { 55 | return new ActiveXObject(activeX[i]); 56 | } catch(_) {} 57 | } 58 | } 59 | }; 60 | 61 | /** 62 | * Make an XML-RPC document from a method name and a set of parameters 63 | */ 64 | xmlrpc.document = function(name, params) { 65 | var doc = xmlrpc.createXMLDocument(); 66 | 67 | 68 | var $xml = function(name) { 69 | return $(doc.createElement(name)); 70 | }; 71 | 72 | var $methodName = $xml('methodName').text(name); 73 | var $params = $xml('params').append($.map(params, function(param) { 74 | var $value = $xml('value').append(xmlrpc.toXmlRpc(param, $xml)); 75 | return $xml('param').append($value); 76 | })); 77 | var $methodCall = $xml('methodCall').append($methodName, $params); 78 | doc.appendChild($methodCall.get(0)); 79 | return doc; 80 | }; 81 | 82 | var _isInt = function(x) { 83 | return (x === parseInt(x, 10)) && !isNaN(x); 84 | }; 85 | 86 | /** 87 | * Take a JavaScript value, and return an XML node representing the value 88 | * in XML-RPC style. If the value is one of the `XmlRpcType`s, that type is 89 | * used. Otherwise, a best guess is made as to its type. The best guess is 90 | * good enough in the vast majority of cases. 91 | */ 92 | xmlrpc.toXmlRpc = function(item, $xml) { 93 | 94 | if (item instanceof XmlRpcType) { 95 | return item.toXmlRpc($xml); 96 | } 97 | 98 | var types = $.xmlrpc.types; 99 | var type = $.type(item); 100 | 101 | switch (type) { 102 | case "undefined": 103 | case "null": 104 | return types.nil.encode(item, $xml); 105 | 106 | case "date": 107 | return types['datetime.iso8601'].encode(item, $xml); 108 | 109 | case "object": 110 | if (item instanceof ArrayBuffer) { 111 | return types.base64.encode(item, $xml); 112 | } else { 113 | return types.struct.encode(item, $xml); 114 | } 115 | break; 116 | 117 | 118 | case "number": 119 | // Ints and Floats encode differently 120 | if (_isInt(item)) { 121 | return types['int'].encode(item, $xml); 122 | } else { 123 | return types['double'].encode(item, $xml); 124 | } 125 | break; 126 | 127 | case "array": 128 | case "boolean": 129 | case "string": 130 | return types[type].encode(item, $xml); 131 | 132 | default: 133 | throw new Error("Unknown type", item); 134 | } 135 | }; 136 | 137 | /** 138 | * Take an XML-RPC document and decode it to an equivalent JavaScript 139 | * representation. 140 | * 141 | * If the XML-RPC document represents a fault, then an equivalent 142 | * XmlRpcFault will be thrown instead 143 | */ 144 | xmlrpc.parseDocument = function(doc) { 145 | var $doc = $(doc); 146 | var $response = $doc.children('methodresponse'); 147 | 148 | var $fault = $response.find('> fault'); 149 | if ($fault.length === 0) { 150 | var $params = $response.find('> params > param > value > *'); 151 | var json = $params.toArray().map(xmlrpc.parseNode); 152 | return json; 153 | } else { 154 | var fault = xmlrpc.parseNode($fault.find('> value > *').get(0)); 155 | var err = new XmlRpcFault(fault.faultString); 156 | err.msg = err.message = fault.faultString; 157 | err.type = err.code = fault.faultCode; 158 | throw err; 159 | } 160 | }; 161 | 162 | /* 163 | * Take an XML-RPC node, and return the JavaScript equivalent 164 | */ 165 | xmlrpc.parseNode = function(node) { 166 | 167 | // Some XML-RPC services return empty elements. This is not 168 | // legal XML-RPC, but we may as well handle it. 169 | if (node === undefined) { 170 | return null; 171 | } 172 | var nodename = node.nodeName.toLowerCase(); 173 | if (nodename in xmlrpc.types) { 174 | return xmlrpc.types[nodename].decode(node); 175 | } else { 176 | throw new Error('Unknown type ' + nodename); 177 | } 178 | }; 179 | 180 | /* 181 | * Take a node, and return the JavaScript equivalent. 182 | */ 183 | xmlrpc.parseValue = function(value) { 184 | var child = $(value).children()[0]; 185 | if (child) { 186 | // Child nodes should be decoded. 187 | return xmlrpc.parseNode(child); 188 | } else { 189 | // If no child nodes, the value is a plain text node. 190 | return $(value).text(); 191 | } 192 | }; 193 | 194 | var XmlRpcType = function() { }; 195 | 196 | $.xmlrpc.types = {}; 197 | 198 | /** 199 | * Make a XML-RPC type. We use these to encode and decode values. You can 200 | * also force a values type using this. See `$.xmlrpc.force()` 201 | */ 202 | xmlrpc.makeType = function(tagName, simple, encode, decode) { 203 | var Type; 204 | 205 | Type = function(value) { 206 | this.value = value; 207 | }; 208 | Type.prototype = new XmlRpcType(); 209 | Type.prototype.tagName = tagName; 210 | 211 | if (simple) { 212 | var simpleEncode = encode, simpleDecode = decode; 213 | encode = function(value, $xml) { 214 | var text = simpleEncode(value); 215 | return $xml(Type.tagName).text(text); 216 | }; 217 | decode = function(node) { 218 | return simpleDecode($(node).text(), node); 219 | }; 220 | } 221 | Type.prototype.toXmlRpc = function($xml) { 222 | return Type.encode(this.value, $xml); 223 | }; 224 | 225 | Type.tagName = tagName; 226 | Type.encode = encode; 227 | Type.decode = decode; 228 | 229 | xmlrpc.types[tagName.toLowerCase()] = Type; 230 | }; 231 | 232 | 233 | // Number types 234 | var _fromInt = function(value) { return '' + Math.floor(value); }; 235 | var _toInt = function(text, _) { return parseInt(text, 10); }; 236 | 237 | xmlrpc.makeType('int', true, _fromInt, _toInt); 238 | xmlrpc.makeType('i4', true, _fromInt, _toInt); 239 | xmlrpc.makeType('i8', true, _fromInt, _toInt); 240 | xmlrpc.makeType('i16', true, _fromInt, _toInt); 241 | xmlrpc.makeType('i32', true, _fromInt, _toInt); 242 | 243 | xmlrpc.makeType('double', true, String, function(text) { 244 | return parseFloat(text, 10); 245 | }); 246 | 247 | // String type. Fairly simple 248 | xmlrpc.makeType('string', true, String, String); 249 | 250 | // Boolean type. True == '1', False == '0' 251 | xmlrpc.makeType('boolean', true, function(value) { 252 | return value ? '1' : '0'; 253 | }, function(text) { 254 | return text === '1'; 255 | }); 256 | 257 | // Dates are a little trickier 258 | var _pad = function(n) { return n<10 ? '0'+n : n; }; 259 | 260 | xmlrpc.makeType('dateTime.iso8601', true, function(d) { 261 | return [ 262 | d.getUTCFullYear(), '-', _pad(d.getUTCMonth()+1), '-', 263 | _pad(d.getUTCDate()), 'T', _pad(d.getUTCHours()), ':', 264 | _pad(d.getUTCMinutes()), ':', _pad(d.getUTCSeconds()), 'Z' 265 | ].join(''); 266 | }, function(text) { 267 | return new Date(text); 268 | }); 269 | 270 | // Go between a base64 string and an ArrayBuffer 271 | xmlrpc.binary = (function() { 272 | var pad = '='; 273 | var toChars = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 274 | 'abcdefghijklmnopqrstuvwxyz0123456789+/').split(""); 275 | var fromChars = toChars.reduce(function(acc, chr, i) { 276 | acc[chr] = i; 277 | return acc; 278 | }, {}); 279 | 280 | /* 281 | * In the following, three bytes are added together into a 24-bit 282 | * number, which is then split up in to 4 6-bit numbers - or vice versa. 283 | * That is why there is lots of shifting by multiples of 6 and 8, and 284 | * the magic numbers 3 and 4. 285 | * 286 | * The modulo 64 is for converting to base 64, and the modulo 256 is for 287 | * converting to 8-bit numbers. 288 | */ 289 | return { 290 | toBase64: function(ab) { 291 | var acc = []; 292 | 293 | var int8View = new Uint8Array(ab); 294 | var int8Index = 0, int24; 295 | for (; int8Index < int8View.length; int8Index += 3) { 296 | 297 | // Grab three bytes 298 | int24 = 299 | (int8View[int8Index + 0] << 16) + 300 | (int8View[int8Index + 1] << 8) + 301 | (int8View[int8Index + 2] << 0); 302 | 303 | // Push four chars 304 | acc.push(toChars[(int24 >> 18) % 64]); 305 | acc.push(toChars[(int24 >> 12) % 64]); 306 | acc.push(toChars[(int24 >> 6) % 64]); 307 | acc.push(toChars[(int24 >> 0)% 64]); 308 | } 309 | 310 | // Set the last few characters to the padding character 311 | var padChars = 3 - ((ab.byteLength % 3) || 3); 312 | while (padChars--) { 313 | acc[acc.length - padChars - 1] = pad; 314 | } 315 | 316 | return acc.join(''); 317 | }, 318 | 319 | fromBase64: function(base64) { 320 | var base64Len = base64.length; 321 | 322 | // Work out the length of the data, accommodating for padding 323 | var abLen = (base64Len / 4) * 3; 324 | if (base64.charAt(base64Len - 1) === pad) { abLen--; } 325 | if (base64.charAt(base64Len - 2) === pad) { abLen--; } 326 | 327 | // Make the ArrayBuffer, and an Int8Array to work with it 328 | var ab = new ArrayBuffer(abLen); 329 | var int8View = new Uint8Array(ab); 330 | 331 | var base64Index = 0, int8Index = 0, int24; 332 | for (; base64Index < base64Len; base64Index += 4, int8Index += 3) { 333 | 334 | // Grab four chars 335 | int24 = 336 | (fromChars[base64[base64Index + 0]] << 18) + 337 | (fromChars[base64[base64Index + 1]] << 12) + 338 | (fromChars[base64[base64Index + 2]] << 6) + 339 | (fromChars[base64[base64Index + 3]] << 0); 340 | 341 | // Push three bytes 342 | int8View[int8Index + 0] = (int24 >> 16) % 256; 343 | int8View[int8Index + 1] = (int24 >> 8) % 256; 344 | int8View[int8Index + 2] = (int24 >> 0) % 256; 345 | 346 | } 347 | 348 | return ab; 349 | } 350 | }; 351 | })(); 352 | 353 | xmlrpc.makeType('base64', true, function(ab) { 354 | return xmlrpc.binary.toBase64(ab); 355 | }, function(text) { 356 | return xmlrpc.binary.fromBase64(text); 357 | }); 358 | 359 | // Nil/null 360 | xmlrpc.makeType('nil', false, 361 | function(val, $xml) { return $xml('nil'); }, 362 | function(_) { return null; } 363 | ); 364 | 365 | // Structs/Objects 366 | xmlrpc.makeType('struct', false, function(value, $xml) { 367 | var $struct = $xml('struct'); 368 | 369 | $.each(value, function(name, value) { 370 | var $name = $xml('name').text(name); 371 | var $value = $xml('value').append(xmlrpc.toXmlRpc(value, $xml)); 372 | $struct.append($xml('member').append($name, $value)); 373 | }); 374 | 375 | return $struct; 376 | 377 | }, function(node) { 378 | return $(node) 379 | .find('> member') 380 | .toArray() 381 | .reduce(function(struct, el) { 382 | var $el = $(el); 383 | var key = $el.find('> name').text(); 384 | var value = xmlrpc.parseValue($el.find('> value')); 385 | 386 | struct[key] = value; 387 | return struct; 388 | }, {}); 389 | 390 | }); 391 | 392 | // Arrays 393 | xmlrpc.makeType('array', false, function(value, $xml) { 394 | var $array = $xml('array'); 395 | var $data = $xml('data'); 396 | $.each(value, function(i, val) { 397 | $data.append($xml('value').append(xmlrpc.toXmlRpc(val, $xml))); 398 | }); 399 | $array.append($data); 400 | return $array; 401 | }, function(node) { 402 | return $(node).find('> data > value').toArray() 403 | .map(xmlrpc.parseValue); 404 | }); 405 | 406 | 407 | /** 408 | * Force a value to an XML-RPC type. All the usual XML-RPC types are 409 | * supported 410 | */ 411 | xmlrpc.force = function(type, value) { 412 | return new xmlrpc.types[type](value); 413 | }; 414 | 415 | })(jQuery); 416 | -------------------------------------------------------------------------------- /tests/qunit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.10.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | (function( window ) { 12 | 13 | var QUnit, 14 | config, 15 | onErrorFnPrev, 16 | testId = 0, 17 | fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), 18 | toString = Object.prototype.toString, 19 | hasOwn = Object.prototype.hasOwnProperty, 20 | // Keep a local reference to Date (GH-283) 21 | Date = window.Date, 22 | defined = { 23 | setTimeout: typeof window.setTimeout !== "undefined", 24 | sessionStorage: (function() { 25 | var x = "qunit-test-string"; 26 | try { 27 | sessionStorage.setItem( x, x ); 28 | sessionStorage.removeItem( x ); 29 | return true; 30 | } catch( e ) { 31 | return false; 32 | } 33 | }()) 34 | }; 35 | 36 | function Test( settings ) { 37 | extend( this, settings ); 38 | this.assertions = []; 39 | this.testNumber = ++Test.count; 40 | } 41 | 42 | Test.count = 0; 43 | 44 | Test.prototype = { 45 | init: function() { 46 | var a, b, li, 47 | tests = id( "qunit-tests" ); 48 | 49 | if ( tests ) { 50 | b = document.createElement( "strong" ); 51 | b.innerHTML = this.name; 52 | 53 | // `a` initialized at top of scope 54 | a = document.createElement( "a" ); 55 | a.innerHTML = "Rerun"; 56 | a.href = QUnit.url({ testNumber: this.testNumber }); 57 | 58 | li = document.createElement( "li" ); 59 | li.appendChild( b ); 60 | li.appendChild( a ); 61 | li.className = "running"; 62 | li.id = this.id = "qunit-test-output" + testId++; 63 | 64 | tests.appendChild( li ); 65 | } 66 | }, 67 | setup: function() { 68 | if ( this.module !== config.previousModule ) { 69 | if ( config.previousModule ) { 70 | runLoggingCallbacks( "moduleDone", QUnit, { 71 | name: config.previousModule, 72 | failed: config.moduleStats.bad, 73 | passed: config.moduleStats.all - config.moduleStats.bad, 74 | total: config.moduleStats.all 75 | }); 76 | } 77 | config.previousModule = this.module; 78 | config.moduleStats = { all: 0, bad: 0 }; 79 | runLoggingCallbacks( "moduleStart", QUnit, { 80 | name: this.module 81 | }); 82 | } else if ( config.autorun ) { 83 | runLoggingCallbacks( "moduleStart", QUnit, { 84 | name: this.module 85 | }); 86 | } 87 | 88 | config.current = this; 89 | 90 | this.testEnvironment = extend({ 91 | setup: function() {}, 92 | teardown: function() {} 93 | }, this.moduleTestEnvironment ); 94 | 95 | runLoggingCallbacks( "testStart", QUnit, { 96 | name: this.testName, 97 | module: this.module 98 | }); 99 | 100 | // allow utility functions to access the current test environment 101 | // TODO why?? 102 | QUnit.current_testEnvironment = this.testEnvironment; 103 | 104 | if ( !config.pollution ) { 105 | saveGlobal(); 106 | } 107 | if ( config.notrycatch ) { 108 | this.testEnvironment.setup.call( this.testEnvironment ); 109 | return; 110 | } 111 | try { 112 | this.testEnvironment.setup.call( this.testEnvironment ); 113 | } catch( e ) { 114 | QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 115 | } 116 | }, 117 | run: function() { 118 | config.current = this; 119 | 120 | var running = id( "qunit-testresult" ); 121 | 122 | if ( running ) { 123 | running.innerHTML = "Running:
" + this.name; 124 | } 125 | 126 | if ( this.async ) { 127 | QUnit.stop(); 128 | } 129 | 130 | if ( config.notrycatch ) { 131 | this.callback.call( this.testEnvironment, QUnit.assert ); 132 | return; 133 | } 134 | 135 | try { 136 | this.callback.call( this.testEnvironment, QUnit.assert ); 137 | } catch( e ) { 138 | QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); 139 | // else next test will carry the responsibility 140 | saveGlobal(); 141 | 142 | // Restart the tests if they're blocking 143 | if ( config.blocking ) { 144 | QUnit.start(); 145 | } 146 | } 147 | }, 148 | teardown: function() { 149 | config.current = this; 150 | if ( config.notrycatch ) { 151 | this.testEnvironment.teardown.call( this.testEnvironment ); 152 | return; 153 | } else { 154 | try { 155 | this.testEnvironment.teardown.call( this.testEnvironment ); 156 | } catch( e ) { 157 | QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); 158 | } 159 | } 160 | checkPollution(); 161 | }, 162 | finish: function() { 163 | config.current = this; 164 | if ( config.requireExpects && this.expected == null ) { 165 | QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); 166 | } else if ( this.expected != null && this.expected != this.assertions.length ) { 167 | QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); 168 | } else if ( this.expected == null && !this.assertions.length ) { 169 | QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); 170 | } 171 | 172 | var assertion, a, b, i, li, ol, 173 | test = this, 174 | good = 0, 175 | bad = 0, 176 | tests = id( "qunit-tests" ); 177 | 178 | config.stats.all += this.assertions.length; 179 | config.moduleStats.all += this.assertions.length; 180 | 181 | if ( tests ) { 182 | ol = document.createElement( "ol" ); 183 | 184 | for ( i = 0; i < this.assertions.length; i++ ) { 185 | assertion = this.assertions[i]; 186 | 187 | li = document.createElement( "li" ); 188 | li.className = assertion.result ? "pass" : "fail"; 189 | li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); 190 | ol.appendChild( li ); 191 | 192 | if ( assertion.result ) { 193 | good++; 194 | } else { 195 | bad++; 196 | config.stats.bad++; 197 | config.moduleStats.bad++; 198 | } 199 | } 200 | 201 | // store result when possible 202 | if ( QUnit.config.reorder && defined.sessionStorage ) { 203 | if ( bad ) { 204 | sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); 205 | } else { 206 | sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); 207 | } 208 | } 209 | 210 | if ( bad === 0 ) { 211 | ol.style.display = "none"; 212 | } 213 | 214 | // `b` initialized at top of scope 215 | b = document.createElement( "strong" ); 216 | b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; 217 | 218 | addEvent(b, "click", function() { 219 | var next = b.nextSibling.nextSibling, 220 | display = next.style.display; 221 | next.style.display = display === "none" ? "block" : "none"; 222 | }); 223 | 224 | addEvent(b, "dblclick", function( e ) { 225 | var target = e && e.target ? e.target : window.event.srcElement; 226 | if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { 227 | target = target.parentNode; 228 | } 229 | if ( window.location && target.nodeName.toLowerCase() === "strong" ) { 230 | window.location = QUnit.url({ testNumber: test.testNumber }); 231 | } 232 | }); 233 | 234 | // `li` initialized at top of scope 235 | li = id( this.id ); 236 | li.className = bad ? "fail" : "pass"; 237 | li.removeChild( li.firstChild ); 238 | a = li.firstChild; 239 | li.appendChild( b ); 240 | li.appendChild ( a ); 241 | li.appendChild( ol ); 242 | 243 | } else { 244 | for ( i = 0; i < this.assertions.length; i++ ) { 245 | if ( !this.assertions[i].result ) { 246 | bad++; 247 | config.stats.bad++; 248 | config.moduleStats.bad++; 249 | } 250 | } 251 | } 252 | 253 | runLoggingCallbacks( "testDone", QUnit, { 254 | name: this.testName, 255 | module: this.module, 256 | failed: bad, 257 | passed: this.assertions.length - bad, 258 | total: this.assertions.length 259 | }); 260 | 261 | QUnit.reset(); 262 | 263 | config.current = undefined; 264 | }, 265 | 266 | queue: function() { 267 | var bad, 268 | test = this; 269 | 270 | synchronize(function() { 271 | test.init(); 272 | }); 273 | function run() { 274 | // each of these can by async 275 | synchronize(function() { 276 | test.setup(); 277 | }); 278 | synchronize(function() { 279 | test.run(); 280 | }); 281 | synchronize(function() { 282 | test.teardown(); 283 | }); 284 | synchronize(function() { 285 | test.finish(); 286 | }); 287 | } 288 | 289 | // `bad` initialized at top of scope 290 | // defer when previous test run passed, if storage is available 291 | bad = QUnit.config.reorder && defined.sessionStorage && 292 | +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); 293 | 294 | if ( bad ) { 295 | run(); 296 | } else { 297 | synchronize( run, true ); 298 | } 299 | } 300 | }; 301 | 302 | // Root QUnit object. 303 | // `QUnit` initialized at top of scope 304 | QUnit = { 305 | 306 | // call on start of module test to prepend name to all tests 307 | module: function( name, testEnvironment ) { 308 | config.currentModule = name; 309 | config.currentModuleTestEnvironment = testEnvironment; 310 | config.modules[name] = true; 311 | }, 312 | 313 | asyncTest: function( testName, expected, callback ) { 314 | if ( arguments.length === 2 ) { 315 | callback = expected; 316 | expected = null; 317 | } 318 | 319 | QUnit.test( testName, expected, callback, true ); 320 | }, 321 | 322 | test: function( testName, expected, callback, async ) { 323 | var test, 324 | name = "" + escapeInnerText( testName ) + ""; 325 | 326 | if ( arguments.length === 2 ) { 327 | callback = expected; 328 | expected = null; 329 | } 330 | 331 | if ( config.currentModule ) { 332 | name = "" + config.currentModule + ": " + name; 333 | } 334 | 335 | test = new Test({ 336 | name: name, 337 | testName: testName, 338 | expected: expected, 339 | async: async, 340 | callback: callback, 341 | module: config.currentModule, 342 | moduleTestEnvironment: config.currentModuleTestEnvironment, 343 | stack: sourceFromStacktrace( 2 ) 344 | }); 345 | 346 | if ( !validTest( test ) ) { 347 | return; 348 | } 349 | 350 | test.queue(); 351 | }, 352 | 353 | // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 354 | expect: function( asserts ) { 355 | if (arguments.length === 1) { 356 | config.current.expected = asserts; 357 | } else { 358 | return config.current.expected; 359 | } 360 | }, 361 | 362 | start: function( count ) { 363 | config.semaphore -= count || 1; 364 | // don't start until equal number of stop-calls 365 | if ( config.semaphore > 0 ) { 366 | return; 367 | } 368 | // ignore if start is called more often then stop 369 | if ( config.semaphore < 0 ) { 370 | config.semaphore = 0; 371 | } 372 | // A slight delay, to avoid any current callbacks 373 | if ( defined.setTimeout ) { 374 | window.setTimeout(function() { 375 | if ( config.semaphore > 0 ) { 376 | return; 377 | } 378 | if ( config.timeout ) { 379 | clearTimeout( config.timeout ); 380 | } 381 | 382 | config.blocking = false; 383 | process( true ); 384 | }, 13); 385 | } else { 386 | config.blocking = false; 387 | process( true ); 388 | } 389 | }, 390 | 391 | stop: function( count ) { 392 | config.semaphore += count || 1; 393 | config.blocking = true; 394 | 395 | if ( config.testTimeout && defined.setTimeout ) { 396 | clearTimeout( config.timeout ); 397 | config.timeout = window.setTimeout(function() { 398 | QUnit.ok( false, "Test timed out" ); 399 | config.semaphore = 1; 400 | QUnit.start(); 401 | }, config.testTimeout ); 402 | } 403 | } 404 | }; 405 | 406 | // Asssert helpers 407 | // All of these must call either QUnit.push() or manually do: 408 | // - runLoggingCallbacks( "log", .. ); 409 | // - config.current.assertions.push({ .. }); 410 | QUnit.assert = { 411 | /** 412 | * Asserts rough true-ish result. 413 | * @name ok 414 | * @function 415 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 416 | */ 417 | ok: function( result, msg ) { 418 | if ( !config.current ) { 419 | throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); 420 | } 421 | result = !!result; 422 | 423 | var source, 424 | details = { 425 | module: config.current.module, 426 | name: config.current.testName, 427 | result: result, 428 | message: msg 429 | }; 430 | 431 | msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); 432 | msg = "" + msg + ""; 433 | 434 | if ( !result ) { 435 | source = sourceFromStacktrace( 2 ); 436 | if ( source ) { 437 | details.source = source; 438 | msg += "
Source:
" + escapeInnerText( source ) + "
"; 439 | } 440 | } 441 | runLoggingCallbacks( "log", QUnit, details ); 442 | config.current.assertions.push({ 443 | result: result, 444 | message: msg 445 | }); 446 | }, 447 | 448 | /** 449 | * Assert that the first two arguments are equal, with an optional message. 450 | * Prints out both actual and expected values. 451 | * @name equal 452 | * @function 453 | * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); 454 | */ 455 | equal: function( actual, expected, message ) { 456 | QUnit.push( expected == actual, actual, expected, message ); 457 | }, 458 | 459 | /** 460 | * @name notEqual 461 | * @function 462 | */ 463 | notEqual: function( actual, expected, message ) { 464 | QUnit.push( expected != actual, actual, expected, message ); 465 | }, 466 | 467 | /** 468 | * @name deepEqual 469 | * @function 470 | */ 471 | deepEqual: function( actual, expected, message ) { 472 | QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); 473 | }, 474 | 475 | /** 476 | * @name notDeepEqual 477 | * @function 478 | */ 479 | notDeepEqual: function( actual, expected, message ) { 480 | QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); 481 | }, 482 | 483 | /** 484 | * @name strictEqual 485 | * @function 486 | */ 487 | strictEqual: function( actual, expected, message ) { 488 | QUnit.push( expected === actual, actual, expected, message ); 489 | }, 490 | 491 | /** 492 | * @name notStrictEqual 493 | * @function 494 | */ 495 | notStrictEqual: function( actual, expected, message ) { 496 | QUnit.push( expected !== actual, actual, expected, message ); 497 | }, 498 | 499 | throws: function( block, expected, message ) { 500 | var actual, 501 | ok = false; 502 | 503 | // 'expected' is optional 504 | if ( typeof expected === "string" ) { 505 | message = expected; 506 | expected = null; 507 | } 508 | 509 | config.current.ignoreGlobalErrors = true; 510 | try { 511 | block.call( config.current.testEnvironment ); 512 | } catch (e) { 513 | actual = e; 514 | } 515 | config.current.ignoreGlobalErrors = false; 516 | 517 | if ( actual ) { 518 | // we don't want to validate thrown error 519 | if ( !expected ) { 520 | ok = true; 521 | // expected is a regexp 522 | } else if ( QUnit.objectType( expected ) === "regexp" ) { 523 | ok = expected.test( actual ); 524 | // expected is a constructor 525 | } else if ( actual instanceof expected ) { 526 | ok = true; 527 | // expected is a validation function which returns true is validation passed 528 | } else if ( expected.call( {}, actual ) === true ) { 529 | ok = true; 530 | } 531 | 532 | QUnit.push( ok, actual, null, message ); 533 | } else { 534 | QUnit.pushFailure( message, null, 'No exception was thrown.' ); 535 | } 536 | } 537 | }; 538 | 539 | /** 540 | * @deprecate since 1.8.0 541 | * Kept assertion helpers in root for backwards compatibility 542 | */ 543 | extend( QUnit, QUnit.assert ); 544 | 545 | /** 546 | * @deprecated since 1.9.0 547 | * Kept global "raises()" for backwards compatibility 548 | */ 549 | QUnit.raises = QUnit.assert.throws; 550 | 551 | /** 552 | * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 553 | * Kept to avoid TypeErrors for undefined methods. 554 | */ 555 | QUnit.equals = function() { 556 | QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); 557 | }; 558 | QUnit.same = function() { 559 | QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); 560 | }; 561 | 562 | // We want access to the constructor's prototype 563 | (function() { 564 | function F() {} 565 | F.prototype = QUnit; 566 | QUnit = new F(); 567 | // Make F QUnit's constructor so that we can add to the prototype later 568 | QUnit.constructor = F; 569 | }()); 570 | 571 | /** 572 | * Config object: Maintain internal state 573 | * Later exposed as QUnit.config 574 | * `config` initialized at top of scope 575 | */ 576 | config = { 577 | // The queue of tests to run 578 | queue: [], 579 | 580 | // block until document ready 581 | blocking: true, 582 | 583 | // when enabled, show only failing tests 584 | // gets persisted through sessionStorage and can be changed in UI via checkbox 585 | hidepassed: false, 586 | 587 | // by default, run previously failed tests first 588 | // very useful in combination with "Hide passed tests" checked 589 | reorder: true, 590 | 591 | // by default, modify document.title when suite is done 592 | altertitle: true, 593 | 594 | // when enabled, all tests must call expect() 595 | requireExpects: false, 596 | 597 | // add checkboxes that are persisted in the query-string 598 | // when enabled, the id is set to `true` as a `QUnit.config` property 599 | urlConfig: [ 600 | { 601 | id: "noglobals", 602 | label: "Check for Globals", 603 | tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." 604 | }, 605 | { 606 | id: "notrycatch", 607 | label: "No try-catch", 608 | tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." 609 | } 610 | ], 611 | 612 | // Set of all modules. 613 | modules: {}, 614 | 615 | // logging callback queues 616 | begin: [], 617 | done: [], 618 | log: [], 619 | testStart: [], 620 | testDone: [], 621 | moduleStart: [], 622 | moduleDone: [] 623 | }; 624 | 625 | // Initialize more QUnit.config and QUnit.urlParams 626 | (function() { 627 | var i, 628 | location = window.location || { search: "", protocol: "file:" }, 629 | params = location.search.slice( 1 ).split( "&" ), 630 | length = params.length, 631 | urlParams = {}, 632 | current; 633 | 634 | if ( params[ 0 ] ) { 635 | for ( i = 0; i < length; i++ ) { 636 | current = params[ i ].split( "=" ); 637 | current[ 0 ] = decodeURIComponent( current[ 0 ] ); 638 | // allow just a key to turn on a flag, e.g., test.html?noglobals 639 | current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; 640 | urlParams[ current[ 0 ] ] = current[ 1 ]; 641 | } 642 | } 643 | 644 | QUnit.urlParams = urlParams; 645 | 646 | // String search anywhere in moduleName+testName 647 | config.filter = urlParams.filter; 648 | 649 | // Exact match of the module name 650 | config.module = urlParams.module; 651 | 652 | config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; 653 | 654 | // Figure out if we're running the tests from a server or not 655 | QUnit.isLocal = location.protocol === "file:"; 656 | }()); 657 | 658 | // Export global variables, unless an 'exports' object exists, 659 | // in that case we assume we're in CommonJS (dealt with on the bottom of the script) 660 | if ( typeof exports === "undefined" ) { 661 | extend( window, QUnit ); 662 | 663 | // Expose QUnit object 664 | window.QUnit = QUnit; 665 | } 666 | 667 | // Extend QUnit object, 668 | // these after set here because they should not be exposed as global functions 669 | extend( QUnit, { 670 | config: config, 671 | 672 | // Initialize the configuration options 673 | init: function() { 674 | extend( config, { 675 | stats: { all: 0, bad: 0 }, 676 | moduleStats: { all: 0, bad: 0 }, 677 | started: +new Date(), 678 | updateRate: 1000, 679 | blocking: false, 680 | autostart: true, 681 | autorun: false, 682 | filter: "", 683 | queue: [], 684 | semaphore: 0 685 | }); 686 | 687 | var tests, banner, result, 688 | qunit = id( "qunit" ); 689 | 690 | if ( qunit ) { 691 | qunit.innerHTML = 692 | "

" + escapeInnerText( document.title ) + "

" + 693 | "

" + 694 | "
" + 695 | "

" + 696 | "
    "; 697 | } 698 | 699 | tests = id( "qunit-tests" ); 700 | banner = id( "qunit-banner" ); 701 | result = id( "qunit-testresult" ); 702 | 703 | if ( tests ) { 704 | tests.innerHTML = ""; 705 | } 706 | 707 | if ( banner ) { 708 | banner.className = ""; 709 | } 710 | 711 | if ( result ) { 712 | result.parentNode.removeChild( result ); 713 | } 714 | 715 | if ( tests ) { 716 | result = document.createElement( "p" ); 717 | result.id = "qunit-testresult"; 718 | result.className = "result"; 719 | tests.parentNode.insertBefore( result, tests ); 720 | result.innerHTML = "Running...
     "; 721 | } 722 | }, 723 | 724 | // Resets the test setup. Useful for tests that modify the DOM. 725 | reset: function() { 726 | var fixture = id( "qunit-fixture" ); 727 | if ( fixture ) { 728 | fixture.innerHTML = config.fixture; 729 | } 730 | }, 731 | 732 | // Trigger an event on an element. 733 | // @example triggerEvent( document.body, "click" ); 734 | triggerEvent: function( elem, type, event ) { 735 | if ( document.createEvent ) { 736 | event = document.createEvent( "MouseEvents" ); 737 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 738 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 739 | 740 | elem.dispatchEvent( event ); 741 | } else if ( elem.fireEvent ) { 742 | elem.fireEvent( "on" + type ); 743 | } 744 | }, 745 | 746 | // Safe object type checking 747 | is: function( type, obj ) { 748 | return QUnit.objectType( obj ) == type; 749 | }, 750 | 751 | objectType: function( obj ) { 752 | if ( typeof obj === "undefined" ) { 753 | return "undefined"; 754 | // consider: typeof null === object 755 | } 756 | if ( obj === null ) { 757 | return "null"; 758 | } 759 | 760 | var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; 761 | 762 | switch ( type ) { 763 | case "Number": 764 | if ( isNaN(obj) ) { 765 | return "nan"; 766 | } 767 | return "number"; 768 | case "String": 769 | case "Boolean": 770 | case "Array": 771 | case "Date": 772 | case "RegExp": 773 | case "Function": 774 | return type.toLowerCase(); 775 | } 776 | if ( typeof obj === "object" ) { 777 | return "object"; 778 | } 779 | return undefined; 780 | }, 781 | 782 | push: function( result, actual, expected, message ) { 783 | if ( !config.current ) { 784 | throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); 785 | } 786 | 787 | var output, source, 788 | details = { 789 | module: config.current.module, 790 | name: config.current.testName, 791 | result: result, 792 | message: message, 793 | actual: actual, 794 | expected: expected 795 | }; 796 | 797 | message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); 798 | message = "" + message + ""; 799 | output = message; 800 | 801 | if ( !result ) { 802 | expected = escapeInnerText( QUnit.jsDump.parse(expected) ); 803 | actual = escapeInnerText( QUnit.jsDump.parse(actual) ); 804 | output += ""; 805 | 806 | if ( actual != expected ) { 807 | output += ""; 808 | output += ""; 809 | } 810 | 811 | source = sourceFromStacktrace(); 812 | 813 | if ( source ) { 814 | details.source = source; 815 | output += ""; 816 | } 817 | 818 | output += "
    Expected:
    " + expected + "
    Result:
    " + actual + "
    Diff:
    " + QUnit.diff( expected, actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 819 | } 820 | 821 | runLoggingCallbacks( "log", QUnit, details ); 822 | 823 | config.current.assertions.push({ 824 | result: !!result, 825 | message: output 826 | }); 827 | }, 828 | 829 | pushFailure: function( message, source, actual ) { 830 | if ( !config.current ) { 831 | throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); 832 | } 833 | 834 | var output, 835 | details = { 836 | module: config.current.module, 837 | name: config.current.testName, 838 | result: false, 839 | message: message 840 | }; 841 | 842 | message = escapeInnerText( message ) || "error"; 843 | message = "" + message + ""; 844 | output = message; 845 | 846 | output += ""; 847 | 848 | if ( actual ) { 849 | output += ""; 850 | } 851 | 852 | if ( source ) { 853 | details.source = source; 854 | output += ""; 855 | } 856 | 857 | output += "
    Result:
    " + escapeInnerText( actual ) + "
    Source:
    " + escapeInnerText( source ) + "
    "; 858 | 859 | runLoggingCallbacks( "log", QUnit, details ); 860 | 861 | config.current.assertions.push({ 862 | result: false, 863 | message: output 864 | }); 865 | }, 866 | 867 | url: function( params ) { 868 | params = extend( extend( {}, QUnit.urlParams ), params ); 869 | var key, 870 | querystring = "?"; 871 | 872 | for ( key in params ) { 873 | if ( !hasOwn.call( params, key ) ) { 874 | continue; 875 | } 876 | querystring += encodeURIComponent( key ) + "=" + 877 | encodeURIComponent( params[ key ] ) + "&"; 878 | } 879 | return window.location.pathname + querystring.slice( 0, -1 ); 880 | }, 881 | 882 | extend: extend, 883 | id: id, 884 | addEvent: addEvent 885 | // load, equiv, jsDump, diff: Attached later 886 | }); 887 | 888 | /** 889 | * @deprecated: Created for backwards compatibility with test runner that set the hook function 890 | * into QUnit.{hook}, instead of invoking it and passing the hook function. 891 | * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. 892 | * Doing this allows us to tell if the following methods have been overwritten on the actual 893 | * QUnit object. 894 | */ 895 | extend( QUnit.constructor.prototype, { 896 | 897 | // Logging callbacks; all receive a single argument with the listed properties 898 | // run test/logs.html for any related changes 899 | begin: registerLoggingCallback( "begin" ), 900 | 901 | // done: { failed, passed, total, runtime } 902 | done: registerLoggingCallback( "done" ), 903 | 904 | // log: { result, actual, expected, message } 905 | log: registerLoggingCallback( "log" ), 906 | 907 | // testStart: { name } 908 | testStart: registerLoggingCallback( "testStart" ), 909 | 910 | // testDone: { name, failed, passed, total } 911 | testDone: registerLoggingCallback( "testDone" ), 912 | 913 | // moduleStart: { name } 914 | moduleStart: registerLoggingCallback( "moduleStart" ), 915 | 916 | // moduleDone: { name, failed, passed, total } 917 | moduleDone: registerLoggingCallback( "moduleDone" ) 918 | }); 919 | 920 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 921 | config.autorun = true; 922 | } 923 | 924 | QUnit.load = function() { 925 | runLoggingCallbacks( "begin", QUnit, {} ); 926 | 927 | // Initialize the config, saving the execution queue 928 | var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter, 929 | numModules = 0, 930 | moduleFilterHtml = "", 931 | urlConfigHtml = "", 932 | oldconfig = extend( {}, config ); 933 | 934 | QUnit.init(); 935 | extend(config, oldconfig); 936 | 937 | config.blocking = false; 938 | 939 | len = config.urlConfig.length; 940 | 941 | for ( i = 0; i < len; i++ ) { 942 | val = config.urlConfig[i]; 943 | if ( typeof val === "string" ) { 944 | val = { 945 | id: val, 946 | label: val, 947 | tooltip: "[no tooltip available]" 948 | }; 949 | } 950 | config[ val.id ] = QUnit.urlParams[ val.id ]; 951 | urlConfigHtml += ""; 952 | } 953 | 954 | moduleFilterHtml += ""; 962 | 963 | // `userAgent` initialized at top of scope 964 | userAgent = id( "qunit-userAgent" ); 965 | if ( userAgent ) { 966 | userAgent.innerHTML = navigator.userAgent; 967 | } 968 | 969 | // `banner` initialized at top of scope 970 | banner = id( "qunit-header" ); 971 | if ( banner ) { 972 | banner.innerHTML = "" + banner.innerHTML + " "; 973 | } 974 | 975 | // `toolbar` initialized at top of scope 976 | toolbar = id( "qunit-testrunner-toolbar" ); 977 | if ( toolbar ) { 978 | // `filter` initialized at top of scope 979 | filter = document.createElement( "input" ); 980 | filter.type = "checkbox"; 981 | filter.id = "qunit-filter-pass"; 982 | 983 | addEvent( filter, "click", function() { 984 | var tmp, 985 | ol = document.getElementById( "qunit-tests" ); 986 | 987 | if ( filter.checked ) { 988 | ol.className = ol.className + " hidepass"; 989 | } else { 990 | tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; 991 | ol.className = tmp.replace( / hidepass /, " " ); 992 | } 993 | if ( defined.sessionStorage ) { 994 | if (filter.checked) { 995 | sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); 996 | } else { 997 | sessionStorage.removeItem( "qunit-filter-passed-tests" ); 998 | } 999 | } 1000 | }); 1001 | 1002 | if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { 1003 | filter.checked = true; 1004 | // `ol` initialized at top of scope 1005 | ol = document.getElementById( "qunit-tests" ); 1006 | ol.className = ol.className + " hidepass"; 1007 | } 1008 | toolbar.appendChild( filter ); 1009 | 1010 | // `label` initialized at top of scope 1011 | label = document.createElement( "label" ); 1012 | label.setAttribute( "for", "qunit-filter-pass" ); 1013 | label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); 1014 | label.innerHTML = "Hide passed tests"; 1015 | toolbar.appendChild( label ); 1016 | 1017 | urlConfigCheckboxes = document.createElement( 'span' ); 1018 | urlConfigCheckboxes.innerHTML = urlConfigHtml; 1019 | addEvent( urlConfigCheckboxes, "change", function( event ) { 1020 | var params = {}; 1021 | params[ event.target.name ] = event.target.checked ? true : undefined; 1022 | window.location = QUnit.url( params ); 1023 | }); 1024 | toolbar.appendChild( urlConfigCheckboxes ); 1025 | 1026 | if (numModules > 1) { 1027 | moduleFilter = document.createElement( 'span' ); 1028 | moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' ); 1029 | moduleFilter.innerHTML = moduleFilterHtml; 1030 | addEvent( moduleFilter, "change", function() { 1031 | var selectBox = moduleFilter.getElementsByTagName("select")[0], 1032 | selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value); 1033 | 1034 | window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } ); 1035 | }); 1036 | toolbar.appendChild(moduleFilter); 1037 | } 1038 | } 1039 | 1040 | // `main` initialized at top of scope 1041 | main = id( "qunit-fixture" ); 1042 | if ( main ) { 1043 | config.fixture = main.innerHTML; 1044 | } 1045 | 1046 | if ( config.autostart ) { 1047 | QUnit.start(); 1048 | } 1049 | }; 1050 | 1051 | addEvent( window, "load", QUnit.load ); 1052 | 1053 | // `onErrorFnPrev` initialized at top of scope 1054 | // Preserve other handlers 1055 | onErrorFnPrev = window.onerror; 1056 | 1057 | // Cover uncaught exceptions 1058 | // Returning true will surpress the default browser handler, 1059 | // returning false will let it run. 1060 | window.onerror = function ( error, filePath, linerNr ) { 1061 | var ret = false; 1062 | if ( onErrorFnPrev ) { 1063 | ret = onErrorFnPrev( error, filePath, linerNr ); 1064 | } 1065 | 1066 | // Treat return value as window.onerror itself does, 1067 | // Only do our handling if not surpressed. 1068 | if ( ret !== true ) { 1069 | if ( QUnit.config.current ) { 1070 | if ( QUnit.config.current.ignoreGlobalErrors ) { 1071 | return true; 1072 | } 1073 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1074 | } else { 1075 | QUnit.test( "global failure", extend( function() { 1076 | QUnit.pushFailure( error, filePath + ":" + linerNr ); 1077 | }, { validTest: validTest } ) ); 1078 | } 1079 | return false; 1080 | } 1081 | 1082 | return ret; 1083 | }; 1084 | 1085 | function done() { 1086 | config.autorun = true; 1087 | 1088 | // Log the last module results 1089 | if ( config.currentModule ) { 1090 | runLoggingCallbacks( "moduleDone", QUnit, { 1091 | name: config.currentModule, 1092 | failed: config.moduleStats.bad, 1093 | passed: config.moduleStats.all - config.moduleStats.bad, 1094 | total: config.moduleStats.all 1095 | }); 1096 | } 1097 | 1098 | var i, key, 1099 | banner = id( "qunit-banner" ), 1100 | tests = id( "qunit-tests" ), 1101 | runtime = +new Date() - config.started, 1102 | passed = config.stats.all - config.stats.bad, 1103 | html = [ 1104 | "Tests completed in ", 1105 | runtime, 1106 | " milliseconds.
    ", 1107 | "", 1108 | passed, 1109 | " tests of ", 1110 | config.stats.all, 1111 | " passed, ", 1112 | config.stats.bad, 1113 | " failed." 1114 | ].join( "" ); 1115 | 1116 | if ( banner ) { 1117 | banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); 1118 | } 1119 | 1120 | if ( tests ) { 1121 | id( "qunit-testresult" ).innerHTML = html; 1122 | } 1123 | 1124 | if ( config.altertitle && typeof document !== "undefined" && document.title ) { 1125 | // show ✖ for good, ✔ for bad suite result in title 1126 | // use escape sequences in case file gets loaded with non-utf-8-charset 1127 | document.title = [ 1128 | ( config.stats.bad ? "\u2716" : "\u2714" ), 1129 | document.title.replace( /^[\u2714\u2716] /i, "" ) 1130 | ].join( " " ); 1131 | } 1132 | 1133 | // clear own sessionStorage items if all tests passed 1134 | if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { 1135 | // `key` & `i` initialized at top of scope 1136 | for ( i = 0; i < sessionStorage.length; i++ ) { 1137 | key = sessionStorage.key( i++ ); 1138 | if ( key.indexOf( "qunit-test-" ) === 0 ) { 1139 | sessionStorage.removeItem( key ); 1140 | } 1141 | } 1142 | } 1143 | 1144 | // scroll back to top to show results 1145 | if ( window.scrollTo ) { 1146 | window.scrollTo(0, 0); 1147 | } 1148 | 1149 | runLoggingCallbacks( "done", QUnit, { 1150 | failed: config.stats.bad, 1151 | passed: passed, 1152 | total: config.stats.all, 1153 | runtime: runtime 1154 | }); 1155 | } 1156 | 1157 | /** @return Boolean: true if this test should be ran */ 1158 | function validTest( test ) { 1159 | var include, 1160 | filter = config.filter && config.filter.toLowerCase(), 1161 | module = config.module && config.module.toLowerCase(), 1162 | fullName = (test.module + ": " + test.testName).toLowerCase(); 1163 | 1164 | // Internally-generated tests are always valid 1165 | if ( test.callback && test.callback.validTest === validTest ) { 1166 | delete test.callback.validTest; 1167 | return true; 1168 | } 1169 | 1170 | if ( config.testNumber ) { 1171 | return test.testNumber === config.testNumber; 1172 | } 1173 | 1174 | if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { 1175 | return false; 1176 | } 1177 | 1178 | if ( !filter ) { 1179 | return true; 1180 | } 1181 | 1182 | include = filter.charAt( 0 ) !== "!"; 1183 | if ( !include ) { 1184 | filter = filter.slice( 1 ); 1185 | } 1186 | 1187 | // If the filter matches, we need to honour include 1188 | if ( fullName.indexOf( filter ) !== -1 ) { 1189 | return include; 1190 | } 1191 | 1192 | // Otherwise, do the opposite 1193 | return !include; 1194 | } 1195 | 1196 | // so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) 1197 | // Later Safari and IE10 are supposed to support error.stack as well 1198 | // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack 1199 | function extractStacktrace( e, offset ) { 1200 | offset = offset === undefined ? 3 : offset; 1201 | 1202 | var stack, include, i, regex; 1203 | 1204 | if ( e.stacktrace ) { 1205 | // Opera 1206 | return e.stacktrace.split( "\n" )[ offset + 3 ]; 1207 | } else if ( e.stack ) { 1208 | // Firefox, Chrome 1209 | stack = e.stack.split( "\n" ); 1210 | if (/^error$/i.test( stack[0] ) ) { 1211 | stack.shift(); 1212 | } 1213 | if ( fileName ) { 1214 | include = []; 1215 | for ( i = offset; i < stack.length; i++ ) { 1216 | if ( stack[ i ].indexOf( fileName ) != -1 ) { 1217 | break; 1218 | } 1219 | include.push( stack[ i ] ); 1220 | } 1221 | if ( include.length ) { 1222 | return include.join( "\n" ); 1223 | } 1224 | } 1225 | return stack[ offset ]; 1226 | } else if ( e.sourceURL ) { 1227 | // Safari, PhantomJS 1228 | // hopefully one day Safari provides actual stacktraces 1229 | // exclude useless self-reference for generated Error objects 1230 | if ( /qunit.js$/.test( e.sourceURL ) ) { 1231 | return; 1232 | } 1233 | // for actual exceptions, this is useful 1234 | return e.sourceURL + ":" + e.line; 1235 | } 1236 | } 1237 | function sourceFromStacktrace( offset ) { 1238 | try { 1239 | throw new Error(); 1240 | } catch ( e ) { 1241 | return extractStacktrace( e, offset ); 1242 | } 1243 | } 1244 | 1245 | function escapeInnerText( s ) { 1246 | if ( !s ) { 1247 | return ""; 1248 | } 1249 | s = s + ""; 1250 | return s.replace( /[\&<>]/g, function( s ) { 1251 | switch( s ) { 1252 | case "&": return "&"; 1253 | case "<": return "<"; 1254 | case ">": return ">"; 1255 | default: return s; 1256 | } 1257 | }); 1258 | } 1259 | 1260 | function synchronize( callback, last ) { 1261 | config.queue.push( callback ); 1262 | 1263 | if ( config.autorun && !config.blocking ) { 1264 | process( last ); 1265 | } 1266 | } 1267 | 1268 | function process( last ) { 1269 | function next() { 1270 | process( last ); 1271 | } 1272 | var start = new Date().getTime(); 1273 | config.depth = config.depth ? config.depth + 1 : 1; 1274 | 1275 | while ( config.queue.length && !config.blocking ) { 1276 | if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { 1277 | config.queue.shift()(); 1278 | } else { 1279 | window.setTimeout( next, 13 ); 1280 | break; 1281 | } 1282 | } 1283 | config.depth--; 1284 | if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) { 1285 | done(); 1286 | } 1287 | } 1288 | 1289 | function saveGlobal() { 1290 | config.pollution = []; 1291 | 1292 | if ( config.noglobals ) { 1293 | for ( var key in window ) { 1294 | // in Opera sometimes DOM element ids show up here, ignore them 1295 | if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { 1296 | continue; 1297 | } 1298 | config.pollution.push( key ); 1299 | } 1300 | } 1301 | } 1302 | 1303 | function checkPollution( name ) { 1304 | var newGlobals, 1305 | deletedGlobals, 1306 | old = config.pollution; 1307 | 1308 | saveGlobal(); 1309 | 1310 | newGlobals = diff( config.pollution, old ); 1311 | if ( newGlobals.length > 0 ) { 1312 | QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); 1313 | } 1314 | 1315 | deletedGlobals = diff( old, config.pollution ); 1316 | if ( deletedGlobals.length > 0 ) { 1317 | QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); 1318 | } 1319 | } 1320 | 1321 | // returns a new Array with the elements that are in a but not in b 1322 | function diff( a, b ) { 1323 | var i, j, 1324 | result = a.slice(); 1325 | 1326 | for ( i = 0; i < result.length; i++ ) { 1327 | for ( j = 0; j < b.length; j++ ) { 1328 | if ( result[i] === b[j] ) { 1329 | result.splice( i, 1 ); 1330 | i--; 1331 | break; 1332 | } 1333 | } 1334 | } 1335 | return result; 1336 | } 1337 | 1338 | function extend( a, b ) { 1339 | for ( var prop in b ) { 1340 | if ( b[ prop ] === undefined ) { 1341 | delete a[ prop ]; 1342 | 1343 | // Avoid "Member not found" error in IE8 caused by setting window.constructor 1344 | } else if ( prop !== "constructor" || a !== window ) { 1345 | a[ prop ] = b[ prop ]; 1346 | } 1347 | } 1348 | 1349 | return a; 1350 | } 1351 | 1352 | function addEvent( elem, type, fn ) { 1353 | if ( elem.addEventListener ) { 1354 | elem.addEventListener( type, fn, false ); 1355 | } else if ( elem.attachEvent ) { 1356 | elem.attachEvent( "on" + type, fn ); 1357 | } else { 1358 | fn(); 1359 | } 1360 | } 1361 | 1362 | function id( name ) { 1363 | return !!( typeof document !== "undefined" && document && document.getElementById ) && 1364 | document.getElementById( name ); 1365 | } 1366 | 1367 | function registerLoggingCallback( key ) { 1368 | return function( callback ) { 1369 | config[key].push( callback ); 1370 | }; 1371 | } 1372 | 1373 | // Supports deprecated method of completely overwriting logging callbacks 1374 | function runLoggingCallbacks( key, scope, args ) { 1375 | //debugger; 1376 | var i, callbacks; 1377 | if ( QUnit.hasOwnProperty( key ) ) { 1378 | QUnit[ key ].call(scope, args ); 1379 | } else { 1380 | callbacks = config[ key ]; 1381 | for ( i = 0; i < callbacks.length; i++ ) { 1382 | callbacks[ i ].call( scope, args ); 1383 | } 1384 | } 1385 | } 1386 | 1387 | // Test for equality any JavaScript type. 1388 | // Author: Philippe Rathé 1389 | QUnit.equiv = (function() { 1390 | 1391 | // Call the o related callback with the given arguments. 1392 | function bindCallbacks( o, callbacks, args ) { 1393 | var prop = QUnit.objectType( o ); 1394 | if ( prop ) { 1395 | if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { 1396 | return callbacks[ prop ].apply( callbacks, args ); 1397 | } else { 1398 | return callbacks[ prop ]; // or undefined 1399 | } 1400 | } 1401 | } 1402 | 1403 | // the real equiv function 1404 | var innerEquiv, 1405 | // stack to decide between skip/abort functions 1406 | callers = [], 1407 | // stack to avoiding loops from circular referencing 1408 | parents = [], 1409 | 1410 | getProto = Object.getPrototypeOf || function ( obj ) { 1411 | return obj.__proto__; 1412 | }, 1413 | callbacks = (function () { 1414 | 1415 | // for string, boolean, number and null 1416 | function useStrictEquality( b, a ) { 1417 | if ( b instanceof a.constructor || a instanceof b.constructor ) { 1418 | // to catch short annotaion VS 'new' annotation of a 1419 | // declaration 1420 | // e.g. var i = 1; 1421 | // var j = new Number(1); 1422 | return a == b; 1423 | } else { 1424 | return a === b; 1425 | } 1426 | } 1427 | 1428 | return { 1429 | "string": useStrictEquality, 1430 | "boolean": useStrictEquality, 1431 | "number": useStrictEquality, 1432 | "null": useStrictEquality, 1433 | "undefined": useStrictEquality, 1434 | 1435 | "nan": function( b ) { 1436 | return isNaN( b ); 1437 | }, 1438 | 1439 | "date": function( b, a ) { 1440 | return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); 1441 | }, 1442 | 1443 | "regexp": function( b, a ) { 1444 | return QUnit.objectType( b ) === "regexp" && 1445 | // the regex itself 1446 | a.source === b.source && 1447 | // and its modifers 1448 | a.global === b.global && 1449 | // (gmi) ... 1450 | a.ignoreCase === b.ignoreCase && 1451 | a.multiline === b.multiline && 1452 | a.sticky === b.sticky; 1453 | }, 1454 | 1455 | // - skip when the property is a method of an instance (OOP) 1456 | // - abort otherwise, 1457 | // initial === would have catch identical references anyway 1458 | "function": function() { 1459 | var caller = callers[callers.length - 1]; 1460 | return caller !== Object && typeof caller !== "undefined"; 1461 | }, 1462 | 1463 | "array": function( b, a ) { 1464 | var i, j, len, loop; 1465 | 1466 | // b could be an object literal here 1467 | if ( QUnit.objectType( b ) !== "array" ) { 1468 | return false; 1469 | } 1470 | 1471 | len = a.length; 1472 | if ( len !== b.length ) { 1473 | // safe and faster 1474 | return false; 1475 | } 1476 | 1477 | // track reference to avoid circular references 1478 | parents.push( a ); 1479 | for ( i = 0; i < len; i++ ) { 1480 | loop = false; 1481 | for ( j = 0; j < parents.length; j++ ) { 1482 | if ( parents[j] === a[i] ) { 1483 | loop = true;// dont rewalk array 1484 | } 1485 | } 1486 | if ( !loop && !innerEquiv(a[i], b[i]) ) { 1487 | parents.pop(); 1488 | return false; 1489 | } 1490 | } 1491 | parents.pop(); 1492 | return true; 1493 | }, 1494 | 1495 | "object": function( b, a ) { 1496 | var i, j, loop, 1497 | // Default to true 1498 | eq = true, 1499 | aProperties = [], 1500 | bProperties = []; 1501 | 1502 | // comparing constructors is more strict than using 1503 | // instanceof 1504 | if ( a.constructor !== b.constructor ) { 1505 | // Allow objects with no prototype to be equivalent to 1506 | // objects with Object as their constructor. 1507 | if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || 1508 | ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { 1509 | return false; 1510 | } 1511 | } 1512 | 1513 | // stack constructor before traversing properties 1514 | callers.push( a.constructor ); 1515 | // track reference to avoid circular references 1516 | parents.push( a ); 1517 | 1518 | for ( i in a ) { // be strict: don't ensures hasOwnProperty 1519 | // and go deep 1520 | loop = false; 1521 | for ( j = 0; j < parents.length; j++ ) { 1522 | if ( parents[j] === a[i] ) { 1523 | // don't go down the same path twice 1524 | loop = true; 1525 | } 1526 | } 1527 | aProperties.push(i); // collect a's properties 1528 | 1529 | if (!loop && !innerEquiv( a[i], b[i] ) ) { 1530 | eq = false; 1531 | break; 1532 | } 1533 | } 1534 | 1535 | callers.pop(); // unstack, we are done 1536 | parents.pop(); 1537 | 1538 | for ( i in b ) { 1539 | bProperties.push( i ); // collect b's properties 1540 | } 1541 | 1542 | // Ensures identical properties name 1543 | return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); 1544 | } 1545 | }; 1546 | }()); 1547 | 1548 | innerEquiv = function() { // can take multiple arguments 1549 | var args = [].slice.apply( arguments ); 1550 | if ( args.length < 2 ) { 1551 | return true; // end transition 1552 | } 1553 | 1554 | return (function( a, b ) { 1555 | if ( a === b ) { 1556 | return true; // catch the most you can 1557 | } else if ( a === null || b === null || typeof a === "undefined" || 1558 | typeof b === "undefined" || 1559 | QUnit.objectType(a) !== QUnit.objectType(b) ) { 1560 | return false; // don't lose time with error prone cases 1561 | } else { 1562 | return bindCallbacks(a, callbacks, [ b, a ]); 1563 | } 1564 | 1565 | // apply transition with (1..n) arguments 1566 | }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); 1567 | }; 1568 | 1569 | return innerEquiv; 1570 | }()); 1571 | 1572 | /** 1573 | * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | 1574 | * http://flesler.blogspot.com Licensed under BSD 1575 | * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008 1576 | * 1577 | * @projectDescription Advanced and extensible data dumping for Javascript. 1578 | * @version 1.0.0 1579 | * @author Ariel Flesler 1580 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 1581 | */ 1582 | QUnit.jsDump = (function() { 1583 | function quote( str ) { 1584 | return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; 1585 | } 1586 | function literal( o ) { 1587 | return o + ""; 1588 | } 1589 | function join( pre, arr, post ) { 1590 | var s = jsDump.separator(), 1591 | base = jsDump.indent(), 1592 | inner = jsDump.indent(1); 1593 | if ( arr.join ) { 1594 | arr = arr.join( "," + s + inner ); 1595 | } 1596 | if ( !arr ) { 1597 | return pre + post; 1598 | } 1599 | return [ pre, inner + arr, base + post ].join(s); 1600 | } 1601 | function array( arr, stack ) { 1602 | var i = arr.length, ret = new Array(i); 1603 | this.up(); 1604 | while ( i-- ) { 1605 | ret[i] = this.parse( arr[i] , undefined , stack); 1606 | } 1607 | this.down(); 1608 | return join( "[", ret, "]" ); 1609 | } 1610 | 1611 | var reName = /^function (\w+)/, 1612 | jsDump = { 1613 | parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance 1614 | stack = stack || [ ]; 1615 | var inStack, res, 1616 | parser = this.parsers[ type || this.typeOf(obj) ]; 1617 | 1618 | type = typeof parser; 1619 | inStack = inArray( obj, stack ); 1620 | 1621 | if ( inStack != -1 ) { 1622 | return "recursion(" + (inStack - stack.length) + ")"; 1623 | } 1624 | //else 1625 | if ( type == "function" ) { 1626 | stack.push( obj ); 1627 | res = parser.call( this, obj, stack ); 1628 | stack.pop(); 1629 | return res; 1630 | } 1631 | // else 1632 | return ( type == "string" ) ? parser : this.parsers.error; 1633 | }, 1634 | typeOf: function( obj ) { 1635 | var type; 1636 | if ( obj === null ) { 1637 | type = "null"; 1638 | } else if ( typeof obj === "undefined" ) { 1639 | type = "undefined"; 1640 | } else if ( QUnit.is( "regexp", obj) ) { 1641 | type = "regexp"; 1642 | } else if ( QUnit.is( "date", obj) ) { 1643 | type = "date"; 1644 | } else if ( QUnit.is( "function", obj) ) { 1645 | type = "function"; 1646 | } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { 1647 | type = "window"; 1648 | } else if ( obj.nodeType === 9 ) { 1649 | type = "document"; 1650 | } else if ( obj.nodeType ) { 1651 | type = "node"; 1652 | } else if ( 1653 | // native arrays 1654 | toString.call( obj ) === "[object Array]" || 1655 | // NodeList objects 1656 | ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) 1657 | ) { 1658 | type = "array"; 1659 | } else { 1660 | type = typeof obj; 1661 | } 1662 | return type; 1663 | }, 1664 | separator: function() { 1665 | return this.multiline ? this.HTML ? "
    " : "\n" : this.HTML ? " " : " "; 1666 | }, 1667 | indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 1668 | if ( !this.multiline ) { 1669 | return ""; 1670 | } 1671 | var chr = this.indentChar; 1672 | if ( this.HTML ) { 1673 | chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); 1674 | } 1675 | return new Array( this._depth_ + (extra||0) ).join(chr); 1676 | }, 1677 | up: function( a ) { 1678 | this._depth_ += a || 1; 1679 | }, 1680 | down: function( a ) { 1681 | this._depth_ -= a || 1; 1682 | }, 1683 | setParser: function( name, parser ) { 1684 | this.parsers[name] = parser; 1685 | }, 1686 | // The next 3 are exposed so you can use them 1687 | quote: quote, 1688 | literal: literal, 1689 | join: join, 1690 | // 1691 | _depth_: 1, 1692 | // This is the list of parsers, to modify them, use jsDump.setParser 1693 | parsers: { 1694 | window: "[Window]", 1695 | document: "[Document]", 1696 | error: "[ERROR]", //when no parser is found, shouldn"t happen 1697 | unknown: "[Unknown]", 1698 | "null": "null", 1699 | "undefined": "undefined", 1700 | "function": function( fn ) { 1701 | var ret = "function", 1702 | name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE 1703 | 1704 | if ( name ) { 1705 | ret += " " + name; 1706 | } 1707 | ret += "( "; 1708 | 1709 | ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); 1710 | return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); 1711 | }, 1712 | array: array, 1713 | nodelist: array, 1714 | "arguments": array, 1715 | object: function( map, stack ) { 1716 | var ret = [ ], keys, key, val, i; 1717 | QUnit.jsDump.up(); 1718 | if ( Object.keys ) { 1719 | keys = Object.keys( map ); 1720 | } else { 1721 | keys = []; 1722 | for ( key in map ) { 1723 | keys.push( key ); 1724 | } 1725 | } 1726 | keys.sort(); 1727 | for ( i = 0; i < keys.length; i++ ) { 1728 | key = keys[ i ]; 1729 | val = map[ key ]; 1730 | ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); 1731 | } 1732 | QUnit.jsDump.down(); 1733 | return join( "{", ret, "}" ); 1734 | }, 1735 | node: function( node ) { 1736 | var a, val, 1737 | open = QUnit.jsDump.HTML ? "<" : "<", 1738 | close = QUnit.jsDump.HTML ? ">" : ">", 1739 | tag = node.nodeName.toLowerCase(), 1740 | ret = open + tag; 1741 | 1742 | for ( a in QUnit.jsDump.DOMAttrs ) { 1743 | val = node[ QUnit.jsDump.DOMAttrs[a] ]; 1744 | if ( val ) { 1745 | ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); 1746 | } 1747 | } 1748 | return ret + close + open + "/" + tag + close; 1749 | }, 1750 | functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function 1751 | var args, 1752 | l = fn.length; 1753 | 1754 | if ( !l ) { 1755 | return ""; 1756 | } 1757 | 1758 | args = new Array(l); 1759 | while ( l-- ) { 1760 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1761 | } 1762 | return " " + args.join( ", " ) + " "; 1763 | }, 1764 | key: quote, //object calls it internally, the key part of an item in a map 1765 | functionCode: "[code]", //function calls it internally, it's the content of the function 1766 | attribute: quote, //node calls it internally, it's an html attribute value 1767 | string: quote, 1768 | date: quote, 1769 | regexp: literal, //regex 1770 | number: literal, 1771 | "boolean": literal 1772 | }, 1773 | DOMAttrs: { 1774 | //attributes to dump from nodes, name=>realName 1775 | id: "id", 1776 | name: "name", 1777 | "class": "className" 1778 | }, 1779 | HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) 1780 | indentChar: " ",//indentation unit 1781 | multiline: true //if true, items in a collection, are separated by a \n, else just a space. 1782 | }; 1783 | 1784 | return jsDump; 1785 | }()); 1786 | 1787 | // from Sizzle.js 1788 | function getText( elems ) { 1789 | var i, elem, 1790 | ret = ""; 1791 | 1792 | for ( i = 0; elems[i]; i++ ) { 1793 | elem = elems[i]; 1794 | 1795 | // Get the text from text nodes and CDATA nodes 1796 | if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 1797 | ret += elem.nodeValue; 1798 | 1799 | // Traverse everything else, except comment nodes 1800 | } else if ( elem.nodeType !== 8 ) { 1801 | ret += getText( elem.childNodes ); 1802 | } 1803 | } 1804 | 1805 | return ret; 1806 | } 1807 | 1808 | // from jquery.js 1809 | function inArray( elem, array ) { 1810 | if ( array.indexOf ) { 1811 | return array.indexOf( elem ); 1812 | } 1813 | 1814 | for ( var i = 0, length = array.length; i < length; i++ ) { 1815 | if ( array[ i ] === elem ) { 1816 | return i; 1817 | } 1818 | } 1819 | 1820 | return -1; 1821 | } 1822 | 1823 | /* 1824 | * Javascript Diff Algorithm 1825 | * By John Resig (http://ejohn.org/) 1826 | * Modified by Chu Alan "sprite" 1827 | * 1828 | * Released under the MIT license. 1829 | * 1830 | * More Info: 1831 | * http://ejohn.org/projects/javascript-diff-algorithm/ 1832 | * 1833 | * Usage: QUnit.diff(expected, actual) 1834 | * 1835 | * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick brown fox jumped jumps over" 1836 | */ 1837 | QUnit.diff = (function() { 1838 | function diff( o, n ) { 1839 | var i, 1840 | ns = {}, 1841 | os = {}; 1842 | 1843 | for ( i = 0; i < n.length; i++ ) { 1844 | if ( ns[ n[i] ] == null ) { 1845 | ns[ n[i] ] = { 1846 | rows: [], 1847 | o: null 1848 | }; 1849 | } 1850 | ns[ n[i] ].rows.push( i ); 1851 | } 1852 | 1853 | for ( i = 0; i < o.length; i++ ) { 1854 | if ( os[ o[i] ] == null ) { 1855 | os[ o[i] ] = { 1856 | rows: [], 1857 | n: null 1858 | }; 1859 | } 1860 | os[ o[i] ].rows.push( i ); 1861 | } 1862 | 1863 | for ( i in ns ) { 1864 | if ( !hasOwn.call( ns, i ) ) { 1865 | continue; 1866 | } 1867 | if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { 1868 | n[ ns[i].rows[0] ] = { 1869 | text: n[ ns[i].rows[0] ], 1870 | row: os[i].rows[0] 1871 | }; 1872 | o[ os[i].rows[0] ] = { 1873 | text: o[ os[i].rows[0] ], 1874 | row: ns[i].rows[0] 1875 | }; 1876 | } 1877 | } 1878 | 1879 | for ( i = 0; i < n.length - 1; i++ ) { 1880 | if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && 1881 | n[ i + 1 ] == o[ n[i].row + 1 ] ) { 1882 | 1883 | n[ i + 1 ] = { 1884 | text: n[ i + 1 ], 1885 | row: n[i].row + 1 1886 | }; 1887 | o[ n[i].row + 1 ] = { 1888 | text: o[ n[i].row + 1 ], 1889 | row: i + 1 1890 | }; 1891 | } 1892 | } 1893 | 1894 | for ( i = n.length - 1; i > 0; i-- ) { 1895 | if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && 1896 | n[ i - 1 ] == o[ n[i].row - 1 ]) { 1897 | 1898 | n[ i - 1 ] = { 1899 | text: n[ i - 1 ], 1900 | row: n[i].row - 1 1901 | }; 1902 | o[ n[i].row - 1 ] = { 1903 | text: o[ n[i].row - 1 ], 1904 | row: i - 1 1905 | }; 1906 | } 1907 | } 1908 | 1909 | return { 1910 | o: o, 1911 | n: n 1912 | }; 1913 | } 1914 | 1915 | return function( o, n ) { 1916 | o = o.replace( /\s+$/, "" ); 1917 | n = n.replace( /\s+$/, "" ); 1918 | 1919 | var i, pre, 1920 | str = "", 1921 | out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), 1922 | oSpace = o.match(/\s+/g), 1923 | nSpace = n.match(/\s+/g); 1924 | 1925 | if ( oSpace == null ) { 1926 | oSpace = [ " " ]; 1927 | } 1928 | else { 1929 | oSpace.push( " " ); 1930 | } 1931 | 1932 | if ( nSpace == null ) { 1933 | nSpace = [ " " ]; 1934 | } 1935 | else { 1936 | nSpace.push( " " ); 1937 | } 1938 | 1939 | if ( out.n.length === 0 ) { 1940 | for ( i = 0; i < out.o.length; i++ ) { 1941 | str += "" + out.o[i] + oSpace[i] + ""; 1942 | } 1943 | } 1944 | else { 1945 | if ( out.n[0].text == null ) { 1946 | for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { 1947 | str += "" + out.o[n] + oSpace[n] + ""; 1948 | } 1949 | } 1950 | 1951 | for ( i = 0; i < out.n.length; i++ ) { 1952 | if (out.n[i].text == null) { 1953 | str += "" + out.n[i] + nSpace[i] + ""; 1954 | } 1955 | else { 1956 | // `pre` initialized at top of scope 1957 | pre = ""; 1958 | 1959 | for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { 1960 | pre += "" + out.o[n] + oSpace[n] + ""; 1961 | } 1962 | str += " " + out.n[i].text + nSpace[i] + pre; 1963 | } 1964 | } 1965 | } 1966 | 1967 | return str; 1968 | }; 1969 | }()); 1970 | 1971 | // for CommonJS enviroments, export everything 1972 | if ( typeof exports !== "undefined" ) { 1973 | extend(exports, QUnit); 1974 | } 1975 | 1976 | // get at whatever the global object is, like window in browsers 1977 | }( (function() {return this;}.call()) )); 1978 | --------------------------------------------------------------------------------