From 50a026183d56b379f9d0f20a8f420106a00aaae6 Mon Sep 17 00:00:00 2001 From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com> Date: Wed, 6 Jan 2021 14:48:34 +0100 Subject: [PATCH] Make Localization.relativeTime testable Problem is global state in static variable prettyTime. But for performance reasons on Android that is preferred. Now allow injecting prettyTime dependency by making init function public. --- app/src/main/java/org/schabi/newpipe/App.java | 37 ++++++++---------- .../java/org/schabi/newpipe/MainActivity.java | 16 +++----- .../org/schabi/newpipe/util/Localization.java | 27 ++++++------- .../schabi/newpipe/util/LocalizationTest.kt | 39 +++++++++++++++++++ 4 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java index 7e3466f67..062a033e9 100644 --- a/app/src/main/java/org/schabi/newpipe/App.java +++ b/app/src/main/java/org/schabi/newpipe/App.java @@ -6,16 +6,26 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Build; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.multidex.MultiDexApplication; import androidx.preference.PreferenceManager; - import com.nostra13.universalimageloader.cache.memory.impl.LRULimitedMemoryCache; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; - +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.CompositeException; +import io.reactivex.rxjava3.exceptions.MissingBackpressureException; +import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; +import io.reactivex.rxjava3.exceptions.UndeliverableException; +import io.reactivex.rxjava3.functions.Consumer; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.acra.ACRA; import org.acra.config.ACRAConfigurationException; import org.acra.config.CoreConfiguration; @@ -31,21 +41,6 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.ServiceHelper; import org.schabi.newpipe.util.StateSaver; -import java.io.IOException; -import java.io.InterruptedIOException; -import java.net.SocketException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.reactivex.rxjava3.disposables.Disposable; -import io.reactivex.rxjava3.exceptions.CompositeException; -import io.reactivex.rxjava3.exceptions.MissingBackpressureException; -import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException; -import io.reactivex.rxjava3.exceptions.UndeliverableException; -import io.reactivex.rxjava3.functions.Consumer; -import io.reactivex.rxjava3.plugins.RxJavaPlugins; - /* * Copyright (C) Hans-Christoph Steiner 2016 * App.java is part of NewPipe. @@ -93,9 +88,9 @@ public class App extends MultiDexApplication { SettingsActivity.initSettings(this); NewPipe.init(getDownloader(), - Localization.getPreferredLocalization(this), - Localization.getPreferredContentCountry(this)); - Localization.init(getApplicationContext()); + Localization.getPreferredLocalization(this), + Localization.getPreferredContentCountry(this)); + Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext())); StateSaver.init(this); initNotificationChannels(); diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 0c784e9d5..1d1f87f6f 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -20,6 +20,8 @@ package org.schabi.newpipe; +import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -41,7 +43,6 @@ import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.Spinner; - import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; @@ -52,9 +53,10 @@ import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.preference.PreferenceManager; - import com.google.android.material.bottomsheet.BottomSheetBehavior; - +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import org.schabi.newpipe.databinding.ActivityMainBinding; import org.schabi.newpipe.databinding.DrawerHeaderBinding; import org.schabi.newpipe.databinding.DrawerLayoutBinding; @@ -87,12 +89,6 @@ import org.schabi.newpipe.util.TLSSocketFactoryCompat; import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.views.FocusOverlayView; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; - public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release"); @@ -468,7 +464,7 @@ public class MainActivity extends AppCompatActivity { protected void onResume() { assureCorrectAppLanguage(this); // Change the date format to match the selected language on resume - Localization.init(getApplicationContext()); + Localization.initPrettyTime(Localization.resolvePrettyTime(getApplicationContext())); super.onResume(); // Close drawer on return, and don't show animation, diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java index 978f558c4..3fdab9a12 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Localization.java +++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java @@ -9,19 +9,10 @@ import android.icu.text.CompactDecimalFormat; import android.os.Build; import android.text.TextUtils; import android.util.DisplayMetrics; - import androidx.annotation.NonNull; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.preference.PreferenceManager; - -import org.ocpsoft.prettytime.PrettyTime; -import org.ocpsoft.prettytime.units.Decade; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.ListExtractor; -import org.schabi.newpipe.extractor.localization.ContentCountry; -import org.schabi.newpipe.ktx.OffsetDateTimeKt; - import java.math.BigDecimal; import java.math.RoundingMode; import java.text.NumberFormat; @@ -33,6 +24,12 @@ import java.util.Arrays; import java.util.Calendar; import java.util.List; import java.util.Locale; +import org.ocpsoft.prettytime.PrettyTime; +import org.ocpsoft.prettytime.units.Decade; +import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.ListExtractor; +import org.schabi.newpipe.extractor.localization.ContentCountry; +import org.schabi.newpipe.ktx.OffsetDateTimeKt; /* @@ -62,10 +59,6 @@ public final class Localization { private Localization() { } - public static void init(final Context context) { - initPrettyTime(context); - } - @NonNull public static String concatenateStrings(final String... strings) { return concatenateStrings(Arrays.asList(strings)); @@ -307,12 +300,16 @@ public final class Localization { // Pretty Time //////////////////////////////////////////////////////////////////////////*/ - private static void initPrettyTime(final Context context) { - prettyTime = new PrettyTime(getAppLocale(context)); + public static void initPrettyTime(final PrettyTime time) { + prettyTime = time; // Do not use decades as YouTube doesn't either. prettyTime.removeUnit(Decade.class); } + public static PrettyTime resolvePrettyTime(final Context context) { + return new PrettyTime(getAppLocale(context)); + } + public static String relativeTime(final OffsetDateTime offsetDateTime) { return relativeTime(OffsetDateTimeKt.toCalendar(offsetDateTime)); } diff --git a/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt new file mode 100644 index 000000000..d0a8e7a40 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/util/LocalizationTest.kt @@ -0,0 +1,39 @@ +package org.schabi.newpipe.util + +import org.junit.Assert.assertEquals +import org.junit.Test +import org.ocpsoft.prettytime.PrettyTime +import java.text.SimpleDateFormat +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.util.GregorianCalendar +import java.util.Locale + +class LocalizationTest { + + @Test + fun `After initializing pretty time relativeTime() with a Calendar must work`() { + val reference = SimpleDateFormat("yyyy/MM/dd").parse("2021/1/1") + Localization.initPrettyTime(PrettyTime(reference, Locale.ENGLISH)) + + val actual = Localization.relativeTime(GregorianCalendar(2021, 1, 6)) + + assertEquals("1 month from now", actual) + } + + @Test(expected = NullPointerException::class) + fun `relativeTime() must fail without initializing pretty time`() { + Localization.relativeTime(GregorianCalendar(2021, 1, 6)) + } + + @Test + fun `relativeTime() with a OffsetDateTime must work`() { + val reference = SimpleDateFormat("yyyy/MM/dd").parse("2021/1/1") + Localization.initPrettyTime(PrettyTime(reference, Locale.ENGLISH)) + + val offset = OffsetDateTime.of(2021, 1, 6, 0, 0, 0, 0, ZoneOffset.UTC) + val actual = Localization.relativeTime(offset) + + assertEquals("5 days from now", actual) + } +}