├── .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("" + tagname + ">")
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 |
--------------------------------------------------------------------------------