<!
DOCTYPE html>
<
script src=
"/resources/testharness.js"></
script>
<
script src=
"/resources/testharnessreport.js"></
script>
<
script src=
"/resources/testdriver.js"></
script>
<
script src=
"/resources/testdriver-vendor.js"></
script>
<
script src=
"/resources/testdriver-actions.js"></
script>
<
script src=
"../include/editor-test-utils.js"></
script>
<
iframe srcdoc=
""></
iframe>
<
script>
"use strict";
const
iframe = document.querySelector(
"iframe");
promise_test(async () => {
await new Promise(resolve => {
addEventListener(
"load", resolve, {once: true});
});
},
"Waiting for load...");
/**
* This test does NOT test whether the edit result is valid or invalid.
* This test just tests whether
"undo" and
"redo" restores previous state
* and additional
"undo" and
"redo" does not run unexpectedly.
*
* description: Set string to explain what
's testing.
* editorInnerHTML: Set initial innerHTML value of editor.
* init: Set a function
object if you need to test complicated cases, e.g.,
* testing with empty text node.
* run: Set a function
object which run something modifying the editor (or
* does nothing).
* expectedUndoResult: Set an expected innerHTML result as string or array
* of the string. If this is not specified, it
's compared
* with editorInnerHTML value.
* cleanUp: Set a function
object if you need to clean something up after the
* test.
*/
const tests = [
{
description:
"insertParagraph at start of a paragraph",
editorInnerHTML:
"[]abcdef
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertParagraph at middle of a paragraph",
editorInnerHTML:
"abc[]def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertParagraph at end of a paragraph",
editorInnerHTML:
"abcdef[]
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertParagraph at start of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertParagraph at middle of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertParagraph at end of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertParagraph");
},
},
{
description:
"insertLineBreak at start of a paragraph",
editorInnerHTML:
"[]abcdef
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"insertLineBreak at middle of a paragraph",
editorInnerHTML:
"abc[]def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"insertLineBreak at end of a paragraph",
editorInnerHTML:
"abcdef[]
",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"insertLineBreak at start of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"insertLineBreak at middle of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"insertLineBreak at end of a listitem",
editorInnerHTML:
"",
run: (win, doc, editingHost) => {
doc.execCommand(
"insertLineBreak");
},
},
{
description:
"delete at start of second paragraph",
editorInnerHTML:
"abc
[]def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"delete");
}
},
{
description:
"forwarddelete at end of first paragraph",
editorInnerHTML:
"abc[]
def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"forwarddelete");
}
},
{
description:
"delete at start of second paragraph starting with an emoji",
editorInnerHTML:
"abc\uD83D\uDC49
[]\uD83D\uDC48def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"delete");
}
},
{
description:
"forwarddelete at end of first paragraph ending with an emoji",
editorInnerHTML:
"abc\uD83D\uDC49[]
\uD83D\uDC48def
",
run: (win, doc, editingHost) => {
doc.execCommand(
"forwarddelete");
}
},
{
description:
"delete at start of second paragraph ending with a non editable item",
editorInnerHTML:
"A line
[]Second line with non-editable item
",
run: (win, doc, editingHost) => {
doc.execCommand(
"delete");
}
}
];
for (const curTest of tests) {
promise_test(async t => {
await new Promise(resolve => {
iframe.addEventListener(
"load", resolve, {once: true});
iframe.srcdoc =
"";
});
const contentDocument =
iframe.contentDocument;
const contentWindow =
iframe.contentWindow;
contentWindow.focus();
const editingHost = contentDocument.querySelector(
"div[contenteditable]");
const utils = new EditorTestUtils(editingHost, window);
utils.setupEditingHost(curTest.editorInnerHTML);
contentDocument.documentElement.scrollHeight; // flush pending things
if (typeof curTest.init ==
"function") {
await curTest.init(contentWindow, contentDocument, editingHost);
}
const initialValue = editingHost.innerHTML;
await curTest.run(contentWindow, contentDocument, editingHost);
const newValue = editingHost.innerHTML;
test(t2 => {
const ret = contentDocument.execCommand(
"undo");
if (curTest.expectedUndoResult !== undefined) {
if (typeof curTest.expectedUndoResult ==
"string") {
assert_equals(
editingHost.innerHTML,
curTest.expectedUndoResult,
`${t2.name}: should restore the innerHTML value`
);
} else {
assert_in_array(
editingHost.innerHTML,
curTest.expectedUndoResult,
`${t2.name}: should restore one of the innerHTML values`
);
}
} else {
assert_equals(
editingHost.innerHTML,
initialValue,
`${t2.name}: should restore the initial innerHTML value`
);
}
assert_true(ret, `${t2.name}: execCommand(
"undo") should return true`);
}, `${t.name} - first undo`);
test(t3 => {
const ret = contentDocument.execCommand(
"redo");
assert_equals(
editingHost.innerHTML,
newValue,
`${t3.name}: should restore the modified innerHTML value`
);
assert_true(ret, `${t3.name}: execCommand(
"redo") should return true`);
}, `${curTest.description} - first redo`);
test(t4 => {
const ret = contentDocument.execCommand(
"redo");
assert_equals(
editingHost.innerHTML,
newValue,
`${t4.name}: should not modify the modified innerHTML value`
);
assert_false(ret, `${t4.name}: execCommand(
"redo") should return false`);
}, `${curTest.description} - second redo`);
if (typeof curTest.cleanUp ==
"function") {
await curTest.cleanUp(contentWindow, contentDocument, editingHost);
}
await new Promise(resolve => {
iframe.addEventListener(
"load", resolve, {once: true});
iframe.srcdoc =
"";
});
contentDocument.documentElement.scrollHeight; // flush pending things
await new Promise(resolve =>
requestAnimationFrame(
() => requestAnimationFrame(resolve)
)
);
}, curTest.description);
}
</
script>