wysihtml5_with_ps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Makefile +98 -0
- data/Rakefile +38 -0
- data/lib/base/base.js +139 -0
- data/lib/rangy/rangy-core.js +3211 -0
- data/lib/wysihtml5_with_ps/version.rb +3 -0
- data/test/assert/html_equal_test.js +32 -0
- data/test/browser_test.js +85 -0
- data/test/dom/auto_link_test.js +105 -0
- data/test/dom/contains_test.js +18 -0
- data/test/dom/convert_to_list_test.js +101 -0
- data/test/dom/copy_attributes_test.js +51 -0
- data/test/dom/copy_styles_test.js +110 -0
- data/test/dom/delegate_test.js +62 -0
- data/test/dom/get_as_dom_test.js +55 -0
- data/test/dom/get_parent_element_test.js +161 -0
- data/test/dom/get_style_test.js +54 -0
- data/test/dom/has_element_with_class_name_test.js +29 -0
- data/test/dom/has_element_with_tag_name_test.js +25 -0
- data/test/dom/insert_css_test.js +31 -0
- data/test/dom/observe_test.js +83 -0
- data/test/dom/parse_test.js +614 -0
- data/test/dom/rename_element_test.js +28 -0
- data/test/dom/resolve_list_test.js +46 -0
- data/test/dom/sandbox_test.js +184 -0
- data/test/dom/set_attributes_test.js +15 -0
- data/test/dom/set_styles_test.js +19 -0
- data/test/editor_test.js +547 -0
- data/test/incompatible_test.js +60 -0
- data/test/index.html +126 -0
- data/test/lang/array_test.js +22 -0
- data/test/lang/object_test.js +22 -0
- data/test/lang/string_test.js +19 -0
- data/test/quirks/clean_pasted_html_test.js +11 -0
- data/test/undo_manager_test.js +94 -0
- metadata +116 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module("wysihtml5.dom.renameElement", {
|
2
|
+
equal: function(actual, expected, message) {
|
3
|
+
return wysihtml5.assert.htmlEqual(actual, expected, message);
|
4
|
+
},
|
5
|
+
|
6
|
+
renameElement: function(html, newNodeName) {
|
7
|
+
var container = wysihtml5.dom.getAsDom(html);
|
8
|
+
wysihtml5.dom.renameElement(container.firstChild, newNodeName);
|
9
|
+
return container.innerHTML;
|
10
|
+
}
|
11
|
+
});
|
12
|
+
|
13
|
+
test("Basic tests", function() {
|
14
|
+
this.equal(
|
15
|
+
this.renameElement("<p>foo</p>", "div"),
|
16
|
+
"<div>foo</div>"
|
17
|
+
);
|
18
|
+
|
19
|
+
this.equal(
|
20
|
+
this.renameElement("<ul><li>foo</li></ul>", "ol"),
|
21
|
+
"<ol><li>foo</li></ol>"
|
22
|
+
);
|
23
|
+
|
24
|
+
this.equal(
|
25
|
+
this.renameElement('<p align="left" class="foo"></p>', "h2"),
|
26
|
+
'<h2 align="left" class="foo"></h2>'
|
27
|
+
);
|
28
|
+
});
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module("wysihtml5.dom.resolveList", {
|
2
|
+
equal: function(actual, expected, message) {
|
3
|
+
return wysihtml5.assert.htmlEqual(actual, expected, message);
|
4
|
+
},
|
5
|
+
|
6
|
+
resolveList: function(html) {
|
7
|
+
var container = wysihtml5.dom.getAsDom(html);
|
8
|
+
document.body.appendChild(container);
|
9
|
+
wysihtml5.dom.resolveList(container.firstChild);
|
10
|
+
var innerHTML = container.innerHTML;
|
11
|
+
container.parentNode.removeChild(container);
|
12
|
+
return innerHTML;
|
13
|
+
}
|
14
|
+
});
|
15
|
+
|
16
|
+
test("Basic tests", function() {
|
17
|
+
this.equal(
|
18
|
+
this.resolveList("<ul><li>foo</li></ul>"),
|
19
|
+
"foo<br>"
|
20
|
+
);
|
21
|
+
|
22
|
+
this.equal(
|
23
|
+
this.resolveList("<ul><li>foo</li><li>bar</li></ul>"),
|
24
|
+
"foo<br>bar<br>"
|
25
|
+
);
|
26
|
+
|
27
|
+
this.equal(
|
28
|
+
this.resolveList("<ol><li>foo</li><li>bar</li></ol>"),
|
29
|
+
"foo<br>bar<br>"
|
30
|
+
);
|
31
|
+
|
32
|
+
this.equal(
|
33
|
+
this.resolveList("<ol><li></li><li>bar</li></ol>"),
|
34
|
+
"bar<br>"
|
35
|
+
);
|
36
|
+
|
37
|
+
this.equal(
|
38
|
+
this.resolveList("<ol><li>foo<br></li><li>bar</li></ol>"),
|
39
|
+
"foo<br>bar<br>"
|
40
|
+
);
|
41
|
+
|
42
|
+
this.equal(
|
43
|
+
this.resolveList("<ul><li><h1>foo</h1></li><li><div>bar</div></li><li>baz</li></ul>"),
|
44
|
+
"<h1>foo</h1><div>bar</div>baz<br>"
|
45
|
+
);
|
46
|
+
});
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module("wysihtml5.dom.Sandbox", {
|
2
|
+
teardown: function() {
|
3
|
+
var iframe;
|
4
|
+
while (iframe = document.querySelector("iframe.wysihtml5-sandbox")) {
|
5
|
+
iframe.parentNode.removeChild(iframe);
|
6
|
+
}
|
7
|
+
},
|
8
|
+
|
9
|
+
getCharset: function(doc) {
|
10
|
+
var charset = doc.characterSet || doc.charset;
|
11
|
+
if (/unicode|utf-8/.test(charset)) {
|
12
|
+
return "utf-8";
|
13
|
+
}
|
14
|
+
return charset;
|
15
|
+
},
|
16
|
+
|
17
|
+
eval: function(iframeWindow, code) {
|
18
|
+
try {
|
19
|
+
return iframeWindow.execScript ? iframeWindow.execScript(code) : iframeWindow.eval(code);
|
20
|
+
} catch(e) {
|
21
|
+
return null;
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
isUnset: function(evalCode, iframeWindow) {
|
26
|
+
var value = this.eval(iframeWindow, evalCode);
|
27
|
+
return !value || value == wysihtml5.EMPTY_FUNCTION;
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
|
32
|
+
asyncTest("Basic Test", function() {
|
33
|
+
expect(8);
|
34
|
+
|
35
|
+
var sandbox = new wysihtml5.dom.Sandbox(function(param) {
|
36
|
+
equal(param, sandbox, "The parameter passed into the readyCallback is the sandbox instance");
|
37
|
+
|
38
|
+
var iframes = document.querySelectorAll("iframe.wysihtml5-sandbox");
|
39
|
+
equal(iframes.length, 1, "iFrame sandbox inserted into dom tree");
|
40
|
+
|
41
|
+
var iframe = iframes[iframes.length - 1],
|
42
|
+
isIframeInvisible = iframe.width == 0 && iframe.height == 0 && iframe.frameBorder == 0;
|
43
|
+
ok(isIframeInvisible, "iframe is not visible");
|
44
|
+
|
45
|
+
var isSandboxed = iframe.getAttribute("security") == "restricted";
|
46
|
+
ok(isSandboxed, "iFrame is sandboxed");
|
47
|
+
|
48
|
+
var isWindowObject = sandbox.getWindow().setInterval && sandbox.getWindow().clearInterval;
|
49
|
+
ok(isWindowObject, "wysihtml5.Sandbox.prototype.getWindow() works properly");
|
50
|
+
|
51
|
+
var isDocumentObject = sandbox.getDocument().appendChild && sandbox.getDocument().body;
|
52
|
+
ok(isDocumentObject, "wysihtml5.Sandbox.prototype.getDocument() works properly");
|
53
|
+
|
54
|
+
equal(sandbox.getIframe(), iframe, "wysihtml5.Sandbox.prototype.getIframe() returns the iframe correctly");
|
55
|
+
equal(typeof(sandbox.getWindow().onerror), "function", "window.onerror is set");
|
56
|
+
|
57
|
+
start();
|
58
|
+
});
|
59
|
+
|
60
|
+
sandbox.insertInto(document.body);
|
61
|
+
});
|
62
|
+
|
63
|
+
|
64
|
+
asyncTest("Security test #1", function() {
|
65
|
+
expect(14);
|
66
|
+
|
67
|
+
var that = this;
|
68
|
+
|
69
|
+
var sandbox = new wysihtml5.dom.Sandbox(function() {
|
70
|
+
var iframeWindow = sandbox.getWindow();
|
71
|
+
|
72
|
+
var isSafari = wysihtml5.browser.USER_AGENT.indexOf("Safari") !== -1 && wysihtml5.browser.USER_AGENT.indexOf("Chrome") === 1;
|
73
|
+
|
74
|
+
if (isSafari) {
|
75
|
+
// This test fails in Safari 5, as it's impossible to unset a cookie there
|
76
|
+
ok(true, "Cookie is NOT unset (but that's expected in Safari)");
|
77
|
+
} else {
|
78
|
+
ok(that.isUnset("document.cookie", iframeWindow), "Cookie is unset");
|
79
|
+
}
|
80
|
+
|
81
|
+
ok(that.isUnset("document.open", iframeWindow), "document.open is unset");
|
82
|
+
ok(that.isUnset("document.write", iframeWindow), "document.write is unset");
|
83
|
+
ok(that.isUnset("window.parent", iframeWindow), "window.parent is unset");
|
84
|
+
ok(that.isUnset("window.opener", iframeWindow), "window.opener is unset");
|
85
|
+
ok(that.isUnset("window.localStorage", iframeWindow), "localStorage is unset");
|
86
|
+
ok(that.isUnset("window.globalStorage", iframeWindow), "globalStorage is unset");
|
87
|
+
ok(that.isUnset("window.XMLHttpRequest", iframeWindow), "XMLHttpRequest is an empty function");
|
88
|
+
ok(that.isUnset("window.XDomainRequest", iframeWindow), "XDomainRequest is an empty function");
|
89
|
+
ok(that.isUnset("window.alert", iframeWindow), "alert is an empty function");
|
90
|
+
ok(that.isUnset("window.prompt", iframeWindow), "prompt is an empty function");
|
91
|
+
ok(that.isUnset("window.openDatabase", iframeWindow), "window.openDatabase is unset");
|
92
|
+
ok(that.isUnset("window.indexedDB", iframeWindow), "window.indexedDB is unset");
|
93
|
+
ok(that.isUnset("window.postMessage", iframeWindow), "window.openDatabase is unset");
|
94
|
+
|
95
|
+
start();
|
96
|
+
});
|
97
|
+
|
98
|
+
sandbox.insertInto(document.body);
|
99
|
+
});
|
100
|
+
|
101
|
+
|
102
|
+
asyncTest("Security test #2", function() {
|
103
|
+
expect(2);
|
104
|
+
|
105
|
+
var sandbox = new wysihtml5.dom.Sandbox(function() {
|
106
|
+
var html = '<img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" onerror="#{script}" onload="try { window.parent._hackedCookie=document.cookie; } catch(e){}; try { window.parent._hackedVariable=1; } catch(e) {}">';
|
107
|
+
sandbox.getDocument().body.innerHTML = html;
|
108
|
+
|
109
|
+
setTimeout(function() {
|
110
|
+
equal(window._hackedCookie || "", "", "Cookie can't be easily stolen");
|
111
|
+
equal(window._hackedVariable || 0, 0, "iFrame has no access to parent");
|
112
|
+
|
113
|
+
start();
|
114
|
+
}, 2000);
|
115
|
+
});
|
116
|
+
|
117
|
+
sandbox.insertInto(document.body);
|
118
|
+
});
|
119
|
+
|
120
|
+
|
121
|
+
asyncTest("Check charset & doctype", function() {
|
122
|
+
expect(3);
|
123
|
+
|
124
|
+
var that = this;
|
125
|
+
|
126
|
+
var sandbox = new wysihtml5.dom.Sandbox(function() {
|
127
|
+
var iframeDocument = sandbox.getDocument(),
|
128
|
+
isQuirksMode = iframeDocument.compatMode == "BackCompat";
|
129
|
+
|
130
|
+
ok(!isQuirksMode, "iFrame isn't in quirks mode");
|
131
|
+
equal(that.getCharset(iframeDocument), that.getCharset(document), "Charset correctly inherited by iframe");
|
132
|
+
|
133
|
+
iframeDocument.body.innerHTML = '<meta charset="iso-8859-1">ü';
|
134
|
+
|
135
|
+
setTimeout(function() {
|
136
|
+
equal(that.getCharset(iframeDocument), that.getCharset(document), "Charset isn't overwritten");
|
137
|
+
start();
|
138
|
+
}, 500);
|
139
|
+
});
|
140
|
+
|
141
|
+
sandbox.insertInto(document.body);
|
142
|
+
});
|
143
|
+
|
144
|
+
|
145
|
+
asyncTest("Check insertion of single stylesheet", function() {
|
146
|
+
expect(1);
|
147
|
+
|
148
|
+
new wysihtml5.dom.Sandbox(function(sandbox) {
|
149
|
+
var doc = sandbox.getDocument();
|
150
|
+
equal(doc.getElementsByTagName("link").length, 1, "Correct amount of stylesheets inserted into the dom tree");
|
151
|
+
start();
|
152
|
+
}, {
|
153
|
+
stylesheets: "https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/blitzer/jquery-ui.css"
|
154
|
+
}).insertInto(document.body);
|
155
|
+
});
|
156
|
+
|
157
|
+
|
158
|
+
asyncTest("Check insertion of multiple stylesheets", function() {
|
159
|
+
expect(1);
|
160
|
+
|
161
|
+
new wysihtml5.dom.Sandbox(function(sandbox) {
|
162
|
+
var doc = sandbox.getDocument();
|
163
|
+
equal(doc.getElementsByTagName("link").length, 2, "Correct amount of stylesheets inserted into the dom tree");
|
164
|
+
start();
|
165
|
+
}, {
|
166
|
+
stylesheets: [
|
167
|
+
"https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/blitzer/jquery-ui.css",
|
168
|
+
"https://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/excite-bike/jquery-ui.css"
|
169
|
+
]
|
170
|
+
}).insertInto(document.body);
|
171
|
+
});
|
172
|
+
|
173
|
+
|
174
|
+
asyncTest("Check X-UA-Compatible", function() {
|
175
|
+
expect(1);
|
176
|
+
|
177
|
+
new wysihtml5.dom.Sandbox(function(sandbox) {
|
178
|
+
var doc = sandbox.getDocument(),
|
179
|
+
docMode = doc.documentMode;
|
180
|
+
|
181
|
+
ok(doc.documentMode === document.documentMode, "iFrame is in in the same document mode as the parent site");
|
182
|
+
start();
|
183
|
+
}).insertInto(document.body);
|
184
|
+
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module("wysihtml5.dom.setAttributes", {
|
2
|
+
setup: function() {
|
3
|
+
this.element = document.createElement("div");
|
4
|
+
}
|
5
|
+
});
|
6
|
+
|
7
|
+
test("Basic test", function() {
|
8
|
+
wysihtml5.dom.setAttributes({
|
9
|
+
id: "foo",
|
10
|
+
"class": "bar"
|
11
|
+
}).on(this.element);
|
12
|
+
|
13
|
+
equal(this.element.id, "foo");
|
14
|
+
equal(this.element.className, "bar");
|
15
|
+
});
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module("wysihtml5.dom.setStyles", {
|
2
|
+
setup: function() {
|
3
|
+
this.element = document.createElement("div");
|
4
|
+
document.body.appendChild(this.element);
|
5
|
+
},
|
6
|
+
|
7
|
+
teardown: function() {
|
8
|
+
this.element.parentNode.removeChild(this.element);
|
9
|
+
}
|
10
|
+
});
|
11
|
+
|
12
|
+
test("Basic test", function() {
|
13
|
+
wysihtml5.dom.setStyles("text-align: right; float: left").on(this.element);
|
14
|
+
equal(wysihtml5.dom.getStyle("text-align").from(this.element), "right");
|
15
|
+
equal(wysihtml5.dom.getStyle("float").from(this.element), "left");
|
16
|
+
|
17
|
+
wysihtml5.dom.setStyles({ "float": "right" }).on(this.element);
|
18
|
+
equal(wysihtml5.dom.getStyle("float").from(this.element), "right");
|
19
|
+
});
|
data/test/editor_test.js
ADDED
@@ -0,0 +1,547 @@
|
|
1
|
+
if (wysihtml5.browser.supported()) {
|
2
|
+
module("wysihtml5.Editor", {
|
3
|
+
setup: function() {
|
4
|
+
wysihtml5.dom.insertCSS([
|
5
|
+
"#wysihtml5-test-textarea { width: 50%; height: 100px; margin-top: 5px; font-style: italic; border: 2px solid red; border-radius: 2px; }",
|
6
|
+
"#wysihtml5-test-textarea:focus { margin-top: 10px; }",
|
7
|
+
"#wysihtml5-test-textarea:disabled { margin-top: 20px; }"
|
8
|
+
]).into(document);
|
9
|
+
|
10
|
+
this.textareaElement = document.createElement("textarea");
|
11
|
+
this.textareaElement.id = "wysihtml5-test-textarea";
|
12
|
+
this.textareaElement.title = "Please enter your foo";
|
13
|
+
this.textareaElement.value = "hey tiff, what's up?";
|
14
|
+
|
15
|
+
this.form = document.createElement("form");
|
16
|
+
this.form.onsubmit = function() { return false; };
|
17
|
+
this.form.appendChild(this.textareaElement);
|
18
|
+
|
19
|
+
this.originalBodyClassName = document.body.className;
|
20
|
+
|
21
|
+
document.body.appendChild(this.form);
|
22
|
+
},
|
23
|
+
|
24
|
+
teardown: function() {
|
25
|
+
var leftover;
|
26
|
+
while (leftover = document.querySelector("iframe.wysihtml5-sandbox, input[name='_wysihtml5_mode']")) {
|
27
|
+
leftover.parentNode.removeChild(leftover);
|
28
|
+
}
|
29
|
+
this.form.parentNode.removeChild(this.form);
|
30
|
+
document.body.className = this.originalBodyClassName;
|
31
|
+
},
|
32
|
+
|
33
|
+
getComposerElement: function() {
|
34
|
+
return this.getIframeElement().contentWindow.document.body;
|
35
|
+
},
|
36
|
+
|
37
|
+
getIframeElement: function() {
|
38
|
+
var iframes = document.querySelectorAll("iframe.wysihtml5-sandbox");
|
39
|
+
return iframes[iframes.length - 1];
|
40
|
+
}
|
41
|
+
});
|
42
|
+
|
43
|
+
asyncTest("Basic test", function() {
|
44
|
+
expect(18);
|
45
|
+
|
46
|
+
var that = this;
|
47
|
+
|
48
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
49
|
+
editor.observe("load", function() {
|
50
|
+
var iframeElement = that.getIframeElement(),
|
51
|
+
composerElement = that.getComposerElement(),
|
52
|
+
textareaElement = that.textareaElement;
|
53
|
+
ok(true, "Load callback triggered");
|
54
|
+
ok(wysihtml5.dom.hasClass(document.body, "wysihtml5-supported"), "<body> received correct class name");
|
55
|
+
equal(textareaElement.style.display, "none", "Textarea not visible");
|
56
|
+
ok(iframeElement.style.display, "", "Editor iFrame is visible");
|
57
|
+
equal(editor.currentView.name, "composer", "Current view is 'composer'");
|
58
|
+
|
59
|
+
// Make textarea visible for a short amount of time, in order to calculate dimensions properly
|
60
|
+
textareaElement.style.display = "block";
|
61
|
+
deepEqual(
|
62
|
+
[iframeElement.offsetHeight, iframeElement.offsetWidth],
|
63
|
+
[textareaElement.offsetHeight, textareaElement.offsetWidth],
|
64
|
+
"Editor has the same dimensions as the original textarea"
|
65
|
+
);
|
66
|
+
textareaElement.style.display = "none";
|
67
|
+
|
68
|
+
var hiddenField = textareaElement.nextSibling;
|
69
|
+
equal(hiddenField.name, "_wysihtml5_mode", "Hidden field has correct name");
|
70
|
+
equal(hiddenField.value, "1", "Hidden field has correct value");
|
71
|
+
equal(hiddenField.type, "hidden", "Hidden field is actually hidden");
|
72
|
+
equal(textareaElement.nextSibling.nextSibling, iframeElement, "Editor iframe is inserted after the textarea");
|
73
|
+
equal(composerElement.getAttribute("contentEditable"), "true", "Body element in iframe is editable");
|
74
|
+
equal(editor.textarea.element, textareaElement, "Textarea correctly available on editor instance");
|
75
|
+
equal(editor.composer.element, composerElement, "contentEditable element available on editor instance");
|
76
|
+
equal(wysihtml5.dom.getStyle("font-style").from(composerElement), "italic", "Correct font-style applied to editor element");
|
77
|
+
equal(wysihtml5.dom.getStyle("width").from(iframeElement), "50%", "Correct width applied to iframe")
|
78
|
+
equal(wysihtml5.dom.getStyle("height").from(iframeElement), "100px", "Correct height applied to iframe")
|
79
|
+
|
80
|
+
if ("borderRadius" in document.createElement("div").style) {
|
81
|
+
expect(19);
|
82
|
+
ok(wysihtml5.dom.getStyle("border-top-right-radius").from(iframeElement).indexOf("2px") !== -1, "border-radius correctly copied");
|
83
|
+
}
|
84
|
+
|
85
|
+
equal(composerElement.innerHTML.toLowerCase(), "hey tiff, what's up?", "Copied the initial textarea value to the editor");
|
86
|
+
ok(wysihtml5.dom.hasClass(composerElement, "wysihtml5-editor"), "Editor element has correct class name");
|
87
|
+
|
88
|
+
start();
|
89
|
+
});
|
90
|
+
});
|
91
|
+
|
92
|
+
|
93
|
+
asyncTest("Check setting of name as class name on iframe and iframe's body", function() {
|
94
|
+
expect(4);
|
95
|
+
|
96
|
+
this.textareaElement.className = "death-star";
|
97
|
+
|
98
|
+
var that = this,
|
99
|
+
name = "star-wars-input",
|
100
|
+
editor = new wysihtml5.Editor(this.textareaElement, { name: "star-wars-input" });
|
101
|
+
|
102
|
+
editor.observe("load", function() {
|
103
|
+
var iframeElement = that.getIframeElement(),
|
104
|
+
composerElement = that.getComposerElement(),
|
105
|
+
textareaElement = that.textareaElement;
|
106
|
+
ok(wysihtml5.dom.hasClass(iframeElement, name), "iFrame has adopted name as className");
|
107
|
+
ok(wysihtml5.dom.hasClass(composerElement, name), "iFrame's body has adopted name as className");
|
108
|
+
ok(wysihtml5.dom.hasClass(composerElement, "death-star"), "iFrame's body has adopted the textarea className");
|
109
|
+
ok(!wysihtml5.dom.hasClass(textareaElement, name), "Textarea has not adopted name as className");
|
110
|
+
start();
|
111
|
+
});
|
112
|
+
});
|
113
|
+
|
114
|
+
|
115
|
+
asyncTest("Check textarea with box-sizing: border-box;", function() {
|
116
|
+
expect(1);
|
117
|
+
|
118
|
+
var that = this;
|
119
|
+
|
120
|
+
wysihtml5.dom.setStyles({
|
121
|
+
MozBoxSizing: "border-box",
|
122
|
+
WebkitBoxSizing: "border-box",
|
123
|
+
MsBoxSizing: "border-box",
|
124
|
+
boxSizing: "border-box"
|
125
|
+
}).on(this.textareaElement);
|
126
|
+
|
127
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
128
|
+
editor.observe("load", function() {
|
129
|
+
// Make textarea visible for a short amount of time, in order to calculate dimensions properly
|
130
|
+
that.textareaElement.style.display = "block";
|
131
|
+
deepEqual(
|
132
|
+
[that.getIframeElement().offsetWidth, that.getIframeElement().offsetHeight],
|
133
|
+
[that.textareaElement.offsetWidth, that.textareaElement.offsetHeight],
|
134
|
+
"Editor has the same dimensions as the original textarea"
|
135
|
+
);
|
136
|
+
that.textareaElement.style.display = "none";
|
137
|
+
|
138
|
+
start();
|
139
|
+
});
|
140
|
+
});
|
141
|
+
|
142
|
+
|
143
|
+
asyncTest("Check whether attributes are copied", function() {
|
144
|
+
expect(1);
|
145
|
+
|
146
|
+
var that = this;
|
147
|
+
|
148
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
149
|
+
editor.observe("load", function() {
|
150
|
+
equal(that.getComposerElement().title, that.textareaElement.title, "Editor got attributes copied over from textarea");
|
151
|
+
start();
|
152
|
+
});
|
153
|
+
});
|
154
|
+
|
155
|
+
|
156
|
+
asyncTest("Check events", function() {
|
157
|
+
expect(8);
|
158
|
+
|
159
|
+
var that = this;
|
160
|
+
|
161
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
162
|
+
|
163
|
+
editor.observe("beforeload", function() {
|
164
|
+
ok(true, "'beforeload' event correctly fired");
|
165
|
+
});
|
166
|
+
|
167
|
+
editor.observe("load", function() {
|
168
|
+
var composerElement = that.getComposerElement(),
|
169
|
+
iframeElement = that.getIframeElement();
|
170
|
+
|
171
|
+
editor.observe("focus", function() {
|
172
|
+
ok(true, "'focus' event correctly fired");
|
173
|
+
});
|
174
|
+
|
175
|
+
editor.observe("blur", function() {
|
176
|
+
ok(true, "'blur' event correctly fired");
|
177
|
+
});
|
178
|
+
|
179
|
+
editor.observe("change", function() {
|
180
|
+
ok(true, "'change' event correctly fired");
|
181
|
+
});
|
182
|
+
|
183
|
+
editor.observe("paste", function() {
|
184
|
+
ok(true, "'paste' event correctly fired");
|
185
|
+
});
|
186
|
+
|
187
|
+
editor.observe("drop", function() {
|
188
|
+
ok(true, "'drop' event correctly fired");
|
189
|
+
});
|
190
|
+
|
191
|
+
editor.observe("custom_event", function() {
|
192
|
+
ok(true, "'custom_event' correctly fired");
|
193
|
+
});
|
194
|
+
|
195
|
+
QUnit.triggerEvent(composerElement, "focus");
|
196
|
+
editor.stopObserving("focus");
|
197
|
+
|
198
|
+
// Modify innerHTML in order to force 'change' event to trigger onblur
|
199
|
+
composerElement.innerHTML = "foobar";
|
200
|
+
QUnit.triggerEvent(composerElement, "blur");
|
201
|
+
QUnit.triggerEvent(composerElement, "focusout");
|
202
|
+
equal(wysihtml5.dom.getStyle("margin-top").from(iframeElement), "5px", ":focus styles are correctly unset");
|
203
|
+
QUnit.triggerEvent(composerElement, "paste");
|
204
|
+
QUnit.triggerEvent(composerElement, "drop");
|
205
|
+
|
206
|
+
editor.fire("custom_event");
|
207
|
+
|
208
|
+
// Delay teardown in order to avoid unwanted js errors caused by a too early removed sandbox iframe
|
209
|
+
// which then causes js errors in Safari 5
|
210
|
+
setTimeout(function() { start(); }, 100);
|
211
|
+
});
|
212
|
+
});
|
213
|
+
|
214
|
+
|
215
|
+
asyncTest("Check sync (basic)", function() {
|
216
|
+
expect(1);
|
217
|
+
|
218
|
+
var that = this;
|
219
|
+
|
220
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
221
|
+
editor.observe("load", function() {
|
222
|
+
var html = "<p>hello foobar, what up?</p>";
|
223
|
+
that.getComposerElement().innerHTML = html;
|
224
|
+
|
225
|
+
setTimeout(function() {
|
226
|
+
equal(that.textareaElement.value.toLowerCase(), html.toLowerCase(), "Editor content got correctly copied over to original textarea");
|
227
|
+
start();
|
228
|
+
}, 500);
|
229
|
+
});
|
230
|
+
});
|
231
|
+
|
232
|
+
|
233
|
+
asyncTest("Check sync (advanced)", function() {
|
234
|
+
expect(5);
|
235
|
+
|
236
|
+
var that = this;
|
237
|
+
|
238
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
239
|
+
parserRules: { tags: { "strong": true } }
|
240
|
+
});
|
241
|
+
|
242
|
+
editor.observe("load", function() {
|
243
|
+
var html = "<strong>timmay!</strong>",
|
244
|
+
composerElement = that.getComposerElement();
|
245
|
+
composerElement.innerHTML = html;
|
246
|
+
|
247
|
+
setTimeout(function() {
|
248
|
+
equal(that.textareaElement.value.toLowerCase(), html.toLowerCase(), "Editor content got correctly copied over to original textarea");
|
249
|
+
|
250
|
+
composerElement.innerHTML = "<font color=\"red\">hey </font><strong>helen!</strong>";
|
251
|
+
editor.fire("change_view", "textarea");
|
252
|
+
equal(that.textareaElement.value.toLowerCase(), "hey <strong>helen!</strong>", "Editor got immediately copied over to textarea after switching the view");
|
253
|
+
|
254
|
+
that.textareaElement.value = "<i>hey </i><strong>richard!</strong>";
|
255
|
+
editor.fire("change_view", "composer");
|
256
|
+
equal(composerElement.innerHTML.toLowerCase(), "hey <strong>richard!</strong>", "Textarea sanitized and copied over it's value to the editor after switch");
|
257
|
+
|
258
|
+
composerElement.innerHTML = "<i>hey </i><strong>timmay!</strong>";
|
259
|
+
QUnit.triggerEvent(that.form, "submit");
|
260
|
+
equal(that.textareaElement.value.toLowerCase(), "hey <strong>timmay!</strong>", "Textarea gets the sanitized content of the editor onsubmit");
|
261
|
+
|
262
|
+
setTimeout(function() {
|
263
|
+
that.form.reset();
|
264
|
+
|
265
|
+
// Timeout needed since reset() isn't executed synchronously
|
266
|
+
setTimeout(function() {
|
267
|
+
equal(wysihtml5.dom.getTextContent(composerElement), "", "Editor is empty after reset");
|
268
|
+
start();
|
269
|
+
}, 100);
|
270
|
+
|
271
|
+
}, 500);
|
272
|
+
|
273
|
+
}, 500);
|
274
|
+
|
275
|
+
});
|
276
|
+
});
|
277
|
+
|
278
|
+
|
279
|
+
asyncTest("Check placeholder", function() {
|
280
|
+
expect(13);
|
281
|
+
|
282
|
+
var that = this;
|
283
|
+
|
284
|
+
var placeholderText = "enter text ...";
|
285
|
+
this.textareaElement.value = "";
|
286
|
+
this.textareaElement.setAttribute("placeholder", "enter text ...");
|
287
|
+
|
288
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
289
|
+
editor.observe("load", function() {
|
290
|
+
var composerElement = that.getComposerElement();
|
291
|
+
equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Placeholder text correctly copied into textarea");
|
292
|
+
ok(wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor got 'placeholder' css class");
|
293
|
+
ok(editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder is actually set");
|
294
|
+
editor.fire("focus:composer");
|
295
|
+
equal(wysihtml5.dom.getTextContent(composerElement), "", "Editor is empty after focus");
|
296
|
+
ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class");
|
297
|
+
ok(!editor.hasPlaceholderSet(), "'hasPlaceholderSet' returns correct value when placeholder isn't actually set");
|
298
|
+
editor.fire("blur:composer");
|
299
|
+
equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "Editor restored placeholder text after unfocus");
|
300
|
+
editor.fire("focus:composer");
|
301
|
+
equal(wysihtml5.dom.getTextContent(composerElement), "");
|
302
|
+
composerElement.innerHTML = "some content";
|
303
|
+
editor.fire("blur:composer");
|
304
|
+
equal(wysihtml5.dom.getTextContent(composerElement), "some content");
|
305
|
+
ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class");
|
306
|
+
editor.fire("focus:composer");
|
307
|
+
// Following html causes innerText and textContent to report an empty string
|
308
|
+
var html = '<img>';
|
309
|
+
composerElement.innerHTML = html;
|
310
|
+
editor.fire("blur:composer");
|
311
|
+
equal(composerElement.innerHTML.toLowerCase(), html, "HTML hasn't been cleared even though the innerText and textContent properties indicate empty content.");
|
312
|
+
ok(!wysihtml5.dom.hasClass(composerElement, "placeholder"), "Editor hasn't got 'placeholder' css class");
|
313
|
+
|
314
|
+
setTimeout(function() {
|
315
|
+
that.form.reset();
|
316
|
+
|
317
|
+
// Timeout needed since reset() isn't executed synchronously
|
318
|
+
setTimeout(function() {
|
319
|
+
equal(wysihtml5.dom.getTextContent(composerElement), placeholderText, "After form reset the editor has the placeholder as content");
|
320
|
+
start();
|
321
|
+
}, 100);
|
322
|
+
|
323
|
+
}, 500);
|
324
|
+
});
|
325
|
+
});
|
326
|
+
|
327
|
+
|
328
|
+
asyncTest("Check public api", function() {
|
329
|
+
expect(13);
|
330
|
+
|
331
|
+
var that = this;
|
332
|
+
|
333
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
334
|
+
parserRules: { tags: { p: { rename_tag: "div" } } },
|
335
|
+
bodyClassName: "editor-is-supported",
|
336
|
+
composerClassName: "editor"
|
337
|
+
});
|
338
|
+
|
339
|
+
editor.observe("load", function() {
|
340
|
+
ok(editor.isCompatible(), "isCompatible() returns correct value");
|
341
|
+
ok(wysihtml5.dom.hasClass(document.body, "editor-is-supported"), "<body> received correct class name");
|
342
|
+
|
343
|
+
var composerElement = that.getComposerElement();
|
344
|
+
editor.clear();
|
345
|
+
equal(wysihtml5.dom.getTextContent(composerElement), "", "Editor empty after calling 'clear'");
|
346
|
+
ok(wysihtml5.dom.hasClass(composerElement, "editor"), "Composer element has correct class name");
|
347
|
+
|
348
|
+
var html = "hello <strong>foo</strong>!";
|
349
|
+
editor.setValue(html);
|
350
|
+
equal(composerElement.innerHTML.toLowerCase(), html, "Editor content correctly set after calling 'setValue'");
|
351
|
+
ok(!editor.isEmpty(), "'isEmpty' returns correct value when the composer element isn't actually empty");
|
352
|
+
|
353
|
+
var value = editor.getValue();
|
354
|
+
equal(value.toLowerCase(), html, "Editor content correctly returned after calling 'getValue'");
|
355
|
+
|
356
|
+
editor.clear();
|
357
|
+
value = editor.getValue();
|
358
|
+
equal(value, "");
|
359
|
+
ok(editor.isEmpty(), "'isEmpty' returns correct value when the composer element is actually empty");
|
360
|
+
|
361
|
+
equal(editor.parse("<p>foo</p>").toLowerCase(), "<div>foo</div>", "'parse' returns correct value");
|
362
|
+
|
363
|
+
// Check disable/enable
|
364
|
+
editor.disable();
|
365
|
+
ok(!composerElement.getAttribute("contentEditable"), "When disabled the composer hasn't the contentEditable attribute");
|
366
|
+
|
367
|
+
editor.enable();
|
368
|
+
equal(composerElement.getAttribute("contentEditable"), "true", "After enabling the editor the contentEditable property is true");
|
369
|
+
ok(!composerElement.getAttribute("disabled"), "After enabling the disabled attribute is unset");
|
370
|
+
|
371
|
+
start();
|
372
|
+
});
|
373
|
+
});
|
374
|
+
|
375
|
+
|
376
|
+
asyncTest("Parser (default parser method with parserRules as object", function() {
|
377
|
+
expect(2);
|
378
|
+
|
379
|
+
var parserRules = {
|
380
|
+
tags: {
|
381
|
+
div: true,
|
382
|
+
p: { rename_tag: "div" },
|
383
|
+
span: true,
|
384
|
+
script: undefined
|
385
|
+
}
|
386
|
+
};
|
387
|
+
|
388
|
+
var input = "<p>foobar</p>",
|
389
|
+
output = "<div>foobar</div>";
|
390
|
+
|
391
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
392
|
+
parserRules: parserRules
|
393
|
+
});
|
394
|
+
|
395
|
+
editor.observe("load", function() {
|
396
|
+
equal(editor.config.parserRules, parserRules, "Parser rules correctly set on config object");
|
397
|
+
// Invoke parsing via second parameter of setValue()
|
398
|
+
editor.setValue(input, true);
|
399
|
+
equal(editor.getValue().toLowerCase(), output, "HTML got correctly parsed within setValue()");
|
400
|
+
start();
|
401
|
+
});
|
402
|
+
});
|
403
|
+
|
404
|
+
|
405
|
+
asyncTest("Parser (custom parser method with parserRules as object", function() {
|
406
|
+
expect(7);
|
407
|
+
|
408
|
+
var that = this,
|
409
|
+
parserRules = { script: undefined },
|
410
|
+
input = this.textareaElement.value,
|
411
|
+
output = input;
|
412
|
+
|
413
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
414
|
+
parserRules: parserRules,
|
415
|
+
parser: function(html, rules, context) {
|
416
|
+
equal(html.toLowerCase(), input, "HTML passed into parser is equal to the one which just got inserted");
|
417
|
+
equal(rules, parserRules, "Rules passed into parser are equal to those given to the editor");
|
418
|
+
equal(context, that.getIframeElement().contentWindow.document, "Context passed into parser is equal the document object of the editor's iframe");
|
419
|
+
return html.replace(/\<script\>.*?\<\/script\>/gi, "");
|
420
|
+
}
|
421
|
+
});
|
422
|
+
|
423
|
+
editor.observe("load", function() {
|
424
|
+
input = "<p>foobar</p><script>alert(1);</script>";
|
425
|
+
output = "<p>foobar</p>";
|
426
|
+
|
427
|
+
// Invoke parsing via second parameter of setValue()
|
428
|
+
editor.setValue(input, true);
|
429
|
+
equal(editor.getValue().toLowerCase(), output, "HTML got correctly parsed within setValue()");
|
430
|
+
|
431
|
+
start();
|
432
|
+
});
|
433
|
+
});
|
434
|
+
|
435
|
+
|
436
|
+
asyncTest("Inserting an element which causes the textContent/innerText of the contentEditable element to be empty works correctly", function() {
|
437
|
+
expect(2);
|
438
|
+
|
439
|
+
var that = this;
|
440
|
+
|
441
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
442
|
+
editor.observe("load", function() {
|
443
|
+
var html = '<img>',
|
444
|
+
composerElement = that.getComposerElement(),
|
445
|
+
textareaElement = that.textareaElement;
|
446
|
+
composerElement.innerHTML = html;
|
447
|
+
|
448
|
+
// Fire events that could cause a change in the composer
|
449
|
+
QUnit.triggerEvent(composerElement, "keypress");
|
450
|
+
QUnit.triggerEvent(composerElement, "keyup");
|
451
|
+
QUnit.triggerEvent(composerElement, "cut");
|
452
|
+
QUnit.triggerEvent(composerElement, "blur");
|
453
|
+
|
454
|
+
setTimeout(function() {
|
455
|
+
equal(composerElement.innerHTML.toLowerCase(), html, "Composer still has correct content");
|
456
|
+
equal(textareaElement.value.toLowerCase(), html, "Textarea got correct value");
|
457
|
+
start();
|
458
|
+
}, 500);
|
459
|
+
});
|
460
|
+
});
|
461
|
+
|
462
|
+
|
463
|
+
asyncTest("Check for stylesheets", function() {
|
464
|
+
expect(5);
|
465
|
+
|
466
|
+
var that = this;
|
467
|
+
|
468
|
+
var stylesheetUrls = [
|
469
|
+
"http://yui.yahooapis.com/2.8.2r1/build/reset/reset-min.css",
|
470
|
+
"http://yui.yahooapis.com/2.8.0/build/reset/reset-min.css"
|
471
|
+
];
|
472
|
+
|
473
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
474
|
+
stylesheets: stylesheetUrls
|
475
|
+
});
|
476
|
+
|
477
|
+
editor.observe("load", function() {
|
478
|
+
var iframeElement = that.getIframeElement(),
|
479
|
+
iframeDoc = iframeElement.contentWindow.document,
|
480
|
+
linkElements = iframeDoc.getElementsByTagName("link");
|
481
|
+
equal(linkElements.length, 2, "Correct amount of stylesheets inserted into the dom tree");
|
482
|
+
equal(linkElements[0].getAttribute("href"), stylesheetUrls[0]);
|
483
|
+
equal(linkElements[0].getAttribute("rel"), "stylesheet");
|
484
|
+
equal(linkElements[1].getAttribute("href"), stylesheetUrls[1]);
|
485
|
+
equal(linkElements[1].getAttribute("rel"), "stylesheet");
|
486
|
+
start();
|
487
|
+
});
|
488
|
+
});
|
489
|
+
|
490
|
+
|
491
|
+
asyncTest("Check config.supportTouchDevices = false", function() {
|
492
|
+
expect(2);
|
493
|
+
|
494
|
+
var that = this;
|
495
|
+
|
496
|
+
var originalIsTouchDevice = wysihtml5.browser.isTouchDevice;
|
497
|
+
wysihtml5.browser.isTouchDevice = function() { return true; };
|
498
|
+
|
499
|
+
var editor = new wysihtml5.Editor(this.textareaElement, {
|
500
|
+
supportTouchDevices: false
|
501
|
+
});
|
502
|
+
|
503
|
+
editor.observe("load", function() {
|
504
|
+
ok(!that.getIframeElement(), "No editor iframe has been inserted");
|
505
|
+
equal(that.textareaElement.style.display, "", "Textarea is visible");
|
506
|
+
|
507
|
+
wysihtml5.browser.isTouchDevice = originalIsTouchDevice;
|
508
|
+
|
509
|
+
start();
|
510
|
+
});
|
511
|
+
});
|
512
|
+
|
513
|
+
asyncTest("Check whether everything works when the textarea is not within a form", function() {
|
514
|
+
expect(3);
|
515
|
+
|
516
|
+
var textareaElement = document.createElement("textarea");
|
517
|
+
document.body.appendChild(textareaElement);
|
518
|
+
var editor = new wysihtml5.Editor(textareaElement);
|
519
|
+
|
520
|
+
var that = this;
|
521
|
+
editor.observe("load", function() {
|
522
|
+
ok(!document.querySelector("input[name='_wysihtml5_mode']"), "No hidden _wysihtml5_mode input has been created");
|
523
|
+
ok(that.getIframeElement(), "Editor's iframe has been created");
|
524
|
+
equal(textareaElement.style.display, "none", "Textarea is not visible");
|
525
|
+
textareaElement.parentNode.removeChild(textareaElement);
|
526
|
+
|
527
|
+
start();
|
528
|
+
});
|
529
|
+
});
|
530
|
+
|
531
|
+
asyncTest("Test disabled textarea", function() {
|
532
|
+
expect(2);
|
533
|
+
|
534
|
+
this.textareaElement.disabled = true;
|
535
|
+
var that = this;
|
536
|
+
|
537
|
+
var editor = new wysihtml5.Editor(this.textareaElement);
|
538
|
+
|
539
|
+
editor.on("load", function() {
|
540
|
+
var iframeElement = that.getIframeElement(),
|
541
|
+
composerElement = that.getComposerElement();
|
542
|
+
equal(wysihtml5.dom.getStyle("margin-top").from(iframeElement), "20px", "Correct :disabled styles applied");
|
543
|
+
ok(!composerElement.hasAttribute("contentEditable"), "Editor is unfocusable");
|
544
|
+
start();
|
545
|
+
});
|
546
|
+
});
|
547
|
+
}
|