Bug 384147 - "tabbing out list item doesn't merge with next list item at same depth" [p=mfenniak-moz@mathieu.fenniak.net (Mathieu Fenniak) r=glazou sr+a1.9=roc]
authorreed@reedloden.com
Tue, 02 Oct 2007 00:32:17 -0700
changeset 6523 439ed02bb67d4f88273e30e8539dbc2adbc65bd3
parent 6522 aeb35bcd5ca57c7c8cead1b42b16383dde4aadd1
child 6524 0063debd6b135d4d031f6d1ed9d2985f1a41765d
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglazou
bugs384147
milestone1.9a9pre
Bug 384147 - "tabbing out list item doesn't merge with next list item at same depth" [p=mfenniak-moz@mathieu.fenniak.net (Mathieu Fenniak) r=glazou sr+a1.9=roc]
editor/composer/test/Makefile.in
editor/composer/test/test_bug384147.html
editor/libeditor/html/nsHTMLEditRules.cpp
--- a/editor/composer/test/Makefile.in
+++ b/editor/composer/test/Makefile.in
@@ -41,12 +41,13 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = editor/composer/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
 		test_bug348497.html \
+		test_bug384147.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/editor/composer/test/test_bug384147.html
@@ -0,0 +1,207 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=384147
+-->
+<head>
+  <title>Test for Bug 384147</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=384147">Mozilla Bug 384147</a>
+<p id="display"></p>
+<div id="content" style="display: block">
+<div contentEditable id="editor"></div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript;version=1.7">
+
+/** Test for Bug 384147 **/
+
+SimpleTest.waitForExplicitFinish();
+
+// netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+var editor = document.getElementById("editor");
+
+editor.innerHTML = "<ol><li>Item 1</li><li>Item 2</li><ol><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>";
+editor.focus();
+
+// If executed directly, a race condition exists that will cause execCommand
+// to fail occasionally (but often).  Defer test execution to page load.
+addLoadEvent(function() {
+
+  var sel = window.getSelection();
+
+  // Test the affect that the tab key has on list items.  Each test is
+  // documented with the initial state of the list on the left, and the
+  // expected state of the list on the right.  {\t} indicates the list item
+  // that will be indented.  {\st} indicates that a shift-tab will be simulated
+  // on that list item, outdenting it.
+  //
+  // Note: any test failing will likely result in all following tests failing
+  // as well, since each test depends on the document being in a given state.
+  // Unfortunately, due to the problems getting document focus and key events
+  // to fire consistently, it's difficult to reset state between tests.
+  // If there are test failures here, only debug the first test failure.
+
+  // *** test 1 ***
+  //  1. Item 1                       1. Item 1
+  //  2. {\t}Item 2                     1. Item 2
+  //    1. Item 3                       2. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 1");
+
+  //  *** test 2 ***
+  //  1. Item 1                     1. Item 1
+  //    1. Item 2                     1. Item 2
+  //    2. {\t}Item 3                   1. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><ol><li>Item 3</li></ol></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 2");
+
+  //  *** test 3 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //      1. {\st}Item 3                2. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  document.execCommand("outdent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 3");
+
+  //  *** test 4 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //    2. {\st}Item 3                2. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  document.execCommand("outdent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 4");
+
+  //  *** test 5 ***
+  //  1. Item 1                       1. Item 1
+  //    1. {\st}Item 2                2. Item 2
+  //  2. Item 3                       3. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[1]);
+  document.execCommand("outdent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><li>Item 2</li><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 5");
+
+  //  *** test 6 ***
+  //  1. Item 1                       1. Item 1
+  //  2. {\t}Item 2                     1. Item 2
+  //  3. Item 3                       2. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li></ol><li>Item 3</li></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 6");
+
+  //  *** test 7 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //  2. {\t}Item 3                     2. Item 3
+  //  * Item 4                        * Item 4
+  //  * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[2]);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 7");
+
+  // That covers the basics of merging lists on indent and outdent.
+  // We also want to check that ul / ol lists won't be merged together,
+  // since they're different types of lists.
+  //  *** test 8 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //    2. Item 3                       2. Item 3
+  //  * {\t}Item 4                      * Item 4
+  //  * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li></ul><li>Item 5</li></ul>",
+    "html output doesn't match expected value in test 8");
+
+  // Better test merging with <ul> rather than <ol> too.
+  //  *** test 9 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //    2. Item 3                       2. Item 3
+  //    * Item 4                        * Item 4
+  //  * {\t}Item 5                      * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[4]);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
+    "html output doesn't match expected value in test 9");
+
+  // Same test as test 8, but with outdent rather than indent.
+  //  *** test 10 ***
+  //  1. Item 1                       1. Item 1
+  //    1. Item 2                       1. Item 2
+  //    2. Item 3                       2. Item 3
+  //    * {\st}Item 4                 * Item 4
+  //    * Item 5                        * Item 5
+  sel.removeAllRanges();
+  sel.selectAllChildren(editor.getElementsByTagName("li")[3]);
+  document.execCommand("outdent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
+    "html output doesn't match expected value in test 10");
+
+  // Test indenting multiple items at once.  Hold down "shift" and select
+  // upwards to get all the <ol> items and the first <ul> item.
+  //  *** test 11 ***
+  //  1. Item 1                       1. Item 1
+  //    1. {\t}Item 2                     1. Item 2
+  //    2. {\t}Item 3                     2. Item 3
+  //  * {\t}Item 4                      * Item 4
+  //    * Item 5                        * Item 5
+  sel.removeAllRanges();
+  var range = document.createRange();
+  range.setStart(editor.getElementsByTagName("li")[1], 0);
+  range.setEnd(editor.getElementsByTagName("li")[3], editor.getElementsByTagName("li")[3].childNodes.length);
+  sel.addRange(range);
+  document.execCommand("indent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><ol><li>Item 2</li><li>Item 3</li></ol></ol></ol><ul><ul><li>Item 4</li><li>Item 5</li></ul></ul>",
+    "html output doesn't match expected value in test 11");
+
+  // Test outdenting multiple items at once.  Selection is already ready...
+  //  *** test 12 ***
+  //  1. Item 1                       1. Item 1
+  //      1. {\st}Item 2                1. Item 2
+  //      2. {\st}Item 3                2. Item 3
+  //    * {\st}Item 4                 * Item 4
+  //    * Item 5                        * Item 5
+  document.execCommand("outdent", false, null);
+  ok(editor.innerHTML == "<ol><li>Item 1</li><ol><li>Item 2</li><li>Item 3</li></ol></ol><ul><li>Item 4</li><ul><li>Item 5</li></ul></ul>",
+    "html output doesn't match expected value in test 12");
+
+  SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/editor/libeditor/html/nsHTMLEditRules.cpp
+++ b/editor/libeditor/html/nsHTMLEditRules.cpp
@@ -3739,24 +3739,59 @@ nsHTMLEditRules::WillHTMLIndent(nsISelec
 
     PRInt32 offset;
     res = nsEditor::GetNodeLocation(curNode, address_of(curParent), &offset);
     if (NS_FAILED(res)) return res;
      
     // some logic for putting list items into nested lists...
     if (nsHTMLEditUtils::IsList(curParent))
     {
+      sibling = nsnull;
+
+      // Check for whether we should join a list that follows curNode.
+      // We do this if the next element is a list, and the list is of the
+      // same type (li/ol) as curNode was a part it.
+      mHTMLEditor->GetNextHTMLSibling(curNode, address_of(sibling));
+      if (sibling && nsHTMLEditUtils::IsList(sibling))
+      {
+        nsAutoString curListTag, siblingListTag;
+        nsEditor::GetTagString(curParent, curListTag);
+        nsEditor::GetTagString(sibling, siblingListTag);
+        if (curListTag == siblingListTag)
+        {
+          res = mHTMLEditor->MoveNode(curNode, sibling, 0);
+          if (NS_FAILED(res)) return res;
+          continue;
+        }
+      }
+
+      // Check for whether we should join a list that preceeds curNode.
+      // We do this if the previous element is a list, and the list is of
+      // the same type (li/ol) as curNode was a part of.
+      mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
+      if (sibling && nsHTMLEditUtils::IsList(sibling))
+      {
+        nsAutoString curListTag, siblingListTag;
+        nsEditor::GetTagString(curParent, curListTag);
+        nsEditor::GetTagString(sibling, siblingListTag);
+        if (curListTag == siblingListTag)
+        {
+          res = mHTMLEditor->MoveNode(curNode, sibling, -1);
+          if (NS_FAILED(res)) return res;
+          continue;
+        }
+      }
+
+      sibling = nsnull;
+
       // check to see if curList is still appropriate.  Which it is if
       // curNode is still right after it in the same list.
       if (curList)
-      {
-        sibling = nsnull;
         mHTMLEditor->GetPriorHTMLSibling(curNode, address_of(sibling));
-      }
-      
+
       if (!curList || (sibling && sibling != curList) )
       {
         nsAutoString listTag;
         nsEditor::GetTagString(curParent,listTag);
         ToLowerCase(listTag);
         // create a new nested list of correct type
         res = SplitAsNeeded(&listTag, address_of(curParent), &offset);
         if (NS_FAILED(res)) return res;