@webcoder49/code-input 2.1.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +11 -1
- package/README.md +26 -11
- package/code-input.css +126 -29
- package/code-input.d.ts +153 -11
- package/code-input.js +218 -193
- package/code-input.min.css +1 -1
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +28 -6
- package/plugins/auto-close-brackets.js +61 -0
- package/plugins/auto-close-brackets.min.js +1 -0
- package/plugins/autocomplete.js +21 -12
- package/plugins/autocomplete.min.js +1 -1
- package/plugins/autodetect.js +4 -4
- package/plugins/autodetect.min.js +1 -1
- package/plugins/find-and-replace.css +145 -0
- package/plugins/find-and-replace.js +746 -0
- package/plugins/find-and-replace.min.css +1 -0
- package/plugins/find-and-replace.min.js +1 -0
- package/plugins/go-to-line.css +77 -0
- package/plugins/go-to-line.js +175 -0
- package/plugins/go-to-line.min.css +1 -0
- package/plugins/go-to-line.min.js +1 -0
- package/plugins/indent.js +166 -15
- package/plugins/indent.min.js +1 -1
- package/plugins/prism-line-numbers.css +10 -9
- package/plugins/prism-line-numbers.min.css +1 -1
- package/plugins/select-token-callbacks.js +289 -0
- package/plugins/select-token-callbacks.min.js +1 -0
- package/plugins/special-chars.css +1 -5
- package/plugins/special-chars.js +65 -61
- package/plugins/special-chars.min.css +2 -2
- package/plugins/special-chars.min.js +1 -1
- package/plugins/test.js +1 -2
- package/plugins/test.min.js +1 -1
- package/tests/hljs.html +55 -0
- package/tests/i18n.html +197 -0
- package/tests/prism-match-braces-compatibility.js +215 -0
- package/tests/prism-match-braces-compatibility.min.js +1 -0
- package/tests/prism.html +54 -0
- package/tests/tester.js +593 -0
- package/tests/tester.min.js +21 -0
- package/plugins/debounce-update.js +0 -40
- package/plugins/debounce-update.min.js +0 -1
package/tests/tester.js
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/* This file contains the main code to test the code-input library with Prism.js and highlight.js. */
|
|
2
|
+
|
|
3
|
+
/* --- Test running functions --- */
|
|
4
|
+
var testsFailed = false;
|
|
5
|
+
|
|
6
|
+
/* Add data to the tests list under a specific group and test description (usually the plugin name then the functionality description) */
|
|
7
|
+
function testData(group, test, data) {
|
|
8
|
+
let resultElem = document.getElementById("test-results");
|
|
9
|
+
let groupElem = resultElem.querySelector("#test-"+group);
|
|
10
|
+
if(groupElem == undefined) {
|
|
11
|
+
groupElem = document.createElement("span");
|
|
12
|
+
groupElem.innerHTML = `Group <b>${group}</b>:\n`
|
|
13
|
+
groupElem.id = "test-" + group;
|
|
14
|
+
resultElem.append(groupElem);
|
|
15
|
+
}
|
|
16
|
+
groupElem.innerHTML += `\t${test}: ${data}\n`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Add a test to the tests list, saying if it has passed (passed parameter), and if it has failed giving a message (messageIfFailed parameter) */
|
|
20
|
+
function testAssertion(group, test, passed, messageIfFailed) {
|
|
21
|
+
let resultElem = document.getElementById("test-results");
|
|
22
|
+
let groupElem = resultElem.querySelector("#test-"+group);
|
|
23
|
+
if(groupElem == undefined) {
|
|
24
|
+
groupElem = document.createElement("span");
|
|
25
|
+
groupElem.innerHTML = `Group <b>${group}</b>:\n`
|
|
26
|
+
groupElem.id = "test-" + group;
|
|
27
|
+
resultElem.append(groupElem);
|
|
28
|
+
}
|
|
29
|
+
groupElem.innerHTML += `\t${test}: ${passed ? '<b style="color: darkgreen;">passed</b>' : '<b style="color: red;">failed</b> ('+messageIfFailed+')' }\n`;
|
|
30
|
+
|
|
31
|
+
if(!passed) testsFailed = true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Run a test that passes if the givenOutput == correctOutput */
|
|
35
|
+
function assertEqual(group, test, givenOutput, correctOutput) {
|
|
36
|
+
let equal = givenOutput == correctOutput;
|
|
37
|
+
testAssertion(group, test, equal, "see console output");
|
|
38
|
+
if(!equal) {
|
|
39
|
+
console.error(group, test, givenOutput, "should be", correctOutput);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Test whether adding text to the textarea (with keyboard events emitted, therefore interacting with plugins) gives the correct output and selection start/end. */
|
|
44
|
+
function testAddingText(group, textarea, action, correctOutput, correctLengthToSelectionStart, correctLengthToSelectionEnd) {
|
|
45
|
+
let origSelectionStart = textarea.selectionStart;
|
|
46
|
+
let origValueBefore = textarea.value.substring(0, textarea.selectionStart);
|
|
47
|
+
let origValueAfter = textarea.value.substring(textarea.selectionEnd);
|
|
48
|
+
action(textarea);
|
|
49
|
+
|
|
50
|
+
let correctOutputValue = origValueBefore+correctOutput+origValueAfter;
|
|
51
|
+
assertEqual(group, "Text Output", textarea.value, correctOutputValue);
|
|
52
|
+
assertEqual(group, "Code-Input Value JS Property Output", textarea.parentElement.value, correctOutputValue);
|
|
53
|
+
assertEqual(group, "Selection Start", textarea.selectionStart, origSelectionStart+correctLengthToSelectionStart);
|
|
54
|
+
assertEqual(group, "Selection End", textarea.selectionEnd, origSelectionStart+correctLengthToSelectionEnd);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/* --- Test helper functions --- */
|
|
58
|
+
|
|
59
|
+
/* Assuming the textarea is focused, add the given text to it, emitting 'input' and 'beforeinput' keyboard events (and 'keydown'/'keyup' Enter on newlines, if enterEvents is true) which plugins can handle */
|
|
60
|
+
function addText(textarea, text, enterEvents=false) {
|
|
61
|
+
for(let i = 0; i < text.length; i++) {
|
|
62
|
+
if(enterEvents && text[i] == "\n") {
|
|
63
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
64
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
65
|
+
} else {
|
|
66
|
+
let beforeInputEvt = new InputEvent("beforeinput", { "cancelable": true, "data": text[i] });
|
|
67
|
+
textarea.dispatchEvent(beforeInputEvt);
|
|
68
|
+
if(!beforeInputEvt.defaultPrevented) {
|
|
69
|
+
textarea.dispatchEvent(new InputEvent("input", { "data": text[i] }));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Emit the necessary events to simulate a backspace keypress in the textarea. */
|
|
76
|
+
function backspace(textarea) {
|
|
77
|
+
let keydownEvt = new KeyboardEvent("keydown", { "cancelable": true, "key": "Backspace" });
|
|
78
|
+
textarea.dispatchEvent(keydownEvt);
|
|
79
|
+
let keyupEvt = new KeyboardEvent("keyup", { "cancelable": true, "key": "Backspace" });
|
|
80
|
+
textarea.dispatchEvent(keyupEvt);
|
|
81
|
+
if(!keydownEvt.defaultPrevented) {
|
|
82
|
+
if(textarea.selectionEnd == textarea.selectionStart) {
|
|
83
|
+
textarea.selectionEnd = textarea.selectionStart;
|
|
84
|
+
textarea.selectionStart--;
|
|
85
|
+
}
|
|
86
|
+
document.execCommand("delete", false, null);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Move the caret numMovesRight characters to the right, in the textarea. */
|
|
91
|
+
function move(textarea, numMovesRight) {
|
|
92
|
+
textarea.selectionStart += numMovesRight;
|
|
93
|
+
textarea.selectionEnd = textarea.selectionStart;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Wait in an asynchronous function for a specified number of milliseconds by using `await waitAsync(milliseconds)`. */
|
|
97
|
+
function waitAsync(milliseconds) {
|
|
98
|
+
return new Promise((resolve) => {
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
resolve();
|
|
101
|
+
}, milliseconds);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* --- Running the tests --- */
|
|
106
|
+
|
|
107
|
+
/* Start the test, for Prism.js if isHLJS is false, or for highlight.js if isHLJS is true. */
|
|
108
|
+
function beginTest(isHLJS) {
|
|
109
|
+
let codeInputElem = document.querySelector("code-input");
|
|
110
|
+
if(isHLJS) {
|
|
111
|
+
codeInput.registerTemplate("code-editor", codeInput.templates.hljs(hljs, [
|
|
112
|
+
new codeInput.plugins.AutoCloseBrackets(),
|
|
113
|
+
new codeInput.plugins.Autocomplete(function(popupElem, textarea, selectionEnd) {
|
|
114
|
+
if(textarea.value.substring(selectionEnd-5, selectionEnd) == "popup") {
|
|
115
|
+
// Show popup
|
|
116
|
+
popupElem.style.display = "block";
|
|
117
|
+
popupElem.innerHTML = "Here's your popup!";
|
|
118
|
+
} else {
|
|
119
|
+
popupElem.style.display = "none";
|
|
120
|
+
}
|
|
121
|
+
}),
|
|
122
|
+
new codeInput.plugins.Autodetect(),
|
|
123
|
+
new codeInput.plugins.FindAndReplace(),
|
|
124
|
+
new codeInput.plugins.GoToLine(),
|
|
125
|
+
new codeInput.plugins.Indent(true, 2),
|
|
126
|
+
new codeInput.plugins.SelectTokenCallbacks(codeInput.plugins.SelectTokenCallbacks.TokenSelectorCallbacks.createClassSynchronisation("in-selection"), false, true, true, true, true, false),
|
|
127
|
+
new codeInput.plugins.SpecialChars(true),
|
|
128
|
+
]));
|
|
129
|
+
} else {
|
|
130
|
+
codeInput.registerTemplate("code-editor", codeInput.templates.prism(Prism, [
|
|
131
|
+
new codeInput.plugins.AutoCloseBrackets(),
|
|
132
|
+
new codeInput.plugins.Autocomplete(function(popupElem, textarea, selectionEnd) {
|
|
133
|
+
if(textarea.value.substring(selectionEnd-5, selectionEnd) == "popup") {
|
|
134
|
+
// Show popup
|
|
135
|
+
popupElem.style.display = "block";
|
|
136
|
+
popupElem.innerHTML = "Here's your popup!";
|
|
137
|
+
} else {
|
|
138
|
+
popupElem.style.display = "none";
|
|
139
|
+
}
|
|
140
|
+
}),
|
|
141
|
+
new codeInput.plugins.FindAndReplace(),
|
|
142
|
+
new codeInput.plugins.GoToLine(),
|
|
143
|
+
new codeInput.plugins.Indent(true, 2),
|
|
144
|
+
new codeInput.plugins.SelectTokenCallbacks(new codeInput.plugins.SelectTokenCallbacks.TokenSelectorCallbacks(selectBrace, deselectAllBraces), true),
|
|
145
|
+
new codeInput.plugins.SpecialChars(true),
|
|
146
|
+
]));
|
|
147
|
+
}
|
|
148
|
+
startLoad(codeInputElem, isHLJS);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Start loading the tests, using the codeInput load time as one of the tests. */
|
|
152
|
+
function startLoad(codeInputElem, isHLJS) {
|
|
153
|
+
let textarea;
|
|
154
|
+
let timeToLoad = 0;
|
|
155
|
+
let interval = window.setInterval(() => {
|
|
156
|
+
textarea = codeInputElem.querySelector("textarea");
|
|
157
|
+
if(textarea != null) window.clearInterval(interval);
|
|
158
|
+
timeToLoad += 10;
|
|
159
|
+
testData("TimeTaken", "Textarea Appears", timeToLoad+"ms (nearest 10)");
|
|
160
|
+
startTests(textarea, isHLJS);
|
|
161
|
+
}, 10);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Make input events work and be trusted in the inputElement - thanks for this SO answer: https://stackoverflow.com/a/49519772/21785620 */
|
|
165
|
+
function allowInputEvents(inputElement) {
|
|
166
|
+
inputElement.addEventListener('input', function(e){
|
|
167
|
+
if(!e.isTrusted){
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
// Manually trigger
|
|
170
|
+
document.execCommand("insertText", false, e.data);
|
|
171
|
+
}
|
|
172
|
+
}, false);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* Start the tests using the textarea inside the code-input element and whether highlight.js is being used (as the Autodetect plugin only works with highlight.js, for example) */
|
|
176
|
+
async function startTests(textarea, isHLJS) {
|
|
177
|
+
textarea.focus();
|
|
178
|
+
allowInputEvents(textarea);
|
|
179
|
+
|
|
180
|
+
codeInputElement = textarea.parentElement;
|
|
181
|
+
|
|
182
|
+
/*--- Tests for core functionality ---*/
|
|
183
|
+
|
|
184
|
+
// Textarea's initial value should be correct.
|
|
185
|
+
assertEqual("Core", "Initial Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
186
|
+
// A second line
|
|
187
|
+
// A third line with <html> tags`);
|
|
188
|
+
// Code element's displayed value, ignoring appearance with HTML tags, should be the initial value but HTML-escaped
|
|
189
|
+
let renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
190
|
+
assertEqual("Core", "Initial Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
191
|
+
// A second line
|
|
192
|
+
// A third line with <html> tags
|
|
193
|
+
`); // Extra newline so line numbers visible if enabled
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
// Update code-input value with JavaScript, new value and num events should be correct.
|
|
197
|
+
codeInputElement.value += `
|
|
198
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`;
|
|
199
|
+
|
|
200
|
+
await waitAsync(50); // Wait for rendered value to update
|
|
201
|
+
|
|
202
|
+
// Textarea's value once updated with JavaScript should be correct.
|
|
203
|
+
assertEqual("Core", "JS-updated Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
204
|
+
// A second line
|
|
205
|
+
// A third line with <html> tags
|
|
206
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`);
|
|
207
|
+
// Code element's displayed value, ignoring appearance with HTML tags, should be the initial value but HTML-escaped
|
|
208
|
+
renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
209
|
+
assertEqual("Core", "JS-updated Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
210
|
+
// A second line
|
|
211
|
+
// A third line with <html> tags
|
|
212
|
+
console.log("I've got another line!", 2 < 3, "should be true.");
|
|
213
|
+
`); // Extra newline so line numbers visible if enabled
|
|
214
|
+
|
|
215
|
+
// Event Listener Tests
|
|
216
|
+
// Function type listeners
|
|
217
|
+
let numTimesInputCalled = 0;
|
|
218
|
+
let numTimesChangeCalled = 0;
|
|
219
|
+
|
|
220
|
+
let inputListener = (evt) => {
|
|
221
|
+
if(!evt.isTrusted) { // To prevent duplicate calling due to allowInputEvents hack
|
|
222
|
+
numTimesInputCalled++;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
codeInputElement.addEventListener("input", inputListener);
|
|
226
|
+
let changeListener = () => {
|
|
227
|
+
numTimesChangeCalled++;
|
|
228
|
+
};
|
|
229
|
+
codeInputElement.addEventListener("change", changeListener);
|
|
230
|
+
|
|
231
|
+
let inputDeletedListenerCalled = false;
|
|
232
|
+
let deletedListener = () => {
|
|
233
|
+
inputDeletedListenerCalled = true;
|
|
234
|
+
};
|
|
235
|
+
codeInputElement.addEventListener("input", deletedListener);
|
|
236
|
+
codeInputElement.removeEventListener("input", deletedListener);
|
|
237
|
+
|
|
238
|
+
// Make listeners be called
|
|
239
|
+
textarea.focus(); // Focus textarea
|
|
240
|
+
addText(textarea, " // Hi");
|
|
241
|
+
textarea.blur(); // Unfocus textarea - calls change event
|
|
242
|
+
textarea.focus();
|
|
243
|
+
|
|
244
|
+
assertEqual("Core", "Function Event Listeners: Input Called Right Number of Times", numTimesInputCalled, 6);
|
|
245
|
+
assertEqual("Core", "Function Event Listeners: Change Called Right Number of Times", numTimesChangeCalled, 1);
|
|
246
|
+
testAssertion("Core", "Function Event Listeners: Input Removed Listener Not Called", !inputDeletedListenerCalled, "(code-input element).removeEventListener did not work.");
|
|
247
|
+
|
|
248
|
+
codeInputElement.removeEventListener("input", inputListener);
|
|
249
|
+
codeInputElement.removeEventListener("change", changeListener);
|
|
250
|
+
|
|
251
|
+
// Repeat for Object type listeners
|
|
252
|
+
numTimesInputCalled = 0;
|
|
253
|
+
numTimesChangeCalled = 0;
|
|
254
|
+
codeInputElement.addEventListener("input", {handleEvent: (evt) => {
|
|
255
|
+
if(!evt.isTrusted) { // To prevent duplicate calling due to allowInputEvents hack
|
|
256
|
+
numTimesInputCalled++;
|
|
257
|
+
}
|
|
258
|
+
}});
|
|
259
|
+
codeInputElement.addEventListener("change", {handleEvent: () => {
|
|
260
|
+
numTimesChangeCalled++;
|
|
261
|
+
}});
|
|
262
|
+
|
|
263
|
+
inputDeletedListenerCalled = false;
|
|
264
|
+
deletedListener = {handleEvent: () => {
|
|
265
|
+
inputDeletedListenerCalled = true;
|
|
266
|
+
}};
|
|
267
|
+
codeInputElement.addEventListener("input", deletedListener);
|
|
268
|
+
codeInputElement.removeEventListener("input", deletedListener);
|
|
269
|
+
|
|
270
|
+
// Make listeners be called
|
|
271
|
+
textarea.focus(); // Focus textarea
|
|
272
|
+
addText(textarea, " // Hi");
|
|
273
|
+
textarea.blur(); // Unfocus textarea - calls change event
|
|
274
|
+
textarea.focus();
|
|
275
|
+
|
|
276
|
+
assertEqual("Core", "Object Event Listeners: Input Called Right Number of Times", numTimesInputCalled, 6);
|
|
277
|
+
assertEqual("Core", "Object Event Listeners: Change Called Right Number of Times", numTimesChangeCalled, 1);
|
|
278
|
+
testAssertion("Core", "Object Event Listeners: Input Removed Listener Not Called", !inputDeletedListenerCalled, "(code-input element).removeEventListener did not work.");
|
|
279
|
+
|
|
280
|
+
// Changing language should be correct
|
|
281
|
+
if(!isHLJS) {
|
|
282
|
+
// Highlight.js has autodetect plugin that should make this fail, so don't run these tests with it.
|
|
283
|
+
testAssertion("Core", "Language attribute Initial value",
|
|
284
|
+
!codeInputElement.codeElement.classList.contains("language-javascript")
|
|
285
|
+
&& !codeInputElement.codeElement.classList.contains("language-html"),
|
|
286
|
+
`Language unset but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
287
|
+
|
|
288
|
+
codeInputElement.setAttribute("language", "HTML");
|
|
289
|
+
|
|
290
|
+
await waitAsync(50); // Wait for attribute change to be handled
|
|
291
|
+
|
|
292
|
+
testAssertion("Core", "Language attribute Changed value 1",
|
|
293
|
+
codeInputElement.codeElement.classList.contains("language-html")
|
|
294
|
+
&& !codeInputElement.codeElement.classList.contains("language-javascript"),
|
|
295
|
+
`Language set to HTML but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
296
|
+
|
|
297
|
+
codeInputElement.setAttribute("language", "JavaScript");
|
|
298
|
+
|
|
299
|
+
await waitAsync(50); // Wait for attribute change to be handled
|
|
300
|
+
|
|
301
|
+
testAssertion("Core", "Language attribute Changed value 2",
|
|
302
|
+
codeInputElement.codeElement.classList.contains("language-javascript")
|
|
303
|
+
&& !codeInputElement.codeElement.classList.contains("language-html"),
|
|
304
|
+
`Language set to JavaScript but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let formElement = codeInputElement.parentElement;
|
|
308
|
+
formElement.reset();
|
|
309
|
+
|
|
310
|
+
await waitAsync(50); // Wait for rendered value to update
|
|
311
|
+
|
|
312
|
+
assertEqual("Core", "Form Reset resets Code-Input Value", codeInputElement.value, `console.log("Hello, World!");
|
|
313
|
+
// A second line
|
|
314
|
+
// A third line with <html> tags`);
|
|
315
|
+
assertEqual("Core", "Form Reset resets Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
316
|
+
// A second line
|
|
317
|
+
// A third line with <html> tags`);
|
|
318
|
+
renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
319
|
+
assertEqual("Core", "Form Reset resets Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
320
|
+
// A second line
|
|
321
|
+
// A third line with <html> tags
|
|
322
|
+
`); // Extra newline so line numbers visible if enabled.
|
|
323
|
+
|
|
324
|
+
/*--- Tests for plugins ---*/
|
|
325
|
+
// AutoCloseBrackets
|
|
326
|
+
testAddingText("AutoCloseBrackets", textarea, function(textarea) {
|
|
327
|
+
addText(textarea, `\nconsole.log("A test message`);
|
|
328
|
+
move(textarea, 2);
|
|
329
|
+
addText(textarea, `;\nconsole.log("Another test message");\n{[{[]}(([[`);
|
|
330
|
+
backspace(textarea);
|
|
331
|
+
backspace(textarea);
|
|
332
|
+
backspace(textarea);
|
|
333
|
+
addText(textarea, `)`);
|
|
334
|
+
}, '\nconsole.log("A test message");\nconsole.log("Another test message");\n{[{[]}()]}', 77, 77);
|
|
335
|
+
|
|
336
|
+
// Autocomplete
|
|
337
|
+
addText(textarea, "popup");
|
|
338
|
+
|
|
339
|
+
await waitAsync(50); // Wait for popup to be rendered
|
|
340
|
+
|
|
341
|
+
testAssertion("Autocomplete", "Popup Shows", confirm("Does the autocomplete popup display correctly? (OK=Yes)"), "user-judged");
|
|
342
|
+
backspace(textarea);
|
|
343
|
+
|
|
344
|
+
await waitAsync(50); // Wait for popup disappearance to be rendered
|
|
345
|
+
|
|
346
|
+
testAssertion("Autocomplete", "Popup Disappears", confirm("Has the popup disappeared? (OK=Yes)"), "user-judged");
|
|
347
|
+
backspace(textarea);
|
|
348
|
+
backspace(textarea);
|
|
349
|
+
backspace(textarea);
|
|
350
|
+
backspace(textarea);
|
|
351
|
+
|
|
352
|
+
// Autodetect - these tests have been made so the programming language is very obvious
|
|
353
|
+
// - the efficacy of autodetection is highlight.js' responsibility.
|
|
354
|
+
if(isHLJS) {
|
|
355
|
+
// Check detects XML - Replace all code with XML
|
|
356
|
+
textarea.selectionStart = 0;
|
|
357
|
+
textarea.selectionEnd = textarea.value.length;
|
|
358
|
+
backspace(textarea);
|
|
359
|
+
addText(textarea, 'console.log("Hello, World!");\nfunction sayHello(name) {\n console.log("Hello, " + name + "!");\n}\nsayHello("code-input");');
|
|
360
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
361
|
+
assertEqual("Autodetect", "Detects JavaScript", codeInputElement.getAttribute("language"), "javascript");
|
|
362
|
+
|
|
363
|
+
// Check detects Python - Replace all code with Python
|
|
364
|
+
textarea.selectionStart = 0;
|
|
365
|
+
textarea.selectionEnd = textarea.value.length;
|
|
366
|
+
backspace(textarea);
|
|
367
|
+
addText(textarea, '#!/usr/bin/python\nprint("Hello, World!")\nfor i in range(5):\n print(i)');
|
|
368
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
369
|
+
assertEqual("Autodetect", "Detects Python", codeInputElement.getAttribute("language"), "python");
|
|
370
|
+
|
|
371
|
+
// Check detects CSS - Replace all code with CSS
|
|
372
|
+
textarea.selectionStart = 0;
|
|
373
|
+
textarea.selectionEnd = textarea.value.length;
|
|
374
|
+
backspace(textarea);
|
|
375
|
+
addText(textarea, "body, html {\n height: 100%;\n background-color: blue;\n color: red;\n}");
|
|
376
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
377
|
+
assertEqual("Autodetect", "Detects CSS", codeInputElement.getAttribute("language"), "css");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// FindAndReplace
|
|
381
|
+
// Replace all code
|
|
382
|
+
textarea.selectionStart = 0;
|
|
383
|
+
textarea.selectionEnd = textarea.value.length;
|
|
384
|
+
backspace(textarea);
|
|
385
|
+
addText(textarea, "// hello /\\S/g\nhe('llo', /\\s/g);\nhello");
|
|
386
|
+
textarea.selectionStart = textarea.selectionEnd = 0; // So focuses on first match
|
|
387
|
+
|
|
388
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
389
|
+
|
|
390
|
+
// Open dialog and get interactive elements
|
|
391
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "f", "ctrlKey": true }));
|
|
392
|
+
let inputBoxes = codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog input");
|
|
393
|
+
let findInput = inputBoxes[0];
|
|
394
|
+
let regExpCheckbox = inputBoxes[1];
|
|
395
|
+
let caseSensitiveCheckbox = inputBoxes[2];
|
|
396
|
+
let replaceInput = inputBoxes[3];
|
|
397
|
+
|
|
398
|
+
let buttons = codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog button");
|
|
399
|
+
let nextMatchButton = buttons[0];
|
|
400
|
+
let previousMatchButton = buttons[1];
|
|
401
|
+
let replaceButton = buttons[2];
|
|
402
|
+
let replaceAllButton = buttons[3];
|
|
403
|
+
|
|
404
|
+
let replaceDropdown = codeInputElement.querySelector(".code-input_find-and-replace_dialog details summary");
|
|
405
|
+
|
|
406
|
+
// Run find/replace tests
|
|
407
|
+
findInput.value = "/\\s/g";
|
|
408
|
+
caseSensitiveCheckbox.click(); // Now case-sensitive
|
|
409
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
410
|
+
testAssertion("FindAndReplace", "Finds Case-Sensitive Matches Correctly", confirm("Is there a match on only the lowercase '/\\s/g'?"), "user-judged");
|
|
411
|
+
|
|
412
|
+
findInput.value = "he[^l]*llo";
|
|
413
|
+
regExpCheckbox.click(); // Now regex
|
|
414
|
+
caseSensitiveCheckbox.click(); // Now not case-sensitive
|
|
415
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
416
|
+
// Focuses on next match after /\s/g, therefore third he...llo
|
|
417
|
+
testAssertion("FindAndReplace", "Finds RegExp Matches Correctly", confirm("Are there matches on all 'he...llo's?"), "user-judged");
|
|
418
|
+
|
|
419
|
+
replaceDropdown.click();
|
|
420
|
+
previousMatchButton.click();
|
|
421
|
+
replaceInput.value = "do('hello";
|
|
422
|
+
replaceButton.click();
|
|
423
|
+
await waitAsync(50); // Wait for buttons to work
|
|
424
|
+
assertEqual("FindAndReplace", "Replaces Once Correctly", textarea.value, "// hello /\\S/g\ndo('hello', /\\s/g);\nhello");
|
|
425
|
+
nextMatchButton.click(); // Back to first match
|
|
426
|
+
|
|
427
|
+
// Exit find input box
|
|
428
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
429
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
430
|
+
|
|
431
|
+
// Check first hello now selected
|
|
432
|
+
assertEqual("FindAndReplace", "Selection Start on Focused Match when Dialog Exited", textarea.selectionStart, 3);
|
|
433
|
+
assertEqual("FindAndReplace", "Selection End on Focused Match when Dialog Exited", textarea.selectionEnd, 8);
|
|
434
|
+
|
|
435
|
+
// Open replace dialog; conduct a find and replace
|
|
436
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "h", "ctrlKey": true }));
|
|
437
|
+
findInput.value = "";
|
|
438
|
+
findInput.focus();
|
|
439
|
+
allowInputEvents(findInput);
|
|
440
|
+
addText(findInput, "hello");
|
|
441
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
442
|
+
|
|
443
|
+
replaceInput.value = "hi";
|
|
444
|
+
replaceAllButton.click();
|
|
445
|
+
assertEqual("FindAndReplace", "Replaces All Correctly", textarea.value, "// hi /\\S/g\ndo('hi', /\\s/g);\nhi");
|
|
446
|
+
|
|
447
|
+
// Exit find input box
|
|
448
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
449
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
450
|
+
|
|
451
|
+
// GoToLine
|
|
452
|
+
// Replace all code
|
|
453
|
+
textarea.selectionStart = 0;
|
|
454
|
+
textarea.selectionEnd = textarea.value.length;
|
|
455
|
+
backspace(textarea);
|
|
456
|
+
addText(textarea, "// 7 times table\nlet i = 1;\nwhile(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n// That's my code.\n// This is another comment\n// Another\n// Line");
|
|
457
|
+
|
|
458
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
459
|
+
let lineInput = codeInputElement.querySelector(".code-input_go-to-line_dialog input");
|
|
460
|
+
lineInput.value = "1";
|
|
461
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
462
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
463
|
+
assertEqual("GoToLine", "Line Only", textarea.selectionStart, 0);
|
|
464
|
+
|
|
465
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
466
|
+
lineInput.value = "3:18";
|
|
467
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
468
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
469
|
+
assertEqual("GoToLine", "Line and Column", textarea.selectionStart, 45);
|
|
470
|
+
|
|
471
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
472
|
+
lineInput.value = "10";
|
|
473
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
474
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
475
|
+
assertEqual("GoToLine", "Rejects Out-of-range Line", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
476
|
+
|
|
477
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
478
|
+
lineInput.value = "2:12";
|
|
479
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
480
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
481
|
+
assertEqual("GoToLine", "Rejects Out-of-range Column", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
482
|
+
|
|
483
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
484
|
+
lineInput.value = "sausages";
|
|
485
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
486
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
487
|
+
assertEqual("GoToLine", "Rejects Invalid Input", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
488
|
+
assertEqual("GoToLine", "Stays open when Rejects Input", lineInput.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"), false);
|
|
489
|
+
|
|
490
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
491
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
492
|
+
assertEqual("GoToLine", "Exits when Esc pressed", lineInput.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"), true);
|
|
493
|
+
|
|
494
|
+
// Indent
|
|
495
|
+
textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
|
|
496
|
+
addText(textarea, "\nfor(let i = 0; i < 100; i++) {\n for(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n }\n}\n{\n // This is indented\n}");
|
|
497
|
+
textarea.selectionStart = 0;
|
|
498
|
+
textarea.selectionEnd = textarea.value.length;
|
|
499
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": false }));
|
|
500
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": false }));
|
|
501
|
+
assertEqual("Indent", "Indents Lines", textarea.value, " // 7 times table\n let i = 1;\n while(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n // That's my code.\n // This is another comment\n // Another\n // Line\n for(let i = 0; i < 100; i++) {\n for(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n }\n }\n {\n // This is indented\n }");
|
|
502
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
503
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
504
|
+
assertEqual("Indent", "Unindents Lines", textarea.value, "// 7 times table\nlet i = 1;\nwhile(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n// That's my code.\n// This is another comment\n// Another\n// Line\nfor(let i = 0; i < 100; i++) {\n for(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n }\n}\n{\n // This is indented\n}");
|
|
505
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
506
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
507
|
+
assertEqual("Indent", "Unindents Lines where some are already fully unindented", textarea.value, "// 7 times table\nlet i = 1;\nwhile(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n// That's my code.\n// This is another comment\n// Another\n// Line\nfor(let i = 0; i < 100; i++) {\nfor(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n}\n}\n{\n// This is indented\n}");
|
|
508
|
+
|
|
509
|
+
textarea.selectionStart = 255;
|
|
510
|
+
textarea.selectionEnd = 274;
|
|
511
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": false }));
|
|
512
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": false }));
|
|
513
|
+
assertEqual("Indent", "Indents Lines by Selection", textarea.value, "// 7 times table\nlet i = 1;\nwhile(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n// That's my code.\n// This is another comment\n// Another\n// Line\nfor(let i = 0; i < 100; i++) {\nfor(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n}\n}\n{\n // This is indented\n}");
|
|
514
|
+
|
|
515
|
+
textarea.selectionStart = 265;
|
|
516
|
+
textarea.selectionEnd = 265;
|
|
517
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
518
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
519
|
+
assertEqual("Indent", "Unindents Lines by Selection", textarea.value, "// 7 times table\nlet i = 1;\nwhile(i <= 12) { console.log(`7 x ${i} = ${7*i}`) }\n// That's my code.\n// This is another comment\n// Another\n// Line\nfor(let i = 0; i < 100; i++) {\nfor(let j = i; j < 100; j++) {\n // Here's some code\n console.log(i,j);\n}\n}\n{\n// This is indented\n}");
|
|
520
|
+
|
|
521
|
+
// Indent+AutoCloseBrackets
|
|
522
|
+
// Clear all code
|
|
523
|
+
textarea.selectionStart = 0;
|
|
524
|
+
textarea.selectionEnd = textarea.value.length;
|
|
525
|
+
backspace(textarea);
|
|
526
|
+
|
|
527
|
+
testAddingText("Indent-AutoCloseBrackets", textarea, function(textarea) {
|
|
528
|
+
addText(textarea, `function printTriples(max) {\nfor(let i = 0; i < max-2; i++) {\nfor(let j = 0; j < max-1; j++) {\nfor(let k = 0; k < max; k++) {\nconsole.log(i,j,k);\n}\n//Hmmm...`, true);
|
|
529
|
+
}, 'function printTriples(max) {\n for(let i = 0; i < max-2; i++) {\n for(let j = 0; j < max-1; j++) {\n for(let k = 0; k < max; k++) {\n console.log(i,j,k);\n }\n //Hmmm...\n }\n }\n }\n}', 189, 189);
|
|
530
|
+
|
|
531
|
+
// SelectTokenCallbacks
|
|
532
|
+
if(isHLJS) {
|
|
533
|
+
addText(textarea, "\nlet x = 1;\nlet y = 2;\nconsole.log(`${x} + ${y} = ${x+y}`);");
|
|
534
|
+
move(textarea, -4); // Ends at |: "${x+y|}`);"
|
|
535
|
+
textarea.selectionStart -= 35; // Starts at |: "let y = |2;"
|
|
536
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
537
|
+
assertEqual("SelectTokenCallbacks", "Number of Selected Tokens", codeInputElement.querySelectorAll(".in-selection").length, 13);
|
|
538
|
+
assertEqual("SelectTokenCallbacks", "Number of Selected .hljs-string Tokens", codeInputElement.querySelectorAll(".hljs-string.in-selection").length, 0); // Since parentTokensAreSelected set to false
|
|
539
|
+
assertEqual("SelectTokenCallbacks", "Number of Selected .hljs-subst Tokens", codeInputElement.querySelectorAll(".hljs-subst.in-selection").length, 2);
|
|
540
|
+
} else {
|
|
541
|
+
// Combined with compatiblity-added match-braces plugin
|
|
542
|
+
addText(textarea, "\n[(),((),'Hi')]");
|
|
543
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
544
|
+
// Move back 2 characters so just after 'Hi'
|
|
545
|
+
move(textarea, -2);
|
|
546
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
547
|
+
assertEqual("SelectTokenCallbacks", "Number of Selected Braces 1", codeInputElement.getElementsByClassName("brace-hover").length, 2);
|
|
548
|
+
// Move forward 1 character so between )]
|
|
549
|
+
move(textarea, 1);
|
|
550
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
551
|
+
assertEqual("SelectTokenCallbacks", "Number of Selected Braces 2", codeInputElement.getElementsByClassName("brace-hover").length, 4);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// SpecialChars
|
|
555
|
+
// Clear all code
|
|
556
|
+
textarea.selectionStart = 0;
|
|
557
|
+
textarea.selectionEnd = textarea.value.length;
|
|
558
|
+
backspace(textarea);
|
|
559
|
+
|
|
560
|
+
addText(textarea, '"Some special characters: \u0096,\u0001\u0003,\u0002..."');
|
|
561
|
+
textarea.selectionStart = textarea.value.length-4;
|
|
562
|
+
textarea.selectionEnd = textarea.value.length;
|
|
563
|
+
|
|
564
|
+
await waitAsync(50); // Wait for special characters to be rendered
|
|
565
|
+
|
|
566
|
+
testAssertion("SpecialChars", "Displays Correctly", confirm("Do the special characters read (0096),(0001)(0003),(0002) and align with the ellipsis? (OK=Yes)"), "user-judged");
|
|
567
|
+
|
|
568
|
+
// Large amounts of code
|
|
569
|
+
// Clear all code
|
|
570
|
+
textarea.selectionStart = 0;
|
|
571
|
+
textarea.selectionEnd = textarea.value.length;
|
|
572
|
+
backspace(textarea);
|
|
573
|
+
fetch(new Request("https://cdn.jsdelivr.net/gh/webcoder49/code-input@2.1/code-input.js"))
|
|
574
|
+
.then((response) => response.text())
|
|
575
|
+
.then((code) => {
|
|
576
|
+
textarea.value = "// code-input v2.1: A large code file (not the latest version!)\n// Editing this here should give little latency.\n\n"+code;
|
|
577
|
+
|
|
578
|
+
textarea.selectionStart = 112;
|
|
579
|
+
textarea.selectionEnd = 112;
|
|
580
|
+
addText(textarea, "\n", true);
|
|
581
|
+
|
|
582
|
+
document.getElementById("collapse-results").setAttribute("open", true);
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
/* Make it clear if any tests have failed */
|
|
586
|
+
if(testsFailed) {
|
|
587
|
+
document.querySelector("h2").style.backgroundColor = "red";
|
|
588
|
+
document.querySelector("h2").textContent = "Some Tests have Failed.";
|
|
589
|
+
} else {
|
|
590
|
+
document.querySelector("h2").style.backgroundColor = "lightgreen";
|
|
591
|
+
document.querySelector("h2").textContent = "All Tests have Passed.";
|
|
592
|
+
}
|
|
593
|
+
}
|