1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.cts;
18 
19 import static org.hamcrest.MatcherAssert.assertThat;
20 import org.hamcrest.Description;
21 import org.hamcrest.BaseMatcher;
22 
23 import android.graphics.Typeface;
24 import android.test.AndroidTestCase;
25 import android.text.Html;
26 import android.text.Layout;
27 import android.text.Spannable;
28 import android.text.SpannableString;
29 import android.text.Spanned;
30 import android.text.Html.ImageGetter;
31 import android.text.Html.TagHandler;
32 import android.text.style.AlignmentSpan;
33 import android.text.style.BackgroundColorSpan;
34 import android.text.style.BulletSpan;
35 import android.text.style.ForegroundColorSpan;
36 import android.text.style.QuoteSpan;
37 import android.text.style.StrikethroughSpan;
38 import android.text.style.StyleSpan;
39 import android.text.style.SubscriptSpan;
40 import android.text.style.SuperscriptSpan;
41 import android.text.style.TypefaceSpan;
42 import android.text.style.URLSpan;
43 import android.text.style.UnderlineSpan;
44 
45 public class HtmlTest extends AndroidTestCase {
46     private final static int SPAN_EXCLUSIVE_INCLUSIVE = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
47 
testSingleTagOnWhileString()48     public void testSingleTagOnWhileString() {
49         final String source = "<b>hello</b>";
50 
51         Spanned spanned = Html.fromHtml(source);
52         assertSingleTagOnWhileString(spanned);
53         spanned = Html.fromHtml(source, null, null);
54         assertSingleTagOnWhileString(spanned);
55     }
56 
assertSingleTagOnWhileString(Spanned spanned)57     private void assertSingleTagOnWhileString(Spanned spanned) {
58         final int expectStart = 0;
59         final int expectEnd = 5;
60         final int expectLen = 1;
61         final int start = -1;
62         final int end = 100;
63 
64         Object[] spans = spanned.getSpans(start, end, Object.class);
65         assertEquals(expectLen, spans.length);
66         assertEquals(expectStart, spanned.getSpanStart(spans[0]));
67         assertEquals(expectEnd, spanned.getSpanEnd(spans[0]));
68     }
69 
testBadHtml()70     public void testBadHtml() {
71         final String source = "Hello <b>b<i>bi</b>i</i>";
72 
73         Spanned spanned = Html.fromHtml(source);
74         assertBadHtml(spanned);
75         spanned = Html.fromHtml(source, null, null);
76         assertBadHtml(spanned);
77     }
78 
assertBadHtml(Spanned spanned)79     private void assertBadHtml(Spanned spanned) {
80         final int start = 0;
81         final int end = 100;
82         final int spansLen = 3;
83 
84         Object[] spans = spanned.getSpans(start, end, Object.class);
85         assertEquals(spansLen, spans.length);
86     }
87 
testSymbols()88     public void testSymbols() {
89         final String source = "&copy; &gt; &lt";
90         final String expected = "\u00a9 > <";
91 
92         String spanned = Html.fromHtml(source).toString();
93         assertEquals(expected, spanned);
94         spanned = Html.fromHtml(source, null, null).toString();
95         assertEquals(expected, spanned);
96     }
97 
testColor()98     public void testColor() throws Exception {
99         final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
100 
101         Spanned s = Html.fromHtml("<font color=\"#00FF00\">something</font>");
102         ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
103         assertEquals(0xFF00FF00, colors[0].getForegroundColor());
104 
105         s = Html.fromHtml("<font color=\"navy\">NAVY</font>");
106         colors = s.getSpans(0, s.length(), type);
107         assertEquals(0xFF000080, colors[0].getForegroundColor());
108 
109         s = Html.fromHtml("<font color=\"gibberish\">something</font>");
110         colors = s.getSpans(0, s.length(), type);
111         assertEquals(0, colors.length);
112 
113         // By default use the color values from android.graphics.Color instead of HTML/CSS
114         s = Html.fromHtml("<font color=\"green\">GREEN</font>");
115         colors = s.getSpans(0, s.length(), type);
116         assertEquals(0xFF00FF00, colors[0].getForegroundColor());
117 
118         s = Html.fromHtml("<font color=\"gray\">GRAY</font>");
119         colors = s.getSpans(0, s.length(), type);
120         assertEquals(0xFF888888, colors[0].getForegroundColor());
121 
122         s = Html.fromHtml("<font color=\"grey\">GREY</font>");
123         colors = s.getSpans(0, s.length(), type);
124         assertEquals(0xFF888888, colors[0].getForegroundColor());
125 
126         s = Html.fromHtml("<font color=\"lightgray\">LIGHTGRAY</font>");
127         colors = s.getSpans(0, s.length(), type);
128         assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
129 
130         s = Html.fromHtml("<font color=\"lightgrey\">LIGHTGREY</font>");
131         colors = s.getSpans(0, s.length(), type);
132         assertEquals(0xFFCCCCCC, colors[0].getForegroundColor());
133 
134         s = Html.fromHtml("<font color=\"darkgray\">DARKGRAY</font>");
135         colors = s.getSpans(0, s.length(), type);
136         assertEquals(0xFF444444, colors[0].getForegroundColor());
137 
138         s = Html.fromHtml("<font color=\"darkgrey\">DARKGREY</font>");
139         colors = s.getSpans(0, s.length(), type);
140         assertEquals(0xFF444444, colors[0].getForegroundColor());
141     }
142 
testUseCssColor()143     public void testUseCssColor() throws Exception {
144         final Class<ForegroundColorSpan> type = ForegroundColorSpan.class;
145         final int flags = Html.FROM_HTML_OPTION_USE_CSS_COLORS;
146 
147         Spanned s = Html.fromHtml("<font color=\"green\">GREEN</font>", flags);
148         ForegroundColorSpan[] colors = s.getSpans(0, s.length(), type);
149         assertEquals(0xFF008000, colors[0].getForegroundColor());
150 
151         s = Html.fromHtml("<font color=\"gray\">GRAY</font>", flags);
152         colors = s.getSpans(0, s.length(), type);
153         assertEquals(0xFF808080, colors[0].getForegroundColor());
154 
155         s = Html.fromHtml("<font color=\"grey\">GREY</font>", flags);
156         colors = s.getSpans(0, s.length(), type);
157         assertEquals(0xFF808080, colors[0].getForegroundColor());
158 
159         s = Html.fromHtml("<font color=\"lightgray\">LIGHTGRAY</font>", flags);
160         colors = s.getSpans(0, s.length(), type);
161         assertEquals(0xFFD3D3D3, colors[0].getForegroundColor());
162 
163         s = Html.fromHtml("<font color=\"lightgrey\">LIGHTGREY</font>", flags);
164         colors = s.getSpans(0, s.length(), type);
165         assertEquals(0xFFD3D3D3, colors[0].getForegroundColor());
166 
167         s = Html.fromHtml("<font color=\"darkgray\">DARKGRAY</font>", flags);
168         colors = s.getSpans(0, s.length(), type);
169         assertEquals(0xFFA9A9A9, colors[0].getForegroundColor());
170 
171         s = Html.fromHtml("<font color=\"darkgrey\">DARKGREY</font>", flags);
172         colors = s.getSpans(0, s.length(), type);
173         assertEquals(0xFFA9A9A9, colors[0].getForegroundColor());
174     }
175 
testStylesFromHtml()176     public void testStylesFromHtml() {
177         Spanned s = Html.fromHtml("<span style=\"color:#FF0000; background-color:#00FF00; "
178                 + "text-decoration:line-through;\">style</span>");
179 
180         ForegroundColorSpan[] foreground = s.getSpans(0, s.length(), ForegroundColorSpan.class);
181         assertEquals(1, foreground.length);
182         assertEquals(0xFFFF0000, foreground[0].getForegroundColor());
183 
184         BackgroundColorSpan[] background = s.getSpans(0, s.length(), BackgroundColorSpan.class);
185         assertEquals(1, background.length);
186         assertEquals(0xFF00FF00, background[0].getBackgroundColor());
187 
188         StrikethroughSpan[] strike = s.getSpans(0, s.length(), StrikethroughSpan.class);
189         assertEquals(1, strike.length);
190     }
191 
testParagraphs()192     public void testParagraphs() throws Exception {
193         SpannableString s = new SpannableString("Hello world");
194         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
195                 "<p dir=\"ltr\">Hello world</p>"));
196 
197         s = new SpannableString("Hello world\nor something");
198         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
199                 "<p dir=\"ltr\">Hello world<br>\nor something</p>"));
200         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
201                 matchesIgnoringTrailingWhitespace(
202                 "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0;\">Hello world</p>\n"
203                 + "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0;\">or something</p>"));
204 
205         s = new SpannableString("Hello world\n\nor something");
206         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
207                 "<p dir=\"ltr\">Hello world</p>\n<p dir=\"ltr\">or something</p>"));
208 
209         s = new SpannableString("Hello world\n\n\nor something");
210         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
211                 "<p dir=\"ltr\">Hello world<br></p>\n<p dir=\"ltr\">or something</p>"));
212         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
213                 matchesIgnoringTrailingWhitespace(
214                 "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0;\">Hello world</p>\n"
215                 + "<br>\n"
216                 + "<br>\n"
217                 + "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0;\">or something</p>"));
218     }
219 
testParagraphStyles()220     public void testParagraphStyles() throws Exception {
221         SpannableString s = new SpannableString("Hello world");
222         s.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER),
223                 0, s.length(), Spanned.SPAN_PARAGRAPH);
224         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
225                 matchesIgnoringTrailingWhitespace(
226                 "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0; text-align:center;\">"
227                 + "Hello world</p>"));
228 
229         // Set another AlignmentSpan of a different alignment. Only the last one should be encoded.
230         s.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE),
231                 0, s.length(), Spanned.SPAN_PARAGRAPH);
232         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
233                 matchesIgnoringTrailingWhitespace(
234                 "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0; text-align:end;\">"
235                 + "Hello world</p>"));
236 
237         // Set another AlignmentSpan without SPAN_PARAGRAPH flag.
238         // This will be ignored and the previous alignment should be encoded.
239         s.setSpan(new AlignmentSpan.Standard(Layout.Alignment.ALIGN_NORMAL),
240                 0, s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
241         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
242                 matchesIgnoringTrailingWhitespace(
243                 "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0; text-align:end;\">"
244                 + "Hello world</p>"));
245     }
246 
testBulletSpan()247     public void testBulletSpan() throws Exception {
248         SpannableString s = new SpannableString("Bullet1\nBullet2\nNormal paragraph");
249         s.setSpan(new BulletSpan(), 0, 8, Spanned.SPAN_PARAGRAPH);
250         s.setSpan(new BulletSpan(), 8, 16, Spanned.SPAN_PARAGRAPH);
251         assertThat(Html.toHtml(s, Html.TO_HTML_PARAGRAPH_LINES_INDIVIDUAL),
252                 matchesIgnoringTrailingWhitespace(
253                 "<ul style=\"margin-top:0; margin-bottom:0;\">\n"
254                 + "<li dir=\"ltr\">Bullet1</li>\n"
255                 + "<li dir=\"ltr\">Bullet2</li>\n"
256                 + "</ul>\n"
257                 + "<p dir=\"ltr\" style=\"margin-top:0; margin-bottom:0;\">Normal paragraph</p>"));
258     }
259 
testBlockquote()260     public void testBlockquote() throws Exception {
261         final int start = 0;
262 
263         SpannableString s = new SpannableString("Hello world");
264         int end = s.length();
265         s.setSpan(new QuoteSpan(), start, end, Spannable.SPAN_PARAGRAPH);
266         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
267                 "<blockquote><p dir=\"ltr\">Hello world</p>\n</blockquote>"));
268 
269         s = new SpannableString("Hello\n\nworld");
270         end = 7;
271         s.setSpan(new QuoteSpan(), start, end, Spannable.SPAN_PARAGRAPH);
272         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
273                 "<blockquote><p dir=\"ltr\">Hello</p>\n</blockquote>\n<p dir=\"ltr\">world</p>"));
274     }
275 
testEntities()276     public void testEntities() throws Exception {
277         SpannableString s = new SpannableString("Hello <&> world");
278         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
279                 "<p dir=\"ltr\">Hello &lt;&amp;&gt; world</p>"));
280 
281         s = new SpannableString("Hello \u03D5 world");
282         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
283                 "<p dir=\"ltr\">Hello &#981; world</p>"));
284 
285         s = new SpannableString("Hello  world");
286         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
287                 "<p dir=\"ltr\">Hello&nbsp; world</p>"));
288     }
289 
testMarkup()290     public void testMarkup() throws Exception {
291         final int start = 6;
292 
293         SpannableString s = new SpannableString("Hello bold world");
294         int end = s.length() - start;
295         s.setSpan(new StyleSpan(Typeface.BOLD), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
296         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
297                 "<p dir=\"ltr\">Hello <b>bold</b> world</p>"));
298 
299         s = new SpannableString("Hello italic world");
300         end = s.length() - start;
301         s.setSpan(new StyleSpan(Typeface.ITALIC), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
302         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
303                 "<p dir=\"ltr\">Hello <i>italic</i> world</p>"));
304 
305         s = new SpannableString("Hello monospace world");
306         end = s.length() - start;
307         s.setSpan(new TypefaceSpan("monospace"), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
308         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
309                 "<p dir=\"ltr\">Hello <tt>monospace</tt> world</p>"));
310 
311         s = new SpannableString("Hello superscript world");
312         end = s.length() - start;
313         s.setSpan(new SuperscriptSpan(), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
314         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
315                 "<p dir=\"ltr\">Hello <sup>superscript</sup> world</p>"));
316 
317         s = new SpannableString("Hello subscript world");
318         end = s.length() - start;
319         s.setSpan(new SubscriptSpan(), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
320         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
321                 "<p dir=\"ltr\">Hello <sub>subscript</sub> world</p>"));
322 
323         s = new SpannableString("Hello underline world");
324         end = s.length() - start;
325         s.setSpan(new UnderlineSpan(), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
326         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
327                 "<p dir=\"ltr\">Hello <u>underline</u> world</p>"));
328 
329         s = new SpannableString("Hello struck world");
330         end = s.length() - start;
331         s.setSpan(new StrikethroughSpan(), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
332         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace("<p dir=\"ltr\">Hello "
333                 + "<span style=\"text-decoration:line-through;\">struck</span> world</p>"));
334 
335         s = new SpannableString("Hello linky world");
336         end = s.length() - start;
337         s.setSpan(new URLSpan("http://www.google.com"), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
338         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace("<p dir=\"ltr\">Hello "
339                 + "<a href=\"http://www.google.com\">linky</a> world</p>"));
340 
341         s = new SpannableString("Hello foreground world");
342         end = s.length() - start;
343         s.setSpan(new ForegroundColorSpan(0x00FF00), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
344         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace("<p dir=\"ltr\">Hello "
345                 + "<span style=\"color:#00FF00;\">foreground</span> world</p>"));
346 
347         s = new SpannableString("Hello background world");
348         end = s.length() - start;
349         s.setSpan(new BackgroundColorSpan(0x00FF00), start, end, SPAN_EXCLUSIVE_INCLUSIVE);
350         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace("<p dir=\"ltr\">Hello "
351                 + "<span style=\"background-color:#00FF00;\">background</span> world</p>"));
352     }
353 
testMarkupFromHtml()354     public void testMarkupFromHtml() throws Exception {
355         final int expectedStart = 6;
356         final int expectedEnd = expectedStart + 6;
357 
358         String tags[] = {"del", "s", "strike"};
359         for (String tag : tags) {
360             String source = String.format("Hello <%s>struck</%s> world", tag, tag);
361             Spanned spanned = Html.fromHtml(source);
362             Object[] spans = spanned.getSpans(0, spanned.length(), Object.class);
363             assertEquals(1, spans.length);
364             assertEquals(StrikethroughSpan.class, spans[0].getClass());
365             assertEquals(expectedStart, spanned.getSpanStart(spans[0]));
366             assertEquals(expectedEnd, spanned.getSpanEnd(spans[0]));
367         }
368     }
369 
370     /**
371      * Tests if text alignments encoded as CSS TEXT-ALIGN property are correctly converted into
372      * {@link AlignmentSpan}s. Note that the span will also cover the first newline character after
373      * the text.
374      */
testTextAlignCssFromHtml()375     public void testTextAlignCssFromHtml() throws Exception {
376         String tags[] = {"p", "h1", "h2", "h3", "h4", "h5", "h6", "div", "blockquote"};
377 
378         for (String tag : tags) {
379             String source = String.format("<%s style=\"text-align:start\">TEXT</%s>"
380                     + "<%s style=\"text-align:center\">TEXT</%s>"
381                     + "<%s style=\"text-align:end\">TEXT</%s>",
382                     tag, tag, tag, tag, tag, tag);
383             Spanned spanned = Html.fromHtml(source);
384             AlignmentSpan[] spans = spanned.getSpans(0, spanned.length(), AlignmentSpan.class);
385             assertEquals(3, spans.length);
386 
387             assertEquals(Layout.Alignment.ALIGN_NORMAL, spans[0].getAlignment());
388             assertEquals(0, spanned.getSpanStart(spans[0]));
389             assertEquals(5, spanned.getSpanEnd(spans[0]));
390 
391             assertEquals(Layout.Alignment.ALIGN_CENTER, spans[1].getAlignment());
392             assertEquals(6, spanned.getSpanStart(spans[1]));
393             assertEquals(11, spanned.getSpanEnd(spans[1]));
394 
395             assertEquals(Layout.Alignment.ALIGN_OPPOSITE, spans[2].getAlignment());
396             assertEquals(12, spanned.getSpanStart(spans[2]));
397             assertEquals(17, spanned.getSpanEnd(spans[2]));
398 
399             // Other valid TEXT-ALIGN property encodings
400             source = String.format("<%s style=\'text-align:center\''>TEXT</%s>"
401                     + "<%s style=\"text-align:center;\">TEXT</%s>"
402                     + "<%s style=\"text-align  :  center  ;  \">TEXT</%s>",
403                     tag, tag, tag, tag, tag, tag);
404             spanned = Html.fromHtml(source);
405             spans = spanned.getSpans(0, spanned.length(), AlignmentSpan.class);
406             assertEquals(3, spans.length);
407 
408             // Invalid TEXT-ALIGN property encodings
409             source = String.format("<%s style=\"text-align:centre\">TEXT</%s>"
410                     + "<%s style=\"text-alignment:center\">TEXT</%s>"
411                     + "<%s style=\"align:center\">TEXT</%s>"
412                     + "<%s style=\"text-align:gibberish\">TEXT</%s>",
413                     tag, tag, tag, tag, tag, tag, tag, tag);
414             spanned = Html.fromHtml(source);
415             spans = spanned.getSpans(0, spanned.length(), AlignmentSpan.class);
416             assertEquals(0, spans.length);
417         }
418     }
419 
testBlockLevelElementsFromHtml()420     public void testBlockLevelElementsFromHtml() throws Exception {
421         String source = "<blockquote>BLOCKQUOTE</blockquote>"
422                 + "<div>DIV</div>"
423                 + "<p>P</p>"
424                 + "<h1>HEADING</h1>";
425 
426         int flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE
427                 | Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV;
428         assertEquals("BLOCKQUOTE\nDIV\n\nP\n\nHEADING\n\n",
429                 Html.fromHtml(source, flags, null, null).toString());
430 
431         flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_DIV
432                 | Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH;
433         assertEquals("BLOCKQUOTE\n\nDIV\nP\n\nHEADING\n\n",
434                 Html.fromHtml(source, flags, null, null).toString());
435 
436         flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH
437                 | Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING;
438         assertEquals("BLOCKQUOTE\n\nDIV\n\nP\nHEADING\n",
439                 Html.fromHtml(source, flags, null, null).toString());
440     }
441 
testListFromHtml()442     public void testListFromHtml() throws Exception {
443         String source = "CITRUS FRUITS:<ul><li>LEMON</li><li>LIME</li><li>ORANGE</li></ul>";
444         assertEquals("CITRUS FRUITS:\n\nLEMON\n\nLIME\n\nORANGE\n\n",
445                 Html.fromHtml(source).toString());
446 
447         int flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST;
448         // The <li> still has to be separated by two newline characters
449         assertEquals("CITRUS FRUITS:\n\nLEMON\n\nLIME\n\nORANGE\n\n",
450                 Html.fromHtml(source, flags, null, null).toString());
451 
452         flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM;
453         assertEquals("CITRUS FRUITS:\n\nLEMON\nLIME\nORANGE\n\n",
454                 Html.fromHtml(source, flags, null, null).toString());
455 
456         flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST
457                 | Html.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM;
458         assertEquals("CITRUS FRUITS:\nLEMON\nLIME\nORANGE\n",
459                 Html.fromHtml(source, flags, null, null).toString());
460     }
461 
testParagraphFromHtml()462     public void testParagraphFromHtml() throws Exception {
463         final int flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH;
464 
465         String source = "<p>Line 1</p><p>Line 2</p>";
466         assertEquals("Line 1\nLine 2\n",
467                 Html.fromHtml(source, flags).toString());
468         assertEquals("Line 1\n\nLine 2\n\n",
469                 Html.fromHtml(source).toString());
470 
471         source = "<br>Line 1<br>Line 2<br>";
472         assertEquals("\nLine 1\nLine 2\n",
473                 Html.fromHtml(source, flags).toString());
474         assertEquals("\nLine 1\nLine 2\n",
475                 Html.fromHtml(source).toString());
476 
477         source = "<br><p>Line 1</p><br><p>Line 2</p><br>";
478         assertEquals("\nLine 1\n\nLine 2\n\n",
479                 Html.fromHtml(source, flags).toString());
480         assertEquals("\n\nLine 1\n\n\nLine 2\n\n\n",
481                 Html.fromHtml(source).toString());
482 
483         source = "<p>Line 1<br>Line 2</p><p>Line 3</p>";
484         assertEquals("Line 1\nLine 2\nLine 3\n",
485                 Html.fromHtml(source, flags).toString());
486         assertEquals("Line 1\nLine 2\n\nLine 3\n\n",
487                 Html.fromHtml(source).toString());
488     }
489 
testHeadingFromHtml()490     public void testHeadingFromHtml() throws Exception {
491         final int flags = Html.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING;
492 
493         String source = "<h1>Heading 1</h1><h1>Heading 2</h1>";
494         assertEquals("Heading 1\nHeading 2\n",
495                 Html.fromHtml(source, flags).toString());
496         assertEquals("Heading 1\n\nHeading 2\n\n",
497                 Html.fromHtml(source).toString());
498 
499         source = "<br><h1>Heading 1</h1><br><h1>Heading 2</h1><br>";
500         assertEquals("\nHeading 1\n\nHeading 2\n\n",
501                 Html.fromHtml(source, flags).toString());
502         assertEquals("\n\nHeading 1\n\n\nHeading 2\n\n\n",
503                 Html.fromHtml(source).toString());
504 
505         source = "<h1>Heading 1<br>Heading 2</h1><h1>Heading 3</h1>";
506         assertEquals("Heading 1\nHeading 2\nHeading 3\n",
507                 Html.fromHtml(source, flags).toString());
508         assertEquals("Heading 1\nHeading 2\n\nHeading 3\n\n",
509                 Html.fromHtml(source).toString());
510     }
511 
testImg()512     public void testImg() throws Exception {
513         Spanned s = Html.fromHtml("yes<img src=\"http://example.com/foo.gif\">no");
514         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
515                 "<p dir=\"ltr\">yes<img src=\"http://example.com/foo.gif\">no</p>"));
516     }
517 
testUtf8()518     public void testUtf8() throws Exception {
519         Spanned s = Html.fromHtml("<p>\u0124\u00eb\u0142\u0142o, world!</p>");
520         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
521                 "<p dir=\"ltr\">&#292;&#235;&#322;&#322;o, world!</p>"));
522     }
523 
testSurrogates()524     public void testSurrogates() throws Exception {
525         Spanned s = Html.fromHtml("\ud83d\udc31");
526         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace(
527                 "<p dir=\"ltr\">&#128049;</p>"));
528     }
529 
testBadSurrogates()530     public void testBadSurrogates() throws Exception {
531         Spanned s = Html.fromHtml("\udc31\ud83d");
532         assertThat(Html.toHtml(s), matchesIgnoringTrailingWhitespace("<p dir=\"ltr\"></p>"));
533     }
534 
matchesIgnoringTrailingWhitespace( String expected)535     private static StringIgnoringTrailingWhitespaceMatcher matchesIgnoringTrailingWhitespace(
536             String expected) {
537         return new StringIgnoringTrailingWhitespaceMatcher(expected);
538     }
539 
540     private static final class StringIgnoringTrailingWhitespaceMatcher extends
541             BaseMatcher<String> {
542         private final String mStrippedString;
543 
StringIgnoringTrailingWhitespaceMatcher(String string)544         public StringIgnoringTrailingWhitespaceMatcher(String string) {
545             mStrippedString = stripTrailingWhitespace(string);
546         }
547 
548         @Override
matches(Object item)549         public boolean matches(Object item) {
550             final String string = (String) item;
551             return mStrippedString.equals(stripTrailingWhitespace(string));
552         }
553 
stripTrailingWhitespace(String text)554         private String stripTrailingWhitespace(String text) {
555             return text.replaceFirst("\\s+$", "");
556         }
557 
558         @Override
describeTo(Description description)559         public void describeTo(Description description) {
560             description.appendText("is equal to ")
561                     .appendText(mStrippedString)
562                     .appendText(" ignoring tailing whitespaces");
563         }
564     }
565 }
566