// publish/utilities.js (word count, tabs, form checks) and utilities.js (the phone menu) "use strict"; const test = require("node:test"); const assert = require("node:assert/strict"); const { load, STORY_FORM } = require("./helpers"); const FORM = STORY_FORM + ` `; function publishWindow(story = "") { const window = load(["publish/utilities.js"], FORM, story); window.alerts = []; window.confirms = []; window.confirmAnswer = true; window.alert = (message) => window.alerts.push(message); window.confirm = (message) => { window.confirms.push(message); return window.confirmAnswer; }; return window; } const window = publishWindow(); test("wc counts words", () => { assert.equal(window.wc(""), 0); assert.equal(window.wc(" "), 0); assert.equal(window.wc("one"), 1); assert.equal(window.wc("one two three"), 3); assert.equal(window.wc(" padded words "), 2); }); test("wc leaves out the HTML", () => { assert.equal(window.wc("

one two

\n

three

"), 3); assert.equal(window.wc("one
two
three"), 3); assert.equal(window.wc("

 

"), 0); assert.equal(window.wc('link'), 1); }); test("wc counts the words around a dash as two", () => { assert.equal(window.wc("left--right"), 2); assert.equal(window.wc("left — right"), 2); assert.equal(window.wc("left–right"), 2); assert.equal(window.wc("well-known"), 1); }); test("wc does not count punctuation on its own", () => { assert.equal(window.wc("one - two ... three"), 3); }); test("wc counts one hundred words", () => { assert.equal(window.wc("

" + "word ".repeat(100) + "

"), 100); }); test("the word counter shows the count and whether it is 100", () => { const w = publishWindow(); const story = w.document.getElementById("story"); const counter = w.document.getElementById("wordcounter"); story.value = "one"; assert.equal(w.updateWordCount(), true); assert.equal(counter.innerHTML, "1 word"); assert.equal(counter.className, "counter"); story.value = "word ".repeat(100); w.updateWordCount(); assert.equal(counter.innerHTML, "100 words"); assert.equal(counter.className, "counter yes"); story.value = "word ".repeat(101); w.updateWordCount(); assert.equal(counter.className, "counter no"); }); test("the word counter does nothing without its elements", () => { const w = load(["publish/utilities.js"], ""); assert.equal(w.updateWordCount(), true); }); test("showHide switches between the text and the image tabs", () => { const w = publishWindow(); const $ = (id) => w.document.getElementById(id); let focused = 0; w.storyEditor = { focus: () => focused++ }; assert.equal(w.showHide("upload"), true); assert.deepEqual([$("textparent").style.display, $("uploadparent").style.display, $("wordcounter").style.display], ["none", "block", "none"]); assert.equal(w.showHide("text"), true); assert.deepEqual([$("textparent").style.display, $("uploadparent").style.display, $("wordcounter").style.display], ["block", "none", "block"]); assert.equal(focused, 1); assert.equal(w.showHide("other"), false); }); test("review asks for a title, then an author", () => { const w = publishWindow("

words

"); assert.equal(w.review(), false); assert.match(w.alerts[0], /add a title/); w.document.getElementById("title").value = "T"; assert.equal(w.review(), false); assert.match(w.alerts[1], /haven't added an author/); }); test("review asks about a missing email address", () => { const w = publishWindow("word ".repeat(100)); w.document.getElementById("title").value = "T"; w.document.getElementById("author").value = "A"; w.confirmAnswer = false; assert.equal(w.review(), false); assert.match(w.confirms[0], /haven't added an email address/); w.confirmAnswer = true; assert.equal(w.review(), true); }); test("review stops an empty story and warns about the length", () => { const w = publishWindow(""); for (const [id, value] of [["title", "T"], ["author", "A"], ["email", "a@example.org"]]) w.document.getElementById(id).value = value; assert.equal(w.review(), false); assert.match(w.alerts[0], /haven't submitted anything/); w.document.getElementById("story").value = "

only a few words

"; w.confirmAnswer = false; assert.equal(w.review(), false); assert.match(w.confirms[0], /doesn't appear to be 100 words long/); w.confirmAnswer = true; assert.equal(w.review(), true); w.confirms.length = 0; w.document.getElementById("story").value = "word ".repeat(100); assert.equal(w.review(), true); assert.deepEqual(w.confirms, []); }); test("delete asks first, and then skips the review", () => { const w = publishWindow(""); w.confirmAnswer = false; assert.equal(w.do_delete(), false); assert.equal(w.eval("is_delete"), 0); w.confirmAnswer = true; assert.equal(w.do_delete(), true); assert.equal(w.review(), true); assert.deepEqual(w.alerts, []); }); test("toggle_remove marks the image for removal and back", () => { const w = publishWindow(); const flag = w.document.getElementById("remove_img"); const icon = w.document.getElementById("remove_img_icon"); w.toggle_remove(); assert.equal(flag.value, "1"); assert.equal(icon.getAttribute("src"), "/graphics/trashcan-open.png"); assert.equal(icon.title, "Image is marked for removal."); w.toggle_remove(); assert.equal(flag.value, "0"); assert.equal(icon.getAttribute("src"), "/graphics/trashcan.png"); assert.equal(icon.title, "Image has been restored; it will not be removed."); }); test("the phone menu closes on Escape and on a click outside it", () => { const w = load(["utilities.js"], `
text
`); w.document.dispatchEvent(new w.Event("DOMContentLoaded")); const toggle = w.document.querySelector("input.menu-toggle"); toggle.checked = true; w.document.getElementById("inside").dispatchEvent(new w.MouseEvent("click", { bubbles: true })); assert.equal(toggle.checked, true, "a click in the menu keeps it open"); w.document.getElementById("outside").dispatchEvent(new w.MouseEvent("click", { bubbles: true })); assert.equal(toggle.checked, false); toggle.checked = true; w.document.dispatchEvent(new w.KeyboardEvent("keyup", { key: "a", keyCode: 65, bubbles: true })); assert.equal(toggle.checked, true); w.document.dispatchEvent(new w.KeyboardEvent("keyup", { key: "Escape", keyCode: 27, bubbles: true })); assert.equal(toggle.checked, false); });