Reject invalid uploads and expand flow tests.
This commit is contained in:
@@ -297,6 +297,18 @@ test("the source button swaps the editor for the textarea and back", () => {
|
||||
assert.equal(env.textarea.value, "<p>edited</p>");
|
||||
});
|
||||
|
||||
test("submitting in source mode sends the source edits", (t) => {
|
||||
const env = editorWindow("<p>Old story</p>");
|
||||
t.after(() => env.window.close());
|
||||
button(env.document, "Source code").click();
|
||||
const source = "<p>Source edit</p>\n<p>Second paragraph.</p>";
|
||||
env.textarea.value = source;
|
||||
env.textarea.form.dispatchEvent(new env.window.Event("submit", { cancelable: true }));
|
||||
assert.equal(env.textarea.value, source);
|
||||
assert.equal(env.editor.getContent(), source);
|
||||
assert.equal(new env.window.FormData(env.textarea.form).get("story"), source);
|
||||
});
|
||||
|
||||
test("setContent leaves source mode", () => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
button(env.document, "Source code").click();
|
||||
@@ -327,6 +339,111 @@ test("undo keeps a run of typing as one step", () => {
|
||||
assert.equal(env.textarea.value, "<p>a</p>");
|
||||
});
|
||||
|
||||
for (const pause of [999, 1000, 1001]) {
|
||||
test(`undo groups typing only when the pause is below one second (${pause} ms)`, (t) => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
t.after(() => env.window.close());
|
||||
let now = 10000;
|
||||
t.mock.method(env.window.Date, "now", () => now);
|
||||
type(env, "<p>ab</p>");
|
||||
now += pause;
|
||||
type(env, "<p>abc</p>");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, pause < 1000 ? "<p>a</p>" : "<p>ab</p>");
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>abc</p>");
|
||||
});
|
||||
}
|
||||
|
||||
test("a change of input type starts a separate undo step", (t) => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
t.after(() => env.window.close());
|
||||
t.mock.method(env.window.Date, "now", () => 10000);
|
||||
type(env, "<p>abc</p>", "insertText");
|
||||
type(env, "<p>ab</p>", "deleteContentBackward");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>abc</p>");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>a</p>");
|
||||
});
|
||||
|
||||
test("a new edit after undo clears the old redo history", (t) => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
t.after(() => env.window.close());
|
||||
let now = 10000;
|
||||
t.mock.method(env.window.Date, "now", () => now);
|
||||
type(env, "<p>ab</p>");
|
||||
now += 1000;
|
||||
type(env, "<p>abc</p>");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>ab</p>");
|
||||
type(env, "<p>abX</p>");
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>abX</p>");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>ab</p>");
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>abX</p>");
|
||||
});
|
||||
|
||||
test("undo keeps the latest 100 steps and redo restores them", (t) => {
|
||||
const env = editorWindow("<p>0</p>");
|
||||
t.after(() => env.window.close());
|
||||
let now = 10000;
|
||||
t.mock.method(env.window.Date, "now", () => now);
|
||||
for (let i = 1; i <= 105; i++) {
|
||||
now += 1000;
|
||||
type(env, `<p>${i}</p>`);
|
||||
}
|
||||
for (let i = 104; i >= 5; i--) {
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, `<p>${i}</p>`);
|
||||
}
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>5</p>", "the oldest five steps are discarded");
|
||||
for (let i = 6; i <= 105; i++) {
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, `<p>${i}</p>`);
|
||||
}
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>105</p>");
|
||||
});
|
||||
|
||||
test("undo restores a selection across nodes and redo restores the caret", (t) => {
|
||||
const original = "<p>alpha<em>beta</em></p>";
|
||||
const env = editorWindow(original);
|
||||
t.after(() => env.window.close());
|
||||
select(env.window, env.box.querySelector("p").firstChild, 2, env.box.querySelector("em").firstChild, 3);
|
||||
type(env, "<p>altered</p>");
|
||||
select(env.window, env.box.querySelector("p").firstChild, 4);
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, original);
|
||||
let range = env.window.getSelection().getRangeAt(0);
|
||||
assert.equal(range.startContainer, env.box.querySelector("p").firstChild);
|
||||
assert.equal(range.startOffset, 2);
|
||||
assert.equal(range.endContainer, env.box.querySelector("em").firstChild);
|
||||
assert.equal(range.endOffset, 3);
|
||||
assert.equal(range.toString(), "phabet");
|
||||
key(env.window, env.box, "y", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>altered</p>");
|
||||
range = env.window.getSelection().getRangeAt(0);
|
||||
assert.equal(range.startContainer, env.box.querySelector("p").firstChild);
|
||||
assert.equal(range.startOffset, 4);
|
||||
assert.equal(range.collapsed, true);
|
||||
});
|
||||
|
||||
test("redo from the browser's history input", (t) => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
t.after(() => env.window.close());
|
||||
type(env, "<p>b</p>");
|
||||
key(env.window, env.box, "z", { ctrlKey: true });
|
||||
assert.equal(env.textarea.value, "<p>a</p>");
|
||||
const event = new env.window.InputEvent("beforeinput", { inputType: "historyRedo", bubbles: true, cancelable: true });
|
||||
env.box.dispatchEvent(event);
|
||||
assert.ok(event.defaultPrevented);
|
||||
assert.equal(env.textarea.value, "<p>b</p>");
|
||||
});
|
||||
|
||||
test("undo from the browser's history input", () => {
|
||||
const env = editorWindow("<p>a</p>");
|
||||
type(env, "<p>b</p>");
|
||||
|
||||
@@ -139,6 +139,48 @@ test("review stops an empty story and warns about the length", () => {
|
||||
assert.deepEqual(w.confirms, []);
|
||||
});
|
||||
|
||||
const imageReviews = [
|
||||
{ name: "a new upload with no text", upload: true },
|
||||
{ name: "a new upload with short text", upload: true, story: "short text" },
|
||||
{ name: "a retained image with no text", uploaded: "5" },
|
||||
{ name: "a retained image with short text", uploaded: "5", story: "short text" },
|
||||
{ name: "a pending image with no text", uploaded: "pending" },
|
||||
{ name: "a pending image with short text", uploaded: "pending", story: "short text" },
|
||||
{ name: "an image removed without text", uploaded: "5", remove: true, alert: true, accepted: false },
|
||||
{ name: "a removed image and cancelled short text", uploaded: "5", remove: true, story: "short text", confirm: true, accepted: false },
|
||||
{ name: "a removed image and accepted short text", uploaded: "5", remove: true, story: "short text", confirm: true },
|
||||
{ name: "an image replaced with 100 words", uploaded: "5", remove: true, story: "word ".repeat(100) },
|
||||
{ name: "an empty image reference and no text", uploaded: "", alert: true, accepted: false },
|
||||
{ name: "a new upload replacing a removed image", upload: true, uploaded: "5", remove: true },
|
||||
];
|
||||
|
||||
for (const entry of imageReviews) {
|
||||
test(`review handles ${entry.name}`, (t) => {
|
||||
const w = publishWindow(entry.story || "");
|
||||
t.after(() => w.close());
|
||||
for (const [id, value] of [["title", "T"], ["author", "A"], ["email", "a@example.org"]])
|
||||
w.document.getElementById(id).value = value;
|
||||
if (entry.upload) {
|
||||
// jsdom cannot choose a file. review() reads only the input's value.
|
||||
Object.defineProperty(w.document.getElementById("upload"), "value", { value: "C:\\fakepath\\a.png" });
|
||||
}
|
||||
if (entry.uploaded !== undefined) {
|
||||
const uploaded = w.document.createElement("input");
|
||||
uploaded.type = "hidden";
|
||||
uploaded.id = "uploaded";
|
||||
uploaded.value = entry.uploaded;
|
||||
w.document.getElementById("form").appendChild(uploaded);
|
||||
}
|
||||
if (entry.remove) w.toggle_remove();
|
||||
w.confirmAnswer = entry.accepted !== false;
|
||||
assert.equal(w.review(), entry.accepted !== false);
|
||||
assert.equal(w.alerts.length, entry.alert ? 1 : 0);
|
||||
assert.equal(w.confirms.length, entry.confirm ? 1 : 0);
|
||||
if (entry.alert) assert.match(w.alerts[0], /haven't submitted anything/);
|
||||
if (entry.confirm) assert.match(w.confirms[0], /doesn't appear to be 100 words long/);
|
||||
});
|
||||
}
|
||||
|
||||
test("delete asks first, and then skips the review", () => {
|
||||
const w = publishWindow("");
|
||||
w.confirmAnswer = false;
|
||||
|
||||
Reference in New Issue
Block a user