Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/src/androidTest/kotlin/org/wordpress/aztec/demo/Matchers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import android.widget.EditText
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.wordpress.aztec.AztecInitialContentHolder
import org.wordpress.aztec.AztecText
import org.wordpress.aztec.source.Format
import org.wordpress.aztec.source.SourceViewEditText

object Matchers {
fun withRegex(expected: Regex): Matcher<View> {
Expand Down Expand Up @@ -45,4 +48,23 @@ object Matchers {
}
}
}

fun hasContentChanges(shouldHaveChanges: AztecInitialContentHolder.EditorHasChanges): TypeSafeMatcher<View> {

return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("User has made changes to the post: $shouldHaveChanges")
}

public override fun matchesSafely(view: View): Boolean {
if (view is SourceViewEditText) {
return view.hasChanges() == shouldHaveChanges
}
if (view is AztecText) {
return view.hasChanges() == shouldHaveChanges
}
return false
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ import android.support.test.espresso.matcher.ViewMatchers.withId
import android.support.test.espresso.matcher.ViewMatchers.withText
import android.view.KeyEvent
import android.view.View
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.hasToString
import org.hamcrest.TypeSafeMatcher
import org.wordpress.aztec.AztecText
import org.wordpress.aztec.AztecInitialContentHolder
import org.wordpress.aztec.demo.Actions
import org.wordpress.aztec.demo.BasePage
import org.wordpress.aztec.demo.Matchers
Expand Down Expand Up @@ -370,22 +368,13 @@ class EditorPage : BasePage() {
return this
}

fun hasChanges(shouldHaveChanges : AztecText.EditorHasChanges): EditorPage {
val hasNoChangesMatcher = object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("User has made changes to the post: $shouldHaveChanges")
}

public override fun matchesSafely(view: View): Boolean {
if (view is AztecText) {
return view.hasChanges() == shouldHaveChanges
}

return false
}
}
fun hasChanges(shouldHaveChanges : AztecInitialContentHolder.EditorHasChanges): EditorPage {
editor.check(matches(Matchers.hasContentChanges(shouldHaveChanges)))
return this
}

