2011-04-13 Xiaomei Ji <xji@chromium.org>
[WebKit-https.git] / LayoutTests / editing / selection / move-by-word-visually.html
1 <!DOCTYPE HTML>
2 <html>
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5 <style>
6 div.test {
7     -webkit-user-modify: read-write;
8     padding: 4px;
9     border: 1px dashed lightblue;
10     margin: 4px 4px 4px 24px;
11     outline: none;
12     font-family: Lucida Grande;
13     counter-increment: test-number;
14 }
15 div.test:before { content: counter(test-number); position: absolute; left: 8px; font-size: x-small; text-align: right; width: 20px; }
16 div.test span { background-color: #def; }
17 div.test img { width: 1em; height: 1em; background-color: lightgreen; }
18 div.test img + img { background-color: lightblue; }
19 div.test div { border: 1px dashed pink; padding: 3px; height: 2em; }
20 test_move_by_word {display: none;}
21 </style>
22 <script>
23 var messages = [];
24
25 function log(message)
26 {
27     messages.push(message);
28 }
29
30 function flushLog()
31 {
32     document.getElementById("console").appendChild(document.createTextNode(messages.join("")));
33 }
34
35 function caretCoordinates()
36 {
37     if (!window.textInputController)
38         return { x: 0, y :0 };
39     var caretRect = textInputController.firstRectForCharacterRange(textInputController.selectedRange()[0], 0);
40     return { x: caretRect[0], y: caretRect[1] };
41 }
42
43
44 function fold(string)
45 {
46     var result = "";
47     for (var i = 0; i < string.length; ++i) {
48         var char = string.charCodeAt(i);
49         if (char >= 0x05d0)
50             char -= 0x058f;
51         else if (char == 10) {
52             result += "\\n";
53             continue;
54         }
55         result += String.fromCharCode(char);
56     }
57     return result;
58 }
59
60 function logPositions(positions)
61 {
62     for (var i = 0; i < positions.length; ++i) {
63         if (i) {
64             if (positions[i].node != positions[i - 1].node)
65                 log("]");
66             log(", ");
67         }
68         if (!i || positions[i].node != positions[i - 1].node)
69             log((positions[i].node instanceof Text ? '"' + fold(positions[i].node.data) + '"' : "<" + positions[i].node.tagName + ">") + "[");
70         log(positions[i].offset);
71     }
72     log("]");
73 }
74
75 function nodeOfWordBreak(nodeAndOffset)
76 {
77     var node = document.getElementById(nodeAndOffset[0]).firstChild;
78     if (nodeAndOffset.length == 3) {
79         var childIndex = nodeAndOffset[2];
80         for (var i = 0; i < childIndex - 1; ++i) {
81             node = node.nextSibling;
82         }
83     }
84     return node;
85 }
86
87 var wordBreaks;
88
89 function logWordBreak(index)
90 {
91     var wordBreak = wordBreaks[index];
92     if (wordBreak.search(',') == -1)
93         log(wordBreak);
94     else {
95         var nodeAndOffset = wordBreak.split(',');
96         var node = nodeOfWordBreak(nodeAndOffset);
97
98         var differentNode = false;
99         if (index != 0) {
100             differentNode = nodeOfWordBreak(nodeAndOffset) != nodeOfWordBreak(wordBreaks[index - 1].split(','));
101         
102         }
103         if (differentNode == true)
104             log("]");
105         if (index == 0 || differentNode == true)
106             log((node instanceof Text ? '"' + fold(node.data) + '"' : "<" + node.tagName + ">") + "[");
107         log(nodeAndOffset[1]);
108     }
109 }
110
111 function positionEqualToWordBreak(position, wordBreak)
112 {
113     if (wordBreak.search(',') == -1)
114         return position.offset == wordBreak;
115     else {
116         var nodeAndOffset = wordBreak.split(',');
117         return position.node == nodeOfWordBreak(nodeAndOffset) && position.offset == nodeAndOffset[1];
118     }
119 }
120
121 function validateData(positions)
122 {
123     var equal = true;
124     if (positions.length != wordBreaks.length)
125         equal = false;
126     for (var i = 0; i < wordBreaks.length - 1; ++i) {
127         if (!positionEqualToWordBreak(positions[i], wordBreaks[i])) {
128             equal = false;
129             break;
130         }
131     }
132     if (equal == false) {
133         log("    FAIL expected: [");
134         for (var i = 0; i < wordBreaks.length; ++i) {
135             logWordBreak(i);
136             if (i != wordBreaks.length - 1)
137                 log(", ");
138         }
139         log("]");
140     }
141 }
142
143 function collectWordBreaks(test, searchDirection)
144 {
145     var title;
146     if (searchDirection == "right") 
147         title = test.title.split("|")[0];
148     else
149         title = test.title.split("|")[1];
150
151     var pattern = /\[(.+?)\]/g;
152     var result;
153     wordBreaks = [];
154     while ((result = pattern.exec(title)) != null) {
155         wordBreaks.push(result[1]);
156     }
157     if (wordBreaks.length == 0) {
158         wordBreaks = title.split(" ");
159     }
160 }
161
162 function setPosition(sel, node, wordBreak)
163 {
164     if (wordBreak.search(',') == -1)
165         sel.setPosition(node, wordBreak);
166     else {
167         var nodeAndOffset = wordBreak.split(',');
168         sel.setPosition(nodeOfWordBreak(nodeAndOffset), nodeAndOffset[1]);
169     }
170 }
171
172 function moveByWord(sel, test, searchDirection, dir)
173 {
174     log("Move " + searchDirection + " by one word\n");
175     var prevOffset = sel.anchorOffset;
176     var prevNode = sel.anchorNode;
177     var positions = [];
178     positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
179
180     while (1) {
181         sel.modify("move", searchDirection, "-webkit-visual-word");
182         if (prevNode == sel.anchorNode && prevOffset == sel.anchorOffset)
183             break;
184         positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
185         prevNode = sel.anchorNode;
186         prevOffset = sel.anchorOffset;
187     };
188     logPositions(positions);
189     collectWordBreaks(test, searchDirection);
190     validateData(positions);
191     log("\n");
192 }
193
194 function moveByWordForEveryPosition(sel, test, dir)
195 {
196     // Check ctrl-right-arrow works for every position.
197     sel.setPosition(test, 0);
198     var direction = "right";
199     if (dir == "rtl")
200         direction = "left";    
201     moveByWord(sel, test, direction, dir);    
202
203     sel.modify("move", "forward", "lineBoundary");
204     // Check ctrl-left-arrow works for every position.
205     if (dir == "ltr")
206         direction = "left";
207     else
208         direction = "right";    
209     moveByWord(sel, test, direction, dir);    
210 }
211
212 function runMoveLeftRight(tests, unit)
213 {
214     var sel = getSelection();
215     for (var i = 0; i < tests.length; ++i) {
216         var positionsMovingRight;
217         var positionsMovingLeft;
218
219         if (tests[i].getAttribute("dir") == 'ltr')
220         {
221             log("Test " + (i + 1) + ", LTR:\n");
222             moveByWordForEveryPosition(sel, tests[i], "ltr");
223         } else {
224             log("Test " + (i + 1) + ", RTL:\n");
225             moveByWordForEveryPosition(sel, tests[i], "rtl");
226         }
227
228     }
229 }
230
231 function runSpecificTest(tests)
232 {
233     var sel = getSelection();
234     log("\n======== Move By Word Specific Test ====\n");
235     for (var i = 0; i < tests.length; ++i) {
236         log("Test " + (i + 1) + "\n");
237         var components = tests[i].title.split(" ");
238         var startOffset = components[0];
239         var movingDirection = components[1];
240         var endingNode = components[2];
241         var endingOffset = components[3];
242  
243         log("Move " + movingDirection + " by one word\n");
244
245         var positions = [];
246         sel.setPosition(tests[i].firstChild, startOffset);
247         positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
248
249         sel.modify("move", movingDirection, "-webkit-visual-word");
250         positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
251
252         logPositions(positions);
253
254         if (sel.anchorNode.parentNode != document.getElementById(endingNode) || sel.anchorOffset != endingOffset) {
255             log("    FAIL: expected ");
256             var endingChildNode = document.getElementById(endingNode).firstChild;
257             log((endingChildNode instanceof Text ? '"' + fold(endingChildNode.data) + '"' : "<" + endingChildNode.tagName + ">") + "[" + endingOffset + "]");
258         }
259     }
260     document.getElementById("testMoveByWord").style.display = "none";
261 }
262
263 function runTest() {
264     log("\n======== Move By Word ====\n");
265     var tests = document.getElementsByClassName("test_move_by_word");
266     runMoveLeftRight(tests, "word");
267     
268     tests = document.getElementsByClassName("specific_test");
269     runSpecificTest(tests);
270 }
271
272 onload = function() {
273     try {
274         runTest();
275     } finally {
276         flushLog();
277     }
278 };
279
280 if (window.layoutTestController)
281     layoutTestController.dumpAsText();
282 </script>
283 </head>
284 <body>
285 <div id="testMoveByWord">
286 <!-- 
287 Title saves the word breaks.
288 The format of title is "xxx|xxxx".
289
290 The sequence on the left of "|" is word boundaries when moving caret from left to right.
291 The sequence on the right of "|" is word boundaries when moving caret from right to left.
292
293 If there is a single node in the line, the sequence are offsets.
294 If there are multiple nodes in the line, the sequence is array of [anchor_node_id, offset, child_node_index], 
295 where child_node_index is optional, default is the first child of the anchor node.
296 -->
297 <div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
298
299 <!-- pure English -->
300 <div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
301 <div dir=rtl class="test_move_by_word" title="19 3 7 11 15 0|0 15 11 7 3" contenteditable>abc def hij opq rst</div>
302
303 <!-- pure Hebrew -->
304 <div dir=ltr class="test_move_by_word" title="0 15 11 7 3|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
305 <div dir=rtl class="test_move_by_word" title="19 16 12 8 4 0|0 4 8 12 16" contenteditable>ששש נננ בבב גגג קקק</div>
306
307 <!-- bidi text -->
308 <!-- English Hebrew English -->
309 <div dir=ltr class="test_move_by_word" title="0 4 8 12 19 15 24 28 32|35 32 28 24 15 19 12 8 4 0" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
310 <div dir=rtl class="test_move_by_word" title="35 27 31 24 20 16 12 3 7 0|0 7 3 12 16 20 24 31 27" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
311
312 <div dir=ltr class="test_move_by_word" title="0 4 8 11 16 20|23 20 16 11 8 4 0" contenteditable>abc def שנב סטז uvw xyz</div>
313 <div dir=rtl class="test_move_by_word" title="23 19 16 12 8 3 0|0 3 8 12 16 19" contenteditable>abc def שנב סטז uvw xyz</div>
314
315 <div dir=ltr class="test_move_by_word" title="0 4 8|11 8 4 0" contenteditable>aaa אאא bbb</div>
316 <div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8 11" contenteditable>aaa אאא bbb</div>
317
318 <!-- Hebrew English Hebrew -->
319 <div dir=ltr class="test_move_by_word" title="0 7 3 12 16 20 24 31 27|35 27 31 24 20 16 12 3 7 0" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
320 <div dir=rtl class="test_move_by_word" title="35 32 28 24 15 19 12 8 4 0|0 4 8 12 19 15 24 28 32" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
321
322 <div dir=ltr class="test_move_by_word" title="0 3 8 12 16 19|23 19 16 12 8 3 0" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
323 <div dir=rtl class="test_move_by_word" title="23 20 16 11 8 4 0|0 4 8 11 16 20" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
324
325 <div dir=ltr class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
326 <div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8" contenteditable>שנב abc סטז</div>
327
328 <!-- multiple spaces between word. 
329      FAILED: word break between "def" and "hij" is unreachable.
330 -->
331 <div dir=ltr class="test_move_by_word" title="0 4 11 15|18 15 11 4 0" contenteditable>abc def    hij opq</div>
332
333 <!-- Inline element -->
334 <div dir=ltr id="div_1" class="test_move_by_word" title="[div_1, 0][div_1, 3]|[span_1, 2][div_1, 3][div_1,0]" contenteditable>אאא <span id="span_1">בב</span></div>
335 <div dir=rtl id="div_2" class="test_move_by_word" title="[span_2, 2][div_2, 4][div_2, 0]|[div_2, 0][div_2, 4]" contenteditable>אאא <span id="span_2">בב</span></div>
336
337 <!-- pure English in inline element with same or different directionality from its parent -->
338 <div dir=ltr id="div_3" class="test_move_by_word" title="[div_3, 0][div_3, 4][div_3, 8][span_3, 4][div_3, 1, 3][div_3, 5, 3]|[div_3, 8, 3][div_3, 5, 3][div_3, 1, 3][span_3, 4][div_3, 8][div_3, 4][div_3, 0]" contenteditable>abc def <span id="span_3">hij opq</span> rst uvw</div>
339
340 <!-- FAILED -->
341 <div dir=rtl id="div_4" class="test_move_by_word" title="[div_4, 8, 3][div_4, 3, 1][div_4, 7, 1][span_4, 3, 1][div_4, 0, 3][div_4, 4, 3][div_4, 0, 1]|[div_4, 0, 1][div_4, 4, 3][div_4, 0, 3][span_4, 3, 1][div_4, 7, 1][div_4, 3, 1]" contenteditable>abc def <span id="span_4">hij opq</span> rst uvw</div>
342
343 <!-- FAILED -->
344 <div id="div_5" dir=rtl class="test_move_by_word" title="[div_5, 8, 3][div_5, 3, 1][div_5, 7, 1][span_5, 3, 1][div_5, 0, 3][div_5, 4, 3][div_5, 0, 1]|[div_5, 0, 1][div_5, 4, 3][div_5, 0, 3][span_5, 3, 1][div_5, 7, 1][div_5, 3, 1]"contenteditable>abc def <span dir=ltr id="span_5">hij opq</span> rst uvw</div>
345
346 <div id="div_6" dir=ltr class="test_move_by_word" title="[div_6, 0, 1][div_6, 4, 1][div_6, 8, 1][span_6, 4, 1][div_6, 1, 3][div_6, 5, 3]|[div_6, 8, 3][div_6, 5, 3][div_6, 1, 3][span_6, 4, 1][div_6, 8, 1][div_6, 4, 1][div_6, 0, 1]" contenteditable>abc def <span dir=rtl id="span_6">hij opq</span> rst uvw</div>
347
348 <!-- pure Hebrew in inline element with same or different directionality from its parent -->
349
350 <div id="div_7" dir=rtl class="test_move_by_word" title="[div_7, 7, 3][div_7, 4, 3][span_7, 4, 1][div_7, 8, 1][div_7, 4, 1][div_7, 0, 1]|[div_7, 0, 1][div_7, 4, 1][div_7, 8, 1][span_7, 4, 1][div_7, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_7">היח ופק</span>ווש כטז</div>
351
352 <div id="div_8" dir=ltr class="test_move_by_word" title="[div_8, 0, 1][div_8, 3, 3][span_8, 3, 1][div_8, 7, 1][div_8, 3, 1]|[div_8, 7, 3][div_8, 3, 1][div_8, 7, 1][span_8, 3, 1][div_8, 3, 3][div_8, 0, 1]" contenteditable>אבד דעפ <span dir=rtl id="span_8">היח ופק</span>ווש כטז</div>
353
354 <div id="div_9" dir=rtl class="test_move_by_word" title="[div_9, 7, 3][div_9, 4, 3][span_9, 4, 1][div_9, 8, 1][div_9, 4, 1][div_9, 0, 1]|[div_9, 0, 1][div_9, 4, 1][div_9, 8, 1][span_9, 4, 1][div_9, 4, 3]" contenteditable>אבד דעפ <span id="span_9">היח ופק</span>ווש כטז</div>
355
356 <div id="div_10" dir=ltr class="test_move_by_word" title="[div_10, 0, 1][div_10, 3, 3][span_10, 3, 1][div_10, 7, 1][div_10, 3, 1]|[div_10, 7, 3][div_10, 3, 1][div_10, 7, 1][span_10, 3, 1][div_10, 3, 3][div_10, 0, 1]" contenteditable>אבד דעפ <span id="span_10">היח ופק</span>ווש כטז</div>
357
358 <!-- bidi in inline element with same or different directionality from its parent -->
359 <div id="div_11" dir=rtl class="test_move_by_word" title="[div_11, 7, 3][div_11, 4, 3][span_11, 3, 1][div_11, 8, 1][div_11, 4, 1][div_11, 0, 1]|[div_11, 0, 1][div_11, 4, 1][div_11, 8, 1][span_11, 3, 1][div_11, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_11">abc def</span>ווש כטז</div>
360
361 <!-- FAIL -->
362 <div id="div_12" dir=ltr class="test_move_by_word" title="[div_12, 0, 1][div_12, 3, 3][div_12, 8, 1][span_12, 4, 1][div_12, 7, 1][div_12, 3, 1]|[div_12, 7, 3][div_12, 3, 1][div_12, 7, 1][span_12, 4, 1][div_12, 8, 1][div_12, 3, 3][div_12, 0]" contenteditable>אבד דעפ <span dir=rtl id="span_12">abc def</span>ווש כטז</div>
363
364 <div id="div_13" dir=rtl class="test_move_by_word" title="[div_13, 7, 3][div_13, 4, 3][span_13, 3, 1][div_13, 8, 1][div_13, 4, 1][div_13, 0, 1]|[div_13, 0, 1][div_13, 4, 1][div_13, 8, 1][span_13, 3, 1][div_13, 4, 3]" contenteditable>אבד דעפ <span id="span_13">abc def</span>ווש כטז</div>
365
366 <div id="div_14" dir=ltr class="test_move_by_word" title="[div_14, 0, 1][div_14, 3, 1][div_14, 8, 1][span_14, 4, 1][div_14, 3, 3]|[div_14, 7, 3][div_14, 3, 3][span_14, 4, 1][div_14, 8, 1][div_14, 4, 1][div_14, 0, 1]" contenteditable>אבד דעפ <span id="span_14">abc def</span>ווש כטז</div>
367
368 <!-- FAILED -->
369 <div id="div_15" dir=rtl class="test_move_by_word" title="[div_15, 11, 3][div_15, 8, 3][div_15, 4, 3][span_15, 3, 1][span_15, 4, 1][div_15, 12, 1][div_15, 8, 1][div_15, 4, 1][div_15, 0, 1]|[div_15, 0, 1][div_15, 4, 1][div_15, 8, 1][div_15, 12, 1][span_15, 4, 1][span_15, 3, 1][div_15, 4, 3][div_15, 8, 3]"
370 contenteditable>אבד opq דעפ <span dir=ltr id="span_15">abc אאא def</span>ווש rst כטז</div>
371
372 <!-- FAILED, and wrong printing result -->
373 <div id="div_16" dir=ltr class="test_move_by_word" title="[div_16, 0, 1][div_16, 4, 1][div_16, 8, 1][span_16, 8, 1][span_16, 7, 1][div_16, 12, 1][div_16, 11, 1][div_16, 4, 3][div_16, 8, 3][div_16, 11, 3]|[div_16, 11, 3][div_16, 8, 3][div_16, 4, 3][div_16, 11, 1][div_16, 12, 1][span_16, 7, 1][span_16, 8, 1][div_16, 8, 1][div_16, 4, 1][div_16, 0, 1]" contenteditable>אבד opq דעפ <span dir=rtl id="span_16">abc אאא def</span>ווש rst כטז</div>
374
375 <!-- FAILED -->
376 <div id="div_17" dir=rtl class="test_move_by_word" title="[div_17, 11, 3][div_17, 8, 3][div_17, 4, 3][span_17, 8, 1][span_17, 4, 1][div_17, 12, 1][div_17, 8, 1][div_17, 4, 1][div_17, 0, 1]|[div_17, 0, 1][div_17, 4, 1][div_17, 8, 1][div_17, 12, 1][span_17, 4, 1][span_17, 8, 1][div_17, 4, 3][div_17, 8, 3]" contenteditable>אבד opq דעפ <span id="span_17">abc אאא def</span>ווש rst כטז</div>
377
378 <div id="div_18" dir=ltr class="test_move_by_word" title="[div_18, 0, 1][div_18, 4, 1][div_18, 8, 1][div_18, 12, 1][span_18, 4, 1][span_18, 8, 1][div_18, 4, 3][div_18, 8, 3][div_18, 11, 3]|[div_18, 11, 3][div_18, 8, 3][div_18, 4, 3][span_18, 8, 1][span_18, 4, 1][div_18, 12, 1][div_18, 8, 1][div_18, 4, 1][div_18, 0, 1]" contenteditable>אבד opq דעפ <span id="span_18">abc אאא def</span>ווש rst כטז</div>
379
380 <div id="div_19" dir=ltr class="test_move_by_word" title="[div_19, 0, 1][div_19, 4, 1][span_19, 4, 1][span_19, 7, 1]|[div_19, 3, 3][span_19, 7, 1][span_19, 4, 1][div_19, 4, 1][div_19, 0, 1]" contenteditable>aaa <span id="span_19">bbb אאא </span>ווש</div>
381
382 <!-- The content in title means "startOffsetInCurrentNode movingDirectionByWord endingNode endingOffsetInEndingNode" -->
383 <div id="div_100" contenteditable>אאא <span id="span_100" class="specific_test" title="1 left div_100 0">בב</span></div>
384 </div>
385
386 <pre id="console"></pre>
387 </body>
388 </html>