├── demos ├── innersvg.svg └── innersvg.html ├── bower.json ├── README.md ├── package.json ├── test ├── qunit.css ├── test.html └── qunit.js ├── innersvg.js └── LICENSE /demos/innersvg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | You should see a green circle and the word PASS below 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | FAIL 15 | 16 | 17 | 23 | 24 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "innersvg-polyfill", 3 | "version": "0.0.5", 4 | "homepage": "https://github.com/dnozay/innersvg-polyfill", 5 | "authors": [ 6 | "Jeff Schiller " 7 | ], 8 | "maintainers": [ 9 | "Damien Nozay " 10 | ], 11 | "description": "innerHTML polyfill for SVGElement", 12 | "main": "innersvg.js", 13 | "moduleType": [ 14 | "globals" 15 | ], 16 | "keywords": [ 17 | "innerHTML", 18 | "polyfill", 19 | "SVG", 20 | "SVGElement" 21 | ], 22 | "license": "Apache License, version 2", 23 | "ignore": [ 24 | "**/.*", 25 | "node_modules", 26 | "bower_components", 27 | "test", 28 | "tests" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | innersvg-polyfill 2 | ================= 3 | 4 | mirror for https://code.google.com/p/innersvg/ + some files to make the package available in [npm](https://www.npmjs.com/package/innersvg-polyfill) and bower. 5 | 6 | 7 | Introduction 8 | ============ 9 | 10 | This JavaScript library provides the innerHTML property on all SVGElements. 11 | 12 | innerHTML in a SVG document works in Chrome 6+, Safari 5+, Firefox 4+ and IE9+. 13 | 14 | innerHTML in a HTML5 document works in Chrome 7+, Firefox 4+ and IE9+. 15 | 16 | Doesn't work in Opera since the SVGElement interface is not exposed. 17 | 18 | Sample Code 19 | =========== 20 | 21 | 22 | 23 | document.getElementId("foo").innerHTML = ""; 24 | 25 | 26 | License 27 | ======= 28 | 29 | Apache License, Version 2.0 30 | -------------------------------------------------------------------------------- /demos/innersvg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | HTML5 Demo Page for innersvg 5 | 6 | 7 | 8 | 9 |

10 | You should see a green circle with the word PASS. 11 |

12 | 13 | 14 | 15 | 16 | 17 | Another <text> element... 18 | 19 | 20 | 21 | FAIL 22 | 23 | 24 | 25 | 32 | 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "innersvg-polyfill", 3 | "version": "0.0.5", 4 | "description": "innerHTML polyfill for SVGElement", 5 | "main": "innersvg.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/dnozay/innersvg-polyfill.git" 15 | }, 16 | "keywords": [ 17 | "innerHTML", 18 | "polyfill", 19 | "SVG", 20 | "SVGElement" 21 | ], 22 | "author": "Jeff Schiller (https://github.com/codedread)", 23 | "maintainer": "Damien Nozay (https://github.com/dnozay)", 24 | "license": "Apache License, version 2", 25 | "bugs": { 26 | "url": "https://github.com/dnozay/innersvg-polyfill/issues" 27 | }, 28 | "homepage": "https://github.com/dnozay/innersvg-polyfill" 29 | } 30 | -------------------------------------------------------------------------------- /test/qunit.css: -------------------------------------------------------------------------------- 1 | 2 | ol#qunit-tests { 3 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 4 | margin:0; 5 | padding:0; 6 | list-style-position:inside; 7 | 8 | font-size: smaller; 9 | } 10 | ol#qunit-tests li{ 11 | padding:0.4em 0.5em 0.4em 2.5em; 12 | border-bottom:1px solid #fff; 13 | font-size:small; 14 | list-style-position:inside; 15 | } 16 | ol#qunit-tests li ol{ 17 | box-shadow: inset 0px 2px 13px #999; 18 | -moz-box-shadow: inset 0px 2px 13px #999; 19 | -webkit-box-shadow: inset 0px 2px 13px #999; 20 | margin-top:0.5em; 21 | margin-left:0; 22 | padding:0.5em; 23 | background-color:#fff; 24 | border-radius:15px; 25 | -moz-border-radius: 15px; 26 | -webkit-border-radius: 15px; 27 | } 28 | ol#qunit-tests li li{ 29 | border-bottom:none; 30 | margin:0.5em; 31 | background-color:#fff; 32 | list-style-position: inside; 33 | padding:0.4em 0.5em 0.4em 0.5em; 34 | } 35 | 36 | ol#qunit-tests li li.pass{ 37 | border-left:26px solid #C6E746; 38 | background-color:#fff; 39 | color:#5E740B; 40 | } 41 | ol#qunit-tests li li.fail{ 42 | border-left:26px solid #EE5757; 43 | background-color:#fff; 44 | color:#710909; 45 | } 46 | ol#qunit-tests li.pass{ 47 | background-color:#D2E0E6; 48 | color:#528CE0; 49 | } 50 | ol#qunit-tests li.fail{ 51 | background-color:#EE5757; 52 | color:#000; 53 | } 54 | ol#qunit-tests li strong { 55 | cursor:pointer; 56 | } 57 | h1#qunit-header{ 58 | background-color:#0d3349; 59 | margin:0; 60 | padding:0.5em 0 0.5em 1em; 61 | color:#fff; 62 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 63 | border-top-right-radius:15px; 64 | border-top-left-radius:15px; 65 | -moz-border-radius-topright:15px; 66 | -moz-border-radius-topleft:15px; 67 | -webkit-border-top-right-radius:15px; 68 | -webkit-border-top-left-radius:15px; 69 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 70 | } 71 | h2#qunit-banner{ 72 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 73 | height:5px; 74 | margin:0; 75 | padding:0; 76 | } 77 | h2#qunit-banner.qunit-pass{ 78 | background-color:#C6E746; 79 | } 80 | h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { 81 | background-color:#EE5757; 82 | } 83 | #qunit-testrunner-toolbar { 84 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 85 | padding:0; 86 | /*width:80%;*/ 87 | padding:0em 0 0.5em 2em; 88 | font-size: small; 89 | } 90 | h2#qunit-userAgent { 91 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 92 | background-color:#2b81af; 93 | margin:0; 94 | padding:0; 95 | color:#fff; 96 | font-size: small; 97 | padding:0.5em 0 0.5em 2.5em; 98 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 99 | } 100 | p#qunit-testresult{ 101 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 102 | margin:0; 103 | font-size: small; 104 | color:#2b81af; 105 | border-bottom-right-radius:15px; 106 | border-bottom-left-radius:15px; 107 | -moz-border-radius-bottomright:15px; 108 | -moz-border-radius-bottomleft:15px; 109 | -webkit-border-bottom-right-radius:15px; 110 | -webkit-border-bottom-left-radius:15px; 111 | background-color:#D2E0E6; 112 | padding:0.5em 0.5em 0.5em 2.5em; 113 | } 114 | strong b.fail{ 115 | color:#710909; 116 | } 117 | strong b.pass{ 118 | color:#5E740B; 119 | } 120 | -------------------------------------------------------------------------------- /innersvg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * innerHTML property for SVGElement 3 | * Copyright(c) 2010, Jeff Schiller 4 | * 5 | * Licensed under the Apache License, Version 2 6 | * 7 | * Works in a SVG document in Chrome 6+, Safari 5+, Firefox 4+ and IE9+. 8 | * Works in a HTML5 document in Chrome 7+, Firefox 4+ and IE9+. 9 | * Does not work in Opera since it doesn't support the SVGElement interface yet. 10 | * 11 | * I haven't decided on the best name for this property - thus the duplication. 12 | */ 13 | 14 | (function() { 15 | var serializeXML = function(node, output) { 16 | var nodeType = node.nodeType; 17 | if (nodeType == 3) { // TEXT nodes. 18 | // Replace special XML characters with their entities. 19 | output.push(node.textContent.replace(/&/, '&').replace(/', '>')); 20 | } else if (nodeType == 1) { // ELEMENT nodes. 21 | // Serialize Element nodes. 22 | output.push('<', node.tagName); 23 | if (node.hasAttributes()) { 24 | var attrMap = node.attributes; 25 | for (var i = 0, len = attrMap.length; i < len; ++i) { 26 | var attrNode = attrMap.item(i); 27 | output.push(' ', attrNode.name, '=\'', attrNode.value, '\''); 28 | } 29 | } 30 | if (node.hasChildNodes()) { 31 | output.push('>'); 32 | var childNodes = node.childNodes; 33 | for (var i = 0, len = childNodes.length; i < len; ++i) { 34 | serializeXML(childNodes.item(i), output); 35 | } 36 | output.push(''); 37 | } else { 38 | output.push('/>'); 39 | } 40 | } else if (nodeType == 8) { 41 | // TODO(codedread): Replace special characters with XML entities? 42 | output.push(''); 43 | } else { 44 | // TODO: Handle CDATA nodes. 45 | // TODO: Handle ENTITY nodes. 46 | // TODO: Handle DOCUMENT nodes. 47 | throw 'Error serializing XML. Unhandled node of type: ' + nodeType; 48 | } 49 | } 50 | // The innerHTML DOM property for SVGElement. 51 | if (!('innerHTML' in SVGElement.prototype)) { 52 | Object.defineProperty(SVGElement.prototype, 'innerHTML', { 53 | get: function() { 54 | var output = []; 55 | var childNode = this.firstChild; 56 | while (childNode) { 57 | serializeXML(childNode, output); 58 | childNode = childNode.nextSibling; 59 | } 60 | return output.join(''); 61 | }, 62 | set: function(markupText) { 63 | // Wipe out the current contents of the element. 64 | while (this.firstChild) { 65 | this.removeChild(this.firstChild); 66 | } 67 | 68 | try { 69 | // Parse the markup into valid nodes. 70 | var dXML = new DOMParser(); 71 | dXML.async = false; 72 | // Wrap the markup into a SVG node to ensure parsing works. 73 | var sXML = '' + markupText + ''; 74 | var svgDocElement = dXML.parseFromString(sXML, 'text/xml').documentElement; 75 | 76 | // Now take each node, import it and append to this element. 77 | var childNode = svgDocElement.firstChild; 78 | while(childNode) { 79 | this.appendChild(this.ownerDocument.importNode(childNode, true)); 80 | childNode = childNode.nextSibling; 81 | } 82 | } catch(e) { 83 | throw new Error('Error parsing XML string'); 84 | }; 85 | } 86 | }); 87 | } 88 | 89 | // The innerSVG DOM property for SVGElement. 90 | if (!('innerSVG' in SVGElement.prototype)) { 91 | Object.defineProperty(SVGElement.prototype, 'innerSVG', { 92 | get: function() { 93 | return this.innerHTML; 94 | }, 95 | set: function(markupText) { 96 | this.innerHTML = markupText; 97 | } 98 | }); 99 | } 100 | 101 | })(); 102 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Automated tests for innersvg 5 | 6 | 7 | 8 | 108 | 109 | 110 |

