Skip to content

Commit

Permalink
For mozilla-mobile#12060 - Add support for select address prompt request
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandru2909 committed May 3, 2022
1 parent ab8b5a5 commit bc27cbb
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* 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 mozilla.components.browser.engine.gecko.ext

import mozilla.components.concept.engine.prompt.Address
import org.mozilla.geckoview.Autocomplete

/**
* Converts a GeckoView [Autocomplete.Address] to an Android Components [Address].
*/
fun Autocomplete.Address.toAddress() = Address(
guid = guid,
givenName = givenName,
additionalName = additionalName,
familyName = familyName,
organization = organization,
streetAddress = streetAddress,
addressLevel3 = addressLevel3,
addressLevel2 = addressLevel2,
addressLevel1 = addressLevel1,
postalCode = postalCode,
country = country,
tel = tel,
email = email
)

/**
* Converts an Android Components [Address] to a GeckoView [Autocomplete.Address].
*/
fun Address.toAutocompleteAddress() = Autocomplete.Address.Builder()
.guid(guid)
.givenName(givenName)
.additionalName(additionalName)
.familyName(familyName)
.organization(organization)
.streetAddress(streetAddress)
.addressLevel3(addressLevel3)
.addressLevel2(addressLevel2)
.addressLevel1(addressLevel1)
.postalCode(postalCode)
.country(country)
.tel(tel)
.email(email)
.build()
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.browser.engine.gecko.ext.toAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard
import mozilla.components.browser.engine.gecko.ext.toCreditCardEntry
import mozilla.components.browser.engine.gecko.ext.toLoginEntry
import mozilla.components.concept.engine.prompt.Address
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
Expand Down Expand Up @@ -224,6 +227,39 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
return geckoResult
}

override fun onAddressSelect(
session: GeckoSession,
request: AutocompleteRequest<Autocomplete.AddressSelectOption>
): GeckoResult<PromptResponse>? {
val geckoResult = GeckoResult<PromptResponse>()

val onConfirm: (Address) -> Unit = { address ->
if (!request.isComplete) {
geckoResult.complete(
request.confirm(
Autocomplete.AddressSelectOption(address.toAutocompleteAddress())
)
)
}
}

val onDismiss: () -> Unit = {
request.dismissSafely(geckoResult)
}

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SelectAddress(
addressList = request.options.map { it.value.toAddress() },
onConfirm = onConfirm,
onDismiss = onDismiss
)
)
}

return geckoResult
}

override fun onAlertPrompt(
session: GeckoSession,
prompt: PromptDelegate.AlertPrompt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import android.net.Uri
import android.os.Looper.getMainLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.browser.engine.gecko.ext.toAutocompleteAddress
import mozilla.components.browser.engine.gecko.ext.toAutocompleteCreditCard
import mozilla.components.browser.engine.gecko.ext.toLoginEntry
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.prompt.Address
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
Expand Down Expand Up @@ -1436,6 +1438,96 @@ class GeckoPromptDelegateTest {
verify(geckoResult, never()).complete(any())
}

@Test
fun `WHEN onAddressSelect is called THEN SelectAddress prompt request must be provided with the correct callbacks`() {
val mockSession = GeckoEngineSession(runtime)

var isOnConfirmCalled = false
var isOnDismissCalled = false

var selectAddressPrompt: PromptRequest.SelectAddress = mock()

val promptDelegate = spy(GeckoPromptDelegate(mockSession))

// Capture the SelectAddress prompt request
mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
selectAddressPrompt = promptRequest as PromptRequest.SelectAddress
}
})

val address = Address(
guid = "1",
givenName = "Firefox",
additionalName = "-",
familyName = "-",
organization = "-",
streetAddress = "street",
addressLevel3 = "address3",
addressLevel2 = "address2",
addressLevel1 = "address1",
postalCode = "1",
country = "Country",
tel = "1",
email = "@"
)
val addressSelectOption =
Autocomplete.AddressSelectOption(address.toAutocompleteAddress())

var geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption))

var geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

// Verify that the onDismiss callback was called
geckoResult!!.accept {
isOnDismissCalled = true
}

selectAddressPrompt.onDismiss()
shadowOf(getMainLooper()).idle()
assertTrue(isOnDismissCalled)

// Verify that the onConfirm callback was called
geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption))

geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

geckoResult!!.accept {
isOnConfirmCalled = true
}

selectAddressPrompt.onConfirm(selectAddressPrompt.addressList.first())
shadowOf(getMainLooper()).idle()
assertTrue(isOnConfirmCalled)

// Verify that when the request is already completed, the onConfirm callback is called
// but the request is not confirmed
isOnConfirmCalled = false
geckoPrompt =
geckoSelectAddressPrompt(arrayOf(addressSelectOption), true)

geckoResult = promptDelegate.onAddressSelect(
mock(),
geckoPrompt
)

