Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve billing information loading state #99149

Draft
wants to merge 4 commits into
base: trunk
Choose a base branch
from

Conversation

StevenDufresne
Copy link
Contributor

@StevenDufresne StevenDufresne commented Jan 31, 2025

Related to #98527

Proposed Changes

This PR updates the "Billing Information" loading state to reduce the number of visual changes that occur on cart load.

It does so by doing two things:

  • Use the return value from useCachedContactDetailsForCheckoutForm to determine when the prefill action has been completed and display a loading string in the meantime.
  • Hide the sibling <button> element .

Before

(Throttling Chrome Slow 4G)

trim_0F6j2M.mp4

After

(Throttling Chrome Slow 4G)

trim_I0sXHB.mp4

Why are these changes being made?

As described in #98527, there are multiple different loading states on cart load. This is a distracting experience.

Considerations

I go into more detail about things I tried in the original issue. I tried a few other things, like calling the domain contact endpoint earlier on page load. That didn't have much of an effect. The visual state was more or less unchanged.

Loading it synchronously is the most logical way to remove these intermediate states or maybe have a lazy load system for users who don't start their session on the checkout page. Regardless, I agree with @sirbrillig that keeping it simple is probably the win.

Testing Instructions

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • Have you used memoizing on expensive computations? More info in Memoizing with create-selector and Using memoizing selectors and Our Approach to Data
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages.
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

@matticbot
Copy link
Contributor

matticbot commented Jan 31, 2025

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

Sections (~66 bytes added 📈 [gzipped])

name      parsed_size           gzip_size
checkout       +158 B  (+0.0%)      +66 B  (+0.0%)

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Async-loaded Components (~66 bytes added 📈 [gzipped])

name                                             parsed_size           gzip_size
async-load-calypso-my-sites-checkout-modal            +158 B  (+0.0%)      +66 B  (+0.0%)
async-load-calypso-blocks-editor-checkout-modal       +158 B  (+0.0%)      +66 B  (+0.0%)

React components that are loaded lazily, when a certain part of UI is displayed for the first time.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@StevenDufresne
Copy link
Contributor Author

@sirbrillig What do you think about this approach?

Additionally, I was able to fix many of the tests by mocking hooks/use-prefill-checkout-contact-form but some of the vat/tax form ones still fail.

I didn't have much success figuring out why but it appears to be something wrong with the mock. You may have more experience here, so wondering if you can think of anything relevant.

@StevenDufresne StevenDufresne force-pushed the try/update-billing-section-loading branch from 03fba43 to 7f9b167 Compare February 3, 2025 22:42
@matticbot
Copy link
Contributor

This PR modifies the release build for the following Calypso Apps:

For info about this notification, see here: PCYsg-OT6-p2

  • notifications
  • wpcom-block-editor

To test WordPress.com changes, run install-plugin.sh $pluginSlug try/update-billing-section-loading on your sandbox.

@sirbrillig
Copy link
Member

I'll take a look tomorrow. 👍

@StevenDufresne
Copy link
Contributor Author

Thanks!

Looking at the broken tests, mocking hooks/use-prefill-checkout-contact-form does indeed work, but tests that rely on state updates initiated in useCachedContactDetailsForCheckoutForm do not. Essentially any test that uses the cachedContact details. I tried unmocking for those tests but it didn't work. I assume we can split those tests out into their own test file, not mock hooks/use-prefill-checkout-contact-form and that should work. I didn't have a chance to test that assumption today.

The alternative would be to update all the tests that don't properly wait for usePrefillCheckoutContactForm to return. Seems like a heavy lift.

@@ -39,6 +39,9 @@ jest.mock( 'calypso/my-sites/checkout/use-cart-key' );
jest.mock( 'calypso/lib/analytics/utils/refresh-country-code-cookie-gdpr' );
jest.mock( 'calypso/state/products-list/selectors/is-marketplace-product' );
jest.mock( 'calypso/lib/navigate' );
jest.mock( '../hooks/use-prefill-checkout-contact-form', () => ( {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think rather than mocking the hook's behavior, you can just let the hook do its normal job and rely on mocking the endpoint that returns the cached contact details (and the countries list) like we do here:

mockGetSupportedCountriesEndpoint( countryList );
mockCachedContactDetailsEndpoint( {
country_code: countryCode,
postal_code: postalCode,
} );

As long as your expectations await or use findBy... methods, then the contact form should show when the data has arrived and the form is populated.

In fact, we already do that in some of these tests like here:

nock.cleanAll();
mockGetSupportedCountriesEndpoint( countryList );
mockGetPaymentMethodsEndpoint( [] );
mockCachedContactDetailsEndpoint( {
country_code: 'GB',
postal_code: '',
} );
mockContactDetailsValidationEndpoint( 'tax', { success: false, messages: [ 'Invalid' ] } );
mockGetVatInfoEndpoint( {
id: '12345',
name: 'Test company',
address: '123 Main Street',
country: 'GB',
} );

If I do the following, then the first VAT test passes (I don't know if all this is required; I just copied and pasted from below):

--- a/client/my-sites/checkout/src/test/checkout-vat-form.tsx
+++ b/client/my-sites/checkout/src/test/checkout-vat-form.tsx
@@ -40,10 +40,6 @@ jest.mock( 'calypso/my-sites/checkout/use-cart-key' );
 jest.mock( 'calypso/lib/analytics/utils/refresh-country-code-cookie-gdpr' );
 jest.mock( 'calypso/state/products-list/selectors/is-marketplace-product' );
 jest.mock( 'calypso/lib/navigate' );
-jest.mock( '../hooks/use-prefill-checkout-contact-form', () => ( {
-       ...jest.requireActual( '../hooks/use-prefill-checkout-contact-form' ), // Spread the real module to keep other functions intact
-       usePrefillCheckoutContactForm: jest.fn().mockReturnValue( true ),
-} ) );

 // These tests seem to be particularly slow (it might be because of using
 // it.each; it's not clear but the timeout might apply to the whole loop
@@ -86,6 +82,21 @@ describe( 'Checkout contact step VAT form', () => {
        } );

        it( 'does not render the VAT field checkbox if the selected country does not support VAT', async () => {
+               nock.cleanAll();
+               mockGetSupportedCountriesEndpoint( countryList );
+               mockGetPaymentMethodsEndpoint( [] );
+               mockCachedContactDetailsEndpoint( {
+                       country_code: 'GB',
+                       postal_code: '',
+               } );
+               mockContactDetailsValidationEndpoint( 'tax', { success: false, messages: [ 'Invalid' ] } );
+               mockGetVatInfoEndpoint( {
+                       id: '12345',
+                       name: 'Test company',
+                       address: '123 Main Street',
+                       country: 'GB',
+               } );
+
                const user = userEvent.setup();
                const cartChanges = { products: [ planWithoutDomain ] };
                render( <MockCheckout { ...defaultPropsForMockCheckout } cartChanges={ cartChanges } />, {

The above technique should also allow you to write a test to prove that this change works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants