Repository: braintree/braintree-web-drop-in (js)

Issue:
Prefilled Credit Card doesn't trigger event paymentMethodRequestable
### General information

* SDK/Library version: ^1.31.1
* Environment: Sandbox and Production
* Browser and OS: Chrome 92.0.4515.131 (64-bit) on MacOS Big Sur 11.4

### Issue description

We use `clearFieldsAfterTokenization: false` to keep card data after call `clearSelectedPaymentMethod()` in error handler. We have noticed that after click on "new card" option the prefilled credit card form is being displayed correctly but it doesn't trigger the `paymentMethodRequestable` event, however the payment method is requestable. Can it be fixed to consolidate logic?

Please see attached example https://codepen.io/andrew-sp/pen/eYWxBoO

Pull request description:
fix: send requestable event when card view is opened
### Summary
closes #761

Currently, if a card has been tokenized and you have Drop-in configured to not clear out the fields after tokenization, then you call `clearSelectedPaymentMethod`, the saved card is cleared, but entering the card form with an already valid card does not trigger `paymentMethodRequestable` when it should.

This is because the `paymentMethodRequestable` and `noPaymentMethodRequestable` event only fires when the hosted fields event listeners fire for validity change.

To fix this, we've set it to fire off those events when the card view is presented, for this case where the card form is already filled out.

### Checklist

- [x] Added a changelog entry

### Authors
> List GitHub usernames for everyone who contributed to this pull request.

- @crookedneighbor 
- @jplukarski 


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c77b81..ebb91ef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ unreleased
 ----------
 - Fix issue where a methods container for a cusotmer with 15+ payment methods would obscure the "Choose Another Way to Pay" button
 - Fix issue where Drop-in could get in an unusable state when `clearSelectedPaymentMethod` was called directly after `requestPaymentMethod`
+- Fix issue where an already filled out card form would not trigger `paymentMethodRequestable` event (#761)
 - Update braintree-web to v3.80.0
 
 1.31.1
diff --git a/src/views/payment-sheet-views/card-view.js b/src/views/payment-sheet-views/card-view.js
index f936905..11dbfcd 100644
--- a/src/views/payment-sheet-views/card-view.js
+++ b/src/views/payment-sheet-views/card-view.js
@@ -553,6 +553,8 @@ CardView.prototype.onSelection = function () {
       this.hostedFieldsInstance.focus('number');
     }
   }.bind(this), 50);
+
+  this._sendRequestableEvent();
 };
 
 CardView.prototype._hideUnsupportedCardIcons = function () {


Test patch:
diff --git a/test/helpers/fake.js b/test/helpers/fake.js
index 05025ec..da9a3d2 100644
--- a/test/helpers/fake.js
+++ b/test/helpers/fake.js
@@ -60,6 +60,7 @@ fakeBTInstances = {
   hostedFields() {
     return {
       clear: jest.fn(),
+      focus: jest.fn(),
       getState: jest.fn().mockReturnValue({
         cards: [{ type: 'visa' }],
         fields: {
diff --git a/test/unit/views/payment-sheet-views/card-view.js b/test/unit/views/payment-sheet-views/card-view.js
index 293cf9d..4e0a51c 100644
--- a/test/unit/views/payment-sheet-views/card-view.js
+++ b/test/unit/views/payment-sheet-views/card-view.js
@@ -2750,11 +2750,13 @@ describe('CardView', () => {
     });
 
     test('focuses on the number field', () => {
-      const view = new CardView({ element: cardElement });
+      const view = new CardView({
+        model: fake.model(),
+        client: fake.client(),
+        element: cardElement
+      });
 
-      view.hostedFieldsInstance = {
-        focus: jest.fn()
-      };
+      view.hostedFieldsInstance = fake.hostedFields();
 
       view.onSelection();
 
@@ -2764,6 +2766,58 @@ describe('CardView', () => {
       expect(view.hostedFieldsInstance.focus).toBeCalledWith('number');
     });
 
+    test('setPaymentMethodRequestable is called on selection', () => {
+      const model = fake.model();
+      const view = new CardView({
+        model,
+        client: fake.client(),
+        element: cardElement
+      });
+      const hf = fake.hostedFields();
+
+      view.hostedFieldsInstance = hf;
+      hf.getState.mockReturnValue({
+        cards: [{ type: 'visa' }],
+        fields: {
+          number: {
+            isValid: true
+          },
+          expirationDate: {
+            isValid: true
+          }
+        }
+      });
+
+      jest.spyOn(model, 'setPaymentMethodRequestable').mockImplementation();
+
+      view.onSelection();
+
+      expect(model.setPaymentMethodRequestable).toBeCalledWith({
+        isRequestable: true,
+        type: 'CreditCard'
+      });
+
+      model.setPaymentMethodRequestable.mockClear();
+      hf.getState.mockReturnValue({
+        cards: [{ type: 'visa' }],
+        fields: {
+          number: {
+            isValid: true
+          },
+          expirationDate: {
+            isValid: false
+          }
+        }
+      });
+
+      view.onSelection();
+
+      expect(model.setPaymentMethodRequestable).toBeCalledWith({
+        isRequestable: false,
+        type: 'CreditCard'
+      });
+    });
+
     test('noops if the hosted fields instance is not available', () => {
       const view = new CardView({ element: cardElement });