geckoResult!!.accept {
isOnConfirmCalled = true
}

selectAddressPrompt.onConfirm(selectAddressPrompt.addressList.first())
shadowOf(getMainLooper()).idle()
assertFalse(isOnConfirmCalled)
}

private fun geckoChoicePrompt(
title: String,
message: String,
Expand Down Expand Up @@ -1595,4 +1687,15 @@ class GeckoPromptDelegateTest {
ReflectionUtils.setField(prompt, "options", creditCards)
return prompt
}

private fun geckoSelectAddressPrompt(
addresses: Array<Autocomplete.AddressSelectOption>,
isComplete: Boolean = false
): GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.AddressSelectOption> {
val prompt: GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.AddressSelectOption> =
mock()
whenever(prompt.isComplete).thenReturn(isComplete)
ReflectionUtils.setField(prompt, "options", addresses)
return prompt
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/* 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 mozilla.components.concept.engine.prompt

import android.annotation.SuppressLint
import android.os.Parcelable
import kotlinx.parcelize.Parcelize

/**
* Information about an address.
*
* @property guid The unique identifier for this address.
* @property givenName First name.
* @property additionalName Middle name.
* @property familyName Last name.
* @property organization Organization.
* @property streetAddress Street address.
* @property addressLevel3 Sublocality (Suburb) name type.
* @property addressLevel2 Locality (City/Town) name type.
* @property addressLevel1 Province/State name type.
* @property postalCode Postal code.
* @property country Country.
* @property tel Telephone number.
* @property email E-mail address.
* @property timeCreated Time of creation in milliseconds from the unix epoch.
* @property timeLastUsed Time of last use in milliseconds from the unix epoch.
* @property timeLastModified Time of last modified in milliseconds from the unix epoch.
* @property timesUsed Number of times the address was used.
*/
@SuppressLint("ParcelCreator")
@Parcelize
data class Address(
val guid: String?,
val givenName: String,
val additionalName: String,
val familyName: String,
val organization: String,
val streetAddress: String,
val addressLevel3: String,
val addressLevel2: String,
val addressLevel1: String,
val postalCode: String,
val country: String,
val tel: String,
val email: String,
val timeCreated: Long = 0L,
val timeLastUsed: Long? = 0L,
val timeLastModified: Long = 0L,
val timesUsed: Long = 0L
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,21 @@ sealed class PromptRequest(
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible

/**
* Value type that represents a request for a select address prompt.
*
* This prompt is triggered by the user focusing on an address field.
*
* @property addressList List of addresses for the user to choose from.
* @property onConfirm Callback used to confirm the selected address.
* @property onDismiss Callback used to dismiss the address prompt.
*/
data class SelectAddress(
val addressList: List<Address>,
val onConfirm: (Address) -> Unit,
override val onDismiss: () -> Unit
) : PromptRequest(), Dismissible

interface Dismissible {
val onDismiss: () -> Unit
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,52 @@ class PromptRequestTest {
assertFalse(onConfirmCalled)
assertNull(confirmedCreditCard)
}

@Test
fun `WHEN calling confirm or dismiss on SelectAddress THEN the respective callback is invoked`() {
val address = Address(
guid = "1",
givenName = "Firefox",
additionalName = "-",
familyName = "-",
organization = "-",
streetAddress = "street",
addressLevel3 = "address3",
addressLevel2 = "address2",
addressLevel1 = "address1",
postalCode = "1",
country = "Country",
tel = "1",
email = "@"
)
var onDismissCalled = false
var onConfirmCalled = false
var confirmedAddress: Address? = null

val selectAddresPromptRequest = PromptRequest.SelectAddress(
addressList = listOf(address),
onDismiss = {
onDismissCalled = true
},
onConfirm = {
confirmedAddress = it
onConfirmCalled = true
}
)

assertEquals(selectAddresPromptRequest.addressList, listOf(address))

selectAddresPromptRequest.onConfirm(address)

assertTrue(onConfirmCalled)
assertFalse(onDismissCalled)
assertEquals(address, confirmedAddress)

onConfirmCalled = false

selectAddresPromptRequest.onDismiss()

assertTrue(onDismissCalled)
assertFalse(onConfirmCalled)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.Popup
import mozilla.components.concept.engine.prompt.PromptRequest.Repost
import mozilla.components.concept.engine.prompt.PromptRequest.SaveLoginPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.SelectAddress
import mozilla.components.concept.engine.prompt.PromptRequest.SelectCreditCard
import mozilla.components.concept.engine.prompt.PromptRequest.SelectLoginPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.Share
Expand Down Expand Up @@ -782,6 +783,7 @@ class PromptFeature private constructor(
is SelectLoginPrompt,
is SelectCreditCard,
is Share -> true
is SelectAddress -> false
is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs
}
}
Expand Down

0 comments on commit bc27cbb

Please sign in to comment.