aboutsummaryrefslogtreecommitdiff
path: root/libs/editor/example/src/test/java/org/wordpress/android
diff options
context:
space:
mode:
Diffstat (limited to 'libs/editor/example/src/test/java/org/wordpress/android')
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/ApplicationTest.java13
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/EditorFragmentAbstractTest.java112
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleTextWatcherTest.java496
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/HtmlStyleUtilsTest.java92
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/JsCallbackReceiverTest.java99
-rw-r--r--libs/editor/example/src/test/java/org/wordpress/android/editor/UtilsTest.java215
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", "&rho;",
+ "<blockquote>some existing content &dagger;</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 + "&amp");
+
+ 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 + "&amp;");
+
+ 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 &amp; 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> &amp; <!--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); // '&amp;'
+ assertEquals(1, content.getSpans(17, 22, StyleSpan.class).length); // '&amp;'
+ assertEquals(1, content.getSpans(17, 22, RelativeSizeSpan.class).length); // '&amp;'
+
+ 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 &amp;");
+
+ HtmlStyleUtils.styleHtmlForDisplay(content);
+
+ assertEquals(1, content.getSpans(0, 3, ForegroundColorSpan.class).length); // '<b>'
+
+ assertEquals(1, content.getSpans(9, 14, ForegroundColorSpan.class).length); // '&amp;'
+ assertEquals(1, content.getSpans(9, 14, StyleSpan.class).length); // '&amp;'
+ assertEquals(1, content.getSpans(9, 14, RelativeSizeSpan.class).length); // '&amp;'
+
+ 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);
+ }
+}