diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 46a551a4..49e9a44b 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -149,13 +149,20 @@ class StatusBarPanel { /** * Counts the stats of a document - * @param {Text} doc + * @param {EditorState} state */ - updateStats(doc) { + updateStats(state) { const length = this.dom.querySelector(".stats-length-value"), lines = this.dom.querySelector(".stats-lines-value"); - length.textContent = doc.length; - lines.textContent = doc.lines; + + let docLength = state.doc.length; + // CodeMirror always counts line breaks as one character. + // We want to show an accurate reading of how many bytes there are. + if (state.lineBreak.length !== 1) { + docLength += (state.lineBreak.length * state.doc.lines) - state.doc.lines - 1; + } + length.textContent = docLength; + lines.textContent = state.doc.lines; } /** @@ -434,7 +441,7 @@ function makePanel(opts) { sbPanel.updateEOL(view.state); sbPanel.updateCharEnc(); sbPanel.updateTiming(); - sbPanel.updateStats(view.state.doc); + sbPanel.updateStats(view.state); sbPanel.updateSelection(view.state, false); sbPanel.monitorHTMLOutput(); @@ -450,7 +457,7 @@ function makePanel(opts) { sbPanel.updateSizing(update.view); } if (update.docChanged) { - sbPanel.updateStats(update.state.doc); + sbPanel.updateStats(update.state); } } }; diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 701be1fd..25c1629d 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -1222,12 +1222,6 @@ class InputWaiter { this.setupInputWorker(); this.manager.worker.setupChefWorker(); this.addInput(true); - - // Fire the statechange event as the input has been modified, - // leaving enough time for workers to be initialised - setTimeout(function() { - window.dispatchEvent(this.manager.statechange); - }.bind(this), 100); } /** diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index de683de2..776f66b5 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -231,9 +231,13 @@ class OutputWaiter { */ async setOutput(data, force=false) { // Don't do anything if the output hasn't changed - if (!force && data === this.currentOutputCache) return; - this.currentOutputCache = data; + if (!force && data === this.currentOutputCache) { + this.manager.controls.hideStaleIndicator(); + this.toggleLoader(false); + return; + } + this.currentOutputCache = data; this.toggleLoader(true); // If data is an ArrayBuffer, convert to a string in the correct character encoding diff --git a/tests/browser/io.js b/tests/browser/io.js index 7d17e820..3451a245 100644 --- a/tests/browser/io.js +++ b/tests/browser/io.js @@ -422,23 +422,207 @@ module.exports = { "Line endings": browser => { /* Dropup works */ /* Selecting changes view in input */ + 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 + 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 + 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 + 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 + 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 */ - /* Other EOL characters are displayed correctly when not being used to end a line */ - /* Changing in output has the correct effect */ + 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 + 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 + setEOLSeq(browser, "input", "CRLF"); + // Output EOL: CR + 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 + 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"); + /* Preserved when changing tabs */ + browser + .click("#btn-new-tab") + .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); + browser.expect.element("#input-text .eol-value").text.that.equals("LF"); + browser.expect.element("#output-text .eol-value").text.that.equals("LF"); + + setEOLSeq(browser, "input", "FF"); + setEOLSeq(browser, "output", "LS"); + + browser + .click("#input-tabs li:nth-of-type(1)") + .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); + browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); + browser.expect.element("#output-text .eol-value").text.that.equals("CR"); }, "File inputs": browser => { - /* By button */ - /* By drag and drop */ + clear(browser); + /* Side panel displays correct info */ + 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 => { - /* By button */ - /* By drag and drop */ + clear(browser); + + /* Side panel displays correct info */ + uploadFolder(browser, "files"); + + // Tab 1 + browser + .click("#input-tabs li:nth-of-type(1)") + .waitForElementVisible("#input-tabs li:nth-of-type(1).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.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%"); + + // Tab 2 + browser + .click("#input-tabs li:nth-of-type(2)") + .waitForElementVisible("#input-tabs li:nth-of-type(2).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.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%"); }, "Loading from URL": browser => { @@ -516,12 +700,30 @@ function setChrEnc(browser, io, enc) { browser .useCss() .click(io + " .chr-enc-value") - .waitForElementVisible(io + " .cm-status-bar-select-scroll") + .waitForElementVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") .click("link text", enc) - .waitForElementNotVisible(io + " .cm-status-bar-select-scroll") + .waitForElementNotVisible(io + " .chr-enc-select .cm-status-bar-select-scroll") .expect.element(io + " .chr-enc-value").text.that.equals(enc); } +/** @function + * Sets the end of line sequence in the input or output + * + * @param {Browser} browser - Nightwatch client + * @param {string} io - Either "input" or "output" + * @param {string} eol - The sequence to set + */ +function setEOLSeq(browser, io, eol) { + io = `#${io}-text`; + browser + .useCss() + .click(io + " .eol-value") + .waitForElementVisible(io + " .eol-select .cm-status-bar-select-content") + .click(`${io} .cm-status-bar-select-content a[data-val=${eol}]`) + .waitForElementNotVisible(io + " .eol-select .cm-status-bar-select-content") + .expect.element(io + " .eol-value").text.that.equals(eol); +} + /** @function * Copies whatever is currently selected * @@ -609,3 +811,51 @@ function expectOutput(browser, expected) { return expected === window.app.manager.output.outputEditorView.state.doc.toString(); }, [expected]); } + +/** @function + * Uploads a file using the #open-file input + * + * @param {Browser} browser - Nightwatch client + * @param {string} filename - A path to a file in the samples directory + */ +function uploadFile(browser, filename) { + const filepath = require("path").resolve(__dirname + "/../samples/" + filename); + + // The file input cannot be interacted with by nightwatch while it is hidden, + // so we temporarily expose it for the purposes of this test. + browser.execute(() => { + document.getElementById("open-file").style.display = "block"; + }); + browser + .pause(100) + .setValue("#open-file", filepath) + .pause(100); + browser.execute(() => { + document.getElementById("open-file").style.display = "none"; + }); + browser.waitForElementVisible("#input-text .cm-file-details"); +} + +/** @function + * Uploads a folder using the #open-folder input + * + * @param {Browser} browser - Nightwatch client + * @param {string} foldername - A path to a folder in the samples directory + */ +function uploadFolder(browser, foldername) { + const folderpath = require("path").resolve(__dirname + "/../samples/" + foldername); + + // The folder input cannot be interacted with by nightwatch while it is hidden, + // so we temporarily expose it for the purposes of this test. + browser.execute(() => { + document.getElementById("open-folder").style.display = "block"; + }); + browser + .pause(100) + .setValue("#open-folder", folderpath) + .pause(500); + browser.execute(() => { + document.getElementById("open-folder").style.display = "none"; + }); + browser.waitForElementVisible("#input-text .cm-file-details"); +} diff --git a/tests/samples/files/Hitchhikers_Guide.jpeg b/tests/samples/files/Hitchhikers_Guide.jpeg new file mode 100644 index 00000000..206f7754 Binary files /dev/null and b/tests/samples/files/Hitchhikers_Guide.jpeg differ diff --git a/tests/samples/files/TowelDay.jpeg b/tests/samples/files/TowelDay.jpeg new file mode 100644 index 00000000..52f03997 Binary files /dev/null and b/tests/samples/files/TowelDay.jpeg differ