io.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. /**
  2. * Tests for input and output of various types to ensure the editors work as expected
  3. * and retain data integrity, especially when it comes to special characters.
  4. *
  5. * @author n1474335 [n1474335@gmail.com]
  6. * @copyright Crown Copyright 2023
  7. * @license Apache-2.0
  8. */
  9. const SPECIAL_CHARS = [
  10. "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f",
  11. "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f",
  12. "\u007f",
  13. "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f",
  14. "\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f",
  15. "\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9\ufffa\ufffb\ufffc"
  16. ].join("");
  17. const ALL_BYTES = [
  18. "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
  19. "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f",
  20. "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f",
  21. "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f",
  22. "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
  23. "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f",
  24. "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f",
  25. "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
  26. "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f",
  27. "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
  28. "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf",
  29. "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf",
  30. "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf",
  31. "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf",
  32. "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
  33. "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
  34. ].join("");
  35. const PUA_CHARS = "\ue000\ue001\uf8fe\uf8ff";
  36. const MULTI_LINE_STRING =`"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young."
  37. "Why, what did she tell you?"
  38. "I don't know, I didn't listen."`;
  39. const SELECTABLE_STRING = `ONE
  40. two
  41. ONE
  42. three
  43. ONE
  44. four
  45. ONE`;
  46. // Descriptions for named control characters
  47. const CONTROL_CHAR_NAMES = {
  48. 0: "null",
  49. 7: "bell",
  50. 8: "backspace",
  51. 10: "line feed",
  52. 11: "vertical tab",
  53. 13: "carriage return",
  54. 27: "escape",
  55. 8203: "zero width space",
  56. 8204: "zero width non-joiner",
  57. 8205: "zero width joiner",
  58. 8206: "left-to-right mark",
  59. 8207: "right-to-left mark",
  60. 8232: "line separator",
  61. 8237: "left-to-right override",
  62. 8238: "right-to-left override",
  63. 8294: "left-to-right isolate",
  64. 8295: "right-to-left isolate",
  65. 8297: "pop directional isolate",
  66. 8233: "paragraph separator",
  67. 65279: "zero width no-break space",
  68. 65532: "object replacement"
  69. };
  70. module.exports = {
  71. before: browser => {
  72. browser
  73. .resizeWindow(1280, 800)
  74. .url(browser.launchUrl)
  75. .useCss()
  76. .waitForElementNotPresent("#preloader", 10000)
  77. .click("#auto-bake-label");
  78. },
  79. "CodeMirror has loaded correctly": browser => {
  80. /* Editor has initialised */
  81. browser
  82. .useCss()
  83. // Input
  84. .waitForElementVisible("#input-text")
  85. .waitForElementVisible("#input-text .cm-editor")
  86. .waitForElementVisible("#input-text .cm-editor .cm-scroller")
  87. .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content")
  88. .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content .cm-line")
  89. // Output
  90. .waitForElementVisible("#output-text")
  91. .waitForElementVisible("#output-text .cm-editor")
  92. .waitForElementVisible("#output-text .cm-editor .cm-scroller")
  93. .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content")
  94. .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content .cm-line");
  95. /* Status bar is showing and has correct values */
  96. browser // Input
  97. .waitForElementVisible("#input-text .cm-status-bar")
  98. .waitForElementVisible("#input-text .cm-status-bar .stats-length-value")
  99. .expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("0");
  100. browser.waitForElementVisible("#input-text .cm-status-bar .stats-lines-value")
  101. .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  102. browser.waitForElementVisible("#input-text .cm-status-bar .chr-enc-value")
  103. .expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes");
  104. browser.waitForElementVisible("#input-text .cm-status-bar .eol-value")
  105. .expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF");
  106. browser // Output
  107. .waitForElementVisible("#output-text .cm-status-bar")
  108. .waitForElementVisible("#output-text .cm-status-bar .stats-length-value")
  109. .expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0");
  110. browser.waitForElementVisible("#output-text .cm-status-bar .stats-lines-value")
  111. .expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  112. browser.waitForElementVisible("#output-text .cm-status-bar .baking-time-info")
  113. .expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms");
  114. browser.waitForElementVisible("#output-text .cm-status-bar .chr-enc-value")
  115. .expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes");
  116. browser.waitForElementVisible("#output-text .cm-status-bar .eol-value")
  117. .expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF");
  118. },
  119. "Adding content": browser => {
  120. /* Status bar updates correctly */
  121. setInput(browser, MULTI_LINE_STRING);
  122. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301");
  123. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3");
  124. browser.expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes");
  125. browser.expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF");
  126. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0");
  127. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  128. browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms");
  129. browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes");
  130. browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF");
  131. /* Output updates correctly */
  132. bake(browser);
  133. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301");
  134. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3");
  135. browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms");
  136. browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes");
  137. browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF");
  138. },
  139. "Special content": browser => {
  140. /* Special characters are rendered correctly */
  141. setInput(browser, SPECIAL_CHARS, false);
  142. // First line
  143. for (let i = 0x0; i <= 0x8; i++) {
  144. browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)
  145. .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`);
  146. browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)
  147. .text.to.equal(String.fromCharCode(0x2400 + i));
  148. }
  149. // Tab \u0009
  150. browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/);
  151. // Line feed \u000a
  152. browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/);
  153. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  154. // Second line
  155. for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) {
  156. const index = SPECIAL_CHARS.charCodeAt(i);
  157. const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16);
  158. const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index);
  159. browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)
  160. .to.have.property("title").equals(`Control character ${name}`);
  161. browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)
  162. .text.to.equal(value);
  163. }
  164. /* Output renders correctly */
  165. setChrEnc(browser, "output", "UTF-8");
  166. bake(browser);
  167. // First line
  168. for (let i = 0x0; i <= 0x8; i++) {
  169. browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)
  170. .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`);
  171. browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`)
  172. .text.to.equal(String.fromCharCode(0x2400 + i));
  173. }
  174. // Tab \u0009
  175. browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/);
  176. // Line feed \u000a
  177. browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/);
  178. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  179. // Second line
  180. for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) {
  181. const index = SPECIAL_CHARS.charCodeAt(i);
  182. const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16);
  183. const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index);
  184. browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)
  185. .to.have.property("title").equals(`Control character ${name}`);
  186. browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`)
  187. .text.to.equal(value);
  188. }
  189. /* Bytes are rendered correctly */
  190. setInput(browser, ALL_BYTES, false);
  191. // Expect length to be 255, since one character is creating a newline
  192. browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{255}$/);
  193. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("256");
  194. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  195. /* PUA \ue000-\uf8ff */
  196. setInput(browser, PUA_CHARS, false);
  197. setChrEnc(browser, "output", "UTF-8");
  198. bake(browser);
  199. // Confirm input and output as expected
  200. /* In order to render whitespace characters as control character pictures in the output, even
  201. when they are the designated line separator, CyberChef sometimes chooses to represent them
  202. internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas).
  203. See `Utils.escapeWhitespace()` for an example of this.
  204. Therefore, PUA characters should be rendered normally in the Input but as control character
  205. pictures in the output.
  206. */
  207. browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^\ue000\ue001\uf8fe\uf8ff$/);
  208. browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^\u2400\u2401\u3cfe\u3cff$/);
  209. /* Can be copied */
  210. setInput(browser, SPECIAL_CHARS, false);
  211. setChrEnc(browser, "output", "UTF-8");
  212. bake(browser);
  213. // Manual copy
  214. browser
  215. .doubleClick("#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)")
  216. .waitForElementVisible("#output-text .cm-selectionBackground");
  217. copy(browser);
  218. paste(browser, "#search"); // Paste into search box as this won't mess with the values
  219. // Ensure that the values are as expected
  220. browser.expect.element("#search").to.have.value.that.equals("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008");
  221. browser.clearValue("#search");
  222. // Raw copy
  223. browser
  224. .click("#copy-output")
  225. .pause(100);
  226. paste(browser, "#search"); // Paste into search box as this won't mess with the values
  227. // Ensure that the values are as expected
  228. browser.expect.element("#search").to.have.value.that.matches(/^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009/);
  229. browser.clearValue("#search");
  230. },
  231. "HTML output": browser => {
  232. /* Displays correctly */
  233. loadRecipe(browser, "Entropy", ALL_BYTES);
  234. bake(browser);
  235. browser
  236. .waitForElementVisible("#output-html")
  237. .waitForElementVisible("#output-html #chart-area");
  238. /* Status bar widgets are disabled */
  239. browser.expect.element("#output-text .cm-status-bar .disabled .stats-length-value").to.be.visible;
  240. browser.expect.element("#output-text .cm-status-bar .disabled .stats-lines-value").to.be.visible;
  241. browser.expect.element("#output-text .cm-status-bar .disabled .chr-enc-value").to.be.visible;
  242. browser.expect.element("#output-text .cm-status-bar .disabled .eol-value").to.be.visible;
  243. /* Displays special chars correctly */
  244. loadRecipe(browser, "To Table", ",\u0000\u0001\u0002\u0003\u0004", [",", "\\r\\n", false, "HTML"]);
  245. bake(browser);
  246. for (let i = 0x0; i <= 0x4; i++) {
  247. browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`)
  248. .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`);
  249. browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`)
  250. .text.to.equal(String.fromCharCode(0x2400 + i));
  251. }
  252. /* Can be copied */
  253. // Raw copy
  254. browser
  255. .click("#copy-output")
  256. .pause(100);
  257. paste(browser, "#search"); // Paste into search box as this won't mess with the values
  258. // Ensure that the values are as expected
  259. browser.expect.element("#search").to.have.value.that.matches(/\u0000\u0001\u0002\u0003\u0004/);
  260. browser.clearValue("#search");
  261. },
  262. "Highlighting": browser => {
  263. setInput(browser, SELECTABLE_STRING);
  264. bake(browser);
  265. /* Selecting input text also selects other instances in input and output */
  266. browser // Input
  267. .click("#auto-bake-label")
  268. .doubleClick("#input-text .cm-content .cm-line:nth-of-type(1)")
  269. .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground")
  270. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch")
  271. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch")
  272. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch")
  273. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch")
  274. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch")
  275. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch")
  276. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch");
  277. browser // Output
  278. .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground")
  279. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch")
  280. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch")
  281. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch")
  282. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch")
  283. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch")
  284. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch")
  285. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch");
  286. /* Selecting output text highlights in input */
  287. browser // Output
  288. .click("#output-text")
  289. .waitForElementNotPresent("#input-text .cm-selectionLayer .cm-selectionBackground")
  290. .waitForElementNotPresent("#output-text .cm-selectionLayer .cm-selectionBackground")
  291. .waitForElementNotPresent("#input-text .cm-content .cm-line .cm-selectionMatch")
  292. .waitForElementNotPresent("#output-text .cm-content .cm-line .cm-selectionMatch")
  293. .doubleClick("#output-text .cm-content .cm-line:nth-of-type(7)")
  294. .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground")
  295. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch")
  296. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch")
  297. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch")
  298. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch")
  299. .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch")
  300. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch")
  301. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch");
  302. browser // Input
  303. .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground")
  304. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch")
  305. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch")
  306. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch")
  307. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch")
  308. .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch")
  309. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch")
  310. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch");
  311. // Turn autobake off again
  312. browser.click("#auto-bake-label");
  313. },
  314. "Character encoding": browser => {
  315. const CHINESE_CHARS = "不要恐慌。";
  316. /* Dropup works */
  317. /* Selecting changes output correctly */
  318. setInput(browser, CHINESE_CHARS, false);
  319. setChrEnc(browser, "input", "UTF-8");
  320. bake(browser);
  321. expectOutput(browser, "\u00E4\u00B8\u008D\u00E8\u00A6\u0081\u00E6\u0081\u0090\u00E6\u0085\u008C\u00E3\u0080\u0082");
  322. /* Changing output to match input works as expected */
  323. setChrEnc(browser, "output", "UTF-8");
  324. bake(browser);
  325. expectOutput(browser, CHINESE_CHARS);
  326. /* Encodings appear in the URL */
  327. browser.assert.urlContains("ienc=65001");
  328. browser.assert.urlContains("oenc=65001");
  329. /* Preserved when changing tabs */
  330. browser
  331. .click("#btn-new-tab")
  332. .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab");
  333. browser.expect.element("#input-text .chr-enc-value").text.that.equals("Raw Bytes");
  334. browser.expect.element("#output-text .chr-enc-value").text.that.equals("Raw Bytes");
  335. setChrEnc(browser, "input", "UTF-7");
  336. setChrEnc(browser, "output", "UTF-7");
  337. browser
  338. .click("#input-tabs li:nth-of-type(1)")
  339. .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab");
  340. browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-8");
  341. browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-8");
  342. /* Try various encodings */
  343. // These are not meant to be realistic encodings for this data
  344. setInput(browser, CHINESE_CHARS, false);
  345. setChrEnc(browser, "input", "UTF-8");
  346. setChrEnc(browser, "output", "UTF-16LE");
  347. bake(browser);
  348. expectOutput(browser, "\uB8E4\uE88D\u81A6\u81E6\uE690\u8C85\u80E3");
  349. setChrEnc(browser, "output", "Simplified Chinese GBK");
  350. bake(browser);
  351. expectOutput(browser, "\u6D93\u5D88\uFDFF\u93AD\u612D\u53A1\u9286\u0000");
  352. setChrEnc(browser, "input", "UTF-7");
  353. bake(browser);
  354. expectOutput(browser, "+Tg0-+iYE-+YFA-+YUw-");
  355. setChrEnc(browser, "input", "Traditional Chinese Big5");
  356. bake(browser);
  357. expectOutput(browser, "\u3043\u74B6\uFDFF\u7A3A\uFDFF");
  358. setChrEnc(browser, "output", "Windows-1251 Cyrillic");
  359. bake(browser);
  360. expectOutput(browser, "\u00A4\u0408\u00ADn\u00AE\u0408\u00B7W\u040EC");
  361. },
  362. "Line endings": browser => {
  363. /* Dropup works */
  364. /* Selecting changes view in input */
  365. setInput(browser, MULTI_LINE_STRING);
  366. // Line endings: LF
  367. // Input
  368. browser
  369. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(3)")
  370. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4)")
  371. .waitForElementNotPresent("#input-text .cm-content .cm-specialChar");
  372. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301");
  373. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3");
  374. // Output
  375. bake(browser);
  376. browser
  377. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)")
  378. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)")
  379. .waitForElementNotPresent("#output-text .cm-content .cm-specialChar");
  380. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301");
  381. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3");
  382. // Input EOL: VT
  383. setEOLSeq(browser, "input", "VT");
  384. // Input
  385. browser
  386. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)")
  387. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)")
  388. .waitForElementPresent("#input-text .cm-content .cm-specialChar");
  389. browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊");
  390. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301");
  391. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  392. // Output
  393. bake(browser);
  394. browser
  395. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)")
  396. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)")
  397. .waitForElementNotPresent("#output-text .cm-content .cm-specialChar");
  398. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301");
  399. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3");
  400. // Output EOL: VT
  401. setEOLSeq(browser, "output", "VT");
  402. // Input
  403. browser
  404. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)")
  405. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)")
  406. .waitForElementPresent("#input-text .cm-content .cm-specialChar");
  407. browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊");
  408. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301");
  409. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  410. // Output
  411. browser
  412. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(1)")
  413. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2)")
  414. .waitForElementPresent("#output-text .cm-content .cm-specialChar");
  415. browser.expect.element("#output-text .cm-content .cm-specialChar").text.to.equal("␊");
  416. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301");
  417. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1");
  418. /* Adding new line ending changes output correctly */
  419. browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN);
  420. // Input
  421. browser
  422. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)")
  423. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)");
  424. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("302");
  425. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  426. // Output
  427. bake(browser);
  428. browser
  429. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)")
  430. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)");
  431. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("302");
  432. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  433. // Input EOL: CRLF
  434. setEOLSeq(browser, "input", "CRLF");
  435. // Output EOL: CR
  436. setEOLSeq(browser, "output", "CR");
  437. browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN);
  438. // Input
  439. browser
  440. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)")
  441. .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)")
  442. .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)");
  443. browser.expect.element("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)").text.to.equal("␋");
  444. browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("304");
  445. browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  446. // Output
  447. bake(browser);
  448. browser
  449. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)")
  450. .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)")
  451. .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar");
  452. browser.expect.element("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar").text.to.equal("␊");
  453. browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("304");
  454. browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2");
  455. /* Line endings appear in the URL */
  456. browser.assert.urlContains("ieol=%0D%0A");
  457. browser.assert.urlContains("oeol=%0D");
  458. /* Preserved when changing tabs */
  459. browser
  460. .click("#btn-new-tab")
  461. .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab");
  462. browser.expect.element("#input-text .eol-value").text.that.equals("LF");
  463. browser.expect.element("#output-text .eol-value").text.that.equals("LF");
  464. setEOLSeq(browser, "input", "FF");
  465. setEOLSeq(browser, "output", "LS");
  466. browser
  467. .click("#input-tabs li:nth-of-type(1)")
  468. .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab");
  469. browser.expect.element("#input-text .eol-value").text.that.equals("CRLF");
  470. browser.expect.element("#output-text .eol-value").text.that.equals("CR");
  471. },
  472. "File inputs": browser => {
  473. clear(browser);
  474. /* Side panel displays correct info */
  475. uploadFile(browser, "files/TowelDay.jpeg");
  476. browser
  477. .waitForElementVisible("#input-text .cm-file-details")
  478. .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
  479. .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail")
  480. .waitForElementVisible("#input-text .cm-file-details .file-details-name")
  481. .waitForElementVisible("#input-text .cm-file-details .file-details-size")
  482. .waitForElementVisible("#input-text .cm-file-details .file-details-type")
  483. .waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
  484. browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg");
  485. browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes");
  486. browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg");
  487. browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%");
  488. /* Side panel can be hidden */
  489. browser
  490. .click("#input-text .cm-file-details .file-details-toggle-shown")
  491. .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-shown")
  492. .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-hidden")
  493. .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("1px");
  494. browser
  495. .click("#input-text .cm-file-details .file-details-toggle-hidden")
  496. .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-hidden")
  497. .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
  498. .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("200px");
  499. },
  500. "Folder inputs": browser => {
  501. clear(browser);
  502. /* Side panel displays correct info */
  503. uploadFolder(browser, "files");
  504. // Tab 1
  505. browser
  506. .click("#input-tabs li:nth-of-type(1)")
  507. .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab");
  508. browser
  509. .waitForElementVisible("#input-text .cm-file-details")
  510. .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
  511. .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail")
  512. .waitForElementVisible("#input-text .cm-file-details .file-details-name")
  513. .waitForElementVisible("#input-text .cm-file-details .file-details-size")
  514. .waitForElementVisible("#input-text .cm-file-details .file-details-type")
  515. .waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
  516. browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg");
  517. browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes");
  518. browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg");
  519. browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%");
  520. // Tab 2
  521. browser
  522. .click("#input-tabs li:nth-of-type(2)")
  523. .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab");
  524. browser
  525. .waitForElementVisible("#input-text .cm-file-details")
  526. .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown")
  527. .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail")
  528. .waitForElementVisible("#input-text .cm-file-details .file-details-name")
  529. .waitForElementVisible("#input-text .cm-file-details .file-details-size")
  530. .waitForElementVisible("#input-text .cm-file-details .file-details-type")
  531. .waitForElementVisible("#input-text .cm-file-details .file-details-loaded");
  532. browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("Hitchhikers_Guide.jpeg");
  533. browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("36,595 bytes");
  534. browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg");
  535. browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%");
  536. },
  537. "Loading from URL": browser => {
  538. /* Complex deep link populates the input correctly (encoding, eol, input) */
  539. },
  540. "Replace input with output": browser => {
  541. /* Input is correctly populated */
  542. /* Special characters, encodings and line endings all as expected */
  543. },
  544. after: browser => {
  545. browser.end();
  546. }
  547. };
  548. /** @function
  549. * Clears the recipe and input
  550. *
  551. * @param {Browser} browser - Nightwatch client
  552. */
  553. function clear(browser) {
  554. browser
  555. .useCss()
  556. .click("#clr-recipe")
  557. .click("#clr-io")
  558. .waitForElementNotPresent("#rec-list li.operation")
  559. .expect.element("#input-text .cm-content").text.that.equals("");
  560. }
  561. /** @function
  562. * Sets the input to the desired string
  563. *
  564. * @param {Browser} browser - Nightwatch client
  565. * @param {string} input - The text to populate the input with
  566. * @param {boolean} [type=true] - Whether to type the characters in by using sendKeys,
  567. * or to set the value of the editor directly (useful for special characters)
  568. */
  569. function setInput(browser, input, type=true) {
  570. clear(browser);
  571. if (type) {
  572. browser
  573. .useCss()
  574. .sendKeys("#input-text .cm-content", input)
  575. .pause(100);
  576. } else {
  577. browser.execute(text => {
  578. window.app.setInput(text);
  579. }, [input]);
  580. }
  581. }
  582. /** @function
  583. * Triggers a bake
  584. *
  585. * @param {Browser} browser - Nightwatch client
  586. */
  587. function bake(browser) {
  588. browser
  589. .click("#bake")
  590. .pause(100)
  591. .waitForElementPresent("#stale-indicator.hidden", 5000);
  592. }
  593. /** @function
  594. * Sets the character encoding in the input or output
  595. *
  596. * @param {Browser} browser - Nightwatch client
  597. * @param {string} io - Either "input" or "output"
  598. * @param {string} enc - The encoding to be set
  599. */
  600. function setChrEnc(browser, io, enc) {
  601. io = `#${io}-text`;
  602. browser
  603. .useCss()
  604. .click(io + " .chr-enc-value")
  605. .waitForElementVisible(io + " .chr-enc-select .cm-status-bar-select-scroll")
  606. .click("link text", enc)
  607. .waitForElementNotVisible(io + " .chr-enc-select .cm-status-bar-select-scroll")
  608. .expect.element(io + " .chr-enc-value").text.that.equals(enc);
  609. }
  610. /** @function
  611. * Sets the end of line sequence in the input or output
  612. *
  613. * @param {Browser} browser - Nightwatch client
  614. * @param {string} io - Either "input" or "output"
  615. * @param {string} eol - The sequence to set
  616. */
  617. function setEOLSeq(browser, io, eol) {
  618. io = `#${io}-text`;
  619. browser
  620. .useCss()
  621. .click(io + " .eol-value")
  622. .waitForElementVisible(io + " .eol-select .cm-status-bar-select-content")
  623. .click(`${io} .cm-status-bar-select-content a[data-val=${eol}]`)
  624. .waitForElementNotVisible(io + " .eol-select .cm-status-bar-select-content")
  625. .expect.element(io + " .eol-value").text.that.equals(eol);
  626. }
  627. /** @function
  628. * Copies whatever is currently selected
  629. *
  630. * @param {Browser} browser - Nightwatch client
  631. */
  632. function copy(browser) {
  633. browser.perform(function() {
  634. const actions = this.actions({async: true});
  635. // Ctrl + Ins used as this works on Windows, Linux and Mac
  636. return actions
  637. .keyDown(browser.Keys.CONTROL)
  638. .keyDown(browser.Keys.INSERT)
  639. .keyUp(browser.Keys.INSERT)
  640. .keyUp(browser.Keys.CONTROL);
  641. });
  642. }
  643. /** @function
  644. * Pastes into the target element
  645. *
  646. * @param {Browser} browser - Nightwatch client
  647. * @param {string} el - Target element selector
  648. */
  649. function paste(browser, el) {
  650. browser
  651. .click(el)
  652. .perform(function() {
  653. const actions = this.actions({async: true});
  654. // Shift + Ins used as this works on Windows, Linux and Mac
  655. return actions
  656. .keyDown(browser.Keys.SHIFT)
  657. .keyDown(browser.Keys.INSERT)
  658. .keyUp(browser.Keys.INSERT)
  659. .keyUp(browser.Keys.SHIFT);
  660. })
  661. .pause(100);
  662. }
  663. /** @function
  664. * Loads a recipe and input
  665. *
  666. * @param {Browser} browser - Nightwatch client
  667. * @param {string|Array<string>} opName - name of operation to be loaded, array for multiple ops
  668. * @param {string} input - input text for test
  669. * @param {Array<string>|Array<Array<string>>} args - arguments, nested if multiple ops
  670. */
  671. function loadRecipe(browser, opName, input, args) {
  672. let recipeConfig;
  673. if (typeof(opName) === "string") {
  674. recipeConfig = JSON.stringify([{
  675. "op": opName,
  676. "args": args
  677. }]);
  678. } else if (opName instanceof Array) {
  679. recipeConfig = JSON.stringify(
  680. opName.map((op, i) => {
  681. return {
  682. op: op,
  683. args: args.length ? args[i] : []
  684. };
  685. })
  686. );
  687. } else {
  688. throw new Error("Invalid operation type. Must be string or array of strings. Received: " + typeof(opName));
  689. }
  690. clear(browser);
  691. setInput(browser, input, false);
  692. browser
  693. .urlHash("recipe=" + recipeConfig)
  694. .waitForElementPresent("#rec-list li.operation");
  695. }
  696. /** @function
  697. * Tests whether the output matches a given value
  698. *
  699. * @param {Browser} browser - Nightwatch client
  700. * @param {string} expected - The expected output value
  701. */
  702. function expectOutput(browser, expected) {
  703. browser.execute(expected => {
  704. return expected === window.app.manager.output.outputEditorView.state.doc.toString();
  705. }, [expected]);
  706. }
  707. /** @function
  708. * Uploads a file using the #open-file input
  709. *
  710. * @param {Browser} browser - Nightwatch client
  711. * @param {string} filename - A path to a file in the samples directory
  712. */
  713. function uploadFile(browser, filename) {
  714. const filepath = require("path").resolve(__dirname + "/../samples/" + filename);
  715. // The file input cannot be interacted with by nightwatch while it is hidden,
  716. // so we temporarily expose it for the purposes of this test.
  717. browser.execute(() => {
  718. document.getElementById("open-file").style.display = "block";
  719. });
  720. browser
  721. .pause(100)
  722. .setValue("#open-file", filepath)
  723. .pause(100);
  724. browser.execute(() => {
  725. document.getElementById("open-file").style.display = "none";
  726. });
  727. browser.waitForElementVisible("#input-text .cm-file-details");
  728. }
  729. /** @function
  730. * Uploads a folder using the #open-folder input
  731. *
  732. * @param {Browser} browser - Nightwatch client
  733. * @param {string} foldername - A path to a folder in the samples directory
  734. */
  735. function uploadFolder(browser, foldername) {
  736. const folderpath = require("path").resolve(__dirname + "/../samples/" + foldername);
  737. // The folder input cannot be interacted with by nightwatch while it is hidden,
  738. // so we temporarily expose it for the purposes of this test.
  739. browser.execute(() => {
  740. document.getElementById("open-folder").style.display = "block";
  741. });
  742. browser
  743. .pause(100)
  744. .setValue("#open-folder", folderpath)
  745. .pause(500);
  746. browser.execute(() => {
  747. document.getElementById("open-folder").style.display = "none";
  748. });
  749. browser.waitForElementVisible("#input-text .cm-file-details");
  750. }