upstream-sync
parent
1128f921ad
commit
6eb528f912
@ -0,0 +1,58 @@
|
||||
/* 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.settings.creditcards
|
||||
|
||||
import mozilla.components.concept.storage.CreditCard
|
||||
import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW
|
||||
import java.util.Calendar
|
||||
|
||||
/**
|
||||
* The state for the [CreditCardEditorFragment].
|
||||
*
|
||||
* @property billingName The credit card billing name to display.
|
||||
* @property cardNumber The credit card number to display.
|
||||
* @property expiryMonth The selected credit card expiry month.
|
||||
* @property expiryYears The range of expiry years to display.
|
||||
* @property isEditing Whether or not the credit is being edited.
|
||||
*/
|
||||
data class CreditCardEditorState(
|
||||
val billingName: String = "",
|
||||
val cardNumber: String = "",
|
||||
val expiryMonth: Int = 1,
|
||||
val expiryYears: Pair<Int, Int>,
|
||||
val isEditing: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Returns a [CreditCardEditorState] from the given [CreditCard].
|
||||
*/
|
||||
fun CreditCard.toCreditCardEditorState(): CreditCardEditorState {
|
||||
val startYear = expiryYear.toInt()
|
||||
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
|
||||
|
||||
return CreditCardEditorState(
|
||||
billingName = billingName,
|
||||
cardNumber = cardNumber,
|
||||
expiryMonth = expiryMonth.toInt(),
|
||||
expiryYears = Pair(startYear, endYear),
|
||||
isEditing = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the initial credit editor state if no credit card is provided.
|
||||
*
|
||||
* @return an empty [CreditCardEditorState] with a range of expiry years based on the latest
|
||||
* 10 years.
|
||||
*/
|
||||
fun getInitialCreditCardEditorState(): CreditCardEditorState {
|
||||
val calendar = Calendar.getInstance()
|
||||
val startYear = calendar.get(Calendar.YEAR)
|
||||
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
|
||||
|
||||
return CreditCardEditorState(
|
||||
expiryYears = Pair(startYear, endYear)
|
||||
)
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/* 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.settings.creditcards.interactor
|
||||
|
||||
import mozilla.components.concept.storage.UpdatableCreditCardFields
|
||||
import org.mozilla.fenix.settings.creditcards.controller.CreditCardEditorController
|
||||
|
||||
/**
|
||||
* Interface for the credit card editor Interactor.
|
||||
*/
|
||||
interface CreditCardEditorInteractor {
|
||||
|
||||
/**
|
||||
* Navigates back to the credit card preference settings. Called when a user taps on the
|
||||
* "Cancel" button.
|
||||
*/
|
||||
fun onCancelButtonClicked()
|
||||
|
||||
/**
|
||||
* Saves the provided credit card field into the credit card storage. Called when a user
|
||||
* taps on the save menu item or "Save" button.
|
||||
*
|
||||
* @param creditCardFields A [UpdatableCreditCardFields] record to add.
|
||||
*/
|
||||
fun onSaveButtonClicked(creditCardFields: UpdatableCreditCardFields)
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation of [CreditCardEditorInteractor].
|
||||
*
|
||||
* @param controller An instance of [CreditCardEditorController] which will be delegated for all
|
||||
* user interactions.
|
||||
*/
|
||||
class DefaultCreditCardEditorInteractor(
|
||||
private val controller: CreditCardEditorController
|
||||
) : CreditCardEditorInteractor {
|
||||
|
||||
override fun onCancelButtonClicked() {
|
||||
controller.handleCancelButtonClicked()
|
||||
}
|
||||
|
||||
override fun onSaveButtonClicked(creditCardFields: UpdatableCreditCardFields) {
|
||||
controller.handleSaveCreditCard(creditCardFields)
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
/* 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.settings.creditcards.view
|
||||
|
||||
import android.R
|
||||
import android.view.View
|
||||
import android.widget.ArrayAdapter
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.fragment_credit_card_editor.*
|
||||
import mozilla.components.concept.storage.UpdatableCreditCardFields
|
||||
import mozilla.components.support.ktx.android.view.hideKeyboard
|
||||
import org.mozilla.fenix.ext.toEditable
|
||||
import org.mozilla.fenix.settings.creditcards.CreditCardEditorState
|
||||
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardEditorInteractor
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Shows a credit card editor for adding or updating a credit card.
|
||||
*/
|
||||
class CreditCardEditorView(
|
||||
override val containerView: View,
|
||||
private val interactor: CreditCardEditorInteractor
|
||||
) : LayoutContainer {
|
||||
|
||||
/**
|
||||
* Binds the given [CreditCardEditorState] in the [CreditCardEditorFragment].
|
||||
*/
|
||||
fun bind(state: CreditCardEditorState) {
|
||||
cancel_button.setOnClickListener {
|
||||
interactor.onCancelButtonClicked()
|
||||
}
|
||||
|
||||
save_button.setOnClickListener {
|
||||
saveCreditCard()
|
||||
}
|
||||
|
||||
card_number_input.text = state.cardNumber.toEditable()
|
||||
name_on_card_input.text = state.billingName.toEditable()
|
||||
|
||||
bindExpiryMonthDropDown(state.expiryMonth)
|
||||
bindExpiryYearDropDown(state.expiryYears)
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the expiry month dropdown by formatting and populating it with the months in a calendar
|
||||
* year, and set the selection to the provided expiry month.
|
||||
*
|
||||
* @param expiryMonth The selected credit card expiry month to display.
|
||||
*/
|
||||
private fun bindExpiryMonthDropDown(expiryMonth: Int) {
|
||||
val adapter =
|
||||
ArrayAdapter<String>(containerView.context, R.layout.simple_spinner_dropdown_item)
|
||||
val dateFormat = SimpleDateFormat("MMMM (MM)", Locale.getDefault())
|
||||
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1)
|
||||
|
||||
for (month in 0..NUMBER_OF_MONTHS) {
|
||||
calendar.set(Calendar.MONTH, month)
|
||||
adapter.add(dateFormat.format(calendar.time))
|
||||
}
|
||||
|
||||
expiry_month_drop_down.adapter = adapter
|
||||
expiry_month_drop_down.setSelection(expiryMonth - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup the expiry year dropdown with the range specified by the provided expiryYears
|
||||
*
|
||||
* @param expiryYears A range specifying the start and end year to display in the expiry year
|
||||
* dropdown.
|
||||
*/
|
||||
private fun bindExpiryYearDropDown(expiryYears: Pair<Int, Int>) {
|
||||
val adapter =
|
||||
ArrayAdapter<String>(containerView.context, R.layout.simple_spinner_dropdown_item)
|
||||
val (startYear, endYear) = expiryYears
|
||||
|
||||
for (year in startYear until endYear) {
|
||||
adapter.add(year.toString())
|
||||
}
|
||||
|
||||
expiry_year_drop_down.adapter = adapter
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function called by the the "Save" button and menu item to save a new credit card
|
||||
* from the entered credit card fields.
|
||||
*/
|
||||
private fun saveCreditCard() {
|
||||
containerView.hideKeyboard()
|
||||
|
||||
interactor.onSaveButtonClicked(
|
||||
UpdatableCreditCardFields(
|
||||
billingName = name_on_card_input.text.toString(),
|
||||
cardNumber = card_number_input.text.toString(),
|
||||
expiryMonth = (expiry_month_drop_down.selectedItemPosition + 1).toLong(),
|
||||
expiryYear = expiry_year_drop_down.selectedItem.toString().toLong(),
|
||||
cardType = "amex"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Number of months in a year (0-indexed).
|
||||
const val NUMBER_OF_MONTHS = 11
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/* 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.settings.creditcards
|
||||
|
||||
import mozilla.components.concept.storage.CreditCard
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW
|
||||
import java.util.Calendar
|
||||
|
||||
class CreditCardEditorStateTest {
|
||||
|
||||
private val creditCard = CreditCard(
|
||||
guid = "id",
|
||||
billingName = "Banana Apple",
|
||||
cardNumber = "4111111111111110",
|
||||
expiryMonth = 5,
|
||||
expiryYear = 2030,
|
||||
cardType = "amex",
|
||||
timeCreated = 1L,
|
||||
timeLastUsed = 1L,
|
||||
timeLastModified = 1L,
|
||||
timesUsed = 1L
|
||||
)
|
||||
|
||||
@Test
|
||||
fun testToCreditCardEditorState() {
|
||||
val state = creditCard.toCreditCardEditorState()
|
||||
val startYear = creditCard.expiryYear.toInt()
|
||||
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
|
||||
|
||||
with(state) {
|
||||
assertEquals(creditCard.billingName, billingName)
|
||||
assertEquals(creditCard.cardNumber, cardNumber)
|
||||
assertEquals(creditCard.expiryMonth.toInt(), expiryMonth)
|
||||
assertEquals(Pair(startYear, endYear), expiryYears)
|
||||
assertTrue(isEditing)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetInitialCreditCardEditorState() {
|
||||
val state = getInitialCreditCardEditorState()
|
||||
val calendar = Calendar.getInstance()
|
||||
val startYear = calendar.get(Calendar.YEAR)
|
||||
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW
|
||||
|
||||
with(state) {
|
||||
assertEquals("", billingName)
|
||||
assertEquals("", cardNumber)
|
||||
assertEquals(1, expiryMonth)
|
||||
assertEquals(Pair(startYear, endYear), expiryYears)
|
||||
assertFalse(isEditing)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/* 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.settings.creditcards
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.android.synthetic.main.fragment_credit_card_editor.view.*
|
||||
import mozilla.components.concept.storage.CreditCard
|
||||
import mozilla.components.concept.storage.UpdatableCreditCardFields
|
||||
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.R
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.settings.creditcards.CreditCardEditorFragment.Companion.NUMBER_OF_YEARS_TO_SHOW
|
||||
import org.mozilla.fenix.settings.creditcards.interactor.CreditCardEditorInteractor
|
||||
import org.mozilla.fenix.settings.creditcards.view.CreditCardEditorView
|
||||
import java.util.Calendar
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class CreditCardEditorViewTest {
|
||||
|
||||
private lateinit var view: View
|
||||
private lateinit var interactor: CreditCardEditorInteractor
|
||||
private lateinit var creditCardEditorView: CreditCardEditorView
|
||||
|
||||
private val creditCard = CreditCard(
|
||||
guid = "id",
|
||||
billingName = "Banana Apple",
|
||||
cardNumber = "4111111111111110",
|
||||
expiryMonth = 5,
|
||||
expiryYear = 2030,
|
||||
cardType = "amex",
|
||||
timeCreated = 1L,
|
||||
timeLastUsed = 1L,
|
||||
timeLastModified = 1L,
|
||||
timesUsed = 1L
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
view = LayoutInflater.from(testContext).inflate(R.layout.fragment_credit_card_editor, null)
|
||||
interactor = mockk(relaxed = true)
|
||||
|
||||
creditCardEditorView = CreditCardEditorView(view, interactor)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN the initial credit card editor state THEN credit card form inputs are in initial state`() {
|
||||
creditCardEditorView.bind(getInitialCreditCardEditorState())
|
||||
|
||||
val calendar = Calendar.getInstance()
|
||||
val startYear = calendar.get(Calendar.YEAR)
|
||||
val endYear = startYear + NUMBER_OF_YEARS_TO_SHOW - 1
|
||||
|
||||
assertEquals("", view.card_number_input.text.toString())
|
||||
assertEquals("", view.name_on_card_input.text.toString())
|
||||
|
||||
with(view.expiry_month_drop_down) {
|
||||
assertEquals(12, count)
|
||||
assertEquals("January (01)", selectedItem.toString())
|
||||
assertEquals("December (12)", getItemAtPosition(count - 1).toString())
|
||||
}
|
||||
|
||||
with(view.expiry_year_drop_down) {
|
||||
assertEquals(10, count)
|
||||
assertEquals(startYear.toString(), selectedItem.toString())
|
||||
assertEquals(endYear.toString(), getItemAtPosition(count - 1).toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a credit card THEN credit card form inputs are displaying the provided credit card information`() {
|
||||
creditCardEditorView.bind(creditCard.toCreditCardEditorState())
|
||||
|
||||
assertEquals(creditCard.cardNumber, view.card_number_input.text.toString())
|
||||
assertEquals(creditCard.billingName, view.name_on_card_input.text.toString())
|
||||
|
||||
with(view.expiry_month_drop_down) {
|
||||
assertEquals(12, count)
|
||||
assertEquals("May (05)", selectedItem.toString())
|
||||
}
|
||||
|
||||
with(view.expiry_year_drop_down) {
|
||||
val endYear = creditCard.expiryYear + NUMBER_OF_YEARS_TO_SHOW - 1
|
||||
|
||||
assertEquals(10, count)
|
||||
assertEquals(creditCard.expiryYear.toString(), selectedItem.toString())
|
||||
assertEquals(endYear.toString(), getItemAtPosition(count - 1).toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN the cancel button is clicked THEN interactor is called`() {
|
||||
creditCardEditorView.bind(getInitialCreditCardEditorState())
|
||||
|
||||
view.cancel_button.performClick()
|
||||
|
||||
verify { interactor.onCancelButtonClicked() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a credit card WHEN the save button is clicked THEN interactor is called`() {
|
||||
creditCardEditorView.bind(creditCard.toCreditCardEditorState())
|
||||
|
||||
view.save_button.performClick()
|
||||
|
||||
verify {
|
||||
interactor.onSaveButtonClicked(
|
||||
UpdatableCreditCardFields(
|
||||
billingName = creditCard.billingName,
|
||||
cardNumber = creditCard.cardNumber,
|
||||
expiryMonth = creditCard.expiryMonth,
|
||||
expiryYear = creditCard.expiryYear,
|
||||
cardType = "amex"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/* 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.settings.creditcards
|
||||
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.concept.storage.UpdatableCreditCardFields
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.settings.creditcards.controller.CreditCardEditorController
|
||||
import org.mozilla.fenix.settings.creditcards.interactor.DefaultCreditCardEditorInteractor
|
||||
|
||||
class DefaultCreditCardEditorInteractorTest {
|
||||
|
||||
private val controller: CreditCardEditorController = mockk(relaxed = true)
|
||||
|
||||
private lateinit var interactor: DefaultCreditCardEditorInteractor
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
interactor = DefaultCreditCardEditorInteractor(controller)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onCancelButtonClicked() {
|
||||
interactor.onCancelButtonClicked()
|
||||
verify { controller.handleCancelButtonClicked() }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onSaveButtonClicked() {
|
||||
val creditCardFields = UpdatableCreditCardFields(
|
||||
billingName = "Banana Apple",
|
||||
cardNumber = "4111111111111112",
|
||||
expiryMonth = 1,
|
||||
expiryYear = 2030,
|
||||
cardType = "discover"
|
||||
)
|
||||
interactor.onSaveButtonClicked(creditCardFields)
|
||||
verify { controller.handleSaveCreditCard(creditCardFields) }
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue