diff --git a/tests/browser/01_io_mobile.js b/tests/browser/01_io_mobile.js new file mode 100644 index 00000000..b26853d7 --- /dev/null +++ b/tests/browser/01_io_mobile.js @@ -0,0 +1,679 @@ +/** + * Tests for input and output of various types to ensure the editors work as expected + * and retain data integrity, especially when it comes to special characters. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +// import { +// clear, +// utils.setInput, +// bake, +// setChrEnc, +// setEOLSeq, +// copy, +// paste, +// loadRecipe, +// expectOutput, +// uploadFile, +// uploadFolder +// } from "./browserUtils.js"; + +const utils = require("./browserUtils.js"); + +const SPECIAL_CHARS = [ + "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f", + "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f", + "\u007f", + "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f", + "\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f", + "\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9\ufffa\ufffb\ufffc" +].join(""); + +const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); + +const PUA_CHARS = "\ue000\ue001\uf8fe\uf8ff"; + +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." +"Why, what did she tell you?" +"I don't know, I didn't listen."`; + +const SELECTABLE_STRING = `ONE +two +ONE +three +ONE +four +ONE`; + +// Descriptions for named control characters +const CONTROL_CHAR_NAMES = { + 0: "null", + 7: "bell", + 8: "backspace", + 10: "line feed", + 11: "vertical tab", + 13: "carriage return", + 27: "escape", + 8203: "zero width space", + 8204: "zero width non-joiner", + 8205: "zero width joiner", + 8206: "left-to-right mark", + 8207: "right-to-left mark", + 8232: "line separator", + 8237: "left-to-right override", + 8238: "right-to-left override", + 8294: "left-to-right isolate", + 8295: "right-to-left isolate", + 8297: "pop directional isolate", + 8233: "paragraph separator", + 65279: "zero width no-break space", + 65532: "object replacement" +}; + +module.exports = { + before: browser => { + browser + .resizeWindow(500, 800) + .url(browser.launchUrl) + .useCss() + .waitForElementNotPresent("#preloader", 10000) + .click("#auto-bake-label"); + }, + + "CodeMirror has loaded correctly": browser => { + /* Editor has initialised */ + browser + .useCss() + // Input + .waitForElementVisible("#input-text") + .waitForElementVisible("#input-text .cm-editor") + .waitForElementVisible("#input-text .cm-editor .cm-scroller") + .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content") + .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content .cm-line") + // Output + .waitForElementVisible("#output-text") + .waitForElementVisible("#output-text .cm-editor") + .waitForElementVisible("#output-text .cm-editor .cm-scroller") + .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content") + .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content .cm-line"); + + /* Status bar is showing and has correct values */ + browser // Input + .waitForElementVisible("#input-text .cm-status-bar") + .waitForElementVisible("#input-text .cm-status-bar .stats-length-value") + .expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.waitForElementVisible("#input-text .cm-status-bar .stats-lines-value") + .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.waitForElementVisible("#input-text .cm-status-bar .chr-enc-value") + .expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); + browser.waitForElementVisible("#input-text .cm-status-bar .eol-value") + .expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); + + browser // Output + .waitForElementVisible("#output-text .cm-status-bar") + .waitForElementVisible("#output-text .cm-status-bar .stats-length-value") + .expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.waitForElementVisible("#output-text .cm-status-bar .stats-lines-value") + .expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.waitForElementVisible("#output-text .cm-status-bar .baking-time-info") + .expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.waitForElementVisible("#output-text .cm-status-bar .chr-enc-value") + .expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); + browser.waitForElementVisible("#output-text .cm-status-bar .eol-value") + .expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + }, + + "Adding content": browser => { + /* Status bar updates correctly */ + utils.setInput(browser, MULTI_LINE_STRING); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + browser.expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); + browser.expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); + + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); + browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + + /* Output updates correctly */ + utils.bake(browser); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); + browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); + browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); + }, + + "Special content": browser => { + /* Special characters are rendered correctly */ + utils.setInput(browser, SPECIAL_CHARS, false); + + // First line + for (let i = 0x0; i <= 0x8; i++) { + browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + // Tab \u0009 + browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); + + // Line feed \u000a + browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Second line + for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { + const index = SPECIAL_CHARS.charCodeAt(i); + const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); + const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); + + browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .to.have.property("title").equals(`Control character ${name}`); + browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .text.to.equal(value); + } + + /* Output renders correctly */ + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // First line + for (let i = 0x0; i <= 0x8; i++) { + browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + // Tab \u0009 + browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); + + // Line feed \u000a + browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Second line + for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { + const index = SPECIAL_CHARS.charCodeAt(i); + const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); + const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); + + browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .to.have.property("title").equals(`Control character ${name}`); + browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) + .text.to.equal(value); + } + + /* Bytes are rendered correctly */ + utils.setInput(browser, ALL_BYTES, false); + // Expect length to be 255, since one character is creating a newline + browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{255}$/); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("256"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + + /* PUA \ue000-\uf8ff */ + utils.setInput(browser, PUA_CHARS, false); + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // Confirm input and output as expected + /* In order to render whitespace characters as control character pictures in the output, even + when they are the designated line separator, CyberChef sometimes chooses to represent them + internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). + See `Utils.escapeWhitespace()` for an example of this. + Therefore, PUA characters should be rendered normally in the Input but as control character + pictures in the output. + */ + browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^\ue000\ue001\uf8fe\uf8ff$/); + browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^\u2400\u2401\u3cfe\u3cff$/); + + /* Can be copied */ + utils.setInput(browser, SPECIAL_CHARS, false); + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + + // Manual copy + browser + .doubleClick("#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)") + .waitForElementVisible("#output-text .cm-selectionBackground"); + utils.copy(browser); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.equals("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"); + browser.clearValue("#search"); + browser.click("#close-ops-dropdown-icon"); + browser.waitForElementNotVisible("#categories", 1000); + + // Raw copy + browser + .click("#copy-output") + .pause(100); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.matches(/^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009/); + browser.clearValue("#search"); + browser.click("#close-ops-dropdown-icon"); + browser.waitForElementNotVisible("#categories", 1000); + + }, + + "HTML output": browser => { + /* Displays correctly */ + utils.loadRecipe(browser, "Entropy", ALL_BYTES); + utils.bake(browser); + + browser + .waitForElementVisible("#output-html") + .waitForElementVisible("#output-html #chart-area"); + + /* Status bar widgets are disabled */ + browser.expect.element("#output-text .cm-status-bar .disabled .stats-length-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .stats-lines-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .chr-enc-value").to.be.visible; + browser.expect.element("#output-text .cm-status-bar .disabled .eol-value").to.be.visible; + + /* Displays special chars correctly */ + utils.loadRecipe(browser, "To Table", ",\u0000\u0001\u0002\u0003\u0004", [",", "\\r\\n", false, "HTML"]); + utils.bake(browser); + + for (let i = 0x0; i <= 0x4; i++) { + browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) + .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); + browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) + .text.to.equal(String.fromCharCode(0x2400 + i)); + } + + /* Can be copied */ + // Raw copy + browser + .click("#copy-output") + .pause(100); + utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values + + // Ensure that the values are as expected + browser.expect.element("#search").to.have.value.that.matches(/\u0000\u0001\u0002\u0003\u0004/); + browser.clearValue("#search"); + browser.click("#close-ops-dropdown-icon"); + browser.waitForElementNotVisible("#categories", 1000); + }, + + "Highlighting": browser => { + utils.setInput(browser, SELECTABLE_STRING); + utils.bake(browser); + + /* Selecting input text also selects other instances in input and output */ + browser // Input + .click("#auto-bake-label") + .doubleClick("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + browser // Output + .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + /* Selecting output text highlights in input */ + browser // Output + .click("#output-text") + .waitForElementNotPresent("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementNotPresent("#input-text .cm-content .cm-line .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line .cm-selectionMatch") + .doubleClick("#output-text .cm-content .cm-line:nth-of-type(7)") + .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + browser // Input + .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") + .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); + + // Turn autobake off again + browser.click("#auto-bake-label"); + }, + + "Character encoding": browser => { + const CHINESE_CHARS = "不要恐慌。"; + /* Dropup works */ + /* Selecting changes output correctly */ + utils.setInput(browser, CHINESE_CHARS, false); + utils.setChrEnc(browser, "input", "UTF-8"); + utils.bake(browser); + utils.expectOutput(browser, "\u00E4\u00B8\u008D\u00E8\u00A6\u0081\u00E6\u0081\u0090\u00E6\u0085\u008C\u00E3\u0080\u0082"); + + /* Changing output to match input works as expected */ + utils.setChrEnc(browser, "output", "UTF-8"); + utils.bake(browser); + utils.expectOutput(browser, CHINESE_CHARS); + + /* Encodings appear in the URL */ + browser.assert.urlContains("ienc=65001"); + browser.assert.urlContains("oenc=65001"); + + /* Try various encodings */ + // These are not meant to be realistic encodings for this data + utils.setInput(browser, CHINESE_CHARS, false); + utils.setChrEnc(browser, "input", "UTF-8"); + utils.setChrEnc(browser, "output", "UTF-16LE"); + utils.bake(browser); + utils.expectOutput(browser, "\uB8E4\uE88D\u81A6\u81E6\uE690\u8C85\u80E3"); + + utils.setChrEnc(browser, "output", "Simplified Chinese GBK"); + utils.bake(browser); + utils.expectOutput(browser, "\u6D93\u5D88\uFDFF\u93AD\u612D\u53A1\u9286\u0000"); + + utils.setChrEnc(browser, "input", "UTF-7"); + utils.bake(browser); + utils.expectOutput(browser, "+Tg0-+iYE-+YFA-+YUw-"); + + utils.setChrEnc(browser, "input", "Traditional Chinese Big5"); + utils.bake(browser); + utils.expectOutput(browser, "\u3043\u74B6\uFDFF\u7A3A\uFDFF"); + + utils.setChrEnc(browser, "output", "Windows-1251 Cyrillic"); + utils.bake(browser); + utils.expectOutput(browser, "\u00A4\u0408\u00ADn\u00AE\u0408\u00B7W\u040EC"); + }, + + "Line endings": browser => { + /* Dropup works */ + /* Selecting changes view in input */ + utils.setInput(browser, MULTI_LINE_STRING); + + // Line endings: LF + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Input EOL: VT + utils.setEOLSeq(browser, "input", "VT"); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") + .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); + + // Output EOL: VT + utils.setEOLSeq(browser, "output", "VT"); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#input-text .cm-content .cm-specialChar"); + browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + // Output + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(1)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementPresent("#output-text .cm-content .cm-specialChar"); + browser.expect.element("#output-text .cm-content .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + + /* Adding new line ending changes output correctly */ + browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("302"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("302"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Input EOL: CRLF + utils.setEOLSeq(browser, "input", "CRLF"); + // Output EOL: CR + utils.setEOLSeq(browser, "output", "CR"); + browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); + + // Input + browser + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)"); + browser.expect.element("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)").text.to.equal("␋"); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("304"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + // Output + utils.bake(browser); + browser + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") + .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)") + .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar"); + browser.expect.element("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar").text.to.equal("␊"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("304"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + /* Line endings appear in the URL */ + browser.assert.urlContains("ieol=%0D%0A"); + browser.assert.urlContains("oeol=%0D"); + }, + + "File inputs": browser => { + utils.clear(browser); + + /* Side panel displays correct info */ + utils.uploadFile(browser, "files/TowelDay.jpeg"); + + browser + .waitForElementVisible("#input-text .cm-file-details") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") + .waitForElementVisible("#input-text .cm-file-details .file-details-name") + .waitForElementVisible("#input-text .cm-file-details .file-details-size") + .waitForElementVisible("#input-text .cm-file-details .file-details-type") + .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + + /* Side panel can be hidden */ + browser + .click("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-hidden") + .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("1px"); + + browser + .click("#input-text .cm-file-details .file-details-toggle-hidden") + .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-hidden") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("200px"); + }, + + "Folder inputs": browser => { + utils.clear(browser); + + /* Side panel displays correct info */ + utils.uploadFolder(browser, "files"); + + // Loop through tabs + for (let i = 1; i < 3; i++) { + browser + .click(`#input-tabs li:nth-of-type(${i})`) + .waitForElementVisible(`#input-tabs li:nth-of-type(${i}).active-input-tab`); + + browser + .waitForElementVisible("#input-text .cm-file-details") + .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") + .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") + .waitForElementVisible("#input-text .cm-file-details .file-details-name") + .waitForElementVisible("#input-text .cm-file-details .file-details-size") + .waitForElementVisible("#input-text .cm-file-details .file-details-type") + .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); + + browser.getText("#input-text .cm-file-details .file-details-name", function(result) { + switch (result.value) { + case "TowelDay.jpeg": + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + break; + case "Hitchhikers_Guide.jpeg": + browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("Hitchhikers_Guide.jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("36,595 bytes"); + browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); + browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); + break; + default: + break; + } + }); + } + }, + + "Loading from URL": browser => { + /* Complex deep link populates the input correctly (encoding, eol, input) */ + browser + .urlHash("recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=%0C&oeol=%E2%80%A9") + .waitForElementVisible("#rec-list li.operation"); + + browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/); + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + + browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic"); + browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE"); + + browser.expect.element("#input-text .eol-value").text.that.equals("FF"); + browser.expect.element("#output-text .eol-value").text.that.equals("PS"); + + utils.bake(browser); + + browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44"); + browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + }, + + "Replace input with output": browser => { + /* Input is correctly populated */ + utils.loadRecipe(browser, "XOR", "The ships hung in the sky in much the same way that bricks don't.", [{ "option": "Hex", "string": "65" }, "Standard", false]); + utils.setChrEnc(browser, "input", "UTF-32LE"); + utils.setChrEnc(browser, "output", "UTF-7"); + utils.setEOLSeq(browser, "input", "CRLF"); + utils.setEOLSeq(browser, "output", "LS"); + + browser + .sendKeys("#input-text .cm-content", browser.Keys.RETURN) + .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + utils.bake(browser); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("67"); + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-32LE"); + browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); + browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("268"); + + browser + .click("#switch") + .waitForElementVisible("#stale-indicator"); + + browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("268"); + + /* Special characters, encodings and line endings all as expected */ + browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); + browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-7"); + browser.expect.element("#input-text .eol-value").text.that.equals("LS"); + browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)").text.to.equal("␍"); + browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(49)").text.to.equal("␑"); + browser.waitForElementNotPresent("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(50)"); + }, + + + after: browser => { + browser.end(); + } +};