├── .gitignore ├── .jshintrc ├── .testem.json ├── .travis.yml ├── CONTRIBUTION.md ├── LICENCE ├── README.md ├── docs.mli ├── document.js ├── dom-comment.js ├── dom-element.js ├── dom-fragment.js ├── dom-text.js ├── event.js ├── event ├── add-event-listener.js ├── dispatch-event.js └── remove-event-listener.js ├── index.js ├── package.json ├── serialize.js └── test ├── cleanup.js ├── index.js ├── static ├── index.html └── test-adapter.js ├── test-document.js ├── test-dom-comment.js └── test-dom-element.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .monitor 3 | .*.swp 4 | .nodemonignore 5 | releases 6 | *.log 7 | *.err 8 | fleet.json 9 | public/browserify 10 | bin/*.json 11 | .bin 12 | build 13 | compile 14 | .lock-wscript 15 | node_modules 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxdepth": 4, 3 | "maxstatements": 200, 4 | "maxcomplexity": 12, 5 | "maxlen": 80, 6 | "maxparams": 5, 7 | 8 | "curly": true, 9 | "eqeqeq": true, 10 | "immed": true, 11 | "latedef": false, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": true, 15 | "undef": true, 16 | "unused": "vars", 17 | "trailing": true, 18 | 19 | "quotmark": true, 20 | "expr": true, 21 | "asi": true, 22 | 23 | "browser": false, 24 | "esnext": true, 25 | "devel": false, 26 | "node": false, 27 | "nonstandard": false, 28 | 29 | "predef": ["require", "module", "__dirname", "__filename"] 30 | } 31 | -------------------------------------------------------------------------------- /.testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "launchers": { 3 | "node": { 4 | "command": "node ./test" 5 | } 6 | }, 7 | "src_files": [ 8 | "./**/*.js" 9 | ], 10 | "before_tests": "npm run build-test", 11 | "on_exit": "rm test/static/bundle.js", 12 | "test_page": "test/static/index.html", 13 | "launch_in_dev": ["node", "phantomjs"] 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - 0.9 5 | - 0.10 6 | script: node ./test/index.js 7 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # This is an OPEN Open Source Project 2 | 3 | ## What? 4 | 5 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 6 | 7 | ## Rules 8 | 9 | There are a few basic ground-rules for contributors: 10 | 11 | - No --force pushes or modifying the Git history in any way. 12 | - Non-master branches ought to be used for ongoing work. 13 | - External API changes and significant modifications ought to be subject to an internal pull-request to solicit feedback from other contributors. 14 | - Internal pull-requests to solicit feedback are encouraged for any other non-trivial contribution but left to the discretion of the contributor. 15 | - For significant changes wait a full 24 hours before merging so that active contributors who are distributed throughout the world have a chance to weigh in. 16 | - Contributors should attempt to adhere to the prevailing code-style. 17 | Releases 18 | 19 | Declaring formal releases requires peer review. 20 | 21 | - A reviewer of a pull request should recommend a new version number (patch, minor or major). 22 | - Once your change is merged feel free to bump the version as recommended by the reviewer. 23 | - A new version number should not be cut without peer review unless done by the project maintainer. 24 | 25 | ## Want to contribute? 26 | 27 | Even though collaborators may contribute as they see fit, if you are not sure what to do, here's a suggested process: 28 | 29 | ## Cutting a new version 30 | 31 | - Get your branch merged on master 32 | - Run `npm version major` or `npm version minor` or `npm version patch` 33 | - `git push origin master --tags` 34 | - If you are a project owner, then `npm publish` 35 | 36 | ## If you want to have a bug fixed or a feature added: 37 | 38 | - Check open issues for what you want. 39 | - If there is an open issue, comment on it, otherwise open an issue describing your bug or feature with use cases. 40 | - Discussion happens on the issue about how to solve your problem. 41 | - You or a core contributor opens a pull request solving the issue with tests and documentation. 42 | - The pull requests gets reviewed and then merged. 43 | - A new release version get's cut. 44 | - (Disclaimer: Your feature might get rejected.) 45 | 46 | ### Changes to this arrangement 47 | 48 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 49 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Colingo. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # min-document 2 | 3 | [![build status][1]][2] [![dependency status][3]][4] 4 | 5 | 6 | 7 | A minimal DOM implementation 8 | 9 | ## Example 10 | 11 | ```js 12 | var document = require("min-document") 13 | 14 | var div = document.createElement("div") 15 | div.className = "foo bar" 16 | 17 | var span = document.createElement("span") 18 | div.appendChild(span) 19 | span.textContent = "Hello!" 20 | 21 | /*
22 | Hello! 23 |
24 | */ 25 | var html = String(div) 26 | ``` 27 | 28 | ## Installation 29 | 30 | `npm install min-document` 31 | 32 | ## Contributors 33 | 34 | - Raynos 35 | 36 | ## MIT Licenced 37 | 38 | [1]: https://secure.travis-ci.org/Raynos/min-document.png 39 | [2]: https://travis-ci.org/Raynos/min-document 40 | [3]: https://david-dm.org/Raynos/min-document.png 41 | [4]: https://david-dm.org/Raynos/min-document 42 | [5]: https://ci.testling.com/Raynos/min-document.png 43 | [6]: https://ci.testling.com/Raynos/min-document 44 | -------------------------------------------------------------------------------- /docs.mli: -------------------------------------------------------------------------------- 1 | type Comment := { 2 | data: String, 3 | length: Number, 4 | nodeName: "#comment", 5 | nodeType: 8, 6 | nodeValue: String, 7 | ownerDoucment: null | Document, 8 | 9 | toString: (this: Comment) => String 10 | } 11 | 12 | type DOMText := { 13 | data: String, 14 | type: "DOMTextNode", 15 | length: Number, 16 | nodeType: 3, 17 | 18 | toString: (this: DOMText) => String, 19 | replaceChild: ( 20 | this: DOMText, 21 | index: Number, 22 | length: Number, 23 | value: String 24 | ) => void 25 | } 26 | 27 | type DOMNode := DOMText | DOMElement | DocumentFragment 28 | type DOMChild := DOMText | DOMElement 29 | 30 | type DOMElement := { 31 | tagName: String, 32 | className: String, 33 | dataset: Object, 34 | childNodes: Array, 35 | parentNode: null | DOMElement, 36 | style: Object, 37 | type: "DOMElement", 38 | nodeType: 1, 39 | ownerDoucment: null | Document, 40 | namespaceURI: null | String, 41 | 42 | appendChild: (this: DOMElement, child: DOMChild) => DOMChild, 43 | replaceChild:( 44 | this: DOMElement, 45 | elem: DOMChild, 46 | needle: DOMChild 47 | ) => DOMChild, 48 | removeChild: (this: DOMElement, child: DOMChild) => DOMChild, 49 | insertBefore: ( 50 | this: DOMElement, 51 | elem: DOMChild, 52 | needle: DOMChild | null | undefined 53 | ) => DOMChild, 54 | addEventListener: addEventListener, 55 | dispatchEvent: dispatchEvent, 56 | focus: () => void, 57 | toString: (this: DOMElement) => String, 58 | getElementsByClassName: ( 59 | this: DOMElement, 60 | className: String 61 | ) => Array, 62 | getElementsByTagName: ( 63 | this: DOMElement, 64 | tagName: String 65 | ) => Array, 66 | } 67 | 68 | type DocumentFragment := { 69 | childNodes: Array, 70 | parentNode: null | DOMElement, 71 | type: "DocumentFragment", 72 | nodeType: 11, 73 | nodeName: "#document-fragment", 74 | ownerDoucment: Document | null, 75 | 76 | appendChild: (this: DocumentFragment, child: DOMChild), 77 | replaceChild: 78 | (this: DocumentFragment, elem: DOMChild, needle: DOMChild), 79 | removeChild: (this: DocumentFragment, child: DOMChild), 80 | toString: (this: DocumentFragment) => String 81 | } 82 | 83 | type Document := { 84 | body: DOMElement, 85 | childNodes: Array, 86 | documentElement: DOMElement, 87 | nodeType: 9, 88 | 89 | createComment: (this: Document, data: String) => Commment, 90 | createTextNode: (this: Document, value: String) => DOMText, 91 | createElement: (this: Document, tagName: String) => DOMElement, 92 | createElementNS: ( 93 | this: Document, 94 | namespace: String | null, 95 | tagName: String 96 | ) => DOMElement, 97 | createDocumentFragment: (this: Document) => DocumentFragment, 98 | createEvent: () => Event, 99 | getElementById: ( 100 | this: Document, 101 | id: String, 102 | ) => null | DOMElement, 103 | getElementsByClassName: ( 104 | this: Document, 105 | className: String 106 | ) => Array, 107 | getElementsByTagName: ( 108 | this: Document, 109 | tagName: String 110 | ) => Array 111 | } 112 | 113 | type Event := { 114 | type: String, 115 | bubbles: Boolean, 116 | cancelable: Boolean, 117 | 118 | initEvent: ( 119 | this: Event, 120 | type: String, 121 | bubbles: Boolean, 122 | cancelable: Boolean 123 | ) => void 124 | } 125 | 126 | type addEventListener := ( 127 | this: DOMElement, 128 | type: String, 129 | listener: Listener 130 | ) => void 131 | 132 | type dispatchEvent := ( 133 | this: DOMElement, 134 | ev: Event 135 | ) 136 | 137 | min-document/event/add-event-listener := addEventListener 138 | 139 | min-document/event/dispatch-event := dispatchEvent 140 | 141 | min-document/document := () => Document 142 | 143 | min-document/dom-element := 144 | (tagName: String, owner?: Document, namespace?: String | null) => DOMElement 145 | 146 | min-document/dom-fragment := 147 | (owner?: Document) => DocumentFragment 148 | 149 | min-document/dom-text := 150 | (value: String, owner?: Document) => DOMText 151 | 152 | min-document/event := () => Event 153 | 154 | min-document/serialize := (DOMElement) => String 155 | 156 | min-document := Document 157 | -------------------------------------------------------------------------------- /document.js: -------------------------------------------------------------------------------- 1 | var domWalk = require("dom-walk") 2 | 3 | var Comment = require("./dom-comment.js") 4 | var DOMText = require("./dom-text.js") 5 | var DOMElement = require("./dom-element.js") 6 | var DocumentFragment = require("./dom-fragment.js") 7 | var Event = require("./event.js") 8 | var dispatchEvent = require("./event/dispatch-event.js") 9 | var addEventListener = require("./event/add-event-listener.js") 10 | var removeEventListener = require("./event/remove-event-listener.js") 11 | 12 | module.exports = Document; 13 | 14 | function Document() { 15 | if (!(this instanceof Document)) { 16 | return new Document(); 17 | } 18 | 19 | this.head = this.createElement("head") 20 | this.body = this.createElement("body") 21 | this.documentElement = this.createElement("html") 22 | this.documentElement.appendChild(this.head) 23 | this.documentElement.appendChild(this.body) 24 | this.childNodes = [this.documentElement] 25 | this.nodeType = 9 26 | } 27 | 28 | var proto = Document.prototype; 29 | proto.createTextNode = function createTextNode(value) { 30 | return new DOMText(value, this) 31 | } 32 | 33 | proto.createElementNS = function createElementNS(namespace, tagName) { 34 | var ns = namespace === null ? null : String(namespace) 35 | return new DOMElement(tagName, this, ns) 36 | } 37 | 38 | proto.createElement = function createElement(tagName) { 39 | return new DOMElement(tagName, this) 40 | } 41 | 42 | proto.createDocumentFragment = function createDocumentFragment() { 43 | return new DocumentFragment(this) 44 | } 45 | 46 | proto.createEvent = function createEvent(family) { 47 | return new Event(family) 48 | } 49 | 50 | proto.createComment = function createComment(data) { 51 | return new Comment(data, this) 52 | } 53 | 54 | proto.getElementById = function getElementById(id) { 55 | id = String(id) 56 | 57 | var result = domWalk(this.childNodes, function (node) { 58 | if (String(node.id) === id) { 59 | return node 60 | } 61 | }) 62 | 63 | return result || null 64 | } 65 | 66 | proto.getElementsByClassName = DOMElement.prototype.getElementsByClassName 67 | proto.getElementsByTagName = DOMElement.prototype.getElementsByTagName 68 | proto.contains = DOMElement.prototype.contains 69 | 70 | proto.removeEventListener = removeEventListener 71 | proto.addEventListener = addEventListener 72 | proto.dispatchEvent = dispatchEvent 73 | -------------------------------------------------------------------------------- /dom-comment.js: -------------------------------------------------------------------------------- 1 | module.exports = Comment 2 | 3 | function Comment(data, owner) { 4 | if (!(this instanceof Comment)) { 5 | return new Comment(data, owner) 6 | } 7 | 8 | this.data = data 9 | this.nodeValue = data 10 | this.length = data.length 11 | this.ownerDocument = owner || null 12 | } 13 | 14 | Comment.prototype.nodeType = 8 15 | Comment.prototype.nodeName = "#comment" 16 | 17 | Comment.prototype.toString = function _Comment_toString() { 18 | return "[object Comment]" 19 | } 20 | -------------------------------------------------------------------------------- /dom-element.js: -------------------------------------------------------------------------------- 1 | var domWalk = require("dom-walk") 2 | var dispatchEvent = require("./event/dispatch-event.js") 3 | var addEventListener = require("./event/add-event-listener.js") 4 | var removeEventListener = require("./event/remove-event-listener.js") 5 | var serializeNode = require("./serialize.js") 6 | 7 | var htmlns = "http://www.w3.org/1999/xhtml" 8 | 9 | module.exports = DOMElement 10 | 11 | function DOMElement(tagName, owner, namespace) { 12 | if (!(this instanceof DOMElement)) { 13 | return new DOMElement(tagName) 14 | } 15 | 16 | var ns = namespace === undefined ? htmlns : (namespace || null) 17 | 18 | this.tagName = ns === htmlns ? String(tagName).toUpperCase() : tagName 19 | this.nodeName = this.tagName 20 | this.className = "" 21 | this.dataset = {} 22 | this.childNodes = [] 23 | this.parentNode = null 24 | this.style = {} 25 | this.ownerDocument = owner || null 26 | this.namespaceURI = ns 27 | this._attributes = {} 28 | 29 | if (this.tagName === 'INPUT') { 30 | this.type = 'text' 31 | } 32 | } 33 | 34 | DOMElement.prototype.type = "DOMElement" 35 | DOMElement.prototype.nodeType = 1 36 | 37 | DOMElement.prototype.appendChild = function _Element_appendChild(child) { 38 | if (child.parentNode) { 39 | child.parentNode.removeChild(child) 40 | } 41 | 42 | this.childNodes.push(child) 43 | child.parentNode = this 44 | 45 | return child 46 | } 47 | 48 | DOMElement.prototype.replaceChild = 49 | function _Element_replaceChild(elem, needle) { 50 | // TODO: Throw NotFoundError if needle.parentNode !== this 51 | 52 | if (elem.parentNode) { 53 | elem.parentNode.removeChild(elem) 54 | } 55 | 56 | var index = this.childNodes.indexOf(needle) 57 | 58 | needle.parentNode = null 59 | this.childNodes[index] = elem 60 | elem.parentNode = this 61 | 62 | return needle 63 | } 64 | 65 | DOMElement.prototype.removeChild = function _Element_removeChild(elem) { 66 | // TODO: Throw NotFoundError if elem.parentNode !== this 67 | 68 | var index = this.childNodes.indexOf(elem) 69 | this.childNodes.splice(index, 1) 70 | 71 | elem.parentNode = null 72 | return elem 73 | } 74 | 75 | DOMElement.prototype.insertBefore = 76 | function _Element_insertBefore(elem, needle) { 77 | // TODO: Throw NotFoundError if referenceElement is a dom node 78 | // and parentNode !== this 79 | 80 | if (elem.parentNode) { 81 | elem.parentNode.removeChild(elem) 82 | } 83 | 84 | var index = needle === null || needle === undefined ? 85 | -1 : 86 | this.childNodes.indexOf(needle) 87 | 88 | if (index > -1) { 89 | this.childNodes.splice(index, 0, elem) 90 | } else { 91 | this.childNodes.push(elem) 92 | } 93 | 94 | elem.parentNode = this 95 | return elem 96 | } 97 | 98 | DOMElement.prototype.setAttributeNS = 99 | function _Element_setAttributeNS(namespace, name, value) { 100 | var prefix = null 101 | var localName = name 102 | var colonPosition = name.indexOf(":") 103 | if (colonPosition > -1) { 104 | prefix = name.substr(0, colonPosition) 105 | localName = name.substr(colonPosition + 1) 106 | } 107 | if (this.tagName === 'INPUT' && name === 'type') { 108 | this.type = value; 109 | } 110 | else { 111 | var attributes = this._attributes[namespace] || (this._attributes[namespace] = {}) 112 | attributes[localName] = {value: value, prefix: prefix} 113 | } 114 | } 115 | 116 | DOMElement.prototype.getAttributeNS = 117 | function _Element_getAttributeNS(namespace, name) { 118 | var attributes = this._attributes[namespace]; 119 | var value = attributes && attributes[name] && attributes[name].value 120 | if (this.tagName === 'INPUT' && name === 'type') { 121 | return this.type; 122 | } 123 | if (typeof value !== "string") { 124 | return null 125 | } 126 | return value 127 | } 128 | 129 | DOMElement.prototype.removeAttributeNS = 130 | function _Element_removeAttributeNS(namespace, name) { 131 | var attributes = this._attributes[namespace]; 132 | if (attributes) { 133 | delete attributes[name] 134 | } 135 | } 136 | 137 | DOMElement.prototype.hasAttributeNS = 138 | function _Element_hasAttributeNS(namespace, name) { 139 | var attributes = this._attributes[namespace] 140 | return !!attributes && name in attributes; 141 | } 142 | 143 | DOMElement.prototype.setAttribute = function _Element_setAttribute(name, value) { 144 | return this.setAttributeNS(null, name, value) 145 | } 146 | 147 | DOMElement.prototype.getAttribute = function _Element_getAttribute(name) { 148 | return this.getAttributeNS(null, name) 149 | } 150 | 151 | DOMElement.prototype.removeAttribute = function _Element_removeAttribute(name) { 152 | return this.removeAttributeNS(null, name) 153 | } 154 | 155 | DOMElement.prototype.hasAttribute = function _Element_hasAttribute(name) { 156 | return this.hasAttributeNS(null, name) 157 | } 158 | 159 | DOMElement.prototype.removeEventListener = removeEventListener 160 | DOMElement.prototype.addEventListener = addEventListener 161 | DOMElement.prototype.dispatchEvent = dispatchEvent 162 | 163 | // Un-implemented 164 | DOMElement.prototype.focus = function _Element_focus() { 165 | return void 0 166 | } 167 | 168 | DOMElement.prototype.toString = function _Element_toString() { 169 | return serializeNode(this) 170 | } 171 | 172 | DOMElement.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) { 173 | var classes = classNames.split(" "); 174 | var elems = [] 175 | 176 | domWalk(this, function (node) { 177 | if (node.nodeType === 1) { 178 | var nodeClassName = node.className || "" 179 | var nodeClasses = nodeClassName.split(" ") 180 | 181 | if (classes.every(function (item) { 182 | return nodeClasses.indexOf(item) !== -1 183 | })) { 184 | elems.push(node) 185 | } 186 | } 187 | }) 188 | 189 | return elems 190 | } 191 | 192 | DOMElement.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) { 193 | tagName = tagName.toLowerCase() 194 | var elems = [] 195 | 196 | domWalk(this.childNodes, function (node) { 197 | if (node.nodeType === 1 && (tagName === '*' || node.tagName.toLowerCase() === tagName)) { 198 | elems.push(node) 199 | } 200 | }) 201 | 202 | return elems 203 | } 204 | 205 | DOMElement.prototype.contains = function _Element_contains(element) { 206 | return domWalk(this, function (node) { 207 | return element === node 208 | }) || false 209 | } 210 | -------------------------------------------------------------------------------- /dom-fragment.js: -------------------------------------------------------------------------------- 1 | var DOMElement = require("./dom-element.js") 2 | 3 | module.exports = DocumentFragment 4 | 5 | function DocumentFragment(owner) { 6 | if (!(this instanceof DocumentFragment)) { 7 | return new DocumentFragment() 8 | } 9 | 10 | this.childNodes = [] 11 | this.parentNode = null 12 | this.ownerDocument = owner || null 13 | } 14 | 15 | DocumentFragment.prototype.type = "DocumentFragment" 16 | DocumentFragment.prototype.nodeType = 11 17 | DocumentFragment.prototype.nodeName = "#document-fragment" 18 | 19 | DocumentFragment.prototype.appendChild = DOMElement.prototype.appendChild 20 | DocumentFragment.prototype.replaceChild = DOMElement.prototype.replaceChild 21 | DocumentFragment.prototype.removeChild = DOMElement.prototype.removeChild 22 | 23 | DocumentFragment.prototype.toString = 24 | function _DocumentFragment_toString() { 25 | return this.childNodes.map(function (node) { 26 | return String(node) 27 | }).join("") 28 | } 29 | -------------------------------------------------------------------------------- /dom-text.js: -------------------------------------------------------------------------------- 1 | module.exports = DOMText 2 | 3 | function DOMText(value, owner) { 4 | if (!(this instanceof DOMText)) { 5 | return new DOMText(value) 6 | } 7 | 8 | this.data = value || "" 9 | this.length = this.data.length 10 | this.ownerDocument = owner || null 11 | } 12 | 13 | DOMText.prototype.type = "DOMTextNode" 14 | DOMText.prototype.nodeType = 3 15 | DOMText.prototype.nodeName = "#text" 16 | 17 | DOMText.prototype.toString = function _Text_toString() { 18 | return this.data 19 | } 20 | 21 | DOMText.prototype.replaceData = function replaceData(index, length, value) { 22 | var current = this.data 23 | var left = current.substring(0, index) 24 | var right = current.substring(index + length, current.length) 25 | this.data = left + value + right 26 | this.length = this.data.length 27 | } 28 | -------------------------------------------------------------------------------- /event.js: -------------------------------------------------------------------------------- 1 | module.exports = Event 2 | 3 | function Event(family) {} 4 | 5 | Event.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) { 6 | this.type = type 7 | this.bubbles = bubbles 8 | this.cancelable = cancelable 9 | } 10 | 11 | Event.prototype.preventDefault = function _Event_preventDefault() { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /event/add-event-listener.js: -------------------------------------------------------------------------------- 1 | module.exports = addEventListener 2 | 3 | function addEventListener(type, listener) { 4 | var elem = this 5 | 6 | if (!elem.listeners) { 7 | elem.listeners = {} 8 | } 9 | 10 | if (!elem.listeners[type]) { 11 | elem.listeners[type] = [] 12 | } 13 | 14 | if (elem.listeners[type].indexOf(listener) === -1) { 15 | elem.listeners[type].push(listener) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /event/dispatch-event.js: -------------------------------------------------------------------------------- 1 | module.exports = dispatchEvent 2 | 3 | function dispatchEvent(ev) { 4 | var elem = this 5 | var type = ev.type 6 | 7 | if (!ev.target) { 8 | ev.target = elem 9 | } 10 | 11 | if (!elem.listeners) { 12 | elem.listeners = {} 13 | } 14 | 15 | var listeners = elem.listeners[type] 16 | 17 | if (listeners) { 18 | return listeners.forEach(function (listener) { 19 | ev.currentTarget = elem 20 | if (typeof listener === 'function') { 21 | listener(ev) 22 | } else { 23 | listener.handleEvent(ev) 24 | } 25 | }) 26 | } 27 | 28 | if (elem.parentNode) { 29 | elem.parentNode.dispatchEvent(ev) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /event/remove-event-listener.js: -------------------------------------------------------------------------------- 1 | module.exports = removeEventListener 2 | 3 | function removeEventListener(type, listener) { 4 | var elem = this 5 | 6 | if (!elem.listeners) { 7 | return 8 | } 9 | 10 | if (!elem.listeners[type]) { 11 | return 12 | } 13 | 14 | var list = elem.listeners[type] 15 | var index = list.indexOf(listener) 16 | if (index !== -1) { 17 | list.splice(index, 1) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Document = require('./document.js'); 2 | 3 | module.exports = new Document(); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "min-document", 3 | "version": "2.19.0", 4 | "description": "A minimal DOM implementation", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/Raynos/min-document.git", 8 | "main": "index.js", 9 | "homepage": "https://github.com/Raynos/min-document", 10 | "contributors": [ 11 | { 12 | "name": "Raynos" 13 | } 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/Raynos/min-document/issues", 17 | "email": "raynos2@gmail.com" 18 | }, 19 | "dependencies": { 20 | "dom-walk": "^0.1.0" 21 | }, 22 | "devDependencies": { 23 | "run-browser": "git://github.com/Raynos/run-browser", 24 | "tap-dot": "^0.2.1", 25 | "tap-spec": "^0.1.8", 26 | "tape": "^2.12.3" 27 | }, 28 | "license": "MIT", 29 | "scripts": { 30 | "test": "node ./test/index.js | tap-spec", 31 | "dot": "node ./test/index.js | tap-dot", 32 | "cover": "istanbul cover --report none --print detail ./test/index.js", 33 | "view-cover": "istanbul report html && google-chrome ./coverage/index.html", 34 | "browser": "run-browser test/index.js", 35 | "phantom": "run-browser test/index.js -b | tap-spec" 36 | }, 37 | "testling": { 38 | "files": "test/index.js", 39 | "browsers": [ 40 | "ie/8..latest", 41 | "firefox/16..latest", 42 | "firefox/nightly", 43 | "chrome/22..latest", 44 | "chrome/canary", 45 | "opera/12..latest", 46 | "opera/next", 47 | "safari/5.1..latest", 48 | "ipad/6.0..latest", 49 | "iphone/6.0..latest", 50 | "android-browser/4.2..latest" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /serialize.js: -------------------------------------------------------------------------------- 1 | module.exports = serializeNode 2 | 3 | var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"]; 4 | 5 | function serializeNode(node) { 6 | switch (node.nodeType) { 7 | case 3: 8 | return escapeText(node.data) 9 | case 8: 10 | return "" 11 | default: 12 | return serializeElement(node) 13 | } 14 | } 15 | 16 | function serializeElement(elem) { 17 | var strings = [] 18 | 19 | var tagname = elem.tagName 20 | 21 | if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") { 22 | tagname = tagname.toLowerCase() 23 | } 24 | 25 | strings.push("<" + tagname + properties(elem) + datasetify(elem)) 26 | 27 | if (voidElements.indexOf(tagname) > -1) { 28 | strings.push(" />") 29 | } else { 30 | strings.push(">") 31 | 32 | if (elem.childNodes.length) { 33 | strings.push.apply(strings, elem.childNodes.map(serializeNode)) 34 | } else if (elem.textContent || elem.innerText) { 35 | strings.push(escapeText(elem.textContent || elem.innerText)) 36 | } else if (elem.innerHTML) { 37 | strings.push(elem.innerHTML) 38 | } 39 | 40 | strings.push("") 41 | } 42 | 43 | return strings.join("") 44 | } 45 | 46 | function isProperty(elem, key) { 47 | var type = typeof elem[key] 48 | 49 | if (key === "style" && Object.keys(elem.style).length > 0) { 50 | return true 51 | } 52 | 53 | return elem.hasOwnProperty(key) && 54 | (type === "string" || type === "boolean" || type === "number") && 55 | key !== "nodeName" && key !== "className" && key !== "tagName" && 56 | key !== "textContent" && key !== "innerText" && key !== "namespaceURI" && key !== "innerHTML" 57 | } 58 | 59 | function stylify(styles) { 60 | if (typeof styles === 'string') return styles 61 | var attr = "" 62 | Object.keys(styles).forEach(function (key) { 63 | var value = styles[key] 64 | key = key.replace(/[A-Z]/g, function(c) { 65 | return "-" + c.toLowerCase(); 66 | }) 67 | attr += key + ":" + value + ";" 68 | }) 69 | return attr 70 | } 71 | 72 | function datasetify(elem) { 73 | var ds = elem.dataset 74 | var props = [] 75 | 76 | for (var key in ds) { 77 | props.push({ name: "data-" + key, value: ds[key] }) 78 | } 79 | 80 | return props.length ? stringify(props) : "" 81 | } 82 | 83 | function stringify(list) { 84 | var attributes = [] 85 | list.forEach(function (tuple) { 86 | var name = tuple.name 87 | var value = tuple.value 88 | 89 | if (name === "style") { 90 | value = stylify(value) 91 | } 92 | 93 | attributes.push(name + "=" + "\"" + escapeAttributeValue(value) + "\"") 94 | }) 95 | 96 | return attributes.length ? " " + attributes.join(" ") : "" 97 | } 98 | 99 | function properties(elem) { 100 | var props = [] 101 | for (var key in elem) { 102 | if (isProperty(elem, key)) { 103 | props.push({ name: key, value: elem[key] }) 104 | } 105 | } 106 | 107 | for (var ns in elem._attributes) { 108 | for (var attribute in elem._attributes[ns]) { 109 | var prop = elem._attributes[ns][attribute] 110 | var name = (prop.prefix ? prop.prefix + ":" : "") + attribute 111 | props.push({ name: name, value: prop.value }) 112 | } 113 | } 114 | 115 | if (elem.className) { 116 | props.push({ name: "class", value: elem.className }) 117 | } 118 | 119 | return props.length ? stringify(props) : "" 120 | } 121 | 122 | function escapeText(s) { 123 | var str = ''; 124 | 125 | if (typeof(s) === 'string') { 126 | str = s; 127 | } else if (s) { 128 | str = s.toString(); 129 | } 130 | 131 | return str 132 | .replace(/&/g, "&") 133 | .replace(//g, ">") 135 | } 136 | 137 | function escapeAttributeValue(str) { 138 | return escapeText(str).replace(/"/g, """) 139 | } 140 | -------------------------------------------------------------------------------- /test/cleanup.js: -------------------------------------------------------------------------------- 1 | module.exports = Cleanup 2 | 3 | function Cleanup (document) { 4 | 5 | return cleanup 6 | 7 | function cleanup () { 8 | var childNodes = document.body.childNodes 9 | for (var i = 0; i < childNodes.length; i++) { 10 | document.body.removeChild(childNodes[i]) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var testDocument = require("./test-document") 2 | var testDomElement = require("./test-dom-element") 3 | var testDomComment = require("./test-dom-comment") 4 | var document = require("../index") 5 | 6 | testDocument(document) 7 | testDomElement(document) 8 | testDomComment(document) 9 | 10 | if (typeof window !== "undefined" && window.document) { 11 | testDocument(window.document) 12 | testDomElement(window.document) 13 | testDomComment(window.document) 14 | } 15 | -------------------------------------------------------------------------------- /test/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TAPE Example 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/static/test-adapter.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var Testem = window.Testem 3 | var regex = /^((?:not )?ok) (\d+) (.+)$/ 4 | 5 | Testem.useCustomAdapter(tapAdapter) 6 | 7 | function tapAdapter(socket){ 8 | var results = { 9 | failed: 0 10 | , passed: 0 11 | , total: 0 12 | , tests: [] 13 | } 14 | 15 | socket.emit('tests-start') 16 | 17 | Testem.handleConsoleMessage = function(msg){ 18 | var m = msg.match(regex) 19 | if (m) { 20 | var passed = m[1] === 'ok' 21 | var test = { 22 | passed: passed ? 1 : 0, 23 | failed: passed ? 0 : 1, 24 | total: 1, 25 | id: m[2], 26 | name: m[3], 27 | items: [] 28 | } 29 | 30 | if (passed) { 31 | results.passed++ 32 | } else { 33 | results.failed++ 34 | } 35 | 36 | results.total++ 37 | 38 | socket.emit('test-result', test) 39 | results.tests.push(test) 40 | } else if (msg === '# ok' || msg.match(/^# tests \d+/)){ 41 | socket.emit('all-test-results', results) 42 | } 43 | 44 | // return false if you want to prevent the console message from 45 | // going to the console 46 | // return false 47 | } 48 | } 49 | }()) 50 | -------------------------------------------------------------------------------- /test/test-document.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | 3 | module.exports = testDocument 4 | 5 | function testDocument(document) { 6 | var cleanup = require('./cleanup')(document) 7 | var Event = require('../event'); 8 | 9 | test("document is a Document", function (assert) { 10 | assert.equal(typeof document.createTextNode, "function") 11 | assert.equal(typeof document.createElement, "function") 12 | assert.equal(typeof document.createDocumentFragment, "function") 13 | 14 | assert.end() 15 | }) 16 | 17 | test("document has a head property", function(assert) { 18 | assert.equal(document.head.tagName, "HEAD") 19 | assert.end() 20 | }) 21 | 22 | test("document has nodeType 9", function (assert) { 23 | assert.equal(document.nodeType, 9) 24 | assert.end() 25 | }) 26 | 27 | test("can do stuff", function (assert) { 28 | var div = document.createElement("div") 29 | div.className = "foo bar" 30 | 31 | var span = document.createElement("span") 32 | div.appendChild(span) 33 | span.textContent = "Hello! <&>" 34 | 35 | var html = String(div.outerHTML || div) 36 | 37 | assert.equal(html, "
" + 38 | "Hello! <&>
") 39 | 40 | cleanup() 41 | assert.end() 42 | }) 43 | 44 | test("can createDocumentFragment", function (assert) { 45 | var frag = document.createDocumentFragment() 46 | 47 | assert.equal(frag.nodeType, 11) 48 | 49 | var h1 = document.createElement("h1") 50 | var h2 = document.createElement("h2") 51 | 52 | assert.equal(h1.nodeType, 1) 53 | assert.equal(h1.nodeType, 1) 54 | 55 | frag.appendChild(h1) 56 | assert.equal(fragString(frag), "

") 57 | 58 | frag.appendChild(h2) 59 | assert.equal(fragString(frag), "

") 60 | 61 | frag.removeChild(h1) 62 | assert.equal(fragString(frag), "

") 63 | 64 | frag.replaceChild(h1, h2) 65 | assert.equal(fragString(frag), "

") 66 | 67 | cleanup() 68 | assert.end() 69 | }) 70 | 71 | test("can getElementById", function (assert) { 72 | 73 | function append_div(id, parent) { 74 | var div = document.createElement("div") 75 | div.id = id 76 | parent.appendChild(div) 77 | return div 78 | } 79 | 80 | var div1 = append_div(1, document.body) 81 | var div2 = append_div(2, document.body) 82 | var div3 = append_div(3, document.body) 83 | 84 | var div11 = append_div(11, div1) 85 | var div12 = append_div(12, div1) 86 | var div21 = append_div(21, div2) 87 | var div22 = append_div(22, div2) 88 | var div221 = append_div(221, div22) 89 | var div222 = append_div(222, div22) 90 | 91 | assert.equal(document.getElementById(1), div1) 92 | assert.equal(document.getElementById("2"), div2) 93 | assert.equal(document.getElementById(3), div3) 94 | assert.equal(document.getElementById(11), div11) 95 | assert.equal(document.getElementById(12), div12) 96 | assert.equal(document.getElementById(21), div21) 97 | assert.equal(document.getElementById(22), div22) 98 | assert.equal(document.getElementById(221), div221) 99 | assert.equal(document.getElementById(222), div222) 100 | 101 | cleanup() 102 | assert.end() 103 | }) 104 | 105 | test("can getElementsByClassName for a single class", function(assert) { 106 | function append_div(className, parent) { 107 | var div = document.createElement("div") 108 | div.className = className 109 | parent.appendChild(div) 110 | return div 111 | } 112 | 113 | function assertSingleMatch(className, expectedElement) { 114 | var result = document.getElementsByClassName(className) 115 | assert.equal(result.length, 1) 116 | assert.equal(result[0], expectedElement) 117 | } 118 | 119 | var divA = append_div("A", document.body) 120 | var divB = append_div("B", document.body) 121 | var divC = append_div("C", document.body) 122 | 123 | var divA1 = append_div("A1", divA) 124 | var divA2 = append_div("A2", divA) 125 | var divB1 = append_div("B1", divB) 126 | var divB2 = append_div("B2", divB) 127 | var divB2a = append_div("B2a", divB2) 128 | var divB2b = append_div("B2b", divB2) 129 | 130 | assertSingleMatch("A", divA) 131 | assertSingleMatch("B", divB) 132 | assertSingleMatch("C", divC) 133 | assertSingleMatch("A1", divA1) 134 | assertSingleMatch("A2", divA2) 135 | assertSingleMatch("B1", divB1) 136 | assertSingleMatch("B2", divB2) 137 | assertSingleMatch("B2a", divB2a) 138 | assertSingleMatch("B2b", divB2b) 139 | 140 | cleanup() 141 | assert.end() 142 | }) 143 | 144 | test("can getElementsByClassName for many elements", function (assert) { 145 | function h(className) { 146 | var div = document.createElement("div") 147 | div.className = className 148 | return div 149 | } 150 | 151 | document.body.appendChild(h("multi-class-bar")) 152 | document.body.appendChild(h("multi-class-bar")) 153 | 154 | var elems = document.getElementsByClassName("multi-class-bar") 155 | assert.equal(elems.length, 2) 156 | 157 | cleanup() 158 | assert.end() 159 | }) 160 | 161 | test("can getElementsByClassName for many classes", function(assert) { 162 | function append_div(classNames, parent) { 163 | var div = document.createElement("div") 164 | div.className = classNames 165 | parent.appendChild(div) 166 | return div 167 | } 168 | 169 | function assertMatch(classNames, expectedElements) { 170 | var result = document.getElementsByClassName(classNames) 171 | assert.equal(result.length, expectedElements.length) 172 | for (var i = 0; i < expectedElements.length; i++) { 173 | assert.notEqual(expectedElements.indexOf(result[i]), -1) 174 | } 175 | } 176 | 177 | var divXYZ = append_div("X Y Z", document.body) 178 | var divYZ = append_div("Y Z", document.body) 179 | var divZX = append_div("Z X", document.body) 180 | 181 | var divX1X2 = append_div("X1 X2", divXYZ) 182 | var divX1X2Y1 = append_div("X1 X2 Y1", divXYZ) 183 | 184 | 185 | assertMatch("X", [divXYZ, divZX]) 186 | assertMatch("Y Z", [divXYZ, divYZ]) 187 | assertMatch("X Y Z", [divXYZ]) 188 | assertMatch("X1 X2", [divX1X2, divX1X2Y1]) 189 | 190 | cleanup() 191 | assert.end() 192 | }) 193 | 194 | test("can create/manipulate textnodes", function (assert) { 195 | var textnode = document.createTextNode("hello") 196 | 197 | assert.equal(textnode.nodeType, 3) 198 | assert.equal(textnode.data, "hello") 199 | assert.equal(typeof textnode.replaceData, "function") 200 | 201 | textnode.replaceData(0, 7, "nightly") 202 | assert.equal(textnode.nodeType, 3) 203 | assert.equal(textnode.data, "nightly") 204 | assert.equal(typeof textnode.replaceData, "function") 205 | 206 | textnode.replaceData(1, 1, "ou") 207 | assert.equal(textnode.nodeType, 3) 208 | assert.equal(textnode.data, "noughtly") 209 | 210 | assert.end() 211 | }) 212 | 213 | test("owner document is set", function (assert) { 214 | var textnode = document.createTextNode("hello") 215 | var domnode = document.createElement("div") 216 | var fragment = document.createDocumentFragment() 217 | 218 | assert.equal(textnode.ownerDocument, document) 219 | assert.equal(domnode.ownerDocument, document) 220 | assert.equal(fragment.ownerDocument, document) 221 | 222 | assert.end() 223 | }) 224 | 225 | test("Create namespaced nodes", function (assert) { 226 | var svgURI = "http://www.w3.org/2000/svg" 227 | var htmlURI = "http://www.w3.org/1999/xhtml" 228 | 229 | var noNS = document.createElement("div") 230 | var svgNS = document.createElementNS(svgURI, "svg") 231 | var emptyNS = document.createElementNS("", "div") 232 | var nullNS = document.createElementNS(null, "div") 233 | var undefNS = document.createElementNS(undefined, "div") 234 | var caseNS = document.createElementNS("Oops", "AbC") 235 | var htmlNS = document.createElement("div") 236 | 237 | assert.equal(noNS.tagName, "DIV") 238 | assert.equal(noNS.namespaceURI, htmlURI) 239 | assert.equal(elemString(noNS), "
") 240 | 241 | assert.equal(svgNS.tagName, "svg") 242 | assert.equal(svgNS.namespaceURI, svgURI) 243 | assert.equal(elemString(svgNS), "") 244 | 245 | assert.equal(emptyNS.tagName, "div") 246 | assert.equal(emptyNS.namespaceURI, null) 247 | assert.equal(elemString(emptyNS), "
") 248 | 249 | assert.equal(nullNS.tagName, "div") 250 | assert.equal(nullNS.namespaceURI, null) 251 | assert.equal(elemString(nullNS), "
") 252 | 253 | assert.equal(undefNS.tagName, "div") 254 | assert.equal(undefNS.namespaceURI, "undefined") 255 | assert.equal(elemString(undefNS), "
") 256 | 257 | assert.equal(caseNS.tagName, "AbC") 258 | assert.equal(caseNS.namespaceURI, "Oops") 259 | assert.equal(elemString(caseNS), "") 260 | 261 | assert.equal(htmlNS.tagName, "DIV") 262 | assert.equal(htmlNS.namespaceURI, htmlURI) 263 | assert.equal(elemString(htmlNS), "
") 264 | 265 | assert.end() 266 | }) 267 | 268 | test("Can insert before", function (assert) { 269 | var rootNode = document.createElement("div") 270 | var child = document.createElement("div") 271 | var newElement = document.createElement("div") 272 | rootNode.appendChild(child) 273 | var el = rootNode.insertBefore(newElement, child) 274 | assert.equal(el, newElement) 275 | assert.equal(rootNode.childNodes.length, 2) 276 | assert.equal(rootNode.childNodes[0], newElement) 277 | assert.equal(rootNode.childNodes[1], child) 278 | cleanup() 279 | assert.end() 280 | }) 281 | 282 | test("Insert before null appends to end", function (assert) { 283 | var rootNode = document.createElement("div") 284 | var child = document.createElement("div") 285 | var newElement = document.createElement("div") 286 | rootNode.appendChild(child) 287 | var el = rootNode.insertBefore(newElement, null) 288 | assert.equal(el, newElement) 289 | assert.equal(rootNode.childNodes.length, 2) 290 | assert.equal(rootNode.childNodes[0], child) 291 | assert.equal(rootNode.childNodes[1], newElement) 292 | cleanup() 293 | assert.end() 294 | }) 295 | 296 | test("Node insertions remove node from parent", function (assert) { 297 | var parent = document.createElement("div") 298 | var c1 = document.createElement("div") 299 | var c2 = document.createElement("div") 300 | var c3 = document.createElement("div") 301 | parent.appendChild(c1) 302 | parent.appendChild(c2) 303 | parent.appendChild(c3) 304 | 305 | var rootNode = document.createElement("div") 306 | 307 | var node1 = rootNode.appendChild(c1) 308 | assert.equal(node1, c1) 309 | assert.equal(parent.childNodes.length, 2) 310 | assert.equal(c1.parentNode, rootNode) 311 | 312 | var node2 = rootNode.insertBefore(c2, c1) 313 | assert.equal(node2, c2) 314 | assert.equal(parent.childNodes.length, 1) 315 | assert.equal(c2.parentNode, rootNode) 316 | 317 | var node3 = rootNode.replaceChild(c3, c2) 318 | assert.equal(node3, c2) 319 | assert.equal(parent.childNodes.length, 0) 320 | assert.equal(c3.parentNode, rootNode) 321 | assert.equal(c2.parentNode, null) 322 | 323 | cleanup() 324 | assert.end() 325 | }) 326 | 327 | test("input has type=text by default", function (assert) { 328 | var elem = document.createElement("input") 329 | assert.equal(elem.getAttribute("type"), "text"); 330 | assert.equal(elemString(elem), "") 331 | assert.end() 332 | }) 333 | 334 | test("input type=text can be overridden", function (assert) { 335 | var elem = document.createElement("input") 336 | elem.setAttribute("type", "hidden") 337 | assert.equal(elem.getAttribute("type"), "hidden"); 338 | assert.equal(elemString(elem), "") 339 | assert.end() 340 | }) 341 | 342 | test("can set and get attributes", function (assert) { 343 | var elem = document.createElement("div") 344 | assert.equal(elem.getAttribute("foo"), null) 345 | assert.equal(elemString(elem), "
") 346 | assert.notOk(elem.hasAttribute('foo')) 347 | 348 | elem.setAttribute("foo", "bar") 349 | assert.equal(elem.getAttribute("foo"), "bar") 350 | assert.equal(elemString(elem), "
") 351 | assert.ok(elem.hasAttribute('foo')) 352 | 353 | elem.removeAttribute("foo") 354 | assert.equal(elem.getAttribute("foo"), null) 355 | assert.equal(elemString(elem), "
") 356 | assert.notOk(elem.hasAttribute('foo')) 357 | 358 | assert.end() 359 | }) 360 | 361 | test("can set and set style properties", function(assert) { 362 | var elem = document.createElement("div") 363 | assert.equal(elemString(elem), "
") 364 | 365 | elem.style.color = "red"; 366 | assert.equal(elem.style.color, "red") 367 | assert.equal(elemString(elem), "
") 368 | 369 | elem.style.background = "blue"; 370 | assert.equal(elem.style.color, "red") 371 | assert.equal(elem.style.background, "blue") 372 | assert.equal(elemString(elem), 373 | "
") 374 | 375 | assert.end() 376 | }) 377 | 378 | test("can set and get namespaced attributes", function(assert) { 379 | var elem = document.createElement("div") 380 | 381 | var ns = "http://ns.com/my" 382 | assert.equal(elem.getAttributeNS(ns, "myattr"), blankAttributeNS()) 383 | elem.setAttributeNS(ns, "myns:myattr", "the value") 384 | assert.equal(elem.getAttributeNS(ns, "myattr"), "the value") 385 | assert.equal(elemString(elem), '
') 386 | elem.removeAttributeNS(ns, "myattr") 387 | assert.equal(elem.getAttributeNS(ns, "myattr"), blankAttributeNS()) 388 | 389 | // Should work much like get/setAttribute when namespace is null. 390 | assert.equal(elem.getAttributeNS(null, "foo"), blankAttributeNS()) 391 | assert.equal(elem.getAttribute("foo"), null) 392 | elem.setAttributeNS(null, "foo", "bar") 393 | assert.equal(elem.getAttributeNS(null, "foo"), "bar") 394 | assert.equal(elem.getAttribute("foo"), "bar") 395 | elem.removeAttributeNS(null, "foo") 396 | assert.equal(elem.getAttributeNS(null, "foo"), blankAttributeNS()) 397 | assert.equal(elem.getAttribute("foo"), null) 398 | assert.end() 399 | }) 400 | 401 | test("can getElementsByTagName", function(assert) { 402 | var parent = document.createElement("div") 403 | var child1 = document.createElement("span") 404 | var child2 = document.createElement("span") 405 | 406 | child1.id = "foo" 407 | child2.id = "bar" 408 | 409 | child1.appendChild(child2) 410 | parent.appendChild(child1) 411 | document.body.appendChild(parent) 412 | 413 | var elems = document.getElementsByTagName("SPAN") 414 | 415 | assert.equal(elems.length, 2) 416 | assert.equal(elems[0].id, "foo") 417 | assert.equal(elems[1].id, "bar") 418 | 419 | cleanup() 420 | assert.end() 421 | }) 422 | 423 | test("can getElementsByTagName with *", function(assert) { 424 | document.body.appendChild(document.createElement("div")) 425 | 426 | var elems = document.getElementsByTagName("*") 427 | 428 | assert.equal(elems.length, 4) 429 | assert.equal(elems[0].tagName, "HTML") 430 | assert.equal(elems[1].tagName, "HEAD") 431 | assert.equal(elems[2].tagName, "BODY") 432 | assert.equal(elems[3].tagName, "DIV") 433 | 434 | cleanup() 435 | assert.end() 436 | }) 437 | 438 | test("getElement* methods search outside the body", function(assert) { 439 | var html = document.documentElement; 440 | assert.equal(document.getElementsByTagName("html")[0], html) 441 | 442 | html.id = "foo" 443 | assert.equal(document.getElementById("foo"), html) 444 | 445 | html.className = "bar" 446 | assert.equal(document.getElementsByClassName("bar")[0], html) 447 | 448 | // cleanup 449 | html.id = "" 450 | html.className = "" 451 | 452 | cleanup() 453 | assert.end() 454 | }) 455 | 456 | test("getElement* methods can be passed to map()", function(assert) { 457 | var e1 = document.createElement("div") 458 | var e2 = document.createElement("span") 459 | 460 | document.body.appendChild(e1) 461 | document.body.appendChild(e2) 462 | 463 | assert.deepEqual( 464 | ["div", "span"].map(document.getElementsByTagName.bind(document)), 465 | [[e1], [e2]] 466 | ) 467 | 468 | e1.id = "1" 469 | e2.id = "2" 470 | 471 | assert.deepEqual( 472 | ["1", "2"].map(document.getElementById.bind(document)), 473 | [e1, e2] 474 | ) 475 | 476 | e1.className = "foo" 477 | e2.className = "bar" 478 | 479 | assert.deepEqual( 480 | ["foo", "bar"].map(document.getElementsByClassName.bind(document)), 481 | [[e1], [e2]] 482 | ) 483 | 484 | cleanup() 485 | assert.end() 486 | }) 487 | 488 | test("can check if it contains an element", function(assert) { 489 | var el = document.createElement("div") 490 | document.body.appendChild(el) 491 | 492 | assert.equals(document.contains(document.body), true) 493 | assert.equals(document.contains(el), true) 494 | 495 | cleanup() 496 | assert.end() 497 | }) 498 | 499 | test("can do events", function (assert) { 500 | var x = 1 501 | function incx() { x++ } 502 | 503 | var ev = new Event(); 504 | ev.initEvent("click"); 505 | document.addEventListener("click", incx) 506 | document.dispatchEvent(ev) 507 | 508 | assert.equal(x, 2) 509 | 510 | document.removeEventListener("click", incx) 511 | document.dispatchEvent(ev) 512 | 513 | assert.equal(x, 2) 514 | assert.end() 515 | }) 516 | 517 | function blankAttributeNS() { 518 | // Most browsers conform to the latest version of the DOM spec, 519 | // which requires `getAttributeNS` to return `null` when the attribute 520 | // doesn't exist, but some browsers (including phantomjs) implement the 521 | // old version of the spec and return an empty string instead, see: 522 | // https://developer.mozilla.org/en-US/docs/Web/API/element.getAttributeNS#Return_value 523 | var div = document.createElement("div") 524 | var blank = div.getAttributeNS(null, "foo") 525 | if (!(blank === null || blank === "")) { 526 | throw "Expected blank attribute to be either null or empty string" 527 | } 528 | return blank; 529 | } 530 | 531 | function elemString(element) { 532 | var html = String(element) || "[]" 533 | 534 | if (html.charAt(0) === "[") { 535 | html = element.outerHTML 536 | if (!html && !element.parentNode) { 537 | var div = document.createElement("div") 538 | div.appendChild(element) 539 | html = div.innerHTML 540 | div.removeChild(element) 541 | } 542 | } 543 | 544 | return html 545 | } 546 | 547 | function fragString(fragment) { 548 | var html = String(fragment) 549 | 550 | 551 | if (html === "[object DocumentFragment]") { 552 | var innerHTML = [] 553 | for (var i = 0; i < fragment.childNodes.length; i++) { 554 | var node = fragment.childNodes[i] 555 | innerHTML.push(String(node.outerHTML || node)) 556 | } 557 | html = innerHTML.join("") 558 | } 559 | 560 | return html 561 | } 562 | } 563 | 564 | 565 | -------------------------------------------------------------------------------- /test/test-dom-comment.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | 3 | module.exports = testDomComment 4 | 5 | function testDomComment(document) { 6 | var cleanup = require('./cleanup')(document) 7 | 8 | test("can createComment", function(assert) { 9 | var comment = document.createComment("test") 10 | assert.equal(comment.data, "test") 11 | assert.equal(comment.length, 4) 12 | assert.equal(comment.nodeName, "#comment") 13 | assert.equal(comment.nodeType, 8) 14 | assert.equal(comment.nodeValue, "test") 15 | assert.equal(comment.ownerDocument, document) 16 | assert.equal(comment.toString(), "[object Comment]") 17 | cleanup() 18 | assert.end() 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /test/test-dom-element.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | 3 | module.exports = testDomElement 4 | 5 | function testDomElement(document) { 6 | 7 | var cleanup = require('./cleanup')(document) 8 | 9 | test("can getElementsByClassName", function(assert) { 10 | function append_div(classNames, parent) { 11 | var div = document.createElement("div") 12 | div.className = classNames 13 | parent.appendChild(div) 14 | return div 15 | } 16 | 17 | function assertMatch(classNames, expectedElements, parent) { 18 | var parent = parent || document 19 | var result = parent.getElementsByClassName(classNames) 20 | assert.equal(result.length, expectedElements.length) 21 | for (var i = 0; i < expectedElements.length; i++) { 22 | assert.notEqual(expectedElements.indexOf(result[i]), -1) 23 | } 24 | } 25 | 26 | var divA = append_div("A", document.body) 27 | var divB = append_div("B", document.body) 28 | var divC = append_div("C", document.body) 29 | 30 | var divA1 = append_div("A1", divA) 31 | var divA2 = append_div("A2", divA) 32 | var divB1 = append_div("B1", divB) 33 | var divB2 = append_div("B2", divB) 34 | var divB2a = append_div("B2a", divB2) 35 | var divB2b = append_div("B2b", divB2) 36 | 37 | assertMatch("A", [divA]) 38 | assertMatch("B", [divB]) 39 | assertMatch("C", [divC]) 40 | assertMatch("A1", [divA1]) 41 | assertMatch("A2", [divA2]) 42 | assertMatch("B1", [divB1]) 43 | assertMatch("B2", [divB2]) 44 | assertMatch("B2a", [divB2a]) 45 | assertMatch("B2b", [divB2b]) 46 | 47 | assertMatch("A1", [divA1], divA) 48 | assertMatch("A2", [divA2], divA) 49 | assertMatch("A1", [], divB) 50 | assertMatch("A2", [], divC) 51 | assertMatch("B1", [divB1], divB) 52 | assertMatch("B2", [divB2], divB) 53 | assertMatch("B2a", [divB2a], divB) 54 | assertMatch("B2a", [divB2a], divB2) 55 | assertMatch("B2b", [], divA) 56 | 57 | cleanup() 58 | assert.end() 59 | }) 60 | 61 | test("does not serialize innerText as an attribute", function(assert) { 62 | var div = document.createElement("div") 63 | div.innerText = "Test <&>" 64 | assert.equal(div.toString(), "
Test <&>
") 65 | cleanup() 66 | assert.end() 67 | }) 68 | 69 | test("does not serialize innerHTML as an attribute", function(assert) { 70 | var div = document.createElement("div") 71 | div.innerHTML = "Test " 72 | assert.equal(div.toString(), "
Test
") 73 | cleanup() 74 | assert.end() 75 | }) 76 | 77 | test("can getElementsByTagName", function(assert) { 78 | var parent = document.createElement("div") 79 | var child1 = document.createElement("span") 80 | var child2 = document.createElement("span") 81 | 82 | child1.id = "foo" 83 | child2.id = "bar" 84 | 85 | child1.appendChild(child2) 86 | parent.appendChild(child1) 87 | 88 | var elems = parent.getElementsByTagName("SPAN") 89 | 90 | assert.equal(elems.length, 2) 91 | assert.equal(elems[0].id, "foo") 92 | assert.equal(elems[1].id, "bar") 93 | 94 | cleanup() 95 | assert.end() 96 | }) 97 | 98 | test("can getElementsByTagName with *", function(assert) { 99 | var e1 = document.createElement("div") 100 | var e2 = document.createElement("p") 101 | var e3 = document.createElement("span") 102 | 103 | e1.appendChild(e2) 104 | e2.appendChild(e3) 105 | // non-elements should be ignored 106 | e3.appendChild(document.createTextNode('foo')) 107 | e3.appendChild(document.createComment('bar')) 108 | 109 | var elems = e1.getElementsByTagName("*") 110 | 111 | assert.equal(elems.length, 2) 112 | assert.equal(elems[0].tagName, "P") 113 | assert.equal(elems[1].tagName, "SPAN") 114 | 115 | cleanup() 116 | assert.end() 117 | }) 118 | 119 | test("getElement* methods can be passed to map()", function(assert) { 120 | var container = document.createElement("div") 121 | var e1 = document.createElement("div") 122 | var e2 = document.createElement("span") 123 | container.appendChild(e1) 124 | container.appendChild(e2) 125 | 126 | assert.deepEqual( 127 | ["div", "span"].map(container.getElementsByTagName.bind(container)), 128 | [[e1], [e2]] 129 | ) 130 | 131 | e1.className = "foo" 132 | e2.className = "bar" 133 | 134 | assert.deepEqual( 135 | ["foo", "bar"].map(container.getElementsByClassName.bind(container)), 136 | [[e1], [e2]] 137 | ) 138 | 139 | cleanup() 140 | assert.end() 141 | }) 142 | 143 | test("can serialize comment nodes", function(assert) { 144 | var div = document.createElement("div") 145 | div.appendChild(document.createComment("test")) 146 | assert.equal(div.toString(), "
") 147 | cleanup() 148 | assert.end() 149 | }) 150 | 151 | test("can serialize style property", function(assert) { 152 | var div = document.createElement("div") 153 | div.style.fontSize = "16px" 154 | assert.equal(div.toString(), "
") 155 | cleanup(); 156 | assert.end() 157 | }) 158 | 159 | test("can serialize style as a string", function(assert) { 160 | var div = document.createElement("div") 161 | div.setAttribute('style', 'display: none') 162 | assert.equal(div.toString(), "
") 163 | cleanup() 164 | assert.end() 165 | }) 166 | 167 | test("can serialize text nodes", function(assert) { 168 | var div = document.createElement("div") 169 | div.appendChild(document.createTextNode(' "&')) 170 | assert.equal(div.toString(), '
<test> "&
') 171 | cleanup() 172 | assert.end() 173 | }) 174 | 175 | test("escapes serialized attribute values", function(assert) { 176 | var div = document.createElement("div") 177 | div.setAttribute("data-foo", '

"&') 178 | assert.equal(div.toString(), '

') 179 | cleanup() 180 | assert.end() 181 | }) 182 | 183 | test("can check if an element contains another", function(assert) { 184 | var parent = document.createElement("div") 185 | var sibling = document.createElement("div") 186 | var child1 = document.createElement("div") 187 | var child2 = document.createElement("div") 188 | 189 | child1.appendChild(child2) 190 | parent.appendChild(child1) 191 | 192 | assert.equal(parent.contains(parent), true) 193 | assert.equal(parent.contains(sibling), false) 194 | assert.equal(parent.contains(child1), true) 195 | assert.equal(parent.contains(child2), true) 196 | 197 | cleanup() 198 | assert.end() 199 | }) 200 | 201 | test("can handle non string attribute values", function(assert) { 202 | var div = document.createElement("div") 203 | div.setAttribute("data-number", 100) 204 | div.setAttribute("data-boolean", true) 205 | div.setAttribute("data-null", null) 206 | assert.equal(div.toString(), '
') 207 | cleanup() 208 | assert.end() 209 | }) 210 | 211 | test("can serialize textarea correctly", function(assert) { 212 | var input = document.createElement("textarea") 213 | input.setAttribute("name", "comment") 214 | input.innerHTML = "user input here" 215 | assert.equal(input.toString(), '') 216 | cleanup() 217 | assert.end() 218 | }) 219 | } 220 | --------------------------------------------------------------------------------