103 | */
104 | $Element.prototype.closest = makeMethod("closest", "parentNode");
105 |
--------------------------------------------------------------------------------
/test/spec/element/set.spec.js:
--------------------------------------------------------------------------------
1 | describe("set", function() {
2 | "use strict";
3 |
4 | var link, input;
5 |
6 | beforeEach(function() {
7 | jasmine.sandbox.set("
set-test ");
8 |
9 | link = DOM.find("#test");
10 | input = DOM.find("#set_input");
11 | });
12 |
13 | it("should return reference to 'this'", function() {
14 | expect(link.set("id", "t")).toBe(link);
15 | // expect(inputs.set("id", "t")).toBe(inputs);
16 | });
17 |
18 | it("should update an appropriate native object attribute", function() {
19 | expect(link.set("data-test", "t")).toHaveAttr("data-test", "t");
20 | });
21 |
22 | it("should try to update an appropriate native object property first", function() {
23 | link.set("href", "#test");
24 |
25 | expect(link).toHaveAttr("href", "#test");
26 | expect(link).not.toHaveAttr("href", "#");
27 | });
28 |
29 | it("should remove attribute if value is null or undefined", function() {
30 | expect(link.set("id", null)).not.toHaveAttr("id");
31 | expect(link.set("href", undefined)).not.toHaveAttr("href");
32 |
33 | // expect(link.set(null)).toHaveHtml("");
34 | // expect(link.set("12345")).not.toHaveHtml("");
35 | // expect(link.set(undefined)).toHaveHtml("");
36 | });
37 |
38 | it("accepts function", function() {
39 | var spy = jasmine.createSpy("setter").and.returnValue("test_changed");
40 |
41 | link.set("id", spy);
42 |
43 | expect(spy).toHaveBeenCalledWith("test");
44 | expect(link).toHaveAttr("id", "test_changed");
45 | });
46 |
47 | it("supports innerHTML shortcut", function() {
48 | expect(link.get("innerHTML")).toBe("set-test");
49 | expect(link.get("firstElementChild")).toBeUndefined();
50 |
51 | link.set("test
content ");
52 | expect(link.get("innerHTML")).toBe("test
content ");
53 | expect(link.get("firstElementChild").nodeType).toBe(1);
54 |
55 | link.set(["a", "b"]);
56 | expect(link.get("innerHTML")).toBe("ab");
57 | });
58 |
59 | it("should accept object with key-value pairs", function() {
60 | link.set({"data-test1": "test1", "data-test2": "test2"});
61 |
62 | expect(link).toHaveAttr("data-test1", "test1");
63 | expect(link).toHaveAttr("data-test2", "test2");
64 | });
65 |
66 | it("should accept array of key values", function() {
67 | link.set(["foo", "bar"], "off");
68 |
69 | expect(link).toHaveAttr("foo", "off");
70 | expect(link).toHaveAttr("bar", "off");
71 | });
72 |
73 | it("polyfills textContent", function() {
74 | expect(link.get("textContent")).toBe("set-test");
75 | link.set("textContent", "
changed ");
76 | expect(link.get("textContent")).toBe("
changed ");
77 | expect(link).toHaveHtml("<i>changed</i>");
78 | });
79 |
80 | it("should throw error if argument is invalid", function() {
81 | expect(function() { link.set(1, ""); }).toThrow();
82 | expect(function() { link.set(true, ""); }).toThrow();
83 | expect(function() { link.set(function() {}, ""); }).toThrow();
84 | });
85 |
86 | it("should read/write current page title", function() {
87 | expect(DOM.get("title")).toBe(document.title);
88 |
89 | expect(DOM.set("title", "abc")).toBe(DOM);
90 | expect(document.title).toBe("abc");
91 | });
92 |
93 | it("should access cssText for the style property", function() {
94 | expect(link).not.toHaveStyle("font-style", "italic");
95 | expect(link).not.toHaveStyle("float", "left");
96 |
97 | link.set("style", "font-style:italic");
98 |
99 | expect(link.css("font-style")).toBe("italic");
100 | expect(link.css("float")).not.toBe("left");
101 |
102 | link.set("style", "float:left");
103 |
104 | expect(link.css("font-style")).not.toBe("italic");
105 | expect(link.css("float")).toBe("left");
106 | });
107 |
108 | it("should return this for empty nodes", function() {
109 | var empty = DOM.find("some-node");
110 |
111 | expect(empty.set("attr", "test")).toBe(empty);
112 | });
113 |
114 | // it("should clear all children with empty string", function () {
115 | // // TODO: need to catch IE bug with innerHTML = ""
116 | // });
117 |
118 | });
119 |
--------------------------------------------------------------------------------
/test/spec/element/value.spec.js:
--------------------------------------------------------------------------------
1 | describe("$Element#value", function() {
2 | "use strict";
3 |
4 | var div, input;
5 |
6 | beforeEach(function() {
7 | div = DOM.create("
");
8 | input = DOM.create("
");
9 | });
10 |
11 | describe("getter", function() {
12 | it("handles different tags", function() {
13 | expect(input.value()).toBe("foo");
14 |
15 | expect(div.value().toLowerCase()).toBe("");
16 | div.append("bar");
17 | expect(div.value().toLowerCase()).toBe("bar");
18 | });
19 |
20 | it("handles textarea", function() {
21 | var textarea = DOM.create("
");
22 |
23 | expect(textarea.value()).toBe("");
24 | textarea.set("value", "123");
25 | expect(textarea.value()).toBe("123");
26 | });
27 |
28 | it("handles select", function() {
29 | var select = DOM.create("
a2 a3 ");
30 | expect(select.value()).toBe("a2");
31 |
32 | select = DOM.create("
a2 a3 ");
33 | expect(select.value()).toBe("a3");
34 |
35 | select.set("selectedIndex", -1);
36 | expect(select.value()).toBe("");
37 | });
38 |
39 | it("handles options", function() {
40 | var select = DOM.create("
a2 a3 ");
41 | expect(select.child(0).value()).toBe("a1");
42 | expect(select.child(1).value()).toBe("a3");
43 | });
44 | });
45 |
46 | describe("setter", function() {
47 | it("should set value of text input to provided string value", function () {
48 | expect(input.value("bar")).toBe(input);
49 | expect(input).toHaveProp("value", "bar");
50 | });
51 |
52 | it("should replace child element(s) from node with provided text", function() {
53 | expect(div.get("childNodes").length).toBe(2);
54 | expect(div.value("foo")).toBe(div);
55 | expect(div.get("childNodes").length).toBe(1);
56 | expect(div.get("firstChild").nodeValue).toBe("foo");
57 | });
58 |
59 | it("should set select value properly", function() {
60 | var select = DOM.create("
AM PM ");
61 |
62 | expect(select.value()).toBe("AM");
63 | select.value("PM");
64 | expect(select.value()).toBe("PM");
65 | select.value("MM");
66 | expect(select.value()).toBe("");
67 | });
68 |
69 | it("accepts primitive types", function() {
70 | expect(div.value(1)).toHaveHtml("1");
71 | expect(div.value(true)).toHaveHtml("true");
72 | });
73 |
74 | it("accepts functor", function() {
75 | expect(div.value(function() { return "5" })).toHaveHtml("5");
76 | });
77 |
78 | // it("uses 'textContent' or 'value' if name argument is undefined", function() {
79 | // var value = "set-test-changed";
80 |
81 | // link.set(value);
82 | // input.set(value);
83 |
84 | // expect(link).toHaveHtml(value);
85 | // expect(input).toHaveProp("value", value);
86 | // });
87 |
88 | // it("should accept function", function() {
89 | // var spy = jasmine.createSpy("set").and.returnValue("ok");
90 |
91 | // link.set(spy);
92 | // input.set(spy);
93 |
94 | // expect(spy.calls.count()).toBe(2);
95 |
96 | // expect(link).toHaveHtml("ok");
97 | // expect(input).toHaveProp("value", "ok");
98 | // });
99 | });
100 |
101 | describe("empty", function() {
102 | it("clears all children", function() {
103 | expect(div).not.toBeEmpty();
104 | div.empty();
105 | expect(div).toBeEmpty();
106 | });
107 |
108 | it("clears input value", function() {
109 | input.set("test");
110 | expect(input).not.toBeEmpty();
111 | input.empty();
112 | expect(input).toBeEmpty();
113 | });
114 | });
115 |
116 | it("works for empty node", function() {
117 | var foo = DOM.find("x-foo");
118 |
119 | expect(foo.value()).toBeUndefined();
120 | expect(foo.value("123")).toBe(foo);
121 | });
122 | });
123 |
--------------------------------------------------------------------------------
/test/spec/element/traversing.spec.js:
--------------------------------------------------------------------------------
1 | describe("traversing", function() {
2 | "use strict";
3 |
4 | var link;
5 |
6 | beforeEach(function() {
7 | jasmine.sandbox.set("
");
8 |
9 | link = DOM.find("#test");
10 | });
11 |
12 | function _forIn(obj, callback, thisPtr) {
13 | for (var prop in obj) {
14 | callback.call(thisPtr, obj[prop], prop, obj);
15 | }
16 | }
17 |
18 | describe("next, prev, closest", function() {
19 | it("should return an appropriate element", function() {
20 | var expectedResults = {
21 | next: "b",
22 | prev: "i"
23 | };
24 |
25 | _forIn(expectedResults, function(tagName, methodName) {
26 | expect(link[methodName]()).toHaveTag(tagName);
27 | });
28 | });
29 |
30 | it("should search for the first matching element if selector exists", function() {
31 | expect(link.next("i")).toHaveTag("i");
32 | expect(link.prev("b")).toHaveTag("b");
33 | });
34 | });
35 |
36 | describe("closest", function() {
37 | it("searches for the first matching element if selector exists", function() {
38 | expect(link.closest("body")).toHaveTag("body");
39 | });
40 |
41 | // it("returns direct parent when no selector specified", function() {
42 | // expect(DOM.find("html").closest()).toBe(DOM);
43 | // expect(DOM.closest()[0]).toBeUndefined();
44 | // });
45 | });
46 |
47 | it("should return empty element if value is not found", function() {
48 | var unknownEl = link.find("unknown");
49 |
50 | expect(unknownEl.next()[0]).toBeUndefined();
51 | expect(unknownEl.prev()[0]).toBeUndefined();
52 | expect(unknownEl.child(0)[0]).toBeUndefined();
53 | });
54 |
55 | it("should throw error if arguments are invalid", function() {
56 | expect(function() { link.child({}) }).toThrow();
57 | expect(function() { link.child(function() {}) }).toThrow();
58 | expect(function() { link.next({}) }).toThrow();
59 | expect(function() { link.prev(function() {}) }).toThrow();
60 | });
61 |
62 | describe("children, nextAll, prevAll", function() {
63 | it("should return an appropriate collection of elements", function() {
64 | var expectedResults = {
65 | children: "strong em".split(" "),
66 | nextAll: "b i i".split(" "),
67 | prevAll: "i b b".split(" ")
68 | },
69 | isOK = function(methodName) {
70 | return function(el, index) {
71 | expect(el).toHaveTag(expectedResults[methodName][index]);
72 | };
73 | };
74 |
75 | _forIn(expectedResults, function(tagName, methodName) {
76 | for (var arr = link[methodName](), i = 0, n = arr.length; i < n; ++i) {
77 | isOK(arr[i]);
78 | }
79 | });
80 | });
81 |
82 | it("should filter matching elements by optional selector", function() {
83 | var filters = {
84 | children: "em",
85 | nextAll: "i",
86 | prevAll: "i"
87 | },
88 | haveTag = function(tagName) {
89 | return function(el) {
90 | expect(el).toHaveTag(tagName);
91 | };
92 | };
93 |
94 | _forIn(filters, function(tagName, methodName) {
95 | for (var arr = link[methodName](tagName), i = 0, n = arr.length; i < n; ++i) {
96 | haveTag(tagName);
97 | }
98 | });
99 | });
100 |
101 | it("should return empty element if value is not found", function() {
102 | var unknownEl = link.find("unknown");
103 |
104 | expect(unknownEl.nextAll().length).toBe(0);
105 | expect(unknownEl.prevAll().length).toBe(0);
106 | expect(unknownEl.children().length).toBe(0);
107 | });
108 |
109 | it("should throw error if arguments are invalid", function() {
110 | expect(function() { link.children({}) }).toThrow();
111 | expect(function() { link.children(function() {}) }).toThrow();
112 | expect(function() { link.nextAll({}) }).toThrow();
113 | expect(function() { link.prevAll(function() {}) }).toThrow();
114 | });
115 | });
116 |
117 | });
--------------------------------------------------------------------------------
/test/lib/jasmine-better-dom-matchers.js:
--------------------------------------------------------------------------------
1 | (function(matchers) {
2 | var el = null;
3 |
4 | jasmine.sandbox = {
5 | set: function(content) {
6 | if (typeof content === "string") {
7 | el.innerHTML = content;
8 | } else if (typeof content === "object") {
9 | el.innerHTML = "";
10 | el.appendChild(content[0]);
11 | }
12 | },
13 | get: function() {
14 | return el.innerHTML;
15 | }
16 | };
17 |
18 | beforeEach(function() {
19 | jasmine.addMatchers(matchers);
20 |
21 | jasmine.sandbox.id = "sandbox-" + Math.random().toString(32).substr(2);
22 |
23 | el = document.createElement("div");
24 | el.id = jasmine.sandbox.id;
25 |
26 | document.body.appendChild(el);
27 | });
28 |
29 | afterEach(function() {
30 | if (el.parentNode) {
31 | el.parentNode.removeChild(el);
32 | }
33 | });
34 | }({
35 | toHaveTag: function() {
36 | return {
37 | compare: function(actual, tagName) {
38 | var result = {};
39 |
40 | if (actual) {
41 | result.pass = actual[0].nodeName.toLowerCase() === tagName;
42 | }
43 |
44 | return result;
45 | }
46 | };
47 | },
48 | toHaveClass: function() {
49 | return {
50 | compare: function(actual, className) {
51 | var result = {};
52 |
53 | if (actual) {
54 | result.pass = ~(" " + actual[0].className + " ").indexOf(" " + className + " ");
55 | }
56 |
57 | return result;
58 | }
59 | };
60 | },
61 | toHaveId: function() {
62 | return {
63 | compare: function(actual, value) {
64 | var result = {};
65 |
66 | if (actual) {
67 | result.pass = actual[0].id === value;
68 | }
69 |
70 | return result;
71 | }
72 | };
73 | },
74 | toHaveAttr: function() {
75 | return {
76 | compare: function(actual, name, value) {
77 | var result = {};
78 | var len = arguments.length;
79 |
80 | if (actual) {
81 | if (len === 2) {
82 | result.pass = actual[0].hasAttribute(name);
83 | } else if (len === 3) {
84 | result.pass = actual[0].getAttribute(name) === value;
85 | }
86 | }
87 |
88 | return result;
89 | }
90 | };
91 | },
92 | toHaveProp: function() {
93 | return {
94 | compare: function(actual, name, value) {
95 | var result = {};
96 |
97 | if (actual) {
98 | result.pass = actual[0][name] === value;
99 | }
100 |
101 | return result;
102 | }
103 | };
104 | },
105 | toBeEmpty: function() {
106 | return {
107 | compare: function(actual) {
108 | var result = {};
109 |
110 | if (actual) {
111 | if ("value" in actual[0]) {
112 | result.pass = actual[0].value === "";
113 | } else {
114 | result.pass = actual[0].innerHTML === "";
115 | }
116 | }
117 |
118 | return result;
119 | }
120 | };
121 | },
122 | toBeMock: function() {
123 | return {
124 | compare: function(actual) {
125 | var result = {};
126 |
127 | if (+actual) {
128 | result.pass = false;
129 | } else {
130 | result.pass = true;
131 | }
132 |
133 | return result;
134 | }
135 | };
136 | },
137 | toHaveHtml: function() {
138 | return {
139 | compare: function(actual, value) {
140 | var result = {};
141 |
142 | if (actual) {
143 | result.pass = actual[0].innerHTML === value;
144 | }
145 |
146 | return result;
147 | }
148 | };
149 | },
150 | toHaveStyle: function() {
151 | return {
152 | compare: function(actual, name, value) {
153 | var result = {};
154 |
155 | if (actual) {
156 | var style = actual[0] && actual[0].style;
157 |
158 | if (style) {
159 | result.pass = style[name] === value;
160 | }
161 | }
162 |
163 | return result;
164 | }
165 | };
166 | }
167 | }));
168 |
--------------------------------------------------------------------------------
/test/spec/element/matches.spec.js:
--------------------------------------------------------------------------------
1 | describe("matches", function() {
2 | "use strict";
3 |
4 | var link, input;
5 |
6 | beforeEach(function() {
7 | jasmine.sandbox.set("
");
8 |
9 | link = DOM.find("#is1");
10 | input = DOM.find("#is2");
11 | });
12 |
13 | it("should match element by a simple selector", function() {
14 | expect(link.matches("a")).toBe(true);
15 | expect(link.matches("[href]")).toBe(true);
16 | expect(link.matches(".test1")).toBe(true);
17 | expect(link.matches("a.test1")).toBe(true);
18 | expect(link.matches("a[href]")).toBe(true);
19 | expect(link.matches("a#is1")).toBe(true);
20 | expect(link.matches("div")).toBe(false);
21 |
22 | expect(input.matches("[required]")).toBe(true);
23 | expect(input.matches("[unknown]")).toBe(false);
24 | expect(input.matches("[checked]")).toBe(true);
25 | expect(input.matches("[type=checkbox]")).toBe(true);
26 | });
27 |
28 | it("should match element by a complex selector", function() {
29 | expect(link.matches("a[href='#matches']")).toBe(true);
30 | expect(link.matches("div a")).toBe(true);
31 | });
32 |
33 | it("returns false for empty nodes", function() {
34 | expect(DOM.mock().matches("a")).toBe(false);
35 | expect(DOM.mock().matches("*")).toBe(false);
36 | });
37 |
38 | it("should throw error if the argument is ommited or not a string", function() {
39 | expect(function() { link.matches(); }).toThrow();
40 | expect(function() { link.matches(1); }).toThrow();
41 | });
42 |
43 | // describe(":visible and :hidden", function() {
44 | // it("should change depending on visibility", function(done) {
45 | // expect(link.matches(":hidden")).toBe(false);
46 | // expect(link.matches(":visible")).toBe(true);
47 |
48 | // link.hide(function() {
49 | // expect(link.matches(":hidden")).toBe(true);
50 | // expect(link.matches(":visible")).toBe(false);
51 |
52 | // link.show(function() {
53 | // expect(link.matches(":hidden")).toBe(false);
54 | // expect(link.matches(":visible")).toBe(true);
55 |
56 | // done();
57 | // });
58 | // });
59 | // });
60 |
61 | // // it("should respect aria-hidden attribute", function() {
62 | // // expect(link.matches(":hidden")).toBe(false);
63 |
64 | // // link.set("aria-hidden", "true");
65 | // // expect(link.matches(":hidden")).toBe(true);
66 |
67 | // // link.set("aria-hidden", "false");
68 | // // expect(link.matches(":hidden")).toBe(false);
69 |
70 | // // link.set("aria-hidden", null);
71 | // // expect(link.matches(":hidden")).toBe(false);
72 | // // });
73 |
74 | // it("should respect CSS property visibility", function() {
75 | // expect(link.matches(":hidden")).toBe(false);
76 |
77 | // link.css("visibility", "hidden");
78 | // expect(link.matches(":hidden")).toBe(true);
79 |
80 | // link.css("visibility", "visible");
81 | // expect(link.matches(":hidden")).toBe(false);
82 |
83 | // link.css("visibility", "inherit");
84 | // expect(link.matches(":hidden")).toBe(false);
85 | // });
86 |
87 | // it("should respect CSS property display", function() {
88 | // expect(link.matches(":hidden")).toBe(false);
89 |
90 | // link.css("display", "none");
91 | // expect(link.matches(":hidden")).toBe(true);
92 |
93 | // link.css("display", "block");
94 | // expect(link.matches(":hidden")).toBe(false);
95 | // });
96 |
97 | // // it("should respect availability in DOM", function() {
98 | // // expect(link.matches(":hidden")).toBe(false);
99 |
100 | // // link.remove();
101 | // // expect(link.matches(":hidden")).toBe(true);
102 | // // });
103 |
104 | // it("should support block elements as well", function(done) {
105 | // link.css("display", "block");
106 |
107 | // expect(link.matches(":hidden")).toBe(false);
108 | // expect(link.matches(":visible")).toBe(true);
109 |
110 | // link.hide(function() {
111 | // expect(link.matches(":hidden")).toBe(true);
112 | // expect(link.matches(":visible")).toBe(false);
113 |
114 | // link.show(function() {
115 | // expect(link.matches(":hidden")).toBe(false);
116 | // expect(link.matches(":visible")).toBe(true);
117 |
118 | // done();
119 | // });
120 | // });
121 | // });
122 | // });
123 | });
124 |
--------------------------------------------------------------------------------
/src/document/extend.js:
--------------------------------------------------------------------------------
1 | import { $Document } from "../document/index";
2 | import { $Element } from "../element/index";
3 | import { keys, each } from "../util/index";
4 | import { WEBKIT_PREFIX, WINDOW, FAKE_ANIMATION_NAME } from "../const";
5 | import { DocumentTypeError } from "../errors";
6 | import SelectorMatcher from "../util/selectormatcher";
7 |
8 | // Inspired by trick discovered by Daniel Buchner:
9 | // https://github.com/csuwldcat/SelectorListener
10 |
11 | const extensions = [];
12 | const EVENT_TYPE = WEBKIT_PREFIX ? "webkitAnimationStart" : "animationstart";
13 | const CSS_IMPORT_TEXT = [
14 | WEBKIT_PREFIX + "animation-name:" + FAKE_ANIMATION_NAME + " !important",
15 | WEBKIT_PREFIX + "animation-duration:1ms !important"
16 | ].join(";");
17 |
18 | function applyLiveExtension(definition, node) {
19 | const el = $Element(node);
20 | const ctr = definition.constructor;
21 | // apply all element mixins
22 | Object.keys(definition).forEach((mixinName) => {
23 | const mixinProperty = definition[mixinName];
24 | if (mixinProperty !== ctr) {
25 | el[mixinName] = mixinProperty;
26 | }
27 | });
28 |
29 | if (ctr) ctr.call(el);
30 | }
31 |
32 | /**
33 | * Declare a live extension
34 | * @param {String} selector css selector of which elements to capture
35 | * @param {Object} definition live extension definition
36 | * @see https://github.com/chemerisuk/better-dom/wiki/Live-extensions
37 | * @example
38 | * DOM.extend("selector", {
39 | * constructor: function() {
40 | * // initialize component
41 | * },
42 | * publicMethod: function() {
43 | * // ...
44 | * }
45 | * });
46 | */
47 | $Document.prototype.extend = function(selector, definition) {
48 | const node = this[0];
49 |
50 | if (!node) return this;
51 |
52 | if (arguments.length === 1 && typeof selector === "object") {
53 | // handle case when $Document protytype is extended
54 | keys(selector).forEach((key) => {
55 | $Document.prototype[key] = selector[key];
56 | });
57 |
58 | return this;
59 | } else if (selector === "*") {
60 | // handle case when $Element protytype is extended
61 | keys(definition).forEach((key) => {
62 | $Element.prototype[key] = definition[key];
63 | });
64 |
65 | return this;
66 | }
67 |
68 | if (typeof definition === "function") {
69 | definition = {constructor: definition};
70 | }
71 |
72 | if (!definition || typeof definition !== "object") {
73 | throw new DocumentTypeError("extend", arguments);
74 | }
75 |
76 | const matcher = SelectorMatcher(selector);
77 |
78 | extensions.push([matcher, definition]);
79 | // use capturing to suppress internal animationstart events
80 | node.addEventListener(EVENT_TYPE, (e) => {
81 | const node = e.target;
82 |
83 | if (e.animationName === FAKE_ANIMATION_NAME && matcher(node)) {
84 | e.stopPropagation(); // this is an internal event
85 | // prevent any future events
86 | node.style.setProperty(WEBKIT_PREFIX + "animation-name", "none", "important");
87 |
88 | applyLiveExtension(definition, node);
89 | }
90 | }, true);
91 |
92 | // initialize extension manually to make sure that all elements
93 | // have appropriate methods before they are used in other DOM.extend
94 | // also fix cases when a matched element already has another LE
95 | each.call(node.querySelectorAll(selector), (node) => {
96 | // prevent any future events
97 | node.style.setProperty(WEBKIT_PREFIX + "animation-name", "none", "important");
98 | // use timeout to invoke constructor safe and async
99 | WINDOW.setTimeout(() => {
100 | applyLiveExtension(definition, node);
101 | }, 0);
102 | });
103 |
104 | // subscribe selector to a fake animation
105 | this.importStyles(selector, CSS_IMPORT_TEXT);
106 | };
107 |
108 | /**
109 | * Return {@link $Element} initialized with all existing live extensions.
110 | * Also exposes private functions that do not usually exist. Accepts the
111 | * same arguments as {@link DOM.create}
112 | * @param {String} content HTMLString
113 | * @param {Object|Array} [varMap] key/value map of variables
114 | * @return {$Element} a mocked instance
115 | * @see $Document#create
116 | */
117 | $Document.prototype.mock = function(content) {
118 | if (!content) return new $Element();
119 |
120 | var result = this.create(content),
121 | applyExtensions = (node) => {
122 | extensions.forEach((args) => {
123 | const matcher = args[0];
124 | const definition = args[1];
125 |
126 | if (matcher(node)) {
127 | applyLiveExtension(definition, node);
128 | }
129 | });
130 |
131 | each.call(node.children, applyExtensions);
132 | };
133 |
134 | if (extensions.length) {
135 | applyExtensions(result[0]);
136 | }
137 |
138 | return result;
139 | };
140 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require("gulp");
2 | var gulpif = require("gulp-if");
3 | var gutil = require("gulp-util");
4 | var pkg = require("./package.json");
5 | var compile = require("./task/compile");
6 | var babel = require("gulp-babel");
7 | var template = require("gulp-template");
8 | var jshint = require("gulp-jshint");
9 | var argv = require("yargs").argv;
10 | var jsdoc = require("gulp-jsdoc3");
11 | var clean = require("gulp-clean");
12 | var uglify = require("gulp-uglify");
13 | var rename = require("gulp-rename");
14 | var deploy = require("gulp-gh-pages");
15 | var replace = require("gulp-replace");
16 | var concat = require("gulp-concat");
17 | var plumber = require("gulp-plumber");
18 | var header = require("gulp-header");
19 | var bump = require("gulp-bump");
20 |
21 | var karma = require("karma");
22 | var karmaConfig = require.resolve("./conf/karma.conf");
23 |
24 | var banner = [
25 | "/**",
26 | " * <%= name %>: <%= description %>",
27 | " * @version <%= version %> <%= new Date().toUTCString() %>",
28 | " * @link <%= homepage %>",
29 | " * @copyright <%= new Date().getFullYear() %> <%= author %>",
30 | " * @license <%= license %>",
31 | " */"
32 | ].join("\n");
33 |
34 | if (process.env.npm_package_version) {
35 | pkg.version = process.env.npm_package_version;
36 | }
37 |
38 | gulp.task("lint-test", function() {
39 | return gulp.src(["test/spec/**/*.js"])
40 | .pipe(jshint(require("./conf/jshintrc-test")))
41 | .pipe(jshint.reporter("jshint-stylish"))
42 | .pipe(gulpif(process.env.TRAVIS_JOB_NUMBER, jshint.reporter("fail")));
43 | });
44 |
45 | gulp.task("compile", function() {
46 | return gulp.src(["**/*.js"], {cwd: "./src"})
47 | .pipe(gulpif(!process.env.TRAVIS_JOB_NUMBER, plumber()))
48 | .pipe(jshint(".jshintrc"))
49 | .pipe(jshint.reporter("jshint-stylish"))
50 | .pipe(jshint.reporter("fail"))
51 | .pipe(compile("better-dom.js", pkg))
52 | .pipe(babel())
53 | .pipe(header(banner + "\n", pkg))
54 | .pipe(gulp.dest("build/"));
55 | });
56 |
57 | gulp.task("test", ["compile", "lint-test"], function(done) {
58 | var config = {preprocessors: []};
59 |
60 | if (process.env.TRAVIS_JOB_NUMBER) {
61 | config = {
62 | reporters: ["coverage", "dots", "coveralls"],
63 | coverageReporter: {
64 | type: "lcovonly",
65 | dir: "coverage/"
66 | }
67 | };
68 | }
69 |
70 | config.configFile = karmaConfig;
71 |
72 | new karma.Server(config, function(resultCode) {
73 | done(resultCode ? new gutil.PluginError("karma", "Tests failed") : null);
74 | }).start();
75 | });
76 |
77 | gulp.task("browsers", ["compile", "lint-test"], function(done) {
78 | var config = {preprocessors: []};
79 |
80 | if (argv.ie10 || argv.ie11) {
81 | config.browsers = ["IE" + (argv.ie10 ? "10" : "11") + " - Win7"];
82 | } else {
83 | config.browsers = ["Chrome", "Safari", "Firefox"];
84 | }
85 |
86 | config.configFile = karmaConfig;
87 |
88 | new karma.Server(config, function(resultCode) {
89 | done(resultCode ? new gutil.PluginError("karma", "Tests failed") : null);
90 | }).start();
91 | });
92 |
93 | gulp.task("dev", ["compile", "lint-test"], function() {
94 | gulp.watch(["src/**/*.js"], ["compile"]);
95 | gulp.watch(["test/spec/**/*.js"], ["lint-test"]);
96 |
97 | new karma.Server({
98 | configFile: karmaConfig,
99 | reporters: ["coverage", "progress"],
100 | background: true,
101 | singleRun: false
102 | }).start();
103 | });
104 |
105 | gulp.task("sauce", function(done) {
106 | new karma.Server({
107 | configFile: require.resolve("./conf/karma.conf-ci.js")
108 | }, function() {
109 | // always return success result for this task
110 | done(null);
111 | }).start();
112 | });
113 |
114 | gulp.task("clean-jsdoc", function() {
115 | return gulp.src("docs", {read: false}).pipe(clean());
116 | });
117 |
118 | gulp.task("build-jsdoc", ["clean-jsdoc"], function(done) {
119 | const config = require("./conf/jsdoc.json");
120 |
121 | gulp.src(["./src/**/*.js"], {read: false})
122 | .pipe(jsdoc(config, done));
123 | });
124 |
125 | gulp.task("gh-pages", ["build-jsdoc"], function() {
126 | // var lib = require.resolve("./build/better-dom");
127 |
128 | return gulp.src("./docs/**/*")
129 | // remove absolute paths from jsdocs
130 | // .pipe(replace(lib, "better-dom.js"))
131 | .pipe(deploy({message: "v" + pkg.version}));
132 | });
133 |
134 | gulp.task("bower", function() {
135 | return gulp.src("./bower.json")
136 | .pipe(bump({version: pkg.version}))
137 | .pipe(gulp.dest("./"));
138 | });
139 |
140 | gulp.task("dist", ["browsers", "bower"], function() {
141 | return gulp.src("build/better-dom.js")
142 | // clienup multiline comments: jsdocs, directives etc.
143 | .pipe(replace(/\/\*([\s\S]*?)\*\/\s+/gm, ""))
144 | .pipe(header(banner + "\n", pkg))
145 | .pipe(gulp.dest("dist/"))
146 | .pipe(uglify({preserveComments: "license"}))
147 | .pipe(rename({extname: ".min.js"}))
148 | .pipe(gulp.dest("dist/"));
149 | });
150 |
--------------------------------------------------------------------------------
/src/element/manipulation.js:
--------------------------------------------------------------------------------
1 | import { $Element } from "../element/index";
2 | import { $Document } from "../document/index";
3 | import { isArray } from "../util/index";
4 |
5 | function makeMethod(fastStrategy, requiresParent, strategy) {
6 | return function(...contents) {
7 | const node = this[0];
8 |
9 | if (!node || requiresParent && !node.parentNode) return this;
10 |
11 | // the idea of the algorithm is to construct HTML string
12 | // when possible or use document fragment as a fallback to
13 | // invoke manipulation using a single method call
14 | var fragment = fastStrategy ? "" : node.ownerDocument.createDocumentFragment();
15 |
16 | contents.forEach((content) => {
17 | if (typeof content === "function") {
18 | content = content(this);
19 | }
20 |
21 | if (typeof content === "string") {
22 | if (typeof fragment === "string") {
23 | fragment += content.trim();
24 | } else {
25 | content = $Document(node.ownerDocument).createAll(content);
26 | }
27 | } else if (content instanceof $Element) {
28 | content = [ content ];
29 | }
30 |
31 | if (isArray(content)) {
32 | if (typeof fragment === "string") {
33 | // append existing string to fragment
34 | content = $Document(node.ownerDocument).createAll(fragment).concat(content);
35 | // fallback to document fragment strategy
36 | fragment = node.ownerDocument.createDocumentFragment();
37 | }
38 |
39 | content.forEach((el) => {
40 | fragment.appendChild(el[0]);
41 | });
42 | }
43 | });
44 |
45 | if (typeof fragment === "string") {
46 | node.insertAdjacentHTML(fastStrategy, fragment);
47 | } else {
48 | strategy(node, fragment);
49 | }
50 |
51 | return this;
52 | };
53 | }
54 |
55 | /**
56 | * Insert HTMLString or {@link $Element} after the current element
57 | * @param {...Mixed} contents HTMLString, {@link $Element}, Array.<{@link $Element}> or functor
58 | * @return {$Element} Self
59 | * @function
60 | * @example
61 | * var link = DOM.create("a"); //
62 | * link.after(DOM.create("b")); //
63 | * link.after(DOM.create("i"), DOM.create("u")); //
64 | */
65 | $Element.prototype.after = makeMethod("afterend", true, (node, relatedNode) => {
66 | node.parentNode.insertBefore(relatedNode, node.nextSibling);
67 | });
68 |
69 | /**
70 | * Insert HTMLString or {@link $Element} before the current element
71 | * @param {...Mixed} contents HTMLString, {@link $Element}, Array.<{@link $Element}> or functor
72 | * @return {$Element} Self
73 | * @function
74 | * @example
75 | * var link = DOM.create("a"); //
76 | * link.before(DOM.create("b")); //
77 | * link.before(DOM.create("i"), DOM.create("u")); //
78 | */
79 | $Element.prototype.before = makeMethod("beforebegin", true, (node, relatedNode) => {
80 | node.parentNode.insertBefore(relatedNode, node);
81 | });
82 |
83 | /**
84 | * Prepend HTMLString or {@link $Element} to the current element
85 | * @param {...Mixed} contents HTMLString, {@link $Element}, Array.<{@link $Element}> or functor
86 | * @return {$Element} Self
87 | * @function
88 | * @example
89 | * var link = DOM.create("a>`foo`"); //
foo
90 | * link.prepend(DOM.create("b")); //
foo
91 | * link.prepend(DOM.create("i"), DOM.create("u")); //
foo
92 | */
93 | $Element.prototype.prepend = makeMethod("afterbegin", false, (node, relatedNode) => {
94 | node.insertBefore(relatedNode, node.firstChild);
95 | });
96 |
97 | /**
98 | * Append HTMLString or {@link $Element} to the current element
99 | * @param {...Mixed} contents HTMLString, {@link $Element}, Array.<{@link $Element}> or functor
100 | * @return {$Element} Self
101 | * @function
102 | * @example
103 | * var link = DOM.create("a>`foo`"); //
foo
104 | * link.append(DOM.create("b")); //
foo
105 | * link.append(DOM.create("i"), DOM.create("u")); //
foo
106 | */
107 | $Element.prototype.append = makeMethod("beforeend", false, (node, relatedNode) => {
108 | node.appendChild(relatedNode);
109 | });
110 |
111 | /**
112 | * Replace current element with HTMLString or {@link $Element}
113 | * @param {Mixed} content HTMLString, {@link $Element}, Array.<{@link $Element}> or functor
114 | * @return {$Element} Self
115 | * @function
116 | * @example
117 | * var div = DOM.create("div>span>`foo`"); //
foo
118 | * div.child(0).replace(DOM.create("b>`bar`")); //
bar
119 | */
120 | $Element.prototype.replace = makeMethod("", true, (node, relatedNode) => {
121 | node.parentNode.replaceChild(relatedNode, node);
122 | });
123 |
124 | /**
125 | * Remove current element from the DOM
126 | * @return {$Element}
127 | * @function
128 | * @example
129 | * var foo = DOM.find(".foo");
130 | * foo.remove();
131 | * DOM.contains(foo); // => false
132 | */
133 | $Element.prototype.remove = makeMethod("", true, (node) => {
134 | node.parentNode.removeChild(node);
135 | });
136 |
--------------------------------------------------------------------------------
/test/spec/element/css.spec.js:
--------------------------------------------------------------------------------
1 | describe("css", function() {
2 | "use strict";
3 |
4 | var link;
5 |
6 | beforeEach(function() {
7 | jasmine.sandbox.set("
test test ");
8 |
9 | link = DOM.find("#test0");
10 | });
11 |
12 | describe("getter", function() {
13 | it("should read style property", function() {
14 | expect(link.css("color")).toBe("red");
15 | });
16 |
17 | it("should read properties by dash-separated key", function() {
18 | expect(link.css("line-height")).toBe("2");
19 | });
20 |
21 | it("should handle vendor-prefixed properties", function() {
22 | // TODO
23 | });
24 |
25 | it("should handle composite properties", function() {
26 | expect(link.css("padding")).toBe("5px 5px 5px 5px");
27 | expect(link.css("margin")).toBe("2px 2px 2px 2px");
28 | expect(link.css("border-width")).toBe("1px 1px 1px 1px");
29 | expect(link.css("border-style")).toBe("solid solid solid solid");
30 | });
31 |
32 | it("should read runtime style property if style doesn't contain any value", function() {
33 | expect(link.css("font-size")).toBeTruthy();
34 | });
35 |
36 | it("should fix float property name", function() {
37 | expect(link.css("float")).toBe("left");
38 | });
39 |
40 | it("should throw error if arguments are invalid", function() {
41 | expect(function() { link.css(1); }).toThrow();
42 | });
43 |
44 | it("should support array", function() {
45 | expect(link.css(["float","line-height"])).toEqual({"float": "left", "line-height": "2"});
46 | expect(DOM.mock().css(["float","line-height"])).toEqual({});
47 | });
48 | });
49 |
50 | describe("setter", function() {
51 | it("should return reference to 'this'", function() {
52 | expect(link.css("color", "white")).toBe(link);
53 | });
54 |
55 | it("should set style properties", function() {
56 | expect(link.css("color", "white")).toHaveStyle("color", "white");
57 | expect(link.css("float", "right").css("float")).toBe("right");
58 | });
59 |
60 | it("should support styles object", function() {
61 | link.css({color: "white", padding: "5px"});
62 |
63 | expect(link).toHaveStyle("color", "white");
64 | expect(link).toHaveStyle("padding", "5px");
65 | });
66 |
67 | it("should support setting of composite properties", function() {
68 | var value = "7px";
69 |
70 | link.css("border-width", value);
71 |
72 | expect(link.css("border-left-width")).toBe(value);
73 | expect(link.css("border-top-width")).toBe(value);
74 | expect(link.css("border-bottom-width")).toBe(value);
75 | expect(link.css("border-right-width")).toBe(value);
76 | });
77 |
78 | it("should support number values", function() {
79 | link.css("line-height", 7);
80 |
81 | expect(link.css("line-height")).toBe("7");
82 |
83 | link.css("width", 50);
84 |
85 | expect(link.css("width")).toBe("50px");
86 | });
87 |
88 | it("should handle vendor-prefixed properties", function() {
89 | var offset = link.offset();
90 |
91 | link.css("box-sizing", "border-box");
92 |
93 | expect(link.offset()).not.toEqual(offset);
94 | });
95 |
96 | it("should not add px suffix to some css properties", function() {
97 | var props = "orphans line-height widows z-index".split(" "),
98 | propName, i, n;
99 |
100 | for (i = 0, n = props.length; i < n; ++i) {
101 | propName = props[i];
102 |
103 | expect(link.css(propName, 5).css(propName)).not.toBe("5px");
104 | }
105 | });
106 |
107 | it("should accept function", function() {
108 | var spy = jasmine.createSpy("value").and.returnValue(7);
109 |
110 | link.css("line-height", spy);
111 |
112 | expect(spy).toHaveBeenCalledWith(link);
113 | expect(link.css("line-height")).toBe("7");
114 | });
115 |
116 | // it("should be suported by collections", function() {
117 | // links.css("float", "right").each(function(el) {
118 | // expect(el.css("float")).toBe("right");
119 | // });
120 | // });
121 |
122 | it("should allow to clear style value", function() {
123 | expect(link.css("padding", null).css("padding")).toBe("0px 0px 0px 0px");
124 | expect(link.css("z-index", "").css("z-index")).toBe("auto");
125 | expect(link.css("float", undefined).css("float")).toBe("none");
126 | });
127 |
128 | it("read/writes non-existent properties", function() {
129 | expect(link.css("some-prop")).toBeUndefined();
130 | expect(link.css("some-prop", "test")).toBe(link);
131 | expect(link.css("some-prop")).toBe("test");
132 | });
133 |
134 | it("should throw error if arguments are invalid", function() {
135 | expect(function() { link.css(1); }).toThrow();
136 | expect(function() { link.css("color", "red", "yellow"); }).toThrow();
137 | });
138 |
139 | it("should return undefined empty nodes", function() {
140 | var emptyEl = DOM.find("xxx");
141 |
142 | expect(emptyEl.css("color")).toBeUndefined();
143 | expect(emptyEl.css("color", "red")).toBe(emptyEl);
144 | });
145 | });
146 |
147 | });
--------------------------------------------------------------------------------
/test/spec/element/manipulation.spec.js:
--------------------------------------------------------------------------------
1 | describe("manipulation", function() {
2 | "use strict";
3 |
4 | describe("remove", function() {
5 | var div;
6 |
7 | beforeEach(function() {
8 | div = DOM.create("div>a+a");
9 | // italics = div.nextAll(".removable");
10 | });
11 |
12 | it("should remove element(s) from DOM", function() {
13 | expect(div.remove()).toBe(div);
14 | expect(document.getElementById("test")).toBeNull();
15 |
16 | // expect(italics.remove()).toBe(italics);
17 | expect(DOM.findAll(".removable").length).toBe(0);
18 | });
19 |
20 | it("should check if element has parent", function() {
21 | expect(div.remove().remove()).toBe(div);
22 | });
23 |
24 | it("does nothing for empty nodes", function() {
25 | var empty = DOM.mock();
26 |
27 | expect(empty.remove()).toBe(empty);
28 | });
29 |
30 | // it("should throw error if argument is invalid", function() {
31 | // expect(function() { div.remove(1); }).toThrow();
32 | // });
33 | });
34 |
35 | function createDivHtml(className) {
36 | return "
";
37 | }
38 |
39 | function createDivHtmlWhitespaced(className) {
40 | return "
";
41 | }
42 |
43 | function createArray(className) {
44 | return DOM.createAll("
".split("$0").join(className));
45 | }
46 |
47 | function expectToBeReplaced(id) {
48 | expect(document.getElementById(id)).toBeNull();
49 | }
50 |
51 | function _forIn(obj, callback, thisPtr) {
52 | for (var prop in obj) {
53 | callback.call(thisPtr, obj[prop], prop, obj);
54 | }
55 | }
56 |
57 | describe("append, prepend, after, before", function() {
58 | var checkStrategies = {
59 | prepend: function(el) { return el.child(0); },
60 | append: function(el) { return el.child(-1); },
61 | after: function(el) { return el.next(); },
62 | before: function(el) { return el.prev(); }
63 | },
64 | div;
65 |
66 | beforeEach(function() {
67 | jasmine.sandbox.set("
");
68 |
69 | div = DOM.find("#test");
70 | });
71 |
72 | it("should accept html string", function() {
73 | _forIn(checkStrategies, function(checkMethod, strategy) {
74 | var arg = createDivHtml(strategy);
75 |
76 | expect(checkMethod(div[strategy](arg))).toHaveClass(strategy);
77 | });
78 | });
79 |
80 | it("should accept empty string", function() {
81 | var link = div.child(0);
82 |
83 | _forIn(checkStrategies, function(checkMethod, strategy) {
84 | expect(checkMethod(link[strategy](""))).toBeMock();
85 | });
86 | });
87 |
88 | it("should trim html string", function() {
89 | _forIn(checkStrategies, function(checkMethod, strategy) {
90 | var arg = createDivHtmlWhitespaced(strategy);
91 |
92 | expect(checkMethod(div[strategy](arg))).toHaveClass(strategy);
93 | });
94 | });
95 |
96 | it("should accept functor", function() {
97 | _forIn(checkStrategies, function(checkMethod, strategy) {
98 | var arg = function() { return createDivHtml(strategy); };
99 |
100 | expect(checkMethod(div[strategy](arg))).toHaveClass(strategy);
101 | });
102 | });
103 |
104 | // it("accept DOMElement", function() {
105 | // _forIn(checkStrategies, function(checkMethod, strategy) {
106 | // //var arg = DOM.create(createDiv(strategy));
107 | // var arg = DOM.create(createDivHtml(strategy)),
108 | // otherDiv = DOM.create("
");
109 |
110 | // expect(checkMethod(div[strategy](arg))).toHaveClass(strategy);
111 |
112 | // otherDiv.value("
");
113 |
114 | // expect(checkMethod(div[strategy](otherDiv))).toHaveTag("div");
115 | // expect(otherDiv.find("section")).toHaveTag("section");
116 | // });
117 | // });
118 |
119 | it("access array of $Element", function() {
120 | var sandbox = DOM.find("#" + jasmine.sandbox.id);
121 |
122 | _forIn(checkStrategies, function(_, strategy) {
123 | div[strategy](createArray(strategy));
124 |
125 | expect(sandbox.findAll("." + strategy).length).toBe(2);
126 | });
127 | });
128 |
129 | // it("should throw error if argument is invalid", function() {
130 | // var callProp = function(strategy) {
131 | // return function() {
132 | // div[strategy](1);
133 | // };
134 | // };
135 |
136 | // _forIn(checkStrategies, function(checkMethod, strategy) {
137 | // expect(callProp(strategy)).toThrow();
138 | // });
139 | // });
140 |
141 | it("should return this", function() {
142 | _forIn(checkStrategies, function(checkMethod, strategy) {
143 | var arg = createDivHtml(strategy);
144 |
145 | expect(div[strategy](arg)).toBe(div);
146 | });
147 | });
148 |
149 | it("should work properly on detached elements", function() {
150 | div.remove();
151 |
152 | expect(div.append(createDivHtml("append")).child(-1)).toHaveClass("append");
153 | expect(div.prepend(createDivHtml("prepend")).child(0)).toHaveClass("prepend");
154 | expect(div.after(createDivHtml("after")).next()).toBeMock();
155 | expect(div.before(createDivHtml("before")).prev()).toBeMock();
156 | });
157 | });
158 |
159 | describe("replace", function() {
160 | var div;
161 |
162 | beforeEach(function() {
163 | jasmine.sandbox.set("
");
164 |
165 | div = DOM.find("#test");
166 | });
167 |
168 | it("should accept html string", function() {
169 | expect(div.replace(createDivHtml("replace"))).toBe(div);
170 |
171 | expectToBeReplaced("test", "replace");
172 | });
173 |
174 | // it("should throw error if argument is invalid", function() {
175 | // expect(function() { div.replace(1); }).toThrow();
176 | // });
177 | });
178 |
179 | });
--------------------------------------------------------------------------------
/test/spec/dom/extend.spec.js:
--------------------------------------------------------------------------------
1 | describe("extend", function() {
2 | "use strict";
3 |
4 | var callback;
5 |
6 | beforeEach(function() {
7 | callback = jasmine.createSpy("callback");
8 | });
9 |
10 | it("executes contructor for each element", function(done) {
11 | jasmine.sandbox.set("
");
12 |
13 | callback.and.callFake(function() {
14 | expect(this).toBeDefined();
15 |
16 | if (callback.calls.count() === 3) {
17 | done();
18 | }
19 | });
20 |
21 | DOM.extend(".t0", { constructor: callback });
22 | });
23 |
24 | it("supports shortcut", function(done) {
25 | DOM.extend(".t1", done);
26 |
27 | jasmine.sandbox.set("
");
28 | });
29 |
30 | it("captures any future element on page", function(done) {
31 | DOM.extend(".t2", {constructor: callback});
32 |
33 | callback.and.callFake(function() {
34 | if (callback.calls.count() === 2) {
35 | done();
36 | }
37 | });
38 |
39 | jasmine.sandbox.set("
");
40 | });
41 |
42 | it("does not execute the same extension twice", function(done) {
43 | var link = DOM.create("
"),
44 | spy = jasmine.createSpy("t3-2"),
45 | complete = function() {
46 | if (callback.calls.count() === 1 && spy.calls.count() === 1) {
47 | link.remove();
48 |
49 | done();
50 | }
51 | };
52 |
53 | DOM.find("body").append(link);
54 |
55 | DOM.extend(".t3-1", {constructor: callback.and.callFake(complete)});
56 | DOM.extend(".t3-2", {constructor: spy.and.callFake(complete)});
57 | });
58 |
59 | it("should accept several watchers of the same selector", function(done) {
60 | var spy = jasmine.createSpy("callback2"),
61 | complete = function() {
62 | if (callback.calls.count() === 2 && spy.calls.count() === 2) done();
63 | };
64 |
65 | jasmine.sandbox.set("
");
66 |
67 | DOM.extend(".t4", {constructor: callback.and.callFake(complete)});
68 | DOM.extend(".t4", {constructor: spy.and.callFake(complete)});
69 | });
70 |
71 | it("accepts different selectors for the same element", function(done) {
72 | var spy = jasmine.createSpy("callback2"),
73 | complete = function() {
74 | if (callback.calls.count() === 2 && spy.calls.count() === 1) done();
75 | };
76 |
77 | jasmine.sandbox.set("
");
78 |
79 | DOM.extend(".t5", {constructor: callback.and.callFake(complete)});
80 | DOM.extend("b", {constructor: spy.and.callFake(complete)});
81 | });
82 |
83 | it("does not matches parent elements", function(done) {
84 | var spy1 = jasmine.createSpy("spy1"),
85 | spy2 = jasmine.createSpy("spy2"),
86 | complete = function() {
87 | if (spy1.calls.count() === 1 && spy2.calls.count() === 1) done();
88 | };
89 |
90 | jasmine.sandbox.set("
");
91 |
92 | DOM.extend("#t6-1", {constructor: spy1.and.callFake(complete)});
93 | DOM.extend("#t6-2", {constructor: spy2.and.callFake(complete)});
94 | });
95 |
96 | it("does not initialize twise after hide/show", function(done) {
97 | jasmine.sandbox.set("
");
98 |
99 | var link = DOM.find(".t7");
100 | var spy = callback;
101 |
102 | callback.and.callFake(function() {
103 | link.hide();
104 |
105 | setTimeout(function() {
106 | expect(spy.calls.count()).toBe(1);
107 |
108 | done();
109 | }, 50);
110 | });
111 |
112 | DOM.extend(".t7", {constructor: callback});
113 | });
114 |
115 | it("does not initialize twise after removing element from DOM", function(done) {
116 | jasmine.sandbox.set("
");
117 |
118 | var link = DOM.find(".t8");
119 | var spy = callback;
120 |
121 | callback.and.callFake(function() {
122 | link.remove();
123 |
124 | DOM.find("body").append(link);
125 |
126 | setTimeout(function() {
127 | expect(spy.calls.count()).toBe(1);
128 |
129 | link.remove();
130 |
131 | done();
132 | }, 50);
133 | });
134 |
135 | DOM.extend(".t8", {constructor: callback});
136 | });
137 |
138 | it("handles nested elements", function(done) {
139 | var spy = jasmine.createSpy("ctr");
140 |
141 | DOM.extend(".t9", {constructor: spy, test: function() {}});
142 |
143 | spy.and.callFake(function() {
144 | if (spy.calls.count() === 1) {
145 | // expect(this).toHaveProp("id", "two");
146 | expect(this.test).toBeDefined();
147 | } else {
148 | // expect(this).toHaveProp("id", "one");
149 | expect(this.test).toBeDefined();
150 |
151 | done();
152 | }
153 | });
154 |
155 | jasmine.sandbox.set("
");
156 | });
157 |
158 | it("is always async", function() {
159 | var spy = jasmine.createSpy("ctr");
160 |
161 | jasmine.sandbox.set("
");
162 |
163 | DOM.extend(".t10", spy);
164 |
165 | expect(spy).not.toHaveBeenCalled();
166 | });
167 |
168 | it("allows extending the $Element prototype", function() {
169 | DOM.extend("*", {
170 | test: function() { return 555 }
171 | });
172 |
173 | expect(DOM.create("
").test()).toBe(555);
174 | // expect(DOM.mock(" ").test()).toBe(555);
175 | });
176 |
177 | it("allows extending the $Document prototype", function() {
178 | DOM.extend({
179 | test: function() { return 555 }
180 | });
181 |
182 | expect(DOM.test()).toBe(555);
183 | });
184 |
185 | // it("should not stop handle other listeners if any throws an error", function(done) {
186 | // var otherCallback = jasmine.createSpy("otherCallback"),
187 | // // DOM.extend uses setTimeout for safe logging of an error
188 | // errorSpy = spyOn(window, "setTimeout");
189 |
190 | // callback.and.throwError("stop listeners");
191 |
192 | // DOM.extend(".t11", callback);
193 | // DOM.extend(".t11", otherCallback);
194 |
195 | // otherCallback.and.callFake(function() {
196 | // expect(errorSpy).toHaveBeenCalled();
197 |
198 | // done();
199 | // });
200 |
201 | // jasmine.sandbox.set(" ");
202 | // });
203 |
204 | it("should throw error if arguments are invalid", function() {
205 | expect(function() { DOM.extend(1); }).toThrow();
206 | // expect(function() { DOM.extend(" * ", function() {}); }).toThrow();
207 | // expect(function() { DOM.extend("div > *", function() {}); }).toThrow();
208 | //expect(function() { DOM.extend("*", {constructor: function() {}}); }).toThrow();
209 | });
210 |
211 | });
--------------------------------------------------------------------------------
/test/spec/element/on.spec.js:
--------------------------------------------------------------------------------
1 | describe("on", function() {
2 | "use strict";
3 |
4 | var link, input, form, spy;
5 |
6 | beforeEach(function() {
7 | jasmine.sandbox.set("
test element ");
8 |
9 | link = DOM.find("#test");
10 | input = DOM.find("#input");
11 | form = DOM.find("#form");
12 |
13 | spy = jasmine.createSpy("callback");
14 | });
15 |
16 | it("should accept single callback with the element as 'this' by default", function() {
17 | input.on("focus", spy);
18 | input.fire("focus");
19 |
20 | spy.and.callFake(function() {
21 | expect(this).toEqual(input);
22 | });
23 |
24 | expect(spy).toHaveBeenCalled();
25 | });
26 |
27 | it("accepts selector option", function() {
28 | DOM.on("focus", {selector: "input", once: true}, spy);
29 |
30 | link.fire("focus");
31 | expect(spy).not.toHaveBeenCalled();
32 |
33 | input.fire("focus");
34 | expect(spy).toHaveBeenCalled();
35 | });
36 |
37 | it("should fix currentTarget when selector exists", function() {
38 | spy.and.callFake(function(currentTarget) {
39 | expect(currentTarget).toHaveTag("a");
40 |
41 | return false;
42 | });
43 |
44 | DOM.on("click", {selector: "a", once: true}, ["currentTarget"], spy);
45 | link.find("i").fire("click");
46 | expect(spy).toHaveBeenCalled();
47 | });
48 |
49 | // it("should accept array or key-value object", function() {
50 | // var otherSpy = jasmine.createSpy("otherSpy"),
51 | // arraySpy = jasmine.createSpy("arraySpy");
52 |
53 | // input.on({focus: spy, click: otherSpy});
54 |
55 | // input.fire("focus");
56 | // expect(spy).toHaveBeenCalled();
57 |
58 | // input.fire("click");
59 | // expect(otherSpy).toHaveBeenCalled();
60 |
61 | // input.on(["focus", "click"], arraySpy);
62 |
63 | // input.fire("focus");
64 | // input.fire("click");
65 | // expect(arraySpy.calls.count()).toBe(2);
66 | // });
67 |
68 | it("should prevent default if handler returns false", function() {
69 | spy.and.returnValue(false);
70 |
71 | link.on("click", spy);
72 | link.fire("click");
73 | expect(spy).toHaveBeenCalled();
74 | expect(location.hash).not.toBe("#test");
75 | });
76 |
77 | describe("handler arguments", function() {
78 | it("handle strings as the event object property names", function() {
79 | spy.and.callFake(function(target, currentTarget, relatedTarget) {
80 | expect(target).toBe(input);
81 | expect(currentTarget).toBe(input);
82 | expect(relatedTarget).not.toBeFalsy();
83 | expect(relatedTarget).toBeMock();
84 | });
85 |
86 | input.on("click", ["target", "currentTarget", "relatedTarget"], spy);
87 | input.fire("click");
88 | expect(spy).toHaveBeenCalled();
89 |
90 | spy.and.callFake(function(type, defaultPrevented, shiftKey) {
91 | expect(type).toBe("focus");
92 | expect(defaultPrevented).toBe(false);
93 | expect(shiftKey).toBeFalsy();
94 | });
95 |
96 | input.on("focus", ["type", "defaultPrevented", "shiftKey"], spy);
97 | input.fire("focus");
98 | expect(spy).toHaveBeenCalled();
99 | });
100 |
101 | // it("handle numbers as event argument index", function() {
102 | // input.on("my:test", [1, 3, "target"], spy);
103 | // input.fire("my:test", 123, 555, "testing");
104 |
105 | // expect(spy).toHaveBeenCalledWith(123, "testing", input);
106 | // });
107 |
108 | // it("can use zero to access event type", function() {
109 | // input.on("focus", [0, "target"], spy);
110 | // input.fire("focus");
111 |
112 | // expect(spy).toHaveBeenCalledWith("focus", input);
113 | // });
114 |
115 | it("may return preventDefault functor", function() {
116 | spy.and.callFake(function(cancel) {
117 | expect(typeof cancel).toBe("function");
118 |
119 | cancel();
120 | });
121 |
122 | link.on("click", ["preventDefault"], spy);
123 | link.fire("click");
124 | expect(spy).toHaveBeenCalled();
125 | expect(location.hash).not.toBe("#test");
126 | });
127 |
128 | it("may return stopPropagation functor", function() {
129 | var parentSpy = jasmine.createSpy("parent");
130 |
131 | spy.and.callFake(function(stop) {
132 | expect(typeof stop).toBe("function");
133 |
134 | stop();
135 | });
136 |
137 | link.closest().on("click", parentSpy);
138 | link.on("click", ["stopPropagation"], spy);
139 | link.fire("click");
140 | expect(spy).toHaveBeenCalled();
141 | expect(parentSpy).not.toHaveBeenCalled();
142 | });
143 | });
144 |
145 | // FIXME: find a way to test without exception in browser
146 | // it("should not stop to call handlers if any of them throws an error inside", function() {
147 | // window.onerror = function() {
148 | // return true; // suppress displaying expected error for this test
149 | // };
150 |
151 | // input.on("click", function() { throw "test"; }).on("click", spy).fire("click");
152 |
153 | // expect(spy).toHaveBeenCalled();
154 |
155 | // window.onerror = null; // restore default error handling
156 | // });
157 |
158 | it("should fix some non-bubbling events", function() {
159 | DOM.on("focus", {once: true}, spy);
160 | input.fire("focus");
161 | expect(spy).toHaveBeenCalled();
162 |
163 | DOM.on("invalid", {once: true}, spy);
164 | input.fire("invalid");
165 | expect(spy.calls.count()).toBe(2);
166 | });
167 |
168 | // it("should fix input event", function() {
169 | // input.on("input", spy).fire("input");
170 | // expect(spy).toHaveBeenCalled();
171 |
172 | // DOM.on("input", "a", spy);
173 | // input.fire("input");
174 | // expect(spy.calls.count()).toBe(2);
175 |
176 | // DOM.on("input", "input", spy);
177 | // input.fire("input");
178 | // expect(spy.calls.count()).toBe(4);
179 | // });
180 |
181 | // it("should fix submit event", function() {
182 | // spy.and.returnValue(false);
183 |
184 | // form.on("submit", spy).fire("submit");
185 | // expect(spy).toHaveBeenCalled();
186 |
187 | // DOM.on("submit", "a", spy);
188 | // form.fire("submit");
189 | // expect(spy.calls.count()).toBe(2);
190 |
191 | // DOM.on("submit", "form", spy);
192 | // form.fire("submit");
193 | // expect(spy.calls.count()).toBe(4);
194 | // });
195 |
196 | // it("should fix reset event", function() {
197 | // form.on("reset", spy).fire("reset");
198 | // expect(spy.calls.count()).toBe(1);
199 |
200 | // DOM.on("reset", spy);
201 | // form.fire("reset");
202 | // expect(spy.calls.count()).toBe(3);
203 | // });
204 |
205 | // it("should support late binding", function() {
206 | // spy.and.callFake(function() { expect(this).toBe(input) });
207 | // input.callback = spy;
208 | // input.on("focus", "callback").fire("focus");
209 | // expect(spy).toHaveBeenCalled();
210 |
211 | // delete input.callback;
212 | // input.fire("focus");
213 | // expect(spy.calls.count()).toBe(1);
214 | // });
215 |
216 | // it("should support late binding for private props", function() {
217 | // spy.and.callFake(function() { expect(this).toBe(input) });
218 | // input.set("--callback", spy);
219 | // input.on("focus", "--callback").fire("focus");
220 | // expect(spy).toHaveBeenCalled();
221 |
222 | // input.set("--callback", null);
223 | // input.fire("focus");
224 | // expect(spy.calls.count()).toBe(1);
225 | // });
226 |
227 | it("should allow to prevent custom events", function() {
228 | var spy2 = jasmine.createSpy("spy2");
229 |
230 | form.on("custom:on", ["defaultPrevented"], spy);
231 | input.on("custom:on", spy2.and.returnValue(false));
232 |
233 | spy.and.callFake(function(defaultPrevented) {
234 | expect(defaultPrevented).toBe(true);
235 | });
236 |
237 | input.fire("custom:on");
238 | expect(spy).toHaveBeenCalled();
239 | expect(spy2).toHaveBeenCalled();
240 | });
241 |
242 | it("DOM could be a target", function() {
243 | var spy = jasmine.createSpy("callback");
244 |
245 | DOM.on("custom:event1", {once: true}, ["target", "defaultPrevented"], spy);
246 | DOM.fire("custom:event1");
247 |
248 | var args = spy.calls.allArgs()[0];
249 |
250 | expect(args[0]).toBe(DOM);
251 | expect(args[1]).toBe(false);
252 |
253 | spy.calls.reset();
254 | DOM.on("custom:event2", {selector: "ul > li", once: true}, spy);
255 | DOM.fire("custom:event2");
256 | expect(spy).not.toHaveBeenCalled();
257 | });
258 |
259 | it("should fix bubbling and triggering of the change event for IE8", function() {
260 | DOM.on("change", spy);
261 |
262 | input.set("123").fire("change");
263 | expect(spy).toHaveBeenCalled();
264 |
265 | // input = DOM.create("
");
266 | // jasmine.sandbox.set(input);
267 |
268 | // spy.calls.reset();
269 | // input.fire("focus");
270 | // input.fire("click");
271 | // expect(spy).toHaveBeenCalled();
272 |
273 | // spy.calls.reset();
274 | // input.fire("click");
275 | // expect(spy).toHaveBeenCalled();
276 |
277 | // input = DOM.create("
");
278 | // jasmine.sandbox.set(input);
279 |
280 | // spy.calls.reset();
281 | // input.fire("focus");
282 | // input.fire("click");
283 | // expect(spy).toHaveBeenCalled();
284 |
285 | // spy.calls.reset();
286 | // input.fire("click");
287 | // expect(spy).not.toHaveBeenCalled();
288 | });
289 |
290 | it("should do nothing for emapty nodes", function() {
291 | var el = DOM.find("some-element");
292 |
293 | expect(function() { el.on("click", function() {}); }).not.toThrow();
294 | });
295 |
296 | it("should throw error if arguments are invalid", function() {
297 | expect(function() { input.on(123); }).toThrow();
298 | expect(function() { input.on("a", 123); }).toThrow();
299 | });
300 |
301 | it("removes event registraction on function onvokation", function() {
302 | var spy1 = jasmine.createSpy("click");
303 | var stop1 = input.on("click", spy1);
304 | stop1();
305 | input.fire("click");
306 | expect(spy).not.toHaveBeenCalled();
307 |
308 | var spy2 = jasmine.createSpy("click");
309 | var stop2 = input.on("click", "a", spy2);
310 | stop2();
311 | input.fire("click");
312 | expect(spy).not.toHaveBeenCalled();
313 | });
314 |
315 | it("supports once option", function() {
316 | spy.and.callFake(function() {
317 | expect(this).toBe(input);
318 | });
319 |
320 | input.on("focus", {once: true}, spy);
321 | input.fire("focus");
322 | expect(spy).toHaveBeenCalled();
323 |
324 | input.fire("focus");
325 | expect(spy.calls.count()).toBe(1);
326 | });
327 | });
--------------------------------------------------------------------------------
/dist/better-dom.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * better-dom: Live extension playground
3 | * @version 4.1.0 Tue, 24 Mar 2020 09:55:50 GMT
4 | * @link https://github.com/chemerisuk/better-dom
5 | * @copyright 2020 Maksim Chemerisuk
6 | * @license MIT
7 | */
8 | !function(){"use strict";function t(t){return t.ownerDocument.defaultView.getComputedStyle(t)}function e(t){if(t&&t.nodeType===E)return t.ownerDocument.getElementsByTagName("head")[0].appendChild(t)}function n(t,e,n){void 0===n&&(n="$Element");var r="http://chemerisuk.github.io/better-dom/"+n+".html#"+t,i="invalid call `"+n+("DOM"===n?".":"#")+t+"(";i+=O.call(e,String).join(", ")+")`. ",this.message=i+"Check "+r+" to verify the arguments"}function r(t,e){n.call(this,t,e,"DOM")}function i(t,e){n.call(this,t,e,"$Document")}function o(t){t&&(this[0]=t,t.__40100__=this)}function s(t){if(!(this instanceof s))return t?t.__40100__||new s(t):new s;o.call(this,t);var n=t.createElement("style");n.innerHTML=P,e(n),t[N]=n.sheet||n.styleSheet}function a(t){return this instanceof a?void o.call(this,t):t?t.__40100__||new a(t):new a}function c(t){return function(e){var r=this[0];if(!r||"string"!=typeof e)throw new n("create"+t,arguments);var i=t?[]:null,o=!i&&q.exec(e);if(o)return new a(r.createElement(o[1]));H.innerHTML=e.trim();for(var s;s=H.firstElementChild;){if(H.removeChild(s),r!==g&&(s=r.adoptNode(s)),!i){i=new a(s);break}i.push(new a(s))}return i||new a}}function f(t,e){var n=a(e),r=t.constructor;Object.keys(t).forEach(function(e){var i=t[e];i!==r&&(n[e]=i)}),r&&r.call(n)}function u(t,e){return function(r){if(r&&typeof r!==e)throw new n(t,arguments);var i=this[0],o=z(r),s=i?i.children:[];return"number"==typeof r?(r<0&&(r=s.length+r),a(s[r])):o?D.call(s,o).map(a):O.call(s,a)}}function p(t,e,n){return function(){var r=this,i=this[0];if(!i||e&&!i.parentNode)return this;for(var o=t?"":i.ownerDocument.createDocumentFragment(),c=arguments.length,f=new Array(c),u=0;u
":"#unknown"};var F=new s(d.document),R=d.DOM;F.constructor=function(t){var e=t&&t.nodeType;return e===E?a(t):e===A?s(t):new o(t)},F.noConflict=function(){return d.DOM===F&&(d.DOM=R),F},d.DOM=F;var q=/^<([a-zA-Z-]+)\/?>$/,H=g.createElement("body");s.prototype.create=c(""),s.prototype.createAll=c("All");var U=/^(\w*)(?:#([\w\-]+))?(?:\[([\w\-\=]+)\])?(?:\.([\w\-]+))?$/,$=C.concat(null).map(function(t){return(t?t.toLowerCase()+"M":"m")+"atchesSelector"}).reduceRight(function(t,e){return t||e in w&&e},null),z=function(t,e){if("string"!=typeof t)return null;var n=U.exec(t);return n&&(n[1]&&(n[1]=n[1].toLowerCase()),n[3]&&(n[3]=n[3].split("=")),n[4]&&(n[4]=" "+n[4]+" ")),function(r){var i,o;for(n||$||(o=(e||r.ownerDocument).querySelectorAll(t));r&&1===r.nodeType;r=r.parentNode){if(n)i=(!n[1]||r.nodeName.toLowerCase()===n[1])&&(!n[2]||r.id===n[2])&&(!n[3]||(n[3][1]?r.getAttribute(n[3][0])===n[3][1]:r.hasAttribute(n[3][0])))&&(!n[4]||(" "+r.className+" ").indexOf(n[4])>=0);else if($)i=r[$](t);else for(var s=0,a=o.length;s=0)},a.prototype.addClass=function(){for(var t=this,e=arguments,r=arguments.length,i=new Array(r),o=0;o