Skip to content

Commit

Permalink
Issue mozilla-mobile#11249: Add support for save credit card prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielluong committed May 9, 2022
1 parent d21b7cf commit 498b53c
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,43 @@ typealias AC_FILE_FACING_MODE = PromptRequest.File.FacingMode
internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSession) :
PromptDelegate {

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

val onConfirm: (CreditCardEntry) -> Unit = { creditCard ->
if (!request.isComplete) {
geckoResult.complete(
request.confirm(
Autocomplete.CreditCardSelectOption(creditCard.toAutocompleteCreditCard())
)
)
}
}

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

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SaveCreditCard(
creditCard = request.options[0].value.toCreditCardEntry(),
onDismiss = onDismiss,
onConfirm = onConfirm
).also {
request.delegate = PromptInstanceDismissDelegate(
geckoEngineSession, it
)
}
)
}

return geckoResult
}

/**
* Handle a credit card selection prompt request. This is triggered by the user
* focusing on a credit card input field.
Expand Down Expand Up @@ -721,7 +758,6 @@ internal fun Date.toString(format: String): String {
* Only dismiss if the prompt is not already dismissed.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)

internal fun PromptDelegate.BasePrompt.dismissSafely(geckoResult: GeckoResult<PromptResponse>) {
if (!this.isComplete) {
geckoResult.complete(dismiss())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,95 @@ class GeckoPromptDelegateTest {
passwordField = passwordField
)

@Test
fun `Calling onCreditCardSave must provide an SaveCreditCard PromptRequest`() {
val mockSession = GeckoEngineSession(runtime)
var onCreditCardSaved = false
var onDismissWasCalled = false

var saveCreditCardPrompt: PromptRequest.SaveCreditCard = mock()

val promptDelegate = spy(GeckoPromptDelegate(mockSession))

mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
saveCreditCardPrompt = promptRequest as PromptRequest.SaveCreditCard
}
})

val creditCard = CreditCardEntry(
guid = "1",
name = "Banana Apple",
number = "4111111111111110",
expiryMonth = "5",
expiryYear = "2030",
cardType = "amex"
)
val creditCardSaveOption =
Autocomplete.CreditCardSaveOption(creditCard.toAutocompleteCreditCard())

var geckoResult = promptDelegate.onCreditCardSave(
mock(),
geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption))
)

geckoResult.accept {
onDismissWasCalled = true
}

saveCreditCardPrompt.onDismiss()
shadowOf(getMainLooper()).idle()
assertTrue(onDismissWasCalled)

val geckoPrompt = geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption))
geckoResult = promptDelegate.onCreditCardSave(mock(), geckoPrompt)

geckoResult.accept {
onCreditCardSaved = true
}

saveCreditCardPrompt.onConfirm(creditCard)
shadowOf(getMainLooper()).idle()

assertTrue(onCreditCardSaved)

whenever(geckoPrompt.isComplete).thenReturn(true)
onCreditCardSaved = false
saveCreditCardPrompt.onConfirm(creditCard)

assertFalse(onCreditCardSaved)
}

@Test
fun `Calling onCreditSave must set a PromptInstanceDismissDelegate`() {
val mockSession = GeckoEngineSession(runtime)
var saveCreditCardPrompt: PromptRequest.SaveCreditCard = mock()
val promptDelegate = spy(GeckoPromptDelegate(mockSession))

mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
saveCreditCardPrompt = promptRequest as PromptRequest.SaveCreditCard
}
})

val creditCard = CreditCardEntry(
guid = "1",
name = "Banana Apple",
number = "4111111111111110",
expiryMonth = "5",
expiryYear = "2030",
cardType = "amex"
)
val creditCardSaveOption =
Autocomplete.CreditCardSaveOption(creditCard.toAutocompleteCreditCard())
val geckoPrompt = geckoCreditCardSavePrompt(arrayOf(creditCardSaveOption))

promptDelegate.onCreditCardSave(mock(), geckoPrompt)

assertNotNull(saveCreditCardPrompt)
assertNotNull(geckoPrompt.delegate)
}

@Test
fun `Calling onCreditCardSelect must provide as CreditCardSelectOption PromptRequest`() {
val mockSession = GeckoEngineSession(runtime)
Expand Down Expand Up @@ -1595,4 +1684,17 @@ class GeckoPromptDelegateTest {
ReflectionUtils.setField(prompt, "options", creditCards)
return prompt
}

@Suppress("UNCHECKED_CAST")
private fun geckoCreditCardSavePrompt(
creditCard: Array<Autocomplete.CreditCardSaveOption>
): GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.CreditCardSaveOption> {
val prompt = Mockito.mock(
GeckoSession.PromptDelegate.AutocompleteRequest::class.java,
Mockito.RETURNS_DEEP_STUBS // for testing prompt.delegate
) as GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.CreditCardSaveOption>

ReflectionUtils.setField(prompt, "options", creditCard)
return prompt
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ sealed class PromptRequest(
val onStay: () -> Unit
) : PromptRequest()

/**
* Value type that represents a request for a save credit card prompt.
* @property creditCard the [CreditCardEntry] to save or update.
* @property onDismiss callback to let the page know the user dismissed the dialog.
* @property onConfirm callback that is called when the user confirms the credit card selection.
*/
data class SaveCreditCard(
val creditCard: CreditCardEntry,
override val onDismiss: () -> Unit,
val onConfirm: (CreditCardEntry) -> Unit
) : PromptRequest(shouldDismissOnLoad = false), Dismissible

/**
* Value type that represents a request for a select credit card prompt.
* @property creditCards a list of [CreditCardEntry]s to select from.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
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.SaveCreditCard
import mozilla.components.concept.engine.prompt.PromptRequest.SaveLoginPrompt
import mozilla.components.concept.engine.prompt.PromptRequest.SelectCreditCard
import mozilla.components.concept.engine.prompt.PromptRequest.SelectLoginPrompt
Expand Down Expand Up @@ -790,6 +791,7 @@ class PromptFeature private constructor(
is SaveLoginPrompt,
is SelectLoginPrompt,
is SelectCreditCard,
is SaveCreditCard,
is Share -> true
is Alert, is TextPrompt, is Confirm, is Repost, is Popup -> promptAbuserDetector.shouldShowMoreDialogs
}
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ permalink: /changelog/
* **concept-storage**:
* Added `CreditCardValidationDelegate` which is a delegate that will check against the `CreditCardsAddressesStorage` to determine if a `CreditCard` can be persisted in storage. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838)
* Refactors `CreditCard` from `concept-engine` to `CreditCardEntry` in `concept-storage` so that it can validated with the `CreditCardValidationDelegate`. [#9838](https://github.com/mozilla-mobile/android-components/issues/9838)

* **concept-engine**
* Adds a new `SaveCreditCard` in `PromptRequest` to display a prompt for saving a credit card on autofill. [#11249](https://github.com/mozilla-mobile/android-components/issues/11249)

# 101.0.0
* [Commits](https://github.com/mozilla-mobile/android-components/compare/v100.0.0...v101.0.0)
Expand Down

0 comments on commit 498b53c

Please sign in to comment.