1
0
Fork 0

Test migration classes (#12677)

master
Tiger Oakes 2020-07-17 14:35:13 -07:00 committed by GitHub
parent 67fda80453
commit 13949d6968
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 243 additions and 100 deletions

View File

@ -5,28 +5,15 @@
package org.mozilla.fenix.migration package org.mozilla.fenix.migration
import android.content.Intent import android.content.Intent
import android.graphics.Rect
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.annotation.DimenRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_migration.* import kotlinx.android.synthetic.main.activity_migration.*
import kotlinx.android.synthetic.main.migration_list_item.view.*
import mozilla.components.support.base.log.logger.Logger import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.migration.AbstractMigrationProgressActivity import mozilla.components.support.migration.AbstractMigrationProgressActivity
import mozilla.components.support.migration.AbstractMigrationService import mozilla.components.support.migration.AbstractMigrationService
import mozilla.components.support.migration.Migration
import mozilla.components.support.migration.Migration.Bookmarks
import mozilla.components.support.migration.Migration.History
import mozilla.components.support.migration.Migration.Logins
import mozilla.components.support.migration.Migration.Settings
import mozilla.components.support.migration.MigrationResults import mozilla.components.support.migration.MigrationResults
import mozilla.components.support.migration.state.MigrationAction import mozilla.components.support.migration.state.MigrationAction
import mozilla.components.support.migration.state.MigrationProgress import mozilla.components.support.migration.state.MigrationProgress
@ -97,91 +84,10 @@ class MigrationProgressActivity : AbstractMigrationProgressActivity() {
migration_button.setBackgroundResource(R.drawable.migration_button_background) migration_button.setBackgroundResource(R.drawable.migration_button_background)
migration_button_progress_bar.visibility = View.INVISIBLE migration_button_progress_bar.visibility = View.INVISIBLE
// Keep the results list up-to-date. // Keep the results list up-to-date.
statusAdapter.submitList(results.toItemList()) statusAdapter.updateData(results)
} }
override fun onMigrationStateChanged(progress: MigrationProgress, results: MigrationResults) { override fun onMigrationStateChanged(progress: MigrationProgress, results: MigrationResults) {
statusAdapter.submitList(results.toItemList()) statusAdapter.updateData(results)
}
}
// These are the only items we want to show migrating in the UI.
internal val whiteList = linkedMapOf(
Settings to R.string.settings_title,
History to R.string.preferences_sync_history,
Bookmarks to R.string.preferences_sync_bookmarks,
Logins to R.string.migration_text_passwords
)
internal fun MigrationResults.toItemList() = whiteList.keys
.map {
if (containsKey(it)) {
MigrationItem(it, getValue(it).success)
} else {
MigrationItem(it)
}
}
internal data class MigrationItem(val migration: Migration, val status: Boolean = false)
internal class MigrationStatusAdapter :
ListAdapter<MigrationItem, MigrationStatusAdapter.ViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
override fun getItemViewType(position: Int): Int = R.layout.migration_list_item
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val context = view.context
private val title = view.migration_item_name
private val status = view.migration_status_image
fun bind(item: MigrationItem) {
// Get the resource ID for the item.
val migrationText = whiteList[item.migration]?.run {
context.getString(this)
} ?: ""
title.text = migrationText
status.visibility = if (item.status) View.VISIBLE else View.INVISIBLE
status.contentDescription = context.getString(R.string.migration_icon_description)
}
}
private object DiffCallback : DiffUtil.ItemCallback<MigrationItem>() {
override fun areItemsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName
override fun areContentsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName &&
oldItem.status == newItem.status
}
}
internal class MigrationStatusItemDecoration(
@DimenRes private val spacing: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildViewHolder(view).adapterPosition
val itemCount = state.itemCount
outRect.left = spacing
outRect.right = spacing
outRect.top = spacing
outRect.bottom = if (position == itemCount - 1) spacing else 0
} }
} }

View File

@ -0,0 +1,107 @@
/* 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.migration
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.Px
import androidx.core.view.isInvisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.migration_list_item.view.*
import mozilla.components.support.migration.Migration
import mozilla.components.support.migration.MigrationResults
import org.mozilla.fenix.R
internal data class MigrationItem(
val migration: Migration,
val status: Boolean = false
)
// These are the only items we want to show migrating in the UI.
internal val whiteList = linkedMapOf(
Migration.Settings to R.string.settings_title,
Migration.History to R.string.preferences_sync_history,
Migration.Bookmarks to R.string.preferences_sync_bookmarks,
Migration.Logins to R.string.migration_text_passwords
)
internal class MigrationStatusAdapter :
ListAdapter<MigrationItem, MigrationStatusAdapter.ViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.migration_list_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position))
}
/**
* Filter the [results] to only include items in [whiteList] and update the adapter.
*/
fun updateData(results: MigrationResults) {
val itemList = whiteList.keys.map {
if (results.containsKey(it)) {
MigrationItem(it, results.getValue(it).success)
} else {
MigrationItem(it)
}
}
submitList(itemList)
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val context = view.context
private val title = view.migration_item_name
private val status = view.migration_status_image
fun bind(item: MigrationItem) {
// Get the resource ID for the item.
val migrationText = whiteList[item.migration]?.let {
context.getString(it)
}.orEmpty()
title.text = migrationText
status.isInvisible = !item.status
status.contentDescription = context.getString(R.string.migration_icon_description)
}
}
private object DiffCallback : DiffUtil.ItemCallback<MigrationItem>() {
override fun areItemsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName
override fun areContentsTheSame(oldItem: MigrationItem, newItem: MigrationItem) =
oldItem.migration.javaClass.simpleName == newItem.migration.javaClass.simpleName &&
oldItem.status == newItem.status
}
}
internal class MigrationStatusItemDecoration(
@Px private val spacing: Int
) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val position = parent.getChildViewHolder(view).adapterPosition
val itemCount = state.itemCount
outRect.left = spacing
outRect.right = spacing
outRect.top = spacing
outRect.bottom = if (position == itemCount - 1) spacing else 0
}
}

