@webcoder49/code-input 2.1.0 → 2.5.0

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