diff options
Diffstat (limited to 'libs/editor/example/src/test/java/org/wordpress/android')
6 files changed, 1027 insertions, 0 deletions
diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/ApplicationTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/ApplicationTest.java new file mode 100644 index 000000000..d2db16a8c --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/ApplicationTest.java @@ -0,0 +1,13 @@ +package org.wordpress.android.editor; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +} diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java new file mode 100644 index 000000000..d9e045b29 --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java @@ -0,0 +1,112 @@ +package org.wordpress.android.editor; + +import android.app.Activity; +import android.text.Spanned; + +import com.android.volley.toolbox.ImageLoader; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.wordpress.android.util.helpers.MediaFile; +import org.wordpress.android.util.helpers.MediaGallery; + +@Config(sdk = 18) +@RunWith(RobolectricTestRunner.class) +public class EditorFragmentAbstractTest { + @Test + public void testActivityMustImplementEditorFragmentListener() { + // Host Activity must implement EditorFragmentListener, exception expected if not + boolean didPassTest = false; + Activity hostActivity = Robolectric.buildActivity(Activity.class).create().get(); + EditorFragmentAbstract testFragment = new DefaultEditorFragment(); + + try { + testFragment.onAttach(hostActivity); + } catch (ClassCastException classCastException) { + didPassTest = true; + } + + Assert.assertTrue(didPassTest); + } + + @Test + public void testOnBackPressReturnsFalseByDefault() { + // The default behavior of onBackPressed should return false + Assert.assertFalse(new DefaultEditorFragment().onBackPressed()); + } + + /** + * Used to test default behavior of non-abstract methods. + */ + public static class DefaultEditorFragment extends EditorFragmentAbstract { + @Override + public void setTitle(CharSequence text) { + } + + @Override + public void setContent(CharSequence text) { + } + + @Override + public CharSequence getTitle() { + return null; + } + + @Override + public CharSequence getContent() { + return null; + } + + @Override + public void appendMediaFile(MediaFile mediaFile, String imageUrl, ImageLoader imageLoader) { + } + + @Override + public void appendGallery(MediaGallery mediaGallery) { + } + + @Override + public void setUrlForVideoPressId(String videoPressId, String url, String posterUrl) { + + } + + @Override + public boolean isUploadingMedia() { + return false; + } + + @Override + public boolean isActionInProgress() { + return false; + } + + @Override + public boolean hasFailedMediaUploads() { + return false; + } + + @Override + public void removeAllFailedMediaUploads() { + + } + + @Override + public void setTitlePlaceholder(CharSequence text) { + + } + + @Override + public void setContentPlaceholder(CharSequence text) { + + } + + @Override + public Spanned getSpannedContent() { + return null; + } + } +} diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleTextWatcherTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleTextWatcherTest.java new file mode 100644 index 000000000..beaabe31c --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleTextWatcherTest.java @@ -0,0 +1,496 @@ +package org.wordpress.android.editor; + +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; + +@Config(sdk = 18) +@RunWith(RobolectricTestRunner.class) +public class HtmlStyleTextWatcherTest { + + private HtmlStyleTextWatcherForTests mWatcher; + private Editable mContent; + private boolean mUpdateSpansWasCalled; + private HtmlStyleTextWatcher.SpanRange mSpanRange; + + @Before + public void setUp() { + mWatcher = new HtmlStyleTextWatcherForTests(); + mUpdateSpansWasCalled = false; + } + + @Test + public void testTypingNormalText() { + // -- Test typing in normal text (non-HTML) in an empty document + mContent = new SpannableStringBuilder("a"); + + mWatcher.onTextChanged(mContent, 0, 0, 1); // Typed "a" + mWatcher.afterTextChanged(mContent); + + assertEquals(false, mUpdateSpansWasCalled); + + mContent = new SpannableStringBuilder("ab"); + + mWatcher.onTextChanged(mContent, 1, 0, 1); // Typed "b" + mWatcher.afterTextChanged(mContent); + + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in normal text after exiting tags + mContent = new SpannableStringBuilder("text <b>bold</b> a"); + + mWatcher.onTextChanged(mContent, 17, 0, 1); // Typed "a" + mWatcher.afterTextChanged(mContent); + + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in normal text before exiting tags + mContent = new SpannableStringBuilder("text a <b>bold</b>"); + + mWatcher.onTextChanged(mContent, 5, 0, 1); // Typed "a" + mWatcher.afterTextChanged(mContent); + + assertEquals(false, mUpdateSpansWasCalled); + } + + @Test + public void testTypingInOpeningTag() { + // Test with several different cases of pre-existing text + String[] previousTextCases = new String[]{"", "plain text", "<i>", + "<blockquote>some existing content</blockquote> "}; + for (String initialText : previousTextCases) { + int offset = initialText.length(); + mUpdateSpansWasCalled = false; + + // -- Test typing in an opening tag symbol + mContent = new SpannableStringBuilder(initialText + "<"); + + mWatcher.onTextChanged(mContent, offset, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in the tag name + mContent = new SpannableStringBuilder(initialText + "<b"); + + mWatcher.onTextChanged(mContent, offset + 1, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in a closing tag symbol + mContent = new SpannableStringBuilder(initialText + "<b>"); + + mWatcher.onTextChanged(mContent, offset + 2, 0, 1); + mWatcher.afterTextChanged(mContent); + + assertEquals(offset, mSpanRange.getOpeningTagLoc()); + assertEquals(offset + 3, mSpanRange.getClosingTagLoc()); + } + } + + @Test + public void testTypingInClosingTag() { + // Test with several different cases of pre-existing text + String[] previousTextCases = new String[]{"<b>stuff", "plain text <b>stuff", "<i><b>stuff", + "<blockquote>some existing content</blockquote> <b>stuff"}; + + for (String initialText : previousTextCases) { + int offset = initialText.length(); + mUpdateSpansWasCalled = false; + + // -- Test typing in an opening tag symbol + mContent = new SpannableStringBuilder(initialText + "<"); + + mWatcher.onTextChanged(mContent, offset, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in the closing tag slash + mContent = new SpannableStringBuilder(initialText + "</"); + + mWatcher.onTextChanged(mContent, offset + 1, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + // -- Test typing in the tag name + mContent = new SpannableStringBuilder(initialText + "</b"); + + mWatcher.onTextChanged(mContent, offset + 2, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in a closing tag symbol + mContent = new SpannableStringBuilder(initialText + "</b>"); + + mWatcher.onTextChanged(mContent, offset + 3, 0, 1); + mWatcher.afterTextChanged(mContent); + + assertEquals(offset, mSpanRange.getOpeningTagLoc()); + assertEquals(offset + 4, mSpanRange.getClosingTagLoc()); + } + } + + @Test + public void testTypingInTagWithSurroundingTags() { + // Spans in this case will be applied until the end of the next tag + // This fixes a pasting bug and might be refined later + // -- Test typing in the opening tag symbol + mContent = new SpannableStringBuilder("some <del>text</del> < <b>bold text</b>"); + + mWatcher.onTextChanged(mContent, 21, 0, 1); // Added lone "<" + mWatcher.afterTextChanged(mContent); + + assertEquals(21, mSpanRange.getOpeningTagLoc()); + assertEquals(26, mSpanRange.getClosingTagLoc()); + + + // -- Test typing in the tag name + mContent = new SpannableStringBuilder("some <del>text</del> <i <b>bold text</b>"); + + mWatcher.onTextChanged(mContent, 22, 0, 1); + mWatcher.afterTextChanged(mContent); + + assertEquals(21, mSpanRange.getOpeningTagLoc()); + assertEquals(27, mSpanRange.getClosingTagLoc()); + + + // -- Test typing in the closing tag symbol + mContent = new SpannableStringBuilder("some <del>text</del> <i> <b>bold text</b>"); + + mWatcher.onTextChanged(mContent, 23, 0, 1); + mWatcher.afterTextChanged(mContent); + + assertEquals(21, mSpanRange.getOpeningTagLoc()); + assertEquals(28, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testTypingInLoneClosingSymbol() { + // -- Test typing in an isolated closing tag symbol + mContent = new SpannableStringBuilder("some text >"); + + mWatcher.onTextChanged(mContent, 10, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in an isolated closing tag symbol with surrounding tags + mContent = new SpannableStringBuilder("some <b>tex>t</b>"); + + mWatcher.onTextChanged(mContent, 11, 0, 1); // Added lone ">" + mWatcher.afterTextChanged(mContent); + + // The span in this case will be applied from the start of the previous tag to the end of the next tag + assertEquals(5, mSpanRange.getOpeningTagLoc()); + assertEquals(17, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testTypingInEntity() { + // Test with several different cases of pre-existing text + String[] previousTextCases = new String[]{"", "plain text", "ρ", + "<blockquote>some existing content †</blockquote> "}; + for (String initialText : previousTextCases) { + int offset = initialText.length(); + mUpdateSpansWasCalled = false; + + // -- Test typing in the entity's opening '&' + mContent = new SpannableStringBuilder(initialText + "&"); + + mWatcher.onTextChanged(mContent, offset, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in the entity's main text + mContent = new SpannableStringBuilder(initialText + "&"); + + mWatcher.onTextChanged(mContent, offset + 3, 0, 1); + mWatcher.afterTextChanged(mContent); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + + + // -- Test typing in the entity's closing ';' + mContent = new SpannableStringBuilder(initialText + "&"); + + mWatcher.onTextChanged(mContent, offset + 4, 0, 1); + mWatcher.afterTextChanged(mContent); + + assertEquals(offset, mSpanRange.getOpeningTagLoc()); + assertEquals(offset + 5, mSpanRange.getClosingTagLoc()); + } + } + + @Test + public void testAddingTagFromFormatBar() { + // -- Test adding a tag to an empty document + mContent = new SpannableStringBuilder("<b>"); + + mWatcher.onTextChanged(mContent, 0, 0, 3); + mWatcher.afterTextChanged(mContent); + + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(3, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a tag at the end of a document with text + mContent = new SpannableStringBuilder("stuff<b>"); + + mWatcher.onTextChanged(mContent, 5, 0, 3); + mWatcher.afterTextChanged(mContent); + + assertEquals(5, mSpanRange.getOpeningTagLoc()); + assertEquals(8, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a tag at the end of a document containing other html + mContent = new SpannableStringBuilder("some text <i>italics</i> <b>"); + + mWatcher.onTextChanged(mContent, 25, 0, 3); // Added "<b>" + mWatcher.afterTextChanged(mContent); + + assertEquals(25, mSpanRange.getOpeningTagLoc()); + assertEquals(28, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a tag at the start of a document with text + mContent = new SpannableStringBuilder("<b>some text"); + + mWatcher.onTextChanged(mContent, 0, 0, 3); + mWatcher.afterTextChanged(mContent); + + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(3, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a tag at the start of a document containing other html + mContent = new SpannableStringBuilder("<b>some text <i>italics</i>"); + + mWatcher.onTextChanged(mContent, 0, 0, 3); + mWatcher.afterTextChanged(mContent); + + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(3, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a tag within another tag pair + mContent = new SpannableStringBuilder("<b>some <i>text</b>"); + + mWatcher.onTextChanged(mContent, 8, 0, 3); // Added <i> + mWatcher.afterTextChanged(mContent); + + assertEquals(8, mSpanRange.getOpeningTagLoc()); + assertEquals(11, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a closing tag within another tag pair + mContent = new SpannableStringBuilder("<b>some <i>text</i></b>"); + + mWatcher.onTextChanged(mContent, 15, 0, 4); // Added "</i>" + mWatcher.afterTextChanged(mContent); + + assertEquals(15, mSpanRange.getOpeningTagLoc()); + assertEquals(19, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testAddingListTagsFromFormatBar() { + // -- Test adding a list tag to an empty document + mContent = new SpannableStringBuilder("<ul>\n\t<li>"); + + mWatcher.onTextChanged(mContent, 0, 0, 10); + mWatcher.afterTextChanged(mContent); + + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(10, mSpanRange.getClosingTagLoc()); + + + // -- Test adding a closing list tag + mContent = new SpannableStringBuilder("<ul>\n" + //5 + "\t<li>list item</li>\n" + //20 + "\t<li>another list item</li>\n" + //22 + "</ul>"); + + mWatcher.onTextChanged(mContent, 47, 0, 11); // Added "</li>\n</ul>" + mWatcher.afterTextChanged(mContent); + + assertEquals(47, mSpanRange.getOpeningTagLoc()); + assertEquals(58, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testDeletingPartsOfTag() { + // -- Test deleting different characters within a tag + mContent = new SpannableStringBuilder("<b>stuff</b>"); + + int deletedChar = 0; + mWatcher.beforeTextChanged(mContent, deletedChar, 1, 0); + // Deleted characters are removed from the string between beforeTextChanged() and onTextChanged() + mContent.delete(deletedChar, deletedChar + 1); + mWatcher.onTextChanged(mContent, deletedChar, 1, 0); + mWatcher.afterTextChanged(mContent); + + // "b>" should be re-styled + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(2, mSpanRange.getClosingTagLoc()); + + for (int i = 8; i < 12; i++) { + mContent = new SpannableStringBuilder("<b>stuff</b>"); + + mWatcher.beforeTextChanged(mContent, i, 1, 0); + mContent.delete(i, i + 1); + mWatcher.afterTextChanged(mContent); + + // Style should be updated starting from the end of 'stuff' + assertEquals(8, mSpanRange.getOpeningTagLoc()); + assertEquals(mContent.length(), mSpanRange.getClosingTagLoc()); + } + } + + @Test + public void testPasteTagPair() { + // -- Test pasting in a set of opening and closing tags at the end of the document + mContent = new SpannableStringBuilder("text <b></b>"); + + mWatcher.onTextChanged(mContent, 5, 0, 7); + mWatcher.afterTextChanged(mContent); + + assertEquals(5, mSpanRange.getOpeningTagLoc()); + assertEquals(12, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testCutAndPasteTagPart() { + // -- Test cutting a tag and part of another tag from the document + mContent = new SpannableStringBuilder("test <b></b> <i>italics</i>"); + + mWatcher.beforeTextChanged(mContent, 5, 4, 0); // Deleted "<b><" + mContent.delete(5, 9); + mWatcher.onTextChanged(mContent, 5, 4, 0); + mWatcher.afterTextChanged(mContent); + + assertEquals(5, mSpanRange.getOpeningTagLoc()); + assertEquals(8, mSpanRange.getClosingTagLoc()); + + + // -- Test pasting the cut text back in + mContent = new SpannableStringBuilder("test <b></b> <i>italics</i>"); + mWatcher.onTextChanged(mContent, 5, 0, 4); // Pasted "<b><" back in + mWatcher.afterTextChanged(mContent); + + assertEquals(5, mSpanRange.getOpeningTagLoc()); + assertEquals(12, mSpanRange.getClosingTagLoc()); + } + + @Test + public void testCutAndPasteTagPartReplacingText() { + // -- Test pasting cut text while text is selected + // Pasted "<b><", replacing "st " of "test " + mContent = new SpannableStringBuilder("test /b> <i>italics</i>"); + mWatcher.beforeTextChanged(mContent, 2, 3, 4); + mContent = new SpannableStringBuilder("te<b></b> <i>italics</i>"); + mWatcher.onTextChanged(mContent, 2, 3, 4); + mWatcher.afterTextChanged(mContent); + + // Should re-style whole document + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(mContent.length(), mSpanRange.getClosingTagLoc()); + + + // -- Test pasting cut text while text is selected, case 2 + // Pasted "i>", replacing "test " + mContent = new SpannableStringBuilder("<test italics</i>"); + mWatcher.beforeTextChanged(mContent, 1, 5, 2); + mContent = new SpannableStringBuilder("<i>italics</i>"); + mWatcher.onTextChanged(mContent, 1, 5, 2); + mWatcher.afterTextChanged(mContent); + + // Should re-style whole document + assertEquals(0, mSpanRange.getOpeningTagLoc()); + assertEquals(mContent.length(), mSpanRange.getClosingTagLoc()); + } + + @Test + public void testNoChange() { + + mWatcher.beforeTextChanged("sample", 0, 0, 0); + mWatcher.onTextChanged("sample", 0, 0, 0); + mWatcher.afterTextChanged(null); + + // No formatting should be applied/removed + assertEquals(false, mUpdateSpansWasCalled); + } + + @Test + public void testUpdateSpans() { + // -- Test tag styling + HtmlStyleTextWatcher watcher = new HtmlStyleTextWatcher(); + Spannable content = new SpannableStringBuilder("<b>stuff</b>"); + watcher.updateSpans(content, new HtmlStyleTextWatcher.SpanRange(0, 3)); + + assertEquals(1, content.getSpans(0, 3, ForegroundColorSpan.class).length); + + // -- Test entity styling + content = new SpannableStringBuilder("text & more text"); + watcher.updateSpans(content, new HtmlStyleTextWatcher.SpanRange(5, 10)); + + assertEquals(1, content.getSpans(5, 10, ForegroundColorSpan.class).length); + assertEquals(1, content.getSpans(5, 10, StyleSpan.class).length); + assertEquals(1, content.getSpans(5, 10, RelativeSizeSpan.class).length); + + // -- Test comment styling + content = new SpannableStringBuilder("text <!--comment--> more text"); + watcher.updateSpans(content, new HtmlStyleTextWatcher.SpanRange(5, 19)); + + assertEquals(1, content.getSpans(5, 19, ForegroundColorSpan.class).length); + assertEquals(1, content.getSpans(5, 19, StyleSpan.class).length); + assertEquals(1, content.getSpans(5, 19, RelativeSizeSpan.class).length); + + content = new SpannableStringBuilder("<b>stuff</b>"); + watcher.updateSpans(content, new HtmlStyleTextWatcher.SpanRange(0, 3)); + + watcher.updateSpans(content, new HtmlStyleTextWatcher.SpanRange(0, 42)); + assertEquals(1, content.getSpans(0, 3, ForegroundColorSpan.class).length); + + } + + private class HtmlStyleTextWatcherForTests extends HtmlStyleTextWatcher { + @Override + protected void updateSpans(Spannable s, SpanRange spanRange) { + mSpanRange = spanRange; + mUpdateSpansWasCalled = true; + } + } +} diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleUtilsTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleUtilsTest.java new file mode 100644 index 000000000..12e7793b1 --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleUtilsTest.java @@ -0,0 +1,92 @@ +package org.wordpress.android.editor; + +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StyleSpan; +import android.text.style.UnderlineSpan; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import static org.junit.Assert.assertEquals; + +@Config(sdk = 18) +@RunWith(RobolectricTestRunner.class) +public class HtmlStyleUtilsTest { + + @Test + public void testBulkStyling() { + // -- Test bulk styling + Spannable content = new SpannableStringBuilder("text <b>bold</b> & <!--a comment--> <a href=\"website\">link</a>"); + HtmlStyleUtils.styleHtmlForDisplay(content); + + assertEquals(0, content.getSpans(0, 5, CharacterStyle.class).length); // 'text ' + + assertEquals(1, content.getSpans(5, 8, ForegroundColorSpan.class).length); // '<b>' + + assertEquals(1, content.getSpans(12, 16, ForegroundColorSpan.class).length); // '</b>' + + assertEquals(1, content.getSpans(17, 22, ForegroundColorSpan.class).length); // '&' + assertEquals(1, content.getSpans(17, 22, StyleSpan.class).length); // '&' + assertEquals(1, content.getSpans(17, 22, RelativeSizeSpan.class).length); // '&' + + assertEquals(1, content.getSpans(23, 39, ForegroundColorSpan.class).length); // '<!--a comment-->' + assertEquals(1, content.getSpans(23, 39, StyleSpan.class).length); // '<!--a comment-->' + assertEquals(1, content.getSpans(23, 39, RelativeSizeSpan.class).length); // '<!--a comment-->' + + assertEquals(2, content.getSpans(40, 58, ForegroundColorSpan.class).length); // '<a href="website">' + assertEquals(1, content.getSpans(40, 48, ForegroundColorSpan.class).length); // '<a href=' + // Attribute span is applied on top of tag span, so there should be 2 ForegroundColorSpans present + assertEquals(2, content.getSpans(48, 57, ForegroundColorSpan.class).length); // '"website"' + assertEquals(1, content.getSpans(57, 58, ForegroundColorSpan.class).length); // '>' + + assertEquals(0, content.getSpans(58, 62, CharacterStyle.class).length); // 'link' + + assertEquals(1, content.getSpans(62, 66, ForegroundColorSpan.class).length); // '</a>' + } + + @Test + public void testClearSpans() { + Spannable content = new SpannableStringBuilder("<b>text &"); + + HtmlStyleUtils.styleHtmlForDisplay(content); + + assertEquals(1, content.getSpans(0, 3, ForegroundColorSpan.class).length); // '<b>' + + assertEquals(1, content.getSpans(9, 14, ForegroundColorSpan.class).length); // '&' + assertEquals(1, content.getSpans(9, 14, StyleSpan.class).length); // '&' + assertEquals(1, content.getSpans(9, 14, RelativeSizeSpan.class).length); // '&' + + HtmlStyleUtils.clearSpans(content, 9, 14); + + assertEquals(1, content.getSpans(0, 3, ForegroundColorSpan.class).length); + + assertEquals(0, content.getSpans(9, 14, ForegroundColorSpan.class).length); + assertEquals(0, content.getSpans(9, 14, StyleSpan.class).length); + assertEquals(0, content.getSpans(9, 14, RelativeSizeSpan.class).length); + + HtmlStyleUtils.clearSpans(content, 0, 3); + + assertEquals(0, content.getSpans(0, 3, ForegroundColorSpan.class).length); + + + } + + @Test + public void testClearSpansShouldIgnoreUnderline() { + // clearSpans() should ignore UnderlineSpan as it's used by the system for spelling suggestions + Spannable content = new SpannableStringBuilder("test"); + + content.setSpan(new UnderlineSpan(), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + HtmlStyleUtils.clearSpans(content, 0, 4); + + assertEquals(1, content.getSpans(0, 4, UnderlineSpan.class).length); + } +} diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/JsCallbackReceiverTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/JsCallbackReceiverTest.java new file mode 100644 index 000000000..7584824f7 --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/JsCallbackReceiverTest.java @@ -0,0 +1,99 @@ +package org.wordpress.android.editor; + +import android.util.Log; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowLog; +import org.wordpress.android.util.AppLog; + +import java.util.List; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static org.mockito.Mockito.mock; +import static org.robolectric.shadows.ShadowLog.LogItem; + +@Config(sdk = 18) +@RunWith(RobolectricTestRunner.class) +public class JsCallbackReceiverTest { + private final static String EDITOR_LOG_TAG = "WordPress-" + AppLog.T.EDITOR.toString(); + + private JsCallbackReceiver mJsCallbackReceiver; + + @Before + public void setUp() { + EditorFragment editorFragment = mock(EditorFragment.class); + mJsCallbackReceiver = new JsCallbackReceiver(editorFragment); + } + + @Test + public void testCallbacksRecognized() { + mJsCallbackReceiver.executeCallback("callback-dom-loaded", ""); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-new-field", "field-name"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-input", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-selection-changed", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-selection-style", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-focus-in", ""); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-focus-out", ""); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-image-replaced", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-image-tap", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-link-tap", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-log", "arguments"); + assertNotLogged("Unhandled callback"); + + mJsCallbackReceiver.executeCallback("callback-response-string", "arguments"); + assertNotLogged("Unhandled callback"); + } + + @Test + public void testUnknownCallbackShouldBeLogged() { + mJsCallbackReceiver.executeCallback("callback-does-not-exist", "content"); + assertLogged(Log.DEBUG, EDITOR_LOG_TAG, "Unhandled callback: callback-does-not-exist:content", null); + } + + @Test + public void testCallbackLog() { + mJsCallbackReceiver.executeCallback("callback-log", "msg=test-message"); + assertLogged(Log.DEBUG, EDITOR_LOG_TAG, "callback-log: test-message", null); + } + + private void assertLogged(int type, String tag, String msg, Throwable throwable) { + LogItem lastLog = ShadowLog.getLogs().get(0); + assertEquals(type, lastLog.type); + assertEquals(msg, lastLog.msg); + assertEquals(tag, lastLog.tag); + assertEquals(throwable, lastLog.throwable); + } + + private void assertNotLogged(String msg) { + List<LogItem> logList = ShadowLog.getLogs(); + if (!logList.isEmpty()) { + assertFalse(logList.get(0).msg.contains(msg)); + ShadowLog.reset(); + } + } +} diff --git a/libs/editor/example/src/test/java/org/wordpress/android/editor/UtilsTest.java b/libs/editor/example/src/test/java/org/wordpress/android/editor/UtilsTest.java new file mode 100644 index 000000000..b7bc70fe8 --- /dev/null +++ b/libs/editor/example/src/test/java/org/wordpress/android/editor/UtilsTest.java @@ -0,0 +1,215 @@ +package org.wordpress.android.editor; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.wordpress.android.editor.Utils.buildMapFromKeyValuePairs; +import static org.wordpress.android.editor.Utils.decodeHtml; +import static org.wordpress.android.editor.Utils.escapeHtml; +import static org.wordpress.android.editor.Utils.getChangeMapFromSets; +import static org.wordpress.android.editor.Utils.splitDelimitedString; +import static org.wordpress.android.editor.Utils.splitValuePairDelimitedString; +import static org.wordpress.android.editor.Utils.getUrlFromClipboard; + +@Config(sdk = 18) +@RunWith(RobolectricTestRunner.class) +public class UtilsTest { + + @Test + public void testEscapeHtml() { + // Test null + assertEquals(null, escapeHtml(null)); + } + + @Test + public void testDecodeHtml() { + // Test null + assertEquals(null, decodeHtml(null)); + + // Test normal usage + assertEquals("http://www.wordpress.com/", decodeHtml("http%3A%2F%2Fwww.wordpress.com%2F")); + } + + @Test + public void testSplitDelimitedString() { + Set<String> splitString = new HashSet<>(); + + // Test normal usage + splitString.add("p"); + splitString.add("bold"); + splitString.add("justifyLeft"); + + assertEquals(splitString, splitDelimitedString("p~bold~justifyLeft", "~")); + + // Test empty string + assertEquals(Collections.emptySet(), splitDelimitedString("", "~")); + } + + @Test + public void testSplitValuePairDelimitedString() { + // Test usage with a URL containing the delimiter + Set<String> keyValueSet = new HashSet<>(); + keyValueSet.add("url=http://www.wordpress.com/~user"); + keyValueSet.add("title=I'm a link!"); + + List<String> identifiers = new ArrayList<>(); + identifiers.add("url"); + identifiers.add("title"); + + assertEquals(keyValueSet, splitValuePairDelimitedString( + "url=http://www.wordpress.com/~user~title=I'm a link!", "~", identifiers)); + + // Test usage with a matching identifier but no delimiters + keyValueSet.clear(); + keyValueSet.add("url=http://www.wordpress.com/"); + + assertEquals(keyValueSet, splitValuePairDelimitedString("url=http://www.wordpress.com/", "~", identifiers)); + + // Test usage with no matching identifier and no delimiters + keyValueSet.clear(); + keyValueSet.add("something=something else"); + + assertEquals(keyValueSet, splitValuePairDelimitedString("something=something else", "~", identifiers)); + } + + @Test + public void testBuildMapFromKeyValuePairs() { + Set<String> keyValueSet = new HashSet<>(); + Map<String, String> expectedMap = new HashMap<>(); + + // Test normal usage + keyValueSet.add("id=test"); + keyValueSet.add("name=example"); + + expectedMap.put("id", "test"); + expectedMap.put("name", "example"); + + assertEquals(expectedMap, buildMapFromKeyValuePairs(keyValueSet)); + + // Test mixed valid and invalid entries + keyValueSet.clear(); + keyValueSet.add("test"); + keyValueSet.add("name=example"); + + expectedMap.clear(); + expectedMap.put("name", "example"); + + assertEquals(expectedMap, buildMapFromKeyValuePairs(keyValueSet)); + + // Test multiple '=' (should split at the first `=` and treat the rest of them as part of the string) + keyValueSet.clear(); + keyValueSet.add("id=test"); + keyValueSet.add("contents=some text\n<a href=\"http://wordpress.com\">WordPress</a>"); + + expectedMap.clear(); + expectedMap.put("id", "test"); + expectedMap.put("contents", "some text\n<a href=\"http://wordpress.com\">WordPress</a>"); + + assertEquals(expectedMap, buildMapFromKeyValuePairs(keyValueSet)); + + // Test invalid entry + keyValueSet.clear(); + keyValueSet.add("test"); + + assertEquals(Collections.emptyMap(), buildMapFromKeyValuePairs(keyValueSet)); + + // Test empty sets + assertEquals(Collections.emptyMap(), buildMapFromKeyValuePairs(Collections.<String>emptySet())); + } + + @Test + public void testGetChangeMapFromSets() { + Set<String> oldSet = new HashSet<>(); + Set<String> newSet = new HashSet<>(); + Map<String, Boolean> expectedMap = new HashMap<>(); + + // Test normal usage + oldSet.add("p"); + oldSet.add("bold"); + oldSet.add("justifyLeft"); + + newSet.add("p"); + newSet.add("justifyRight"); + + expectedMap.put("bold", false); + expectedMap.put("justifyLeft", false); + expectedMap.put("justifyRight", true); + + assertEquals(expectedMap, getChangeMapFromSets(oldSet, newSet)); + + // Test no changes + oldSet.clear(); + oldSet.add("p"); + oldSet.add("bold"); + + newSet.clear(); + newSet.add("p"); + newSet.add("bold"); + + assertEquals(Collections.emptyMap(), getChangeMapFromSets(oldSet, newSet)); + + // Test empty sets + assertEquals(Collections.emptyMap(), getChangeMapFromSets(Collections.emptySet(), Collections.emptySet())); + } + + @Test + public void testClipboardUrlWithNullContext() { + assertNull(getUrlFromClipboard(null)); + } + + @Test + public void testClipboardUrlWithNoClipData() { + assertNull(getClipboardUrlHelper(0, null)); + } + + @Test + public void testClipboardUrlWithNonUriData() { + assertNull(getClipboardUrlHelper(1, "not a URL")); + } + + @Test + public void testClipboardUrlWithLocalUriData() { + assertNull(getClipboardUrlHelper(1, "file://test.png")); + } + + @Test + public void testClipboardWithUrlData() { + String testUrl = "google.com"; + assertEquals(testUrl, getClipboardUrlHelper(1, testUrl)); + } + + private String getClipboardUrlHelper(int itemCount, String clipText) { + ClipData.Item mockItem = mock(ClipData.Item.class); + when(mockItem.getText()).thenReturn(clipText); + + ClipData mockPrimary = mock(ClipData.class); + when(mockPrimary.getItemCount()).thenReturn(itemCount); + when(mockPrimary.getItemAt(0)).thenReturn(mockItem); + + ClipboardManager mockManager = mock(ClipboardManager.class); + when(mockManager.getPrimaryClip()).thenReturn(mockPrimary); + + Context mockContext = mock(Context.class); + when(mockContext.getSystemService(Context.CLIPBOARD_SERVICE)).thenReturn(mockManager); + + return getUrlFromClipboard(mockContext); + } +} |