View File

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public /* 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 * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.migration package org.mozilla.fenix.migration
@ -15,7 +15,8 @@ import org.mozilla.fenix.components.metrics.MetricController
class MigrationTelemetryListener( class MigrationTelemetryListener(
private val metrics: MetricController, private val metrics: MetricController,
private val store: MigrationStore private val store: MigrationStore,
private val logger: Logger = Logger("MigrationTelemetryListener")
) { ) {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@ -23,7 +24,7 @@ class MigrationTelemetryListener(
// Observe for migration completed. // Observe for migration completed.
store.flowScoped { flow -> store.flowScoped { flow ->
flow.collect { state -> flow.collect { state ->
Logger("MigrationTelemetryListener").debug("Migration state: ${state.progress}") logger.debug("Migration state: ${state.progress}")
if (state.progress == MigrationProgress.COMPLETED) { if (state.progress == MigrationProgress.COMPLETED) {
metrics.track(Event.FennecToFenixMigrated) metrics.track(Event.FennecToFenixMigrated)
} }

View File

@ -0,0 +1,59 @@
/* 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.migration
import android.view.View
import android.widget.FrameLayout
import kotlinx.android.synthetic.main.migration_list_item.view.*
import mozilla.components.support.migration.Migration
import mozilla.components.support.migration.MigrationRun
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.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class MigrationStatusAdapterTest {
private lateinit var adapter: MigrationStatusAdapter
@Before
fun setup() {
adapter = MigrationStatusAdapter()
}
@Test
fun `getItemCount should return the number of items in whitelist`() {
assertEquals(0, adapter.itemCount)
adapter.updateData(mapOf(
Migration.Addons to MigrationRun(0, success = true),
Migration.Settings to MigrationRun(0, success = true),
Migration.Bookmarks to MigrationRun(0, success = false)
))
assertEquals(4, adapter.itemCount)
}
@Test
fun `creates and binds viewholder`() {
adapter.updateData(mapOf(
Migration.History to MigrationRun(0, success = true)
))
val holder1 = adapter.createViewHolder(FrameLayout(testContext), 0)
val holder2 = adapter.createViewHolder(FrameLayout(testContext), 0)
adapter.bindViewHolder(holder1, 0)
adapter.bindViewHolder(holder2, 1)
assertEquals("Settings", holder1.itemView.migration_item_name.text)
assertEquals(View.INVISIBLE, holder1.itemView.migration_status_image.visibility)
assertEquals("History", holder2.itemView.migration_item_name.text)
assertEquals(View.VISIBLE, holder2.itemView.migration_status_image.visibility)
assertEquals("Migration completed", holder2.itemView.migration_status_image.contentDescription)
}
}

View File

@ -0,0 +1,70 @@
/* 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.migration
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.migration.state.MigrationAction
import mozilla.components.support.migration.state.MigrationStore
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
@ExperimentalCoroutinesApi
class MigrationTelemetryListenerTest {
private val testDispatcher = TestCoroutineDispatcher()
@get:Rule
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
@MockK(relaxed = true) private lateinit var metrics: MetricController
@MockK(relaxed = true) private lateinit var logger: Logger
private lateinit var store: MigrationStore
private lateinit var listener: MigrationTelemetryListener
@Before
fun setup() {
MockKAnnotations.init(this)
store = MigrationStore()
listener = MigrationTelemetryListener(
metrics = metrics,
store = store,
logger = logger
)
}
@Test
fun `progress state is logged`() = testDispatcher.runBlockingTest {
listener.start()
store.dispatch(MigrationAction.Started).joinBlocking()
store.dispatch(MigrationAction.Completed).joinBlocking()
store.dispatch(MigrationAction.Clear).joinBlocking()
verifyOrder {
logger.debug("Migration state: MIGRATING")
logger.debug("Migration state: COMPLETED")
logger.debug("Migration state: NONE")
}
}
@Test
fun `metrics are logged when migration is completed`() = testDispatcher.runBlockingTest {
listener.start()
store.dispatch(MigrationAction.Completed).joinBlocking()
verify { metrics.track(Event.FennecToFenixMigrated) }
}
}