From e3ed84aa46e215a44cb5a785949b89736b53a4bd Mon Sep 17 00:00:00 2001 From: Veena Date: Mon, 25 Nov 2019 17:36:24 +0530 Subject: [PATCH] Fix part #361: HTML formatting throughout app (#404) * Update HtmlParser.kt * Update UrlImageParser.kt * nit changes. * update changes * Update HtmlParserTest.kt * Update HtmlParserTest.kt * Update HtmlParserTest.kt * updated testcase * Update HtmlParserTest.kt * Update HtmlParserTest.kt * Update HtmlParser.kt * Update UrlImageParser.kt * Update HtmlParserTest.kt * Update HtmlParserTest.kt * Update HtmlParser.kt * Update UrlImageParser.kt * Update HtmlParserTest.kt * Update HtmlParser.kt * updated imports * Update HtmlParser.kt --- .../org/oppia/app/parser/HtmlParserTest.kt | 128 ++++++++++++++++++ .../java/org/oppia/util/parser/HtmlParser.kt | 32 ++++- 2 files changed, 158 insertions(+), 2 deletions(-) diff --git a/app/src/sharedTest/java/org/oppia/app/parser/HtmlParserTest.kt b/app/src/sharedTest/java/org/oppia/app/parser/HtmlParserTest.kt index 3e7a3a74fe5..5fdebf928f5 100644 --- a/app/src/sharedTest/java/org/oppia/app/parser/HtmlParserTest.kt +++ b/app/src/sharedTest/java/org/oppia/app/parser/HtmlParserTest.kt @@ -1,9 +1,12 @@ package org.oppia.app.parser import android.app.Activity +import android.app.Application +import android.content.Context import android.content.Intent import android.text.Spannable import android.widget.TextView +import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents @@ -13,6 +16,14 @@ import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.rule.ActivityTestRule import com.google.common.truth.Truth.assertThat +import dagger.Binds +import dagger.BindsInstance +import dagger.Component +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher import org.hamcrest.Matchers.not import org.junit.After import org.junit.Before @@ -21,9 +32,24 @@ import org.junit.Test import org.junit.runner.RunWith import org.oppia.app.R import org.oppia.app.testing.HtmlParserTestActivity +import org.oppia.util.caching.CacheAssetsLocally +import org.oppia.util.logging.EnableConsoleLog +import org.oppia.util.logging.EnableFileLog +import org.oppia.util.logging.GlobalLogLevel +import org.oppia.util.logging.LogLevel +import org.oppia.util.parser.DefaultGcsPrefix +import org.oppia.util.parser.DefaultGcsResource +import org.oppia.util.parser.GlideImageLoader import org.oppia.util.parser.HtmlParser +import org.oppia.util.parser.ImageDownloadUrlTemplate +import org.oppia.util.parser.ImageLoader +import org.oppia.util.threading.BackgroundDispatcher +import org.oppia.util.threading.BlockingDispatcher import javax.inject.Inject +import javax.inject.Qualifier +import javax.inject.Singleton +// TODO(#277): Add tests for UrlImageParser. /** Tests for [HtmlParser]. */ @RunWith(AndroidJUnit4::class) class HtmlParserTest { @@ -39,11 +65,19 @@ class HtmlParserTest { @Before fun setUp() { + setUpTestApplicationComponent() Intents.init() val intent = Intent(Intent.ACTION_PICK) launchedActivity = activityTestRule.launchActivity(intent) } + private fun setUpTestApplicationComponent() { + DaggerHtmlParserTest_TestApplicationComponent.builder() + .setApplication(ApplicationProvider.getApplicationContext()) + .build() + .inject(this) + } + @Test fun testHtmlContent_handleCustomOppiaTags_parsedHtmlDisplaysStyledText() { val textView = activityTestRule.activity.findViewById(R.id.test_html_content_text_view) as TextView @@ -83,4 +117,98 @@ class HtmlParserTest { fun tearDown() { Intents.release() } + + @Qualifier + annotation class TestDispatcher + + // TODO(#89): Move this to a common test application component. + @Module + class TestModule { + @Provides + @Singleton + fun provideContext(application: Application): Context { + return application + } + + @ExperimentalCoroutinesApi + @Singleton + @Provides + @TestDispatcher + fun provideTestDispatcher(): CoroutineDispatcher { + return TestCoroutineDispatcher() + } + + @Singleton + @Provides + @BackgroundDispatcher + fun provideBackgroundDispatcher(@TestDispatcher testDispatcher: CoroutineDispatcher): CoroutineDispatcher { + return testDispatcher + } + + @Singleton + @Provides + @BlockingDispatcher + fun provideBlockingDispatcher(@TestDispatcher testDispatcher: CoroutineDispatcher): CoroutineDispatcher { + return testDispatcher + } + + @Provides + @CacheAssetsLocally + fun provideCacheAssetsLocally(): Boolean = false + + @Provides + @DefaultGcsPrefix + @Singleton + fun provideDefaultGcsPrefix(): String { + return "https://storage.googleapis.com/" + } + + @Provides + @DefaultGcsResource + @Singleton + fun provideDefaultGcsResource(): String { + return "oppiaserver-resources/" + } + + @Provides + @ImageDownloadUrlTemplate + @Singleton + fun provideImageDownloadUrlTemplate(): String { + return "%s/%s/assets/image/%s" + } + + // TODO(#59): Either isolate these to their own shared test module, or use the real logging + // module in tests to avoid needing to specify these settings for tests. + @EnableConsoleLog + @Provides + fun provideEnableConsoleLog(): Boolean = true + + @EnableFileLog + @Provides + fun provideEnableFileLog(): Boolean = false + + @GlobalLogLevel + @Provides + fun provideGlobalLogLevel(): LogLevel = LogLevel.VERBOSE + } + + @Module + abstract class ImageTestModule { + @Binds + abstract fun provideGlideImageLoader(impl: GlideImageLoader): ImageLoader + } + + @Singleton + @Component(modules = [TestModule::class, ImageTestModule::class]) + interface TestApplicationComponent { + @Component.Builder + interface Builder { + @BindsInstance + fun setApplication(application: Application): Builder + + fun build(): TestApplicationComponent + } + + fun inject(htmlParserTest: HtmlParserTest) + } } diff --git a/utility/src/main/java/org/oppia/util/parser/HtmlParser.kt b/utility/src/main/java/org/oppia/util/parser/HtmlParser.kt index 14f1205dd64..affe7096d5f 100755 --- a/utility/src/main/java/org/oppia/util/parser/HtmlParser.kt +++ b/utility/src/main/java/org/oppia/util/parser/HtmlParser.kt @@ -2,6 +2,7 @@ package org.oppia.util.parser import android.text.Html import android.text.Spannable +import android.text.SpannableStringBuilder import android.widget.TextView import javax.inject.Inject @@ -32,6 +33,7 @@ class HtmlParser private constructor( if (htmlContent.contains("\n\n")) { htmlContent = htmlContent.replace("\n\n", "") } + if (htmlContent.contains(CUSTOM_IMG_TAG)) { htmlContent = htmlContent.replace(CUSTOM_IMG_TAG, REPLACE_IMG_TAG, /* ignoreCase= */false) htmlContent = htmlContent.replace( @@ -43,10 +45,36 @@ class HtmlParser private constructor( val imageGetter = urlImageParserFactory.create(htmlContentTextView, entityType, entityId, imageCenterAlign) return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - Html.fromHtml(htmlContent, Html.FROM_HTML_MODE_LEGACY, imageGetter, /* tagHandler= */ null) as Spannable + trimSpannable( + Html.fromHtml( + htmlContent, + Html.FROM_HTML_MODE_LEGACY, + imageGetter, /* tagHandler= */ + null + ) as SpannableStringBuilder + ) } else { - Html.fromHtml(htmlContent, imageGetter, /* tagHandler= */ null) as Spannable + trimSpannable(Html.fromHtml(htmlContent, imageGetter, /* tagHandler= */ null) as SpannableStringBuilder) + } + } + + private fun trimSpannable(spannable: SpannableStringBuilder): SpannableStringBuilder { + var trimStart = 0 + var trimEnd = 0 + + var text = spannable.toString() + + if (text.startsWith("\n")) { + text = text.substring(1) + trimStart += 1 } + + if (text.endsWith("\n")) { + text = text.substring(0, text.length - 1) + trimEnd += 2 + } + + return spannable.delete(0, trimStart).delete(spannable.length - trimEnd, spannable.length) } class Factory @Inject constructor(private val urlImageParserFactory: UrlImageParser.Factory) {