@webcoder49/code-input 2.1.0 → 2.2.1
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 +17 -8
- package/code-input.css +47 -22
- package/code-input.d.ts +65 -4
- package/code-input.js +127 -176
- package/code-input.min.css +1 -1
- package/code-input.min.js +1 -1
- package/package.json +1 -1
- package/plugins/README.md +21 -6
- package/plugins/auto-close-brackets.js +61 -0
- package/plugins/auto-close-brackets.min.js +1 -0
- package/plugins/autocomplete.js +18 -11
- 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 +652 -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 +157 -0
- package/plugins/go-to-line.min.css +1 -0
- package/plugins/go-to-line.min.js +1 -0
- package/plugins/indent.js +71 -11
- package/plugins/indent.min.js +1 -1
- package/plugins/special-chars.css +0 -4
- package/plugins/special-chars.js +60 -58
- package/plugins/special-chars.min.css +1 -1
- 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 +54 -0
- package/tests/prism.html +55 -0
- package/tests/tester.js +529 -0
- package/tests/tester.min.js +18 -0
- package/plugins/debounce-update.js +0 -40
- package/plugins/debounce-update.min.js +0 -1
package/tests/tester.js
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
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.SpecialChars(true),
|
|
127
|
+
]));
|
|
128
|
+
} else {
|
|
129
|
+
codeInput.registerTemplate("code-editor", codeInput.templates.prism(Prism, [
|
|
130
|
+
new codeInput.plugins.AutoCloseBrackets(),
|
|
131
|
+
new codeInput.plugins.Autocomplete(function(popupElem, textarea, selectionEnd) {
|
|
132
|
+
if(textarea.value.substring(selectionEnd-5, selectionEnd) == "popup") {
|
|
133
|
+
// Show popup
|
|
134
|
+
popupElem.style.display = "block";
|
|
135
|
+
popupElem.innerHTML = "Here's your popup!";
|
|
136
|
+
} else {
|
|
137
|
+
popupElem.style.display = "none";
|
|
138
|
+
}
|
|
139
|
+
}),
|
|
140
|
+
new codeInput.plugins.FindAndReplace(),
|
|
141
|
+
new codeInput.plugins.GoToLine(),
|
|
142
|
+
new codeInput.plugins.Indent(true, 2),
|
|
143
|
+
new codeInput.plugins.SpecialChars(true),
|
|
144
|
+
]));
|
|
145
|
+
}
|
|
146
|
+
startLoad(codeInputElem, isHLJS);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Start loading the tests, using the codeInput load time as one of the tests. */
|
|
150
|
+
function startLoad(codeInputElem, isHLJS) {
|
|
151
|
+
let textarea;
|
|
152
|
+
let timeToLoad = 0;
|
|
153
|
+
let interval = window.setInterval(() => {
|
|
154
|
+
textarea = codeInputElem.querySelector("textarea");
|
|
155
|
+
if(textarea != null) window.clearInterval(interval);
|
|
156
|
+
timeToLoad += 10;
|
|
157
|
+
testData("TimeTaken", "Textarea Appears", timeToLoad+"ms (nearest 10)");
|
|
158
|
+
startTests(textarea, isHLJS);
|
|
159
|
+
}, 10);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Make input events work and be trusted in the inputElement - thanks for this SO answer: https://stackoverflow.com/a/49519772/21785620 */
|
|
163
|
+
function allowInputEvents(inputElement) {
|
|
164
|
+
inputElement.addEventListener('input', function(e){
|
|
165
|
+
if(!e.isTrusted){
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
// Manually trigger
|
|
168
|
+
document.execCommand("insertText", false, e.data);
|
|
169
|
+
}
|
|
170
|
+
}, false);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* 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) */
|
|
174
|
+
async function startTests(textarea, isHLJS) {
|
|
175
|
+
textarea.focus();
|
|
176
|
+
allowInputEvents(textarea);
|
|
177
|
+
|
|
178
|
+
codeInputElement = textarea.parentElement;
|
|
179
|
+
|
|
180
|
+
/*--- Tests for core functionality ---*/
|
|
181
|
+
|
|
182
|
+
// Textarea's initial value should be correct.
|
|
183
|
+
assertEqual("Core", "Initial Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
184
|
+
// A second line
|
|
185
|
+
// A third line with <html> tags`);
|
|
186
|
+
// Code element's displayed value, ignoring appearance with HTML tags, should be the initial value but HTML-escaped
|
|
187
|
+
let renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
188
|
+
assertEqual("Core", "Initial Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
189
|
+
// A second line
|
|
190
|
+
// A third line with <html> tags`);
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
// Update code-input value with JavaScript, new value and num events should be correct.
|
|
194
|
+
codeInputElement.value += `
|
|
195
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`;
|
|
196
|
+
|
|
197
|
+
await waitAsync(50); // Wait for rendered value to update
|
|
198
|
+
|
|
199
|
+
// Textarea's value once updated with JavaScript should be correct.
|
|
200
|
+
assertEqual("Core", "JS-updated Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
201
|
+
// A second line
|
|
202
|
+
// A third line with <html> tags
|
|
203
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`);
|
|
204
|
+
// Code element's displayed value, ignoring appearance with HTML tags, should be the initial value but HTML-escaped
|
|
205
|
+
renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
206
|
+
assertEqual("Core", "JS-updated Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
207
|
+
// A second line
|
|
208
|
+
// A third line with <html> tags
|
|
209
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`);
|
|
210
|
+
|
|
211
|
+
// Event Tests
|
|
212
|
+
let numTimesInputCalled = 0;
|
|
213
|
+
let numTimesChangeCalled = 0;
|
|
214
|
+
codeInputElement.addEventListener("input", (evt) => {
|
|
215
|
+
if(!evt.isTrusted) { // To prevent duplicate calling due to allowInputEvents hack
|
|
216
|
+
numTimesInputCalled++;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
codeInputElement.addEventListener("change", () => {
|
|
220
|
+
numTimesChangeCalled++;
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let inputDeletedListenerCalled = false;
|
|
224
|
+
let deletedListener = () => {
|
|
225
|
+
inputDeletedListenerCalled = true;
|
|
226
|
+
};
|
|
227
|
+
codeInputElement.addEventListener("input", deletedListener);
|
|
228
|
+
codeInputElement.removeEventListener("input", deletedListener);
|
|
229
|
+
|
|
230
|
+
// Make listeners be called
|
|
231
|
+
textarea.focus(); // Focus textarea
|
|
232
|
+
addText(textarea, " // Hi");
|
|
233
|
+
textarea.blur(); // Unfocus textarea - calls change event
|
|
234
|
+
textarea.focus();
|
|
235
|
+
|
|
236
|
+
assertEqual("Core", "Input Event Listener Called Right Number of Times", numTimesInputCalled, 6);
|
|
237
|
+
assertEqual("Core", "Change Event Listener Called Right Number of Times", numTimesChangeCalled, 1);
|
|
238
|
+
testAssertion("Core", "Input Event Removed Listener Not Called", !inputDeletedListenerCalled, "(code-input element).removeEventListener did not work.");
|
|
239
|
+
|
|
240
|
+
// Changing language should be correct
|
|
241
|
+
if(!isHLJS) {
|
|
242
|
+
// Highlight.js has autodetect plugin that should make this fail, so don't run these tests with it.
|
|
243
|
+
testAssertion("Core", "Language attribute Initial value",
|
|
244
|
+
codeInputElement.codeElement.classList.contains("language-javascript")
|
|
245
|
+
&& !codeInputElement.codeElement.classList.contains("language-html"),
|
|
246
|
+
`Language set to JavaScript but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
247
|
+
|
|
248
|
+
codeInputElement.setAttribute("language", "HTML");
|
|
249
|
+
|
|
250
|
+
await waitAsync(50); // Wait for attribute change to be handled
|
|
251
|
+
|
|
252
|
+
testAssertion("Core", "Language attribute Changed value 1",
|
|
253
|
+
codeInputElement.codeElement.classList.contains("language-html")
|
|
254
|
+
&& !codeInputElement.codeElement.classList.contains("language-javascript"),
|
|
255
|
+
`Language set to HTML but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
256
|
+
|
|
257
|
+
codeInputElement.setAttribute("language", "JavaScript");
|
|
258
|
+
|
|
259
|
+
await waitAsync(50); // Wait for attribute change to be handled
|
|
260
|
+
|
|
261
|
+
testAssertion("Core", "Language attribute Changed value 2",
|
|
262
|
+
codeInputElement.codeElement.classList.contains("language-javascript")
|
|
263
|
+
&& !codeInputElement.codeElement.classList.contains("language-html"),
|
|
264
|
+
`Language set to JavaScript but code element's class name is ${codeInputElement.codeElement.className}.`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let formElement = codeInputElement.parentElement;
|
|
268
|
+
formElement.reset();
|
|
269
|
+
|
|
270
|
+
await waitAsync(50); // Wait for rendered value to update
|
|
271
|
+
|
|
272
|
+
assertEqual("Core", "Form Reset resets Code-Input Value", codeInputElement.value, `console.log("Hello, World!");
|
|
273
|
+
// A second line
|
|
274
|
+
// A third line with <html> tags`);
|
|
275
|
+
assertEqual("Core", "Form Reset resets Textarea Value", textarea.value, `console.log("Hello, World!");
|
|
276
|
+
// A second line
|
|
277
|
+
// A third line with <html> tags`);
|
|
278
|
+
renderedValue = codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g, "");
|
|
279
|
+
assertEqual("Core", "Form Reset resets Rendered Value", renderedValue, `console.log("Hello, World!");
|
|
280
|
+
// A second line
|
|
281
|
+
// A third line with <html> tags`);
|
|
282
|
+
|
|
283
|
+
/*--- Tests for plugins ---*/
|
|
284
|
+
// AutoCloseBrackets
|
|
285
|
+
testAddingText("AutoCloseBrackets", textarea, function(textarea) {
|
|
286
|
+
addText(textarea, `\nconsole.log("A test message`);
|
|
287
|
+
move(textarea, 2);
|
|
288
|
+
addText(textarea, `;\nconsole.log("Another test message");\n{[{[]}(([[`);
|
|
289
|
+
backspace(textarea);
|
|
290
|
+
backspace(textarea);
|
|
291
|
+
backspace(textarea);
|
|
292
|
+
addText(textarea, `)`);
|
|
293
|
+
}, '\nconsole.log("A test message");\nconsole.log("Another test message");\n{[{[]}()]}', 77, 77);
|
|
294
|
+
|
|
295
|
+
// Autocomplete
|
|
296
|
+
addText(textarea, "popup");
|
|
297
|
+
|
|
298
|
+
await waitAsync(50); // Wait for popup to be rendered
|
|
299
|
+
|
|
300
|
+
testAssertion("Autocomplete", "Popup Shows", confirm("Does the autocomplete popup display correctly? (OK=Yes)"), "user-judged");
|
|
301
|
+
backspace(textarea);
|
|
302
|
+
|
|
303
|
+
await waitAsync(50); // Wait for popup disappearance to be rendered
|
|
304
|
+
|
|
305
|
+
testAssertion("Autocomplete", "Popup Disappears", confirm("Has the popup disappeared? (OK=Yes)"), "user-judged");
|
|
306
|
+
backspace(textarea);
|
|
307
|
+
backspace(textarea);
|
|
308
|
+
backspace(textarea);
|
|
309
|
+
backspace(textarea);
|
|
310
|
+
|
|
311
|
+
// Autodetect - these tests have been made so the programming language is very obvious
|
|
312
|
+
// - the efficacy of autodetection is highlight.js' responsibility.
|
|
313
|
+
if(isHLJS) {
|
|
314
|
+
// Check detects XML - Replace all code with XML
|
|
315
|
+
textarea.selectionStart = 0;
|
|
316
|
+
textarea.selectionEnd = textarea.value.length;
|
|
317
|
+
backspace(textarea);
|
|
318
|
+
addText(textarea, 'console.log("Hello, World!");\nfunction sayHello(name) {\n console.log("Hello, " + name + "!");\n}\nsayHello("code-input");');
|
|
319
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
320
|
+
assertEqual("Autodetect", "Detects JavaScript", codeInputElement.getAttribute("language"), "javascript");
|
|
321
|
+
|
|
322
|
+
// Check detects Python - Replace all code with Python
|
|
323
|
+
textarea.selectionStart = 0;
|
|
324
|
+
textarea.selectionEnd = textarea.value.length;
|
|
325
|
+
backspace(textarea);
|
|
326
|
+
addText(textarea, '#!/usr/bin/python\nprint("Hello, World!")\nfor i in range(5):\n print(i)');
|
|
327
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
328
|
+
assertEqual("Autodetect", "Detects Python", codeInputElement.getAttribute("language"), "python");
|
|
329
|
+
|
|
330
|
+
// Check detects CSS - Replace all code with CSS
|
|
331
|
+
textarea.selectionStart = 0;
|
|
332
|
+
textarea.selectionEnd = textarea.value.length;
|
|
333
|
+
backspace(textarea);
|
|
334
|
+
addText(textarea, "body, html {\n height: 100%;\n background-color: blue;\n color: red;\n}");
|
|
335
|
+
await waitAsync(50); // Wait for highlighting so language attribute updates
|
|
336
|
+
assertEqual("Autodetect", "Detects CSS", codeInputElement.getAttribute("language"), "css");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// FindAndReplace
|
|
340
|
+
// Replace all code
|
|
341
|
+
textarea.selectionStart = 0;
|
|
342
|
+
textarea.selectionEnd = textarea.value.length;
|
|
343
|
+
backspace(textarea);
|
|
344
|
+
addText(textarea, "// hello /\\S/g\nhe('llo', /\\s/g);\nhello");
|
|
345
|
+
textarea.selectionStart = textarea.selectionEnd = 0; // So focuses on first match
|
|
346
|
+
|
|
347
|
+
await waitAsync(50); // Wait for highlighting so text updates
|
|
348
|
+
|
|
349
|
+
// Open dialog and get interactive elements
|
|
350
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "f", "ctrlKey": true }));
|
|
351
|
+
let inputBoxes = codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog input");
|
|
352
|
+
let findInput = inputBoxes[0];
|
|
353
|
+
let regExpCheckbox = inputBoxes[1];
|
|
354
|
+
let caseSensitiveCheckbox = inputBoxes[2];
|
|
355
|
+
let replaceInput = inputBoxes[3];
|
|
356
|
+
|
|
357
|
+
let buttons = codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog button");
|
|
358
|
+
let nextMatchButton = buttons[0];
|
|
359
|
+
let previousMatchButton = buttons[1];
|
|
360
|
+
let replaceButton = buttons[2];
|
|
361
|
+
let replaceAllButton = buttons[3];
|
|
362
|
+
|
|
363
|
+
let replaceDropdown = codeInputElement.querySelector(".code-input_find-and-replace_dialog details summary");
|
|
364
|
+
|
|
365
|
+
// Run find/replace tests
|
|
366
|
+
findInput.value = "/\\s/g";
|
|
367
|
+
caseSensitiveCheckbox.click(); // Now case-sensitive
|
|
368
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
369
|
+
testAssertion("FindAndReplace", "Finds Case-Sensitive Matches Correctly", confirm("Is there a match on only the lowercase '/\\s/g'?"), "user-judged");
|
|
370
|
+
|
|
371
|
+
findInput.value = "he[^l]*llo";
|
|
372
|
+
regExpCheckbox.click(); // Now regex
|
|
373
|
+
caseSensitiveCheckbox.click(); // Now not case-sensitive
|
|
374
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
375
|
+
// Focuses on next match after /\s/g, therefore third he...llo
|
|
376
|
+
testAssertion("FindAndReplace", "Finds RegExp Matches Correctly", confirm("Are there matches on all 'he...llo's?"), "user-judged");
|
|
377
|
+
|
|
378
|
+
replaceDropdown.click();
|
|
379
|
+
previousMatchButton.click();
|
|
380
|
+
replaceInput.value = "do('hello";
|
|
381
|
+
replaceButton.click();
|
|
382
|
+
await waitAsync(50); // Wait for buttons to work
|
|
383
|
+
assertEqual("FindAndReplace", "Replaces Once Correctly", textarea.value, "// hello /\\S/g\ndo('hello', /\\s/g);\nhello");
|
|
384
|
+
nextMatchButton.click(); // Back to first match
|
|
385
|
+
|
|
386
|
+
// Exit find input box
|
|
387
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
388
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
389
|
+
|
|
390
|
+
// Check first hello now selected
|
|
391
|
+
assertEqual("FindAndReplace", "Selection Start on Focused Match when Dialog Exited", textarea.selectionStart, 3);
|
|
392
|
+
assertEqual("FindAndReplace", "Selection End on Focused Match when Dialog Exited", textarea.selectionEnd, 8);
|
|
393
|
+
|
|
394
|
+
// Open replace dialog; conduct a find and replace
|
|
395
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "h", "ctrlKey": true }));
|
|
396
|
+
findInput.value = "";
|
|
397
|
+
findInput.focus();
|
|
398
|
+
allowInputEvents(findInput);
|
|
399
|
+
addText(findInput, "hello");
|
|
400
|
+
await waitAsync(150); // Wait for highlighting so matches update
|
|
401
|
+
|
|
402
|
+
replaceInput.value = "hi";
|
|
403
|
+
replaceAllButton.click();
|
|
404
|
+
assertEqual("FindAndReplace", "Replaces All Correctly", textarea.value, "// hi /\\S/g\ndo('hi', /\\s/g);\nhi");
|
|
405
|
+
|
|
406
|
+
// Exit find input box
|
|
407
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
408
|
+
codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
409
|
+
|
|
410
|
+
// GoToLine
|
|
411
|
+
// Replace all code
|
|
412
|
+
textarea.selectionStart = 0;
|
|
413
|
+
textarea.selectionEnd = textarea.value.length;
|
|
414
|
+
backspace(textarea);
|
|
415
|
+
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");
|
|
416
|
+
|
|
417
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
418
|
+
let lineInput = codeInputElement.querySelector(".code-input_go-to-line_dialog input");
|
|
419
|
+
lineInput.value = "1";
|
|
420
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
421
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
422
|
+
assertEqual("GoToLine", "Line Only", textarea.selectionStart, 0);
|
|
423
|
+
|
|
424
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
425
|
+
lineInput.value = "3:18";
|
|
426
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
427
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
428
|
+
assertEqual("GoToLine", "Line and Column", textarea.selectionStart, 45);
|
|
429
|
+
|
|
430
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
431
|
+
lineInput.value = "10";
|
|
432
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
433
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
434
|
+
assertEqual("GoToLine", "Rejects Out-of-range Line", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
435
|
+
|
|
436
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
437
|
+
lineInput.value = "2:12";
|
|
438
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
439
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
440
|
+
assertEqual("GoToLine", "Rejects Out-of-range Column", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
441
|
+
|
|
442
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "cancelable": true, "key": "g", "ctrlKey": true }));
|
|
443
|
+
lineInput.value = "sausages";
|
|
444
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Enter" }));
|
|
445
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Enter" }));
|
|
446
|
+
assertEqual("GoToLine", "Rejects Invalid Input", lineInput.classList.contains("code-input_go-to-line_error"), true);
|
|
447
|
+
assertEqual("GoToLine", "Stays open when Rejects Input", lineInput.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"), false);
|
|
448
|
+
|
|
449
|
+
lineInput.dispatchEvent(new KeyboardEvent("keydown", { "key": "Escape" }));
|
|
450
|
+
lineInput.dispatchEvent(new KeyboardEvent("keyup", { "key": "Escape" }));
|
|
451
|
+
assertEqual("GoToLine", "Exits when Esc pressed", lineInput.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"), true);
|
|
452
|
+
|
|
453
|
+
// Indent
|
|
454
|
+
textarea.selectionStart = textarea.selectionEnd = textarea.value.length;
|
|
455
|
+
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}");
|
|
456
|
+
textarea.selectionStart = 0;
|
|
457
|
+
textarea.selectionEnd = textarea.value.length;
|
|
458
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": false }));
|
|
459
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": false }));
|
|
460
|
+
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 }");
|
|
461
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
462
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
463
|
+
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}");
|
|
464
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
465
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
466
|
+
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}");
|
|
467
|
+
|
|
468
|
+
textarea.selectionStart = 255;
|
|
469
|
+
textarea.selectionEnd = 274;
|
|
470
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": false }));
|
|
471
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": false }));
|
|
472
|
+
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}");
|
|
473
|
+
|
|
474
|
+
textarea.selectionStart = 265;
|
|
475
|
+
textarea.selectionEnd = 265;
|
|
476
|
+
textarea.dispatchEvent(new KeyboardEvent("keydown", { "key": "Tab", "shiftKey": true }));
|
|
477
|
+
textarea.dispatchEvent(new KeyboardEvent("keyup", { "key": "Tab", "shiftKey": true }));
|
|
478
|
+
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}");
|
|
479
|
+
|
|
480
|
+
// Indent+AutoCloseBrackets
|
|
481
|
+
// Clear all code
|
|
482
|
+
textarea.selectionStart = 0;
|
|
483
|
+
textarea.selectionEnd = textarea.value.length;
|
|
484
|
+
backspace(textarea);
|
|
485
|
+
|
|
486
|
+
testAddingText("Indent-AutoCloseBrackets", textarea, function(textarea) {
|
|
487
|
+
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);
|
|
488
|
+
}, '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);
|
|
489
|
+
|
|
490
|
+
// SpecialChars
|
|
491
|
+
// Clear all code
|
|
492
|
+
textarea.selectionStart = 0;
|
|
493
|
+
textarea.selectionEnd = textarea.value.length;
|
|
494
|
+
backspace(textarea);
|
|
495
|
+
|
|
496
|
+
addText(textarea, '"Some special characters: \u0096,\u0001\u0003,\u0002..."');
|
|
497
|
+
textarea.selectionStart = textarea.value.length-4;
|
|
498
|
+
textarea.selectionEnd = textarea.value.length;
|
|
499
|
+
|
|
500
|
+
await waitAsync(50); // Wait for special characters to be rendered
|
|
501
|
+
|
|
502
|
+
testAssertion("SpecialChars", "Displays Correctly", confirm("Do the special characters read (0096),(0001)(0003),(0002) and align with the ellipsis? (OK=Yes)"), "user-judged");
|
|
503
|
+
|
|
504
|
+
// Large amounts of code
|
|
505
|
+
// Clear all code
|
|
506
|
+
textarea.selectionStart = 0;
|
|
507
|
+
textarea.selectionEnd = textarea.value.length;
|
|
508
|
+
backspace(textarea);
|
|
509
|
+
fetch(new Request("https://cdn.jsdelivr.net/gh/webcoder49/code-input@2.1/code-input.js"))
|
|
510
|
+
.then((response) => response.text())
|
|
511
|
+
.then((code) => {
|
|
512
|
+
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;
|
|
513
|
+
|
|
514
|
+
textarea.selectionStart = 112;
|
|
515
|
+
textarea.selectionEnd = 112;
|
|
516
|
+
addText(textarea, "\n", true);
|
|
517
|
+
|
|
518
|
+
document.getElementById("collapse-results").setAttribute("open", true);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
/* Make it clear if any tests have failed */
|
|
522
|
+
if(testsFailed) {
|
|
523
|
+
document.querySelector("h2").style.backgroundColor = "red";
|
|
524
|
+
document.querySelector("h2").textContent = "Some Tests have Failed.";
|
|
525
|
+
} else {
|
|
526
|
+
document.querySelector("h2").style.backgroundColor = "lightgreen";
|
|
527
|
+
document.querySelector("h2").textContent = "All Tests have Passed.";
|
|
528
|
+
}
|
|
529
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
var testsFailed=!1;function testData(a,b,c){let d=document.getElementById("test-results"),e=d.querySelector("#test-"+a);e==null&&(e=document.createElement("span"),e.innerHTML=`Group <b>${a}</b>:\n`,e.id="test-"+a,d.append(e)),e.innerHTML+=`\t${b}: ${c}\n`}function testAssertion(a,b,c,d){let e=document.getElementById("test-results"),f=e.querySelector("#test-"+a);f==null&&(f=document.createElement("span"),f.innerHTML=`Group <b>${a}</b>:\n`,f.id="test-"+a,e.append(f)),f.innerHTML+=`\t${b}: ${c?"<b style=\"color: darkgreen;\">passed</b>":"<b style=\"color: red;\">failed</b> ("+d+")"}\n`,c||(testsFailed=!0)}function assertEqual(a,b,c,d){let e=c==d;testAssertion(a,b,e,"see console output"),e||console.error(a,b,c,"should be",d)}function testAddingText(a,b,c,d,e,f){let g=b.selectionStart,h=b.value.substring(0,b.selectionStart),i=b.value.substring(b.selectionEnd);c(b);let j=h+d+i;assertEqual(a,"Text Output",b.value,j),assertEqual(a,"Code-Input Value JS Property Output",b.parentElement.value,j),assertEqual(a,"Selection Start",b.selectionStart,g+e),assertEqual(a,"Selection End",b.selectionEnd,g+f)}function addText(a,b,c=!1){for(let d=0;d<b.length;d++)if(c&&"\n"==b[d])a.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"}));else{let c=new InputEvent("beforeinput",{cancelable:!0,data:b[d]});a.dispatchEvent(c),c.defaultPrevented||a.dispatchEvent(new InputEvent("input",{data:b[d]}))}}function backspace(a){let b=new KeyboardEvent("keydown",{cancelable:!0,key:"Backspace"});a.dispatchEvent(b);let c=new KeyboardEvent("keyup",{cancelable:!0,key:"Backspace"});a.dispatchEvent(c),b.defaultPrevented||(a.selectionEnd==a.selectionStart&&(a.selectionEnd=a.selectionStart,a.selectionStart--),document.execCommand("delete",!1,null))}function move(a,b){a.selectionStart+=b,a.selectionEnd=a.selectionStart}function waitAsync(a){return new Promise(b=>{setTimeout(()=>{b()},a)})}function beginTest(a){let b=document.querySelector("code-input");a?codeInput.registerTemplate("code-editor",codeInput.templates.hljs(hljs,[new codeInput.plugins.AutoCloseBrackets,new codeInput.plugins.Autocomplete(function(a,b,c){"popup"==b.value.substring(c-5,c)?(a.style.display="block",a.innerHTML="Here's your popup!"):a.style.display="none"}),new codeInput.plugins.Autodetect,new codeInput.plugins.FindAndReplace,new codeInput.plugins.GoToLine,new codeInput.plugins.Indent(!0,2),new codeInput.plugins.SpecialChars(!0)])):codeInput.registerTemplate("code-editor",codeInput.templates.prism(Prism,[new codeInput.plugins.AutoCloseBrackets,new codeInput.plugins.Autocomplete(function(a,b,c){"popup"==b.value.substring(c-5,c)?(a.style.display="block",a.innerHTML="Here's your popup!"):a.style.display="none"}),new codeInput.plugins.FindAndReplace,new codeInput.plugins.GoToLine,new codeInput.plugins.Indent(!0,2),new codeInput.plugins.SpecialChars(!0)])),startLoad(b,a)}function startLoad(a,b){let c,d=0,e=window.setInterval(()=>{c=a.querySelector("textarea"),null!=c&&window.clearInterval(e),d+=10,testData("TimeTaken","Textarea Appears",d+"ms (nearest 10)"),startTests(c,b)},10)}function allowInputEvents(a){a.addEventListener("input",function(a){a.isTrusted||(a.preventDefault(),document.execCommand("insertText",!1,a.data))},!1)}async function startTests(a,b){a.focus(),allowInputEvents(a),codeInputElement=a.parentElement,assertEqual("Core","Initial Textarea Value",a.value,`console.log("Hello, World!");
|
|
2
|
+
// A second line
|
|
3
|
+
// A third line with <html> tags`);let c=codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g,"");assertEqual("Core","Initial Rendered Value",c,`console.log("Hello, World!");
|
|
4
|
+
// A second line
|
|
5
|
+
// A third line with <html> tags`),codeInputElement.value+=`
|
|
6
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`,await waitAsync(50),assertEqual("Core","JS-updated Textarea Value",a.value,`console.log("Hello, World!");
|
|
7
|
+
// A second line
|
|
8
|
+
// A third line with <html> tags
|
|
9
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`),c=codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g,""),assertEqual("Core","JS-updated Rendered Value",c,`console.log("Hello, World!");
|
|
10
|
+
// A second line
|
|
11
|
+
// A third line with <html> tags
|
|
12
|
+
console.log("I've got another line!", 2 < 3, "should be true.");`);let d=0,e=0;codeInputElement.addEventListener("input",a=>{a.isTrusted||d++}),codeInputElement.addEventListener("change",()=>{e++});let f=!1,g=()=>{f=!0};codeInputElement.addEventListener("input",g),codeInputElement.removeEventListener("input",g),a.focus(),addText(a," // Hi"),a.blur(),a.focus(),assertEqual("Core","Input Event Listener Called Right Number of Times",d,6),assertEqual("Core","Change Event Listener Called Right Number of Times",e,1),testAssertion("Core","Input Event Removed Listener Not Called",!f,"(code-input element).removeEventListener did not work."),b||(testAssertion("Core","Language attribute Initial value",codeInputElement.codeElement.classList.contains("language-javascript")&&!codeInputElement.codeElement.classList.contains("language-html"),`Language set to JavaScript but code element's class name is ${codeInputElement.codeElement.className}.`),codeInputElement.setAttribute("language","HTML"),await waitAsync(50),testAssertion("Core","Language attribute Changed value 1",codeInputElement.codeElement.classList.contains("language-html")&&!codeInputElement.codeElement.classList.contains("language-javascript"),`Language set to HTML but code element's class name is ${codeInputElement.codeElement.className}.`),codeInputElement.setAttribute("language","JavaScript"),await waitAsync(50),testAssertion("Core","Language attribute Changed value 2",codeInputElement.codeElement.classList.contains("language-javascript")&&!codeInputElement.codeElement.classList.contains("language-html"),`Language set to JavaScript but code element's class name is ${codeInputElement.codeElement.className}.`));let h=codeInputElement.parentElement;h.reset(),await waitAsync(50),assertEqual("Core","Form Reset resets Code-Input Value",codeInputElement.value,`console.log("Hello, World!");
|
|
13
|
+
// A second line
|
|
14
|
+
// A third line with <html> tags`),assertEqual("Core","Form Reset resets Textarea Value",a.value,`console.log("Hello, World!");
|
|
15
|
+
// A second line
|
|
16
|
+
// A third line with <html> tags`),c=codeInputElement.codeElement.innerHTML.replace(/<[^>]+>/g,""),assertEqual("Core","Form Reset resets Rendered Value",c,`console.log("Hello, World!");
|
|
17
|
+
// A second line
|
|
18
|
+
// A third line with <html> tags`),testAddingText("AutoCloseBrackets",a,function(a){addText(a,`\nconsole.log("A test message`),move(a,2),addText(a,`;\nconsole.log("Another test message");\n{[{[]}(([[`),backspace(a),backspace(a),backspace(a),addText(a,`)`)},"\nconsole.log(\"A test message\");\nconsole.log(\"Another test message\");\n{[{[]}()]}",77,77),addText(a,"popup"),await waitAsync(50),testAssertion("Autocomplete","Popup Shows",confirm("Does the autocomplete popup display correctly? (OK=Yes)"),"user-judged"),backspace(a),await waitAsync(50),testAssertion("Autocomplete","Popup Disappears",confirm("Has the popup disappeared? (OK=Yes)"),"user-judged"),backspace(a),backspace(a),backspace(a),backspace(a),b&&(a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"console.log(\"Hello, World!\");\nfunction sayHello(name) {\n console.log(\"Hello, \" + name + \"!\");\n}\nsayHello(\"code-input\");"),await waitAsync(50),assertEqual("Autodetect","Detects JavaScript",codeInputElement.getAttribute("language"),"javascript"),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"#!/usr/bin/python\nprint(\"Hello, World!\")\nfor i in range(5):\n print(i)"),await waitAsync(50),assertEqual("Autodetect","Detects Python",codeInputElement.getAttribute("language"),"python"),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"body, html {\n height: 100%;\n background-color: blue;\n color: red;\n}"),await waitAsync(50),assertEqual("Autodetect","Detects CSS",codeInputElement.getAttribute("language"),"css")),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"// hello /\\S/g\nhe('llo', /\\s/g);\nhello"),a.selectionStart=a.selectionEnd=0,await waitAsync(50),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"f",ctrlKey:!0}));let i=codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog input"),j=i[0],k=i[1],l=i[2],m=i[3],n=codeInputElement.querySelectorAll(".code-input_find-and-replace_dialog button"),o=n[0],p=n[1],q=n[2],r=n[3],s=codeInputElement.querySelector(".code-input_find-and-replace_dialog details summary");j.value="/\\s/g",l.click(),await waitAsync(150),testAssertion("FindAndReplace","Finds Case-Sensitive Matches Correctly",confirm("Is there a match on only the lowercase '/\\s/g'?"),"user-judged"),j.value="he[^l]*llo",k.click(),l.click(),await waitAsync(150),testAssertion("FindAndReplace","Finds RegExp Matches Correctly",confirm("Are there matches on all 'he...llo's?"),"user-judged"),s.click(),p.click(),m.value="do('hello",q.click(),await waitAsync(50),assertEqual("FindAndReplace","Replaces Once Correctly",a.value,"// hello /\\S/g\ndo('hello', /\\s/g);\nhello"),o.click(),codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown",{key:"Escape"})),codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup",{key:"Escape"})),assertEqual("FindAndReplace","Selection Start on Focused Match when Dialog Exited",a.selectionStart,3),assertEqual("FindAndReplace","Selection End on Focused Match when Dialog Exited",a.selectionEnd,8),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"h",ctrlKey:!0})),j.value="",j.focus(),allowInputEvents(j),addText(j,"hello"),await waitAsync(150),m.value="hi",r.click(),assertEqual("FindAndReplace","Replaces All Correctly",a.value,"// hi /\\S/g\ndo('hi', /\\s/g);\nhi"),codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keydown",{key:"Escape"})),codeInputElement.querySelector(".code-input_find-and-replace_dialog").dispatchEvent(new KeyboardEvent("keyup",{key:"Escape"})),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"// 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"),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"g",ctrlKey:!0}));let t=codeInputElement.querySelector(".code-input_go-to-line_dialog input");t.value="1",t.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"})),assertEqual("GoToLine","Line Only",a.selectionStart,0),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"g",ctrlKey:!0})),t.value="3:18",t.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"})),assertEqual("GoToLine","Line and Column",a.selectionStart,45),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"g",ctrlKey:!0})),t.value="10",t.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"})),assertEqual("GoToLine","Rejects Out-of-range Line",t.classList.contains("code-input_go-to-line_error"),!0),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"g",ctrlKey:!0})),t.value="2:12",t.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"})),assertEqual("GoToLine","Rejects Out-of-range Column",t.classList.contains("code-input_go-to-line_error"),!0),a.dispatchEvent(new KeyboardEvent("keydown",{cancelable:!0,key:"g",ctrlKey:!0})),t.value="sausages",t.dispatchEvent(new KeyboardEvent("keydown",{key:"Enter"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Enter"})),assertEqual("GoToLine","Rejects Invalid Input",t.classList.contains("code-input_go-to-line_error"),!0),assertEqual("GoToLine","Stays open when Rejects Input",t.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"),!1),t.dispatchEvent(new KeyboardEvent("keydown",{key:"Escape"})),t.dispatchEvent(new KeyboardEvent("keyup",{key:"Escape"})),assertEqual("GoToLine","Exits when Esc pressed",t.parentElement.classList.contains("code-input_go-to-line_hidden-dialog"),!0),a.selectionStart=a.selectionEnd=a.value.length,addText(a,"\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}"),a.selectionStart=0,a.selectionEnd=a.value.length,a.dispatchEvent(new KeyboardEvent("keydown",{key:"Tab",shiftKey:!1})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Tab",shiftKey:!1})),assertEqual("Indent","Indents Lines",a.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 }"),a.dispatchEvent(new KeyboardEvent("keydown",{key:"Tab",shiftKey:!0})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Tab",shiftKey:!0})),assertEqual("Indent","Unindents Lines",a.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}"),a.dispatchEvent(new KeyboardEvent("keydown",{key:"Tab",shiftKey:!0})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Tab",shiftKey:!0})),assertEqual("Indent","Unindents Lines where some are already fully unindented",a.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}"),a.selectionStart=255,a.selectionEnd=274,a.dispatchEvent(new KeyboardEvent("keydown",{key:"Tab",shiftKey:!1})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Tab",shiftKey:!1})),assertEqual("Indent","Indents Lines by Selection",a.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}"),a.selectionStart=265,a.selectionEnd=265,a.dispatchEvent(new KeyboardEvent("keydown",{key:"Tab",shiftKey:!0})),a.dispatchEvent(new KeyboardEvent("keyup",{key:"Tab",shiftKey:!0})),assertEqual("Indent","Unindents Lines by Selection",a.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}"),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),testAddingText("Indent-AutoCloseBrackets",a,function(a){addText(a,`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...`,!0)},"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),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),addText(a,"\"Some special characters: \x96,\x01\x03,\x02...\""),a.selectionStart=a.value.length-4,a.selectionEnd=a.value.length,await waitAsync(50),testAssertion("SpecialChars","Displays Correctly",confirm("Do the special characters read (0096),(0001)(0003),(0002) and align with the ellipsis? (OK=Yes)"),"user-judged"),a.selectionStart=0,a.selectionEnd=a.value.length,backspace(a),fetch(new Request("https://cdn.jsdelivr.net/gh/webcoder49/code-input@2.1/code-input.js")).then(a=>a.text()).then(b=>{a.value="// code-input v2.1: A large code file (not the latest version!)\n// Editing this here should give little latency.\n\n"+b,a.selectionStart=112,a.selectionEnd=112,addText(a,"\n",!0),document.getElementById("collapse-results").setAttribute("open",!0)}),testsFailed?(document.querySelector("h2").style.backgroundColor="red",document.querySelector("h2").textContent="Some Tests have Failed."):(document.querySelector("h2").style.backgroundColor="lightgreen",document.querySelector("h2").textContent="All Tests have Passed.")}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debounce the update and highlighting function
|
|
3
|
-
* https://medium.com/@jamischarles/what-is-debouncing-2505c0648ff1
|
|
4
|
-
* Files: debounce-update.js
|
|
5
|
-
*/
|
|
6
|
-
codeInput.plugins.DebounceUpdate = class extends codeInput.Plugin {
|
|
7
|
-
/**
|
|
8
|
-
* Create a debounced update plugin to pass into a template
|
|
9
|
-
* @param {Number} delayMs Delay, in ms, to wait until updating the syntax highlighting
|
|
10
|
-
*/
|
|
11
|
-
constructor(delayMs) {
|
|
12
|
-
super([]); // No observed attributes
|
|
13
|
-
this.delayMs = delayMs;
|
|
14
|
-
}
|
|
15
|
-
/* Runs before elements are added into a `code-input`; Params: codeInput element) */
|
|
16
|
-
beforeElementsAdded(codeInput) {
|
|
17
|
-
this.update = codeInput.update.bind(codeInput); // Save previous update func
|
|
18
|
-
codeInput.update = this.updateDebounced.bind(this, codeInput);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Debounce the `update` function
|
|
23
|
-
*/
|
|
24
|
-
updateDebounced(codeInput, text) {
|
|
25
|
-
// Editing - cancel prev. timeout
|
|
26
|
-
if(this.debounceTimeout != null) {
|
|
27
|
-
window.clearTimeout(this.debounceTimeout);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this.debounceTimeout = window.setTimeout(() => {
|
|
31
|
-
// Closure arrow function can take in variables like `text`
|
|
32
|
-
this.update(text);
|
|
33
|
-
}, this.delayMs);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// this.`update` function is original function
|
|
37
|
-
|
|
38
|
-
debounceTimeout = null; // Timeout until update
|
|
39
|
-
delayMs = 0; // Time until update
|
|
40
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
codeInput.plugins.DebounceUpdate=class extends codeInput.Plugin{constructor(a){super([]),this.delayMs=a}beforeElementsAdded(a){this.update=a.update.bind(a),a.update=this.updateDebounced.bind(this,a)}updateDebounced(a,b){null!=this.debounceTimeout&&window.clearTimeout(this.debounceTimeout),this.debounceTimeout=window.setTimeout(()=>{this.update(b)},this.delayMs)}debounceTimeout=null;delayMs=0};
|