@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.
@@ -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 &lt;html&gt; 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 &lt;html&gt; tags
209
+ console.log("I've got another line!", 2 &lt; 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 &lt;html&gt; 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 &lt;html&gt; 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 &lt;html&gt; tags
12
+ console.log("I've got another line!", 2 &lt; 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 &lt;html&gt; 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};