tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

undo-redo.html (7987B)


      1 <!DOCTYPE html>
      2 <script src="/resources/testharness.js"></script>
      3 <script src="/resources/testharnessreport.js"></script>
      4 <script src="/resources/testdriver.js"></script>
      5 <script src="/resources/testdriver-vendor.js"></script>
      6 <script src="/resources/testdriver-actions.js"></script>
      7 <script src="../include/editor-test-utils.js"></script>
      8 <iframe srcdoc=""></iframe>
      9 <script>
     10 "use strict";
     11 const iframe = document.querySelector("iframe");
     12 
     13 promise_test(async () => {
     14  await new Promise(resolve => {
     15    addEventListener("load", resolve, {once: true});
     16  });
     17 }, "Waiting for load...");
     18 
     19 /**
     20 * This test does NOT test whether the edit result is valid or invalid.
     21 * This test just tests whether "undo" and "redo" restores previous state
     22 * and additional "undo" and "redo" does not run unexpectedly.
     23 *
     24 * description: Set string to explain what's testing.
     25 * editorInnerHTML: Set initial innerHTML value of editor.
     26 * init: Set a function object if you need to test complicated cases, e.g.,
     27 *       testing with empty text node.
     28 * run: Set a function object which run something modifying the editor (or
     29 *      does nothing).
     30 * expectedUndoResult: Set an expected innerHTML result as string or array
     31 *                     of the string.  If this is not specified, it's compared
     32 *                     with editorInnerHTML value.
     33 * cleanUp: Set a function object if you need to clean something up after the
     34 *          test.
     35 */
     36 
     37 const tests = [
     38  {
     39    description: "insertParagraph at start of a paragraph",
     40    editorInnerHTML: "<p>[]abcdef</p>",
     41    run: (win, doc, editingHost) => {
     42      doc.execCommand("insertParagraph");
     43    },
     44  },
     45  {
     46    description: "insertParagraph at middle of a paragraph",
     47    editorInnerHTML: "<p>abc[]def</p>",
     48    run: (win, doc, editingHost) => {
     49      doc.execCommand("insertParagraph");
     50    },
     51  },
     52  {
     53    description: "insertParagraph at end of a paragraph",
     54    editorInnerHTML: "<p>abcdef[]</p>",
     55    run: (win, doc, editingHost) => {
     56      doc.execCommand("insertParagraph");
     57    },
     58  },
     59  {
     60    description: "insertParagraph at start of a listitem",
     61    editorInnerHTML: "<ul><li>[]abcdef</li></ul>",
     62    run: (win, doc, editingHost) => {
     63      doc.execCommand("insertParagraph");
     64    },
     65  },
     66  {
     67    description: "insertParagraph at middle of a listitem",
     68    editorInnerHTML: "<ul><li>abc[]def</li></ul>",
     69    run: (win, doc, editingHost) => {
     70      doc.execCommand("insertParagraph");
     71    },
     72  },
     73  {
     74    description: "insertParagraph at end of a listitem",
     75    editorInnerHTML: "<ul><li>abcdef[]</li></ul>",
     76    run: (win, doc, editingHost) => {
     77      doc.execCommand("insertParagraph");
     78    },
     79  },
     80  {
     81    description: "insertLineBreak at start of a paragraph",
     82    editorInnerHTML: "<p>[]abcdef</p>",
     83    run: (win, doc, editingHost) => {
     84      doc.execCommand("insertLineBreak");
     85    },
     86  },
     87  {
     88    description: "insertLineBreak at middle of a paragraph",
     89    editorInnerHTML: "<p>abc[]def</p>",
     90    run: (win, doc, editingHost) => {
     91      doc.execCommand("insertLineBreak");
     92    },
     93  },
     94  {
     95    description: "insertLineBreak at end of a paragraph",
     96    editorInnerHTML: "<p>abcdef[]</p>",
     97    run: (win, doc, editingHost) => {
     98      doc.execCommand("insertLineBreak");
     99    },
    100  },
    101  {
    102    description: "insertLineBreak at start of a listitem",
    103    editorInnerHTML: "<ul><li>[]abcdef</li></ul>",
    104    run: (win, doc, editingHost) => {
    105      doc.execCommand("insertLineBreak");
    106    },
    107  },
    108  {
    109    description: "insertLineBreak at middle of a listitem",
    110    editorInnerHTML: "<ul><li>abc[]def</li></ul>",
    111    run: (win, doc, editingHost) => {
    112      doc.execCommand("insertLineBreak");
    113    },
    114  },
    115  {
    116    description: "insertLineBreak at end of a listitem",
    117    editorInnerHTML: "<ul><li>abcdef[]</li></ul>",
    118    run: (win, doc, editingHost) => {
    119      doc.execCommand("insertLineBreak");
    120    },
    121  },
    122  {
    123    description: "delete at start of second paragraph",
    124    editorInnerHTML: "<p>abc</p><p>[]def</p>",
    125    run: (win, doc, editingHost) => {
    126      doc.execCommand("delete");
    127    }
    128  },
    129  {
    130    description: "forwarddelete at end of first paragraph",
    131    editorInnerHTML: "<p>abc[]</p><p>def</p>",
    132    run: (win, doc, editingHost) => {
    133      doc.execCommand("forwarddelete");
    134    }
    135  },
    136  {
    137    description: "delete at start of second paragraph starting with an emoji",
    138    editorInnerHTML: "<p>abc\uD83D\uDC49</p><p>[]\uD83D\uDC48def</p>",
    139    run: (win, doc, editingHost) => {
    140      doc.execCommand("delete");
    141    }
    142  },
    143  {
    144    description: "forwarddelete at end of first paragraph ending with an emoji",
    145    editorInnerHTML: "<p>abc\uD83D\uDC49[]</p><p>\uD83D\uDC48def</p>",
    146    run: (win, doc, editingHost) => {
    147      doc.execCommand("forwarddelete");
    148    }
    149  },
    150  {
    151    description: "delete at start of second paragraph ending with a non editable item",
    152    editorInnerHTML: "<p>A line</p><p>[]Second line with <b contenteditable='false'>non-editable item</b></p>",
    153    run: (win, doc, editingHost) => {
    154      doc.execCommand("delete");
    155    }
    156  }
    157 ];
    158 
    159 for (const curTest of tests) {
    160  promise_test(async t => {
    161    await new Promise(resolve => {
    162      iframe.addEventListener("load", resolve, {once: true});
    163      iframe.srcdoc = "<html><body><div contenteditable></div></body></html>";
    164    });
    165    const contentDocument = iframe.contentDocument;
    166    const contentWindow = iframe.contentWindow;
    167    contentWindow.focus();
    168    const editingHost = contentDocument.querySelector("div[contenteditable]");
    169    const utils = new EditorTestUtils(editingHost, window);
    170    utils.setupEditingHost(curTest.editorInnerHTML);
    171    contentDocument.documentElement.scrollHeight;  // flush pending things
    172    if (typeof curTest.init == "function") {
    173      await curTest.init(contentWindow, contentDocument, editingHost);
    174    }
    175    const initialValue = editingHost.innerHTML;
    176    await curTest.run(contentWindow, contentDocument, editingHost);
    177    const newValue = editingHost.innerHTML;
    178    test(t2 => {
    179      const ret = contentDocument.execCommand("undo");
    180      if (curTest.expectedUndoResult !== undefined) {
    181        if (typeof curTest.expectedUndoResult == "string") {
    182          assert_equals(
    183            editingHost.innerHTML,
    184            curTest.expectedUndoResult,
    185            `${t2.name}: should restore the innerHTML value`
    186          );
    187        } else {
    188          assert_in_array(
    189            editingHost.innerHTML,
    190            curTest.expectedUndoResult,
    191            `${t2.name}: should restore one of the innerHTML values`
    192          );
    193        }
    194      } else {
    195        assert_equals(
    196          editingHost.innerHTML,
    197          initialValue,
    198          `${t2.name}: should restore the initial innerHTML value`
    199        );
    200      }
    201      assert_true(ret, `${t2.name}: execCommand("undo") should return true`);
    202    }, `${t.name} - first undo`);
    203    test(t3 => {
    204      const ret = contentDocument.execCommand("redo");
    205      assert_equals(
    206        editingHost.innerHTML,
    207        newValue,
    208        `${t3.name}: should restore the modified innerHTML value`
    209      );
    210      assert_true(ret, `${t3.name}: execCommand("redo") should return true`);
    211    }, `${curTest.description} - first redo`);
    212    test(t4 => {
    213      const ret = contentDocument.execCommand("redo");
    214      assert_equals(
    215        editingHost.innerHTML,
    216        newValue,
    217        `${t4.name}: should not modify the modified innerHTML value`
    218      );
    219      assert_false(ret, `${t4.name}: execCommand("redo") should return false`);
    220    }, `${curTest.description} - second redo`);
    221    if (typeof curTest.cleanUp == "function") {
    222      await curTest.cleanUp(contentWindow, contentDocument, editingHost);
    223    }
    224    await new Promise(resolve => {
    225      iframe.addEventListener("load", resolve, {once: true});
    226      iframe.srcdoc = "";
    227    });
    228    contentDocument.documentElement.scrollHeight; // flush pending things
    229    await new Promise(resolve =>
    230      requestAnimationFrame(
    231        () => requestAnimationFrame(resolve)
    232      )
    233    );
    234  }, curTest.description);
    235 }
    236 </script>