From 9ae1aa6f16a24a8cf50afe2f2c242e74533740aa Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Wed, 8 Jul 2020 12:07:59 -0700 Subject: [PATCH] Add tests for exceptions --- .../fenix/exceptions/ExceptionsAdapter.kt | 14 ++-- .../fenix/exceptions/ExceptionsFragment.kt | 4 +- .../fenix/exceptions/ExceptionsView.kt | 33 ++++---- .../fenix/exceptions/ExceptionsAdapterTest.kt | 42 ++++++++++ .../fenix/exceptions/ExceptionsViewTest.kt | 82 +++++++++++++++++++ .../ExceptionsDeleteButtonViewHolderTest.kt | 43 ++++++++++ .../ExceptionsListItemViewHolderTest.kt | 56 +++++++++++++ 7 files changed, 249 insertions(+), 25 deletions(-) create mode 100644 app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt index 92053ba9e..1f5c62dd7 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsAdapter.kt @@ -14,19 +14,13 @@ import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder -sealed class AdapterItem { - object DeleteButton : AdapterItem() - object Header : AdapterItem() - data class Item(val item: TrackingProtectionException) : AdapterItem() -} - /** * Adapter for a list of sites that are exempted from Tracking Protection, * along with controls to remove the exception. */ class ExceptionsAdapter( private val interactor: ExceptionsInteractor -) : ListAdapter(DiffCallback) { +) : ListAdapter(DiffCallback) { /** * Change the list of items that are displayed. @@ -67,6 +61,12 @@ class ExceptionsAdapter( } } + sealed class AdapterItem { + object DeleteButton : AdapterItem() + object Header : AdapterItem() + data class Item(val item: TrackingProtectionException) : AdapterItem() + } + private object DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: AdapterItem, newItem: AdapterItem) = areContentsTheSame(oldItem, newItem) diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt index 8f193f222..f1ba7582e 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsFragment.kt @@ -28,6 +28,7 @@ import org.mozilla.fenix.settings.SupportUtils * along with controls to remove the exception. */ class ExceptionsFragment : Fragment() { + private lateinit var exceptionsStore: ExceptionsFragmentStore private lateinit var exceptionsView: ExceptionsView private lateinit var exceptionsInteractor: ExceptionsInteractor @@ -48,7 +49,7 @@ class ExceptionsFragment : Fragment() { exceptionsStore = StoreProvider.get(this) { ExceptionsFragmentStore( ExceptionsFragmentState( - items = listOf() + items = emptyList() ) ) } @@ -61,7 +62,6 @@ class ExceptionsFragment : Fragment() { @ExperimentalCoroutinesApi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) consumeFrom(exceptionsStore) { exceptionsView.update(it) } diff --git a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt index c9cbe39c2..b40dcf5f0 100644 --- a/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt +++ b/app/src/main/java/org/mozilla/fenix/exceptions/ExceptionsView.kt @@ -4,16 +4,16 @@ package org.mozilla.fenix.exceptions -import android.text.SpannableString import android.text.method.LinkMovementMethod import android.text.style.UnderlineSpan import android.view.LayoutInflater import android.view.ViewGroup import android.widget.FrameLayout +import androidx.core.text.toSpannable import androidx.core.view.isVisible import androidx.recyclerview.widget.LinearLayoutManager import kotlinx.android.extensions.LayoutContainer -import kotlinx.android.synthetic.main.component_exceptions.view.* +import kotlinx.android.synthetic.main.component_exceptions.* import mozilla.components.concept.engine.content.blocking.TrackingProtectionException import org.mozilla.fenix.R @@ -42,35 +42,36 @@ interface ExceptionsViewInteractor { * View that contains and configures the Exceptions List */ class ExceptionsView( - override val containerView: ViewGroup, - val interactor: ExceptionsInteractor + container: ViewGroup, + interactor: ExceptionsInteractor ) : LayoutContainer { - val view: FrameLayout = LayoutInflater.from(containerView.context) - .inflate(R.layout.component_exceptions, containerView, true) + override val containerView: FrameLayout = LayoutInflater.from(container.context) + .inflate(R.layout.component_exceptions, container, true) .findViewById(R.id.exceptions_wrapper) private val exceptionsAdapter = ExceptionsAdapter(interactor) init { - view.exceptions_list.apply { + exceptions_list.apply { adapter = exceptionsAdapter - layoutManager = LinearLayoutManager(containerView.context) + layoutManager = LinearLayoutManager(container.context) } - val learnMoreText = view.exceptions_learn_more.text.toString() - val textWithLink = SpannableString(learnMoreText).apply { - setSpan(UnderlineSpan(), 0, learnMoreText.length, 0) - } - with(view.exceptions_learn_more) { + + with(exceptions_learn_more) { + val learnMoreText = text + text = learnMoreText.toSpannable().apply { + setSpan(UnderlineSpan(), 0, learnMoreText.length, 0) + } + movementMethod = LinkMovementMethod.getInstance() - text = textWithLink setOnClickListener { interactor.onLearnMore() } } } fun update(state: ExceptionsFragmentState) { - view.exceptions_empty_view.isVisible = state.items.isEmpty() - view.exceptions_list.isVisible = state.items.isNotEmpty() + exceptions_empty_view.isVisible = state.items.isEmpty() + exceptions_list.isVisible = state.items.isNotEmpty() exceptionsAdapter.updateData(state.items) } } diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt new file mode 100644 index 000000000..dae9ff464 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsAdapterTest.kt @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.exceptions + +import io.mockk.mockk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder +import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@ExperimentalCoroutinesApi +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsAdapterTest { + + private lateinit var interactor: ExceptionsInteractor + private lateinit var adapter: ExceptionsAdapter + + @Before + fun setup() { + interactor = mockk() + adapter = ExceptionsAdapter(interactor) + } + + @Test + fun `binds header and delete button with other adapter items`() = runBlockingTest { + adapter.updateData(listOf(mockk(), mockk())) + + assertEquals(4, adapter.itemCount) + assertEquals(ExceptionsHeaderViewHolder.LAYOUT_ID, adapter.getItemViewType(0)) + assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(1)) + assertEquals(ExceptionsListItemViewHolder.LAYOUT_ID, adapter.getItemViewType(2)) + assertEquals(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, adapter.getItemViewType(3)) + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt new file mode 100644 index 000000000..4adc07709 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/ExceptionsViewTest.kt @@ -0,0 +1,82 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.exceptions + +import android.text.Spannable +import android.text.method.LinkMovementMethod +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.core.view.isVisible +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.unmockkConstructor +import io.mockk.verify +import kotlinx.android.synthetic.main.component_exceptions.* +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsViewTest { + + private lateinit var container: ViewGroup + private lateinit var interactor: ExceptionsInteractor + private lateinit var exceptionsView: ExceptionsView + + @Before + fun setup() { + mockkConstructor(ExceptionsAdapter::class) + container = FrameLayout(testContext) + interactor = mockk() + + exceptionsView = ExceptionsView(container, interactor) + every { anyConstructed().updateData(any()) } just Runs + } + + @After + fun teardown() { + unmockkConstructor(ExceptionsAdapter::class) + } + + @Test + fun `binds exception text`() { + assertTrue(exceptionsView.exceptions_learn_more.movementMethod is LinkMovementMethod) + assertTrue(exceptionsView.exceptions_learn_more.text is Spannable) + assertEquals("Learn more", exceptionsView.exceptions_learn_more.text.toString()) + + every { interactor.onLearnMore() } just Runs + exceptionsView.exceptions_learn_more.performClick() + verify { interactor.onLearnMore() } + } + + @Test + fun `binds empty list to adapter`() { + exceptionsView.update(ExceptionsFragmentState(emptyList())) + + assertTrue(exceptionsView.exceptions_empty_view.isVisible) + assertFalse(exceptionsView.exceptions_list.isVisible) + verify { anyConstructed().updateData(emptyList()) } + } + + @Test + fun `binds list with items to adapter`() { + val items = listOf(mockk(), mockk()) + exceptionsView.update(ExceptionsFragmentState(items)) + + assertFalse(exceptionsView.exceptions_empty_view.isVisible) + assertTrue(exceptionsView.exceptions_list.isVisible) + verify { anyConstructed().updateData(items) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt new file mode 100644 index 000000000..28bb40dc2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsDeleteButtonViewHolderTest.kt @@ -0,0 +1,43 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.exceptions.viewholders + +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.view.ContextThemeWrapper +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.delete_exceptions_button.view.* +import mozilla.components.support.test.robolectric.testContext +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.R +import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsDeleteButtonViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: ExceptionsInteractor + private lateinit var viewHolder: ExceptionsDeleteButtonViewHolder + + @Before + fun setup() { + val appCompatContext = ContextThemeWrapper(testContext, R.style.NormalTheme) + view = LayoutInflater.from(appCompatContext) + .inflate(ExceptionsDeleteButtonViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + viewHolder = ExceptionsDeleteButtonViewHolder(view, interactor) + } + + @Test + fun `calls onDeleteAll on click`() { + view.removeAllExceptions.performClick() + + verify { interactor.onDeleteAll() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt new file mode 100644 index 000000000..7dd1ddb5e --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/exceptions/viewholders/ExceptionsListItemViewHolderTest.kt @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.exceptions.viewholders + +import android.view.LayoutInflater +import android.view.View +import io.mockk.mockk +import io.mockk.verify +import kotlinx.android.synthetic.main.exception_item.view.* +import mozilla.components.concept.engine.content.blocking.TrackingProtectionException +import mozilla.components.support.test.robolectric.testContext +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.exceptions.ExceptionsInteractor +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner + +@RunWith(FenixRobolectricTestRunner::class) +class ExceptionsListItemViewHolderTest { + + private lateinit var view: View + private lateinit var interactor: ExceptionsInteractor + private lateinit var viewHolder: ExceptionsListItemViewHolder + + @Before + fun setup() { + view = LayoutInflater.from(testContext) + .inflate(ExceptionsListItemViewHolder.LAYOUT_ID, null) + interactor = mockk(relaxed = true) + viewHolder = ExceptionsListItemViewHolder(view, interactor) + } + + @Test + fun `bind url and icon`() { + val exception = object : TrackingProtectionException { + override val url = "https://example.com/icon.svg" + } + viewHolder.bind(exception) + + assertEquals(exception.url, view.webAddressView.text) + } + + @Test + fun `calls onDeleteOne on click`() { + val exception = object : TrackingProtectionException { + override val url = "https://example.com/icon.svg" + } + viewHolder.bind(exception) + view.delete_exception.performClick() + + verify { interactor.onDeleteOne(exception) } + } +}