editor.check(matches(hasNoChangesMatcher))
fun hasChangesHTML(shouldHaveChanges : AztecInitialContentHolder.EditorHasChanges): EditorPage {
htmlEditor.check(matches(Matchers.hasContentChanges(shouldHaveChanges)))
return this
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import android.support.test.rule.ActivityTestRule
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.wordpress.aztec.AztecText
import org.wordpress.aztec.AztecInitialContentHolder
import org.wordpress.aztec.demo.BaseTest
import org.wordpress.aztec.demo.MainActivity
import org.wordpress.aztec.demo.pages.EditorPage
Expand Down Expand Up @@ -251,7 +251,7 @@ class MixedTextFormattingTests : BaseTest() {
.insertHTML(input)
.toggleHtml()
.toggleHtml()
.hasChanges(AztecText.EditorHasChanges.NO_CHANGES) // Verify that the user had not changed the input
.hasChanges(AztecInitialContentHolder.EditorHasChanges.NO_CHANGES) // Verify that the user had not changed the input
}

@Test
Expand All @@ -267,7 +267,38 @@ class MixedTextFormattingTests : BaseTest() {
.setCursorPositionAtEnd()
.insertText(insertedText)
.toggleHtml()
.hasChanges(AztecText.EditorHasChanges.CHANGES)
.hasChanges(AztecInitialContentHolder.EditorHasChanges.CHANGES)
.verifyHTML(afterParser)
}

@Test
fun testHasChangesOnHTMLEditor() {
val input = "<b>Test</b>"
val insertedText = " text added"
val afterParser = "<b>Test</b>$insertedText"

EditorPage().toggleHtml()
.insertHTML(input)
.toggleHtml()
.toggleHtml() // switch back to HTML editor
.insertHTML(insertedText)
.hasChangesHTML(AztecInitialContentHolder.EditorHasChanges.CHANGES)
.verifyHTML(afterParser)
}

@Test
fun testHasChangesOnHTMLEditorTestedFromVisualEditor() {
val input = "<b>Test</b>"
val insertedText = " text added"
val afterParser = "Test$insertedText"

EditorPage().toggleHtml()
.insertHTML(input)
.toggleHtml()
.toggleHtml() // switch back to HTML editor
.insertHTML(insertedText)
.hasChangesHTML(AztecInitialContentHolder.EditorHasChanges.CHANGES)
.toggleHtml() // switch back to Visual editor
.verify(afterParser)
}
}
8 changes: 8 additions & 0 deletions aztec/src/main/kotlin/org/wordpress/aztec/Aztec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ open class Aztec private constructor(val visualEditor: AztecText, val toolbar: I

init {
initToolbar()
initInitialContentHolder()
}

private constructor(activity: Activity, @IdRes aztecTextId: Int,
Expand All @@ -46,6 +47,7 @@ open class Aztec private constructor(val visualEditor: AztecText, val toolbar: I

initToolbar()
initSourceEditorHistory()
initInitialContentHolder()
}

companion object Factory {
Expand All @@ -67,6 +69,12 @@ open class Aztec private constructor(val visualEditor: AztecText, val toolbar: I
}
}

private fun initInitialContentHolder() {
val initialContentHolder = AztecInitialContentHolder()
visualEditor.initialContentHolder = initialContentHolder
sourceEditor?.initialContentHolder = initialContentHolder
}

fun setImageGetter(imageGetter: Html.ImageGetter): Aztec {
this.imageGetter = imageGetter
initImageGetter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.wordpress.aztec

import android.os.Parcel
import android.os.Parcelable
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Arrays

class AztecInitialContentHolder() : Parcelable {

enum class EditorHasChanges {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call moving the EditorHasChanges enum to the AztecInitialContentHolder class 👍

CHANGES, NO_CHANGES, UNKNOWN
}

interface EditorHasChangesInterface {
fun hasChanges(): AztecInitialContentHolder.EditorHasChanges
}

constructor(parcel : Parcel) : this() {
initialEditorContentParsedSHA256 = ByteArray(parcel.readInt())
parcel.readByteArray(initialEditorContentParsedSHA256)
}

override fun writeToParcel(dest: Parcel?, flags: Int) {
dest?.writeInt(initialEditorContentParsedSHA256.size)
dest?.writeByteArray(initialEditorContentParsedSHA256)
}

override fun describeContents(): Int {
return 0
}

companion object {
@JvmField
val CREATOR: Parcelable.Creator<AztecInitialContentHolder> = object : Parcelable.Creator<AztecInitialContentHolder> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AS says this property val CREATOR is never used, wonder if we should do something about it. Been reading and I think we need it anyway for Java to know, but felt I'd comment here and see if you'd know better.

Copy link
Contributor Author

@daniloercoli daniloercoli May 25, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to suppress with annotation, but no luck. There are other instances of it in Aztec without any annotation, so I left it without adding it.

override fun createFromParcel(`in`: Parcel): AztecInitialContentHolder {
return AztecInitialContentHolder(`in`)
}

override fun newArray(size: Int): Array<AztecInitialContentHolder?> {
return arrayOfNulls(size)
}
}
}

private var initialEditorContentParsedSHA256: ByteArray = ByteArray(0)

fun needToSetInitialValue(): Boolean {
return (initialEditorContentParsedSHA256.isEmpty() || Arrays.equals(initialEditorContentParsedSHA256, calculateSHA256("")))
}

fun setInitialContent(source: String) {
try {
// Do not recalculate the hash if it's not the first call to `fromHTML`.
if (needToSetInitialValue()) {
initialEditorContentParsedSHA256 = calculateSHA256(source)
}
} catch (e: Throwable) {
// Do nothing here. calculateSHA256 -> NoSuchAlgorithmException
}
}

@Throws(NoSuchAlgorithmException::class)
private fun calculateSHA256(s: String): ByteArray {
val digest = MessageDigest.getInstance("SHA-256")
digest.update(s.toByteArray())
return digest.digest()
}

fun hasChanges(source: String): EditorHasChanges {
if (!initialEditorContentParsedSHA256.isEmpty()) {
try {
if (Arrays.equals(initialEditorContentParsedSHA256, calculateSHA256(source))) {
return EditorHasChanges.NO_CHANGES
}
return EditorHasChanges.CHANGES
} catch (e: Throwable) {
// Do nothing here. calculateSHA256 -> NoSuchAlgorithmException
}
}
return EditorHasChanges.UNKNOWN
}
}
71 changes: 27 additions & 44 deletions aztec/src/main/kotlin/org/wordpress/aztec/AztecText.kt
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,13 @@ import org.wordpress.aztec.watchers.event.text.BeforeTextChangedEventData
import org.wordpress.aztec.watchers.event.text.OnTextChangedEventData
import org.wordpress.aztec.watchers.event.text.TextWatcherEvent
import org.xml.sax.Attributes
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.ArrayList
import java.util.Arrays
import java.util.LinkedList

@Suppress("UNUSED_PARAMETER")
open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlTappedListener, IEventInjector {
open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknownHtmlTappedListener, IEventInjector,
AztecInitialContentHolder.EditorHasChangesInterface {
companion object {
val BLOCK_EDITOR_HTML_KEY = "RETAINED_BLOCK_HTML_KEY"
val BLOCK_EDITOR_START_INDEX_KEY = "BLOCK_EDITOR_START_INDEX_KEY"
Expand Down Expand Up @@ -162,10 +161,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
}
}

enum class EditorHasChanges {
CHANGES, NO_CHANGES, UNKNOWN
}

private var historyEnable = resources.getBoolean(R.bool.history_enable)
private var historySize = resources.getInteger(R.integer.history_size)

Expand All @@ -175,7 +170,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
private var consumeSelectionChangedEvent: Boolean = false
private var isInlineTextHandlerEnabled: Boolean = true
private var bypassObservationQueue: Boolean = false
private var initialEditorContentParsedSHA256: ByteArray = ByteArray(0)

private var onSelectionChangedListener: OnSelectionChangedListener? = null
private var onImeBackListener: OnImeBackListener? = null
Expand All @@ -185,6 +179,7 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
private var onMediaDeletedListener: OnMediaDeletedListener? = null
private var onVideoInfoRequestedListener: OnVideoInfoRequestedListener? = null
var externalLogger: AztecLog.ExternalLogger? = null
var initialContentHolder: AztecInitialContentHolder? = null

private var isViewInitialized = false
private var isLeadingStyleRemoved = false
Expand Down Expand Up @@ -539,7 +534,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
history.inputLast = InstanceStateUtils.readAndPurgeTempInstance<String>(INPUT_LAST_KEY, "", savedState.state)
visibility = customState.getInt(VISIBILITY_KEY)

initialEditorContentParsedSHA256 = customState.getByteArray(RETAINED_INITIAL_HTML_PARSED_SHA256_KEY)
initialContentHolder = customState.getParcelable(RETAINED_INITIAL_HTML_PARSED_SHA256_KEY)

val retainedHtml = InstanceStateUtils.readAndPurgeTempInstance<String>(RETAINED_HTML_KEY, "", savedState.state)
fromHtml(retainedHtml)

Expand Down Expand Up @@ -592,7 +588,8 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
bundle.putInt(HISTORY_CURSOR_KEY, history.historyCursor)
InstanceStateUtils.writeTempInstance(context, externalLogger, INPUT_LAST_KEY, history.inputLast, bundle)
bundle.putInt(VISIBILITY_KEY, visibility)
bundle.putByteArray(RETAINED_INITIAL_HTML_PARSED_SHA256_KEY, initialEditorContentParsedSHA256)
bundle.putParcelable(RETAINED_INITIAL_HTML_PARSED_SHA256_KEY, initialContentHolder)

InstanceStateUtils.writeTempInstance(context, externalLogger, RETAINED_HTML_KEY, toHtml(false), bundle)
bundle.putInt(SELECTION_START_KEY, selectionStart)
bundle.putInt(SELECTION_END_KEY, selectionEnd)
Expand Down Expand Up @@ -991,12 +988,31 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown

setSelection(cursorPosition)

calculateInitialHTMLSHA()
initialContentHolder?.let {
if (it.needToSetInitialValue()) {
try {
it.setInitialContent(toPlainHtml(false))
} catch (e: Throwable) {
// Do nothing here. `toPlainHtml` can throw exceptions
}
}
}

loadImages()
loadVideos()
}

override fun hasChanges(): AztecInitialContentHolder.EditorHasChanges {
initialContentHolder?.let {
try {
return it.hasChanges(toPlainHtml(false))
} catch (e: Throwable) {
// Do nothing here. `toPlainHtml` can throw exceptions
}
}
return AztecInitialContentHolder.EditorHasChanges.UNKNOWN
}

private fun loadImages() {
val spans = this.text.getSpans(0, text.length, AztecImageSpan::class.java)
val loadingDrawable = AztecText.getPlaceholderDrawableFromResID(context, drawableLoading, maxImagesWidth)
Expand Down Expand Up @@ -1067,39 +1083,6 @@ open class AztecText : AppCompatEditText, TextWatcher, UnknownHtmlSpan.OnUnknown
}
}

private fun calculateInitialHTMLSHA() {
try {
// Do not recalculate the hash if it's not the first call to `fromHTML`.
if (initialEditorContentParsedSHA256.isEmpty() || Arrays.equals(initialEditorContentParsedSHA256, calculateSHA256(""))) {
val initialHTMLParsed = toPlainHtml(false)
initialEditorContentParsedSHA256 = calculateSHA256(initialHTMLParsed)
}
} catch (e: Throwable) {
// Do nothing here. `toPlainHtml` can throw exceptions, also calculateSHA256 -> NoSuchAlgorithmException
}
}

@Throws(NoSuchAlgorithmException::class)
private fun calculateSHA256(s: String): ByteArray {
val digest = MessageDigest.getInstance("SHA-256")
digest.update(s.toByteArray())
return digest.digest()
}

open fun hasChanges(): EditorHasChanges {
if (!initialEditorContentParsedSHA256.isEmpty()) {
try {
if (Arrays.equals(initialEditorContentParsedSHA256, calculateSHA256(toPlainHtml(false)))) {
return EditorHasChanges.NO_CHANGES
}
return EditorHasChanges.CHANGES
} catch (e: Throwable) {
// Do nothing here. `toPlainHtml` can throw exceptions, also calculateSHA256 -> NoSuchAlgorithmException
}
}
return EditorHasChanges.UNKNOWN
}

// returns regular or "calypso" html depending on the mode
fun toHtml(withCursorTag: Boolean = false): String {
val html = toPlainHtml(withCursorTag)
Expand Down
Loading