innersvg unit tests

111 |

112 |

113 |
    114 |
115 | 116 | 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /test/qunit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * QUnit - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2009 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * and GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | (function(window) { 12 | 13 | var QUnit = { 14 | 15 | // Initialize the configuration options 16 | init: function() { 17 | config = { 18 | stats: { all: 0, bad: 0 }, 19 | moduleStats: { all: 0, bad: 0 }, 20 | started: +new Date, 21 | blocking: false, 22 | autorun: false, 23 | assertions: [], 24 | filters: [], 25 | queue: [] 26 | }; 27 | 28 | var tests = id("qunit-tests"), 29 | banner = id("qunit-banner"), 30 | result = id("qunit-testresult"); 31 | 32 | if ( tests ) { 33 | tests.innerHTML = ""; 34 | } 35 | 36 | if ( banner ) { 37 | banner.className = ""; 38 | } 39 | 40 | if ( result ) { 41 | result.parentNode.removeChild( result ); 42 | } 43 | }, 44 | 45 | // call on start of module test to prepend name to all tests 46 | module: function(name, testEnvironment) { 47 | config.currentModule = name; 48 | 49 | synchronize(function() { 50 | if ( config.currentModule ) { 51 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 52 | } 53 | 54 | config.currentModule = name; 55 | config.moduleTestEnvironment = testEnvironment; 56 | config.moduleStats = { all: 0, bad: 0 }; 57 | 58 | QUnit.moduleStart( name, testEnvironment ); 59 | }); 60 | }, 61 | 62 | asyncTest: function(testName, expected, callback) { 63 | if ( arguments.length === 2 ) { 64 | callback = expected; 65 | expected = 0; 66 | } 67 | 68 | QUnit.test(testName, expected, callback, true); 69 | }, 70 | 71 | test: function(testName, expected, callback, async) { 72 | var name = testName, testEnvironment, testEnvironmentArg; 73 | 74 | if ( arguments.length === 2 ) { 75 | callback = expected; 76 | expected = null; 77 | } 78 | // is 2nd argument a testEnvironment? 79 | if ( expected && typeof expected === 'object') { 80 | testEnvironmentArg = expected; 81 | expected = null; 82 | } 83 | 84 | if ( config.currentModule ) { 85 | name = config.currentModule + " module: " + name; 86 | } 87 | 88 | if ( !validTest(name) ) { 89 | return; 90 | } 91 | 92 | synchronize(function() { 93 | QUnit.testStart( testName ); 94 | 95 | testEnvironment = extend({ 96 | setup: function() {}, 97 | teardown: function() {} 98 | }, config.moduleTestEnvironment); 99 | if (testEnvironmentArg) { 100 | extend(testEnvironment,testEnvironmentArg); 101 | } 102 | 103 | // allow utility functions to access the current test environment 104 | QUnit.current_testEnvironment = testEnvironment; 105 | 106 | config.assertions = []; 107 | config.expected = expected; 108 | 109 | try { 110 | if ( !config.pollution ) { 111 | saveGlobal(); 112 | } 113 | 114 | testEnvironment.setup.call(testEnvironment); 115 | } catch(e) { 116 | QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); 117 | } 118 | 119 | if ( async ) { 120 | QUnit.stop(); 121 | } 122 | 123 | try { 124 | callback.call(testEnvironment); 125 | } catch(e) { 126 | fail("Test " + name + " died, exception and test follows", e, callback); 127 | QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); 128 | // else next test will carry the responsibility 129 | saveGlobal(); 130 | 131 | // Restart the tests if they're blocking 132 | if ( config.blocking ) { 133 | start(); 134 | } 135 | } 136 | }); 137 | 138 | synchronize(function() { 139 | try { 140 | checkPollution(); 141 | testEnvironment.teardown.call(testEnvironment); 142 | } catch(e) { 143 | QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); 144 | } 145 | 146 | try { 147 | QUnit.reset(); 148 | } catch(e) { 149 | fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); 150 | } 151 | 152 | if ( config.expected && config.expected != config.assertions.length ) { 153 | QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); 154 | } 155 | 156 | var good = 0, bad = 0, 157 | tests = id("qunit-tests"); 158 | 159 | config.stats.all += config.assertions.length; 160 | config.moduleStats.all += config.assertions.length; 161 | 162 | if ( tests ) { 163 | var ol = document.createElement("ol"); 164 | ol.style.display = "none"; 165 | 166 | for ( var i = 0; i < config.assertions.length; i++ ) { 167 | var assertion = config.assertions[i]; 168 | 169 | var li = document.createElement("li"); 170 | li.className = assertion.result ? "pass" : "fail"; 171 | li.appendChild(document.createTextNode(assertion.message || "(no message)")); 172 | ol.appendChild( li ); 173 | 174 | if ( assertion.result ) { 175 | good++; 176 | } else { 177 | bad++; 178 | config.stats.bad++; 179 | config.moduleStats.bad++; 180 | } 181 | } 182 | 183 | var b = document.createElement("strong"); 184 | b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; 185 | 186 | addEvent(b, "click", function() { 187 | var next = b.nextSibling, display = next.style.display; 188 | next.style.display = display === "none" ? "block" : "none"; 189 | }); 190 | 191 | addEvent(b, "dblclick", function(e) { 192 | var target = e && e.target ? e.target : window.event.srcElement; 193 | if ( target.nodeName.toLowerCase() === "strong" ) { 194 | var text = "", node = target.firstChild; 195 | 196 | while ( node.nodeType === 3 ) { 197 | text += node.nodeValue; 198 | node = node.nextSibling; 199 | } 200 | 201 | text = text.replace(/(^\s*|\s*$)/g, ""); 202 | 203 | if ( window.location ) { 204 | window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); 205 | } 206 | } 207 | }); 208 | 209 | var li = document.createElement("li"); 210 | li.className = bad ? "fail" : "pass"; 211 | li.appendChild( b ); 212 | li.appendChild( ol ); 213 | tests.appendChild( li ); 214 | 215 | if ( bad ) { 216 | var toolbar = id("qunit-testrunner-toolbar"); 217 | if ( toolbar ) { 218 | toolbar.style.display = "block"; 219 | id("qunit-filter-pass").disabled = null; 220 | id("qunit-filter-missing").disabled = null; 221 | } 222 | } 223 | 224 | } else { 225 | for ( var i = 0; i < config.assertions.length; i++ ) { 226 | if ( !config.assertions[i].result ) { 227 | bad++; 228 | config.stats.bad++; 229 | config.moduleStats.bad++; 230 | } 231 | } 232 | } 233 | 234 | QUnit.testDone( testName, bad, config.assertions.length ); 235 | 236 | if ( !window.setTimeout && !config.queue.length ) { 237 | done(); 238 | } 239 | }); 240 | 241 | if ( window.setTimeout && !config.doneTimer ) { 242 | config.doneTimer = window.setTimeout(function(){ 243 | if ( !config.queue.length ) { 244 | done(); 245 | } else { 246 | synchronize( done ); 247 | } 248 | }, 13); 249 | } 250 | }, 251 | 252 | /** 253 | * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. 254 | */ 255 | expect: function(asserts) { 256 | config.expected = asserts; 257 | }, 258 | 259 | /** 260 | * Asserts true. 261 | * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); 262 | */ 263 | ok: function(a, msg) { 264 | QUnit.log(a, msg); 265 | 266 | config.assertions.push({ 267 | result: !!a, 268 | message: msg 269 | }); 270 | }, 271 | 272 | /** 273 | * Checks that the first two arguments are equal, with an optional message. 274 | * Prints out both actual and expected values. 275 | * 276 | * Prefered to ok( actual == expected, message ) 277 | * 278 | * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); 279 | * 280 | * @param Object actual 281 | * @param Object expected 282 | * @param String message (optional) 283 | */ 284 | equal: function(actual, expected, message) { 285 | push(expected == actual, actual, expected, message); 286 | }, 287 | 288 | notEqual: function(actual, expected, message) { 289 | push(expected != actual, actual, expected, message); 290 | }, 291 | 292 | deepEqual: function(a, b, message) { 293 | push(QUnit.equiv(a, b), a, b, message); 294 | }, 295 | 296 | notDeepEqual: function(a, b, message) { 297 | push(!QUnit.equiv(a, b), a, b, message); 298 | }, 299 | 300 | strictEqual: function(actual, expected, message) { 301 | push(expected === actual, actual, expected, message); 302 | }, 303 | 304 | notStrictEqual: function(actual, expected, message) { 305 | push(expected !== actual, actual, expected, message); 306 | }, 307 | 308 | start: function() { 309 | // A slight delay, to avoid any current callbacks 310 | if ( window.setTimeout ) { 311 | window.setTimeout(function() { 312 | if ( config.timeout ) { 313 | clearTimeout(config.timeout); 314 | } 315 | 316 | config.blocking = false; 317 | process(); 318 | }, 13); 319 | } else { 320 | config.blocking = false; 321 | process(); 322 | } 323 | }, 324 | 325 | stop: function(timeout) { 326 | config.blocking = true; 327 | 328 | if ( timeout && window.setTimeout ) { 329 | config.timeout = window.setTimeout(function() { 330 | QUnit.ok( false, "Test timed out" ); 331 | QUnit.start(); 332 | }, timeout); 333 | } 334 | }, 335 | 336 | /** 337 | * Resets the test setup. Useful for tests that modify the DOM. 338 | */ 339 | reset: function() { 340 | if ( window.jQuery ) { 341 | jQuery("#main").html( config.fixture ); 342 | jQuery.event.global = {}; 343 | jQuery.ajaxSettings = extend({}, config.ajaxSettings); 344 | } 345 | }, 346 | 347 | /** 348 | * Trigger an event on an element. 349 | * 350 | * @example triggerEvent( document.body, "click" ); 351 | * 352 | * @param DOMElement elem 353 | * @param String type 354 | */ 355 | triggerEvent: function( elem, type, event ) { 356 | if ( document.createEvent ) { 357 | event = document.createEvent("MouseEvents"); 358 | event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 359 | 0, 0, 0, 0, 0, false, false, false, false, 0, null); 360 | elem.dispatchEvent( event ); 361 | 362 | } else if ( elem.fireEvent ) { 363 | elem.fireEvent("on"+type); 364 | } 365 | }, 366 | 367 | // Safe object type checking 368 | is: function( type, obj ) { 369 | return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; 370 | }, 371 | 372 | // Logging callbacks 373 | done: function(failures, total) {}, 374 | log: function(result, message) {}, 375 | testStart: function(name) {}, 376 | testDone: function(name, failures, total) {}, 377 | moduleStart: function(name, testEnvironment) {}, 378 | moduleDone: function(name, failures, total) {} 379 | }; 380 | 381 | // Backwards compatibility, deprecated 382 | QUnit.equals = QUnit.equal; 383 | QUnit.same = QUnit.deepEqual; 384 | 385 | // Maintain internal state 386 | var config = { 387 | // The queue of tests to run 388 | queue: [], 389 | 390 | // block until document ready 391 | blocking: true 392 | }; 393 | 394 | // Load paramaters 395 | (function() { 396 | var location = window.location || { search: "", protocol: "file:" }, 397 | GETParams = location.search.slice(1).split('&'); 398 | 399 | for ( var i = 0; i < GETParams.length; i++ ) { 400 | GETParams[i] = decodeURIComponent( GETParams[i] ); 401 | if ( GETParams[i] === "noglobals" ) { 402 | GETParams.splice( i, 1 ); 403 | i--; 404 | config.noglobals = true; 405 | } else if ( GETParams[i].search('=') > -1 ) { 406 | GETParams.splice( i, 1 ); 407 | i--; 408 | } 409 | } 410 | 411 | // restrict modules/tests by get parameters 412 | config.filters = GETParams; 413 | 414 | // Figure out if we're running the tests from a server or not 415 | QUnit.isLocal = !!(location.protocol === 'file:'); 416 | })(); 417 | 418 | // Expose the API as global variables, unless an 'exports' 419 | // object exists, in that case we assume we're in CommonJS 420 | if ( typeof exports === "undefined" || typeof require === "undefined" ) { 421 | extend(window, QUnit); 422 | window.QUnit = QUnit; 423 | } else { 424 | extend(exports, QUnit); 425 | exports.QUnit = QUnit; 426 | } 427 | 428 | if ( typeof document === "undefined" || document.readyState === "complete" ) { 429 | config.autorun = true; 430 | } 431 | 432 | addEvent(window, "load", function() { 433 | // Initialize the config, saving the execution queue 434 | var oldconfig = extend({}, config); 435 | QUnit.init(); 436 | extend(config, oldconfig); 437 | 438 | config.blocking = false; 439 | 440 | var userAgent = id("qunit-userAgent"); 441 | if ( userAgent ) { 442 | userAgent.innerHTML = navigator.userAgent; 443 | } 444 | 445 | var toolbar = id("qunit-testrunner-toolbar"); 446 | if ( toolbar ) { 447 | toolbar.style.display = "none"; 448 | 449 | var filter = document.createElement("input"); 450 | filter.type = "checkbox"; 451 | filter.id = "qunit-filter-pass"; 452 | filter.disabled = true; 453 | addEvent( filter, "click", function() { 454 | var li = document.getElementsByTagName("li"); 455 | for ( var i = 0; i < li.length; i++ ) { 456 | if ( li[i].className.indexOf("pass") > -1 ) { 457 | li[i].style.display = filter.checked ? "none" : ""; 458 | } 459 | } 460 | }); 461 | toolbar.appendChild( filter ); 462 | 463 | var label = document.createElement("label"); 464 | label.setAttribute("for", "qunit-filter-pass"); 465 | label.innerHTML = "Hide passed tests"; 466 | toolbar.appendChild( label ); 467 | 468 | var missing = document.createElement("input"); 469 | missing.type = "checkbox"; 470 | missing.id = "qunit-filter-missing"; 471 | missing.disabled = true; 472 | addEvent( missing, "click", function() { 473 | var li = document.getElementsByTagName("li"); 474 | for ( var i = 0; i < li.length; i++ ) { 475 | if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { 476 | li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; 477 | } 478 | } 479 | }); 480 | toolbar.appendChild( missing ); 481 | 482 | label = document.createElement("label"); 483 | label.setAttribute("for", "qunit-filter-missing"); 484 | label.innerHTML = "Hide missing tests (untested code is broken code)"; 485 | toolbar.appendChild( label ); 486 | } 487 | 488 | var main = id('main'); 489 | if ( main ) { 490 | config.fixture = main.innerHTML; 491 | } 492 | 493 | if ( window.jQuery ) { 494 | config.ajaxSettings = window.jQuery.ajaxSettings; 495 | } 496 | 497 | QUnit.start(); 498 | }); 499 | 500 | function done() { 501 | if ( config.doneTimer && window.clearTimeout ) { 502 | window.clearTimeout( config.doneTimer ); 503 | config.doneTimer = null; 504 | } 505 | 506 | if ( config.queue.length ) { 507 | config.doneTimer = window.setTimeout(function(){ 508 | if ( !config.queue.length ) { 509 | done(); 510 | } else { 511 | synchronize( done ); 512 | } 513 | }, 13); 514 | 515 | return; 516 | } 517 | 518 | config.autorun = true; 519 | 520 | // Log the last module results 521 | if ( config.currentModule ) { 522 | QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); 523 | } 524 | 525 | var banner = id("qunit-banner"), 526 | tests = id("qunit-tests"), 527 | html = ['Tests completed in ', 528 | +new Date - config.started, ' milliseconds.
', 529 | '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); 530 | 531 | if ( banner ) { 532 | banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); 533 | } 534 | 535 | if ( tests ) { 536 | var result = id("qunit-testresult"); 537 | 538 | if ( !result ) { 539 | result = document.createElement("p"); 540 | result.id = "qunit-testresult"; 541 | result.className = "result"; 542 | tests.parentNode.insertBefore( result, tests.nextSibling ); 543 | } 544 | 545 | result.innerHTML = html; 546 | } 547 | 548 | QUnit.done( config.stats.bad, config.stats.all ); 549 | } 550 | 551 | function validTest( name ) { 552 | var i = config.filters.length, 553 | run = false; 554 | 555 | if ( !i ) { 556 | return true; 557 | } 558 | 559 | while ( i-- ) { 560 | var filter = config.filters[i], 561 | not = filter.charAt(0) == '!'; 562 | 563 | if ( not ) { 564 | filter = filter.slice(1); 565 | } 566 | 567 | if ( name.indexOf(filter) !== -1 ) { 568 | return !not; 569 | } 570 | 571 | if ( not ) { 572 | run = true; 573 | } 574 | } 575 | 576 | return run; 577 | } 578 | 579 | function push(result, actual, expected, message) { 580 | message = message || (result ? "okay" : "failed"); 581 | QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); 582 | } 583 | 584 | function synchronize( callback ) { 585 | config.queue.push( callback ); 586 | 587 | if ( config.autorun && !config.blocking ) { 588 | process(); 589 | } 590 | } 591 | 592 | function process() { 593 | while ( config.queue.length && !config.blocking ) { 594 | config.queue.shift()(); 595 | } 596 | } 597 | 598 | function saveGlobal() { 599 | config.pollution = []; 600 | 601 | if ( config.noglobals ) { 602 | for ( var key in window ) { 603 | config.pollution.push( key ); 604 | } 605 | } 606 | } 607 | 608 | function checkPollution( name ) { 609 | var old = config.pollution; 610 | saveGlobal(); 611 | 612 | var newGlobals = diff( old, config.pollution ); 613 | if ( newGlobals.length > 0 ) { 614 | ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); 615 | config.expected++; 616 | } 617 | 618 | var deletedGlobals = diff( config.pollution, old ); 619 | if ( deletedGlobals.length > 0 ) { 620 | ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); 621 | config.expected++; 622 | } 623 | } 624 | 625 | // returns a new Array with the elements that are in a but not in b 626 | function diff( a, b ) { 627 | var result = a.slice(); 628 | for ( var i = 0; i < result.length; i++ ) { 629 | for ( var j = 0; j < b.length; j++ ) { 630 | if ( result[i] === b[j] ) { 631 | result.splice(i, 1); 632 | i--; 633 | break; 634 | } 635 | } 636 | } 637 | return result; 638 | } 639 | 640 | function fail(message, exception, callback) { 641 | if ( typeof console !== "undefined" && console.error && console.warn ) { 642 | console.error(message); 643 | console.error(exception); 644 | console.warn(callback.toString()); 645 | 646 | } else if ( window.opera && opera.postError ) { 647 | opera.postError(message, exception, callback.toString); 648 | } 649 | } 650 | 651 | function extend(a, b) { 652 | for ( var prop in b ) { 653 | a[prop] = b[prop]; 654 | } 655 | 656 | return a; 657 | } 658 | 659 | function addEvent(elem, type, fn) { 660 | if ( elem.addEventListener ) { 661 | elem.addEventListener( type, fn, false ); 662 | } else if ( elem.attachEvent ) { 663 | elem.attachEvent( "on" + type, fn ); 664 | } else { 665 | fn(); 666 | } 667 | } 668 | 669 | function id(name) { 670 | return !!(typeof document !== "undefined" && document && document.getElementById) && 671 | document.getElementById( name ); 672 | } 673 | 674 | // Test for equality any JavaScript type. 675 | // Discussions and reference: http://philrathe.com/articles/equiv 676 | // Test suites: http://philrathe.com/tests/equiv 677 | // Author: Philippe Rathé 678 | QUnit.equiv = function () { 679 | 680 | var innerEquiv; // the real equiv function 681 | var callers = []; // stack to decide between skip/abort functions 682 | 683 | 684 | // Determine what is o. 685 | function hoozit(o) { 686 | if (QUnit.is("String", o)) { 687 | return "string"; 688 | 689 | } else if (QUnit.is("Boolean", o)) { 690 | return "boolean"; 691 | 692 | } else if (QUnit.is("Number", o)) { 693 | 694 | if (isNaN(o)) { 695 | return "nan"; 696 | } else { 697 | return "number"; 698 | } 699 | 700 | } else if (typeof o === "undefined") { 701 | return "undefined"; 702 | 703 | // consider: typeof null === object 704 | } else if (o === null) { 705 | return "null"; 706 | 707 | // consider: typeof [] === object 708 | } else if (QUnit.is( "Array", o)) { 709 | return "array"; 710 | 711 | // consider: typeof new Date() === object 712 | } else if (QUnit.is( "Date", o)) { 713 | return "date"; 714 | 715 | // consider: /./ instanceof Object; 716 | // /./ instanceof RegExp; 717 | // typeof /./ === "function"; // => false in IE and Opera, 718 | // true in FF and Safari 719 | } else if (QUnit.is( "RegExp", o)) { 720 | return "regexp"; 721 | 722 | } else if (typeof o === "object") { 723 | return "object"; 724 | 725 | } else if (QUnit.is( "Function", o)) { 726 | return "function"; 727 | } else { 728 | return undefined; 729 | } 730 | } 731 | 732 | // Call the o related callback with the given arguments. 733 | function bindCallbacks(o, callbacks, args) { 734 | var prop = hoozit(o); 735 | if (prop) { 736 | if (hoozit(callbacks[prop]) === "function") { 737 | return callbacks[prop].apply(callbacks, args); 738 | } else { 739 | return callbacks[prop]; // or undefined 740 | } 741 | } 742 | } 743 | 744 | var callbacks = function () { 745 | 746 | // for string, boolean, number and null 747 | function useStrictEquality(b, a) { 748 | if (b instanceof a.constructor || a instanceof b.constructor) { 749 | // to catch short annotaion VS 'new' annotation of a declaration 750 | // e.g. var i = 1; 751 | // var j = new Number(1); 752 | return a == b; 753 | } else { 754 | return a === b; 755 | } 756 | } 757 | 758 | return { 759 | "string": useStrictEquality, 760 | "boolean": useStrictEquality, 761 | "number": useStrictEquality, 762 | "null": useStrictEquality, 763 | "undefined": useStrictEquality, 764 | 765 | "nan": function (b) { 766 | return isNaN(b); 767 | }, 768 | 769 | "date": function (b, a) { 770 | return hoozit(b) === "date" && a.valueOf() === b.valueOf(); 771 | }, 772 | 773 | "regexp": function (b, a) { 774 | return hoozit(b) === "regexp" && 775 | a.source === b.source && // the regex itself 776 | a.global === b.global && // and its modifers (gmi) ... 777 | a.ignoreCase === b.ignoreCase && 778 | a.multiline === b.multiline; 779 | }, 780 | 781 | // - skip when the property is a method of an instance (OOP) 782 | // - abort otherwise, 783 | // initial === would have catch identical references anyway 784 | "function": function () { 785 | var caller = callers[callers.length - 1]; 786 | return caller !== Object && 787 | typeof caller !== "undefined"; 788 | }, 789 | 790 | "array": function (b, a) { 791 | var i; 792 | var len; 793 | 794 | // b could be an object literal here 795 | if ( ! (hoozit(b) === "array")) { 796 | return false; 797 | } 798 | 799 | len = a.length; 800 | if (len !== b.length) { // safe and faster 801 | return false; 802 | } 803 | for (i = 0; i < len; i++) { 804 | if ( ! innerEquiv(a[i], b[i])) { 805 | return false; 806 | } 807 | } 808 | return true; 809 | }, 810 | 811 | "object": function (b, a) { 812 | var i; 813 | var eq = true; // unless we can proove it 814 | var aProperties = [], bProperties = []; // collection of strings 815 | 816 | // comparing constructors is more strict than using instanceof 817 | if ( a.constructor !== b.constructor) { 818 | return false; 819 | } 820 | 821 | // stack constructor before traversing properties 822 | callers.push(a.constructor); 823 | 824 | for (i in a) { // be strict: don't ensures hasOwnProperty and go deep 825 | 826 | aProperties.push(i); // collect a's properties 827 | 828 | if ( ! innerEquiv(a[i], b[i])) { 829 | eq = false; 830 | } 831 | } 832 | 833 | callers.pop(); // unstack, we are done 834 | 835 | for (i in b) { 836 | bProperties.push(i); // collect b's properties 837 | } 838 | 839 | // Ensures identical properties name 840 | return eq && innerEquiv(aProperties.sort(), bProperties.sort()); 841 | } 842 | }; 843 | }(); 844 | 845 | innerEquiv = function () { // can take multiple arguments 846 | var args = Array.prototype.slice.apply(arguments); 847 | if (args.length < 2) { 848 | return true; // end transition 849 | } 850 | 851 | return (function (a, b) { 852 | if (a === b) { 853 | return true; // catch the most you can 854 | } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { 855 | return false; // don't lose time with error prone cases 856 | } else { 857 | return bindCallbacks(a, callbacks, [b, a]); 858 | } 859 | 860 | // apply transition with (1..n) arguments 861 | })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); 862 | }; 863 | 864 | return innerEquiv; 865 | 866 | }(); 867 | 868 | /** 869 | * jsDump 870 | * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com 871 | * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) 872 | * Date: 5/15/2008 873 | * @projectDescription Advanced and extensible data dumping for Javascript. 874 | * @version 1.0.0 875 | * @author Ariel Flesler 876 | * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} 877 | */ 878 | QUnit.jsDump = (function() { 879 | function quote( str ) { 880 | return '"' + str.toString().replace(/"/g, '\\"') + '"'; 881 | }; 882 | function literal( o ) { 883 | return o + ''; 884 | }; 885 | function join( pre, arr, post ) { 886 | var s = jsDump.separator(), 887 | base = jsDump.indent(), 888 | inner = jsDump.indent(1); 889 | if ( arr.join ) 890 | arr = arr.join( ',' + s + inner ); 891 | if ( !arr ) 892 | return pre + post; 893 | return [ pre, inner + arr, base + post ].join(s); 894 | }; 895 | function array( arr ) { 896 | var i = arr.length, ret = Array(i); 897 | this.up(); 898 | while ( i-- ) 899 | ret[i] = this.parse( arr[i] ); 900 | this.down(); 901 | return join( '[', ret, ']' ); 902 | }; 903 | 904 | var reName = /^function (\w+)/; 905 | 906 | var jsDump = { 907 | parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance 908 | var parser = this.parsers[ type || this.typeOf(obj) ]; 909 | type = typeof parser; 910 | 911 | return type == 'function' ? parser.call( this, obj ) : 912 | type == 'string' ? parser : 913 | this.parsers.error; 914 | }, 915 | typeOf:function( obj ) { 916 | var type; 917 | if ( obj === null ) { 918 | type = "null"; 919 | } else if (typeof obj === "undefined") { 920 | type = "undefined"; 921 | } else if (QUnit.is("RegExp", obj)) { 922 | type = "regexp"; 923 | } else if (QUnit.is("Date", obj)) { 924 | type = "date"; 925 | } else if (QUnit.is("Function", obj)) { 926 | type = "function"; 927 | } else if (QUnit.is("Array", obj)) { 928 | type = "array"; 929 | } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { 930 | type = "window"; 931 | } else if (QUnit.is("HTMLDocument", obj)) { 932 | type = "document"; 933 | } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { 934 | type = "nodelist"; 935 | } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { 936 | type = "node"; 937 | } else { 938 | type = typeof obj; 939 | } 940 | return type; 941 | }, 942 | separator:function() { 943 | return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; 944 | }, 945 | indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing 946 | if ( !this.multiline ) 947 | return ''; 948 | var chr = this.indentChar; 949 | if ( this.HTML ) 950 | chr = chr.replace(/\t/g,' ').replace(/ /g,' '); 951 | return Array( this._depth_ + (extra||0) ).join(chr); 952 | }, 953 | up:function( a ) { 954 | this._depth_ += a || 1; 955 | }, 956 | down:function( a ) { 957 | this._depth_ -= a || 1; 958 | }, 959 | setParser:function( name, parser ) { 960 | this.parsers[name] = parser; 961 | }, 962 | // The next 3 are exposed so you can use them 963 | quote:quote, 964 | literal:literal, 965 | join:join, 966 | // 967 | _depth_: 1, 968 | // This is the list of parsers, to modify them, use jsDump.setParser 969 | parsers:{ 970 | window: '[Window]', 971 | document: '[Document]', 972 | error:'[ERROR]', //when no parser is found, shouldn't happen 973 | unknown: '[Unknown]', 974 | 'null':'null', 975 | undefined:'undefined', 976 | 'function':function( fn ) { 977 | var ret = 'function', 978 | name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE 979 | if ( name ) 980 | ret += ' ' + name; 981 | ret += '('; 982 | 983 | ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); 984 | return join( ret, this.parse(fn,'functionCode'), '}' ); 985 | }, 986 | array: array, 987 | nodelist: array, 988 | arguments: array, 989 | object:function( map ) { 990 | var ret = [ ]; 991 | this.up(); 992 | for ( var key in map ) 993 | ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); 994 | this.down(); 995 | return join( '{', ret, '}' ); 996 | }, 997 | node:function( node ) { 998 | var open = this.HTML ? '<' : '<', 999 | close = this.HTML ? '>' : '>'; 1000 | 1001 | var tag = node.nodeName.toLowerCase(), 1002 | ret = open + tag; 1003 | 1004 | for ( var a in this.DOMAttrs ) { 1005 | var val = node[this.DOMAttrs[a]]; 1006 | if ( val ) 1007 | ret += ' ' + a + '=' + this.parse( val, 'attribute' ); 1008 | } 1009 | return ret + close + open + '/' + tag + close; 1010 | }, 1011 | functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function 1012 | var l = fn.length; 1013 | if ( !l ) return ''; 1014 | 1015 | var args = Array(l); 1016 | while ( l-- ) 1017 | args[l] = String.fromCharCode(97+l);//97 is 'a' 1018 | return ' ' + args.join(', ') + ' '; 1019 | }, 1020 | key:quote, //object calls it internally, the key part of an item in a map 1021 | functionCode:'[code]', //function calls it internally, it's the content of the function 1022 | attribute:quote, //node calls it internally, it's an html attribute value 1023 | string:quote, 1024 | date:quote, 1025 | regexp:literal, //regex 1026 | number:literal, 1027 | 'boolean':literal 1028 | }, 1029 | DOMAttrs:{//attributes to dump from nodes, name=>realName 1030 | id:'id', 1031 | name:'name', 1032 | 'class':'className' 1033 | }, 1034 | HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) 1035 | indentChar:' ',//indentation unit 1036 | multiline:true //if true, items in a collection, are separated by a \n, else just a space. 1037 | }; 1038 | 1039 | return jsDump; 1040 | })(); 1041 | 1042 | })(this); 1043 | --------------------------------------------------------------------------------