/** * External dependencies */ import { Fragment } from '@wordpress/element'; import { recordEvent } from '@woocommerce/tracks'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ import { acceptWcsTos, getWcsAssets, getWcsLabelPurchaseConfigs, } from '../../wcs-api.js'; import { ShippingBanner } from '../index.js'; jest.mock( '../../wcs-api.js' ); acceptWcsTos.mockReturnValue( Promise.resolve() ); jest.mock( '@woocommerce/tracks' ); const wcsPluginSlug = 'woocommerce-shipping'; const wcstPluginSlug = 'woocommerce-services'; describe( 'Tracking impression in shippingBanner', () => { const expectedTrackingData = { banner_name: 'wcadmin_install_wcs_prompt', jetpack_connected: true, jetpack_installed: true, wcs_installed: false, }; it( 'should record an event when user sees banner loaded', () => { render( ); expect( recordEvent ).toHaveBeenCalledTimes( 1 ); expect( recordEvent ).toHaveBeenCalledWith( 'banner_impression', expectedTrackingData ); } ); } ); describe( 'Tracking clicks in shippingBanner', () => { const getExpectedTrackingData = ( element, wcsInstalled = true ) => { return { banner_name: 'wcadmin_install_wcs_prompt', jetpack_connected: true, jetpack_installed: true, wcs_installed: wcsInstalled, element, }; }; it( 'should record an event when user clicks "Create shipping label"', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) ); await waitFor( () => expect( recordEvent ).toHaveBeenCalledWith( 'banner_element_clicked', getExpectedTrackingData( 'shipping_banner_create_label', false ) ) ); } ); it( 'should record an event when user clicks "WooCommerce Shipping"', async () => { // Render the banner without WCS being active. const { getByRole } = render( ); userEvent.click( getByRole( 'link', { name: /WooCommerce Shipping/ } ) ); await waitFor( () => expect( recordEvent ).toHaveBeenCalledWith( 'banner_element_clicked', getExpectedTrackingData( 'shipping_banner_woocommerce_service_link', false ) ) ); } ); it( 'should record an event when user clicks "x" to dismiss the banner', async () => { const { getByRole } = render( ); userEvent.click( getByRole( 'button', { name: 'Close Print Label Banner.' } ) ); await waitFor( () => expect( recordEvent ).toHaveBeenCalledWith( 'banner_element_clicked', getExpectedTrackingData( 'shipping_banner_dimiss', false ) ) ); } ); } ); describe( 'Create shipping label button', () => { const installPlugins = jest.fn().mockReturnValue( { success: true, } ); const activatePlugins = jest.fn().mockReturnValue( { success: true, } ); delete window.location; // jsdom won't allow to rewrite window.location unless deleted first window.location = { href: 'http://wcship.test/wp-admin/post.php?post=1000&action=edit', }; it( 'should install WooCommerce Shipping when button is clicked', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel, } ) ); await waitFor( () => expect( installPlugins ).toHaveBeenCalledWith( [ 'woocommerce-shipping', ] ) ); } ); it( 'should activate WooCommerce Shipping when installation finishes', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel, } ) ); await waitFor( () => expect( activatePlugins ).toHaveBeenCalledWith( [ 'woocommerce-shipping', ] ) ); } ); it( 'should perform a request to accept the TOS and get WCS assets to load', async () => { getWcsLabelPurchaseConfigs.mockReturnValueOnce( Promise.resolve( {} ) ); getWcsAssets.mockReturnValueOnce( Promise.resolve( {} ) ); const actionButtonLabel = 'Create shipping label'; const { getByRole } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel, } ) ); await waitFor( () => expect( acceptWcsTos ).toHaveBeenCalled() ); expect( getWcsAssets ).toHaveBeenCalled(); } ); it( 'should load WCS assets when a path is provided', async () => { const actionButtonLabel = 'Create shipping label'; getWcsLabelPurchaseConfigs.mockReturnValueOnce( Promise.resolve( {} ) ); const mockAssets = { wcshipping_create_label_script: '/path/to/wcs.js', wcshipping_create_label_style: '/path/to/wcs.css', wcshipping_shipment_tracking_script: '/wcshipping_shipment_tracking_script', wcshipping_shipment_tracking_style: '/wcshipping_shipment_tracking_style', }; getWcsAssets.mockReturnValueOnce( Promise.resolve( { assets: mockAssets, } ) ); const { getByRole } = render(
); userEvent.click( getByRole( 'button', { name: actionButtonLabel, } ) ); // Check that the metaboxes have been created. await waitFor( () => expect( getByRole( 'heading', { level: 2, name: 'Shipping Label' } ) ).toBeInTheDocument() ); expect( getByRole( 'heading', { level: 2, name: 'Shipment Tracking' } ) ).toBeInTheDocument(); // Check that the script and style elements have been created. const allScriptSrcs = Array.from( document.querySelectorAll( 'script' ) ).map( ( script ) => script.src ); const allLinkHrefs = Array.from( document.querySelectorAll( 'link' ) ).map( ( link ) => link.href ); expect( allScriptSrcs ).toContain( 'http://localhost' + mockAssets.wcshipping_create_label_script ); expect( allScriptSrcs ).toContain( 'http://localhost' + mockAssets.wcshipping_shipment_tracking_script ); expect( allLinkHrefs ).toContain( 'http://localhost' + mockAssets.wcshipping_create_label_style ); expect( allLinkHrefs ).toContain( 'http://localhost' + mockAssets.wcshipping_shipment_tracking_style ); } ); it( 'should open WCS modal', async () => { const actionButtonLabel = 'Create shipping label'; getWcsLabelPurchaseConfigs.mockReturnValueOnce( Promise.resolve( {} ) ); getWcsAssets.mockReturnValueOnce( Promise.resolve( { assets: { // Easy to identify string in our hijacked setter function. wcshipping_create_label_script: 'wcshipping_create_label_script', wcshipping_shipment_tracking_script: 'wcshipping_create_label_script', // Empty string to avoid creating a script tag we also have to hijack. wcshipping_create_label_style: '', wcshipping_shipment_tracking_style: '', }, } ) ); // Force the script tag to trigger its onload(). // Adapted from https://stackoverflow.com/a/49204336. // const scriptSrcProperty = window.HTMLScriptElement.prototype.src; Object.defineProperty( window.HTMLScriptElement.prototype, 'src', { set( src ) { if ( [ 'wcshipping_create_label_script', 'wcshipping_shipment_tracking_script', ].includes( src ) ) { setTimeout( () => { this.onload(); }, 1 ); } }, } ); const { getByRole } = render(
); const openWcsModalSpy = jest.spyOn( ShippingBanner.prototype, 'openWcsModal' ); // Initiate the loading of WCS assets on first click. userEvent.click( getByRole( 'button', { name: actionButtonLabel, } ) ); await waitFor( () => { expect( document.getElementById( 'woocommerce-admin-print-label' ) ).not.toBeVisible(); } ); expect( openWcsModalSpy ).toHaveBeenCalledTimes( 1 ); } ); } ); describe( 'In the process of installing, activating, loading assets for WooCommerce Service', () => { it( 'should show a busy loading state on "Create shipping label" and should disable "Close Print Label Banner"', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole } = render( ); expect( getByRole( 'button', { name: actionButtonLabel } ) ).not.toHaveClass( 'is-busy' ); expect( getByRole( 'button', { name: 'Close Print Label Banner.' } ) ).toBeEnabled(); userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) ); await waitFor( () => expect( getByRole( 'button', { name: actionButtonLabel } ) ).toHaveClass( 'is-busy' ) ); expect( getByRole( 'button', { name: 'Close Print Label Banner.' } ) ).toBeDisabled(); } ); } ); describe( 'Setup error message', () => { it( 'should not show if there is no error (no interaction)', () => { const { container } = render( ); expect( container.getElementsByClassName( 'wc-admin-shipping-banner-install-error' ) ).toHaveLength( 0 ); } ); it( 'should show if there is installation error', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole, getByText } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) ); await waitFor( () => expect( getByText( 'Unable to install the plugin. Refresh the page and try again.' ) ).toBeInTheDocument() ); } ); it( 'should show if there is activation error', async () => { const actionButtonLabel = 'Create shipping label'; const { getByRole, getByText } = render( ); userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) ); await waitFor( () => expect( getByText( 'Unable to activate the plugin. Refresh the page and try again.' ) ).toBeInTheDocument() ); } ); } ); describe( 'The message in the banner', () => { const createShippingBannerWrapper = ( { activePlugins } ) => render( ); const notActivatedMessage = 'By clicking "Create shipping label", WooCommerce Shipping(opens in a new tab) will be installed and you agree to its Terms of Service(opens in a new tab).'; it( 'should show install text "By clicking "Create shipping label"..." when first loaded.', () => { const { container } = createShippingBannerWrapper( { activePlugins: [], } ); expect( container.querySelector( '.wc-admin-shipping-banner-blob p' ) .textContent ).toBe( notActivatedMessage ); } ); it( 'should continue to show the initial message "By clicking "Create shipping label"..." after WooCommerce Service is installed successfully.', () => { const { container, rerender } = createShippingBannerWrapper( { activePlugins: [], } ); rerender( ); expect( container.querySelector( '.wc-admin-shipping-banner-blob p' ) .textContent ).toBe( notActivatedMessage ); } ); } ); describe( 'If incompatible WCS&T is active', () => { const installPlugins = jest.fn().mockReturnValue( { success: true, } ); const activatePlugins = jest.fn().mockReturnValue( { success: true, } ); beforeEach( () => { acceptWcsTos.mockClear(); } ); it( 'should install and activate but show an error notice when an incompatible version of WCS&T is installed', async () => { const actionButtonLabel = 'Install WooCommerce Shipping'; const { getByRole, getByText } = render(
); userEvent.click( getByRole( 'button', { name: actionButtonLabel } ) ); await waitFor( () => { expect( installPlugins ).toHaveBeenCalledWith( [ wcsPluginSlug ] ); } ); await waitFor( () => { expect( activatePlugins ).toHaveBeenCalledWith( [ wcsPluginSlug ] ); } ); await waitFor( () => { expect( acceptWcsTos ).not.toHaveBeenCalled(); } ); const notice = getByText( ( _, element ) => { const hasText = ( node ) => node.textContent === 'Please update the WooCommerce Shipping & Tax plugin to the latest version to ensure compatibility with WooCommerce Shipping.'; const nodeHasText = hasText( element ); const childrenDontHaveText = Array.from( element.children ).every( ( child ) => ! hasText( child ) ); return nodeHasText && childrenDontHaveText; } ); await waitFor( () => expect( notice ).toBeInTheDocument() ); // Assert that the "update" link is present const updateLink = getByText( /update/i ); expect( updateLink ).toBeInTheDocument(); expect( updateLink.tagName ).toBe( 'A' ); // Ensures it's a link } ); } );