diff --git a/js/src/components/audience-country-select.js b/js/src/components/audience-country-select.js
deleted file mode 100644
index 02a39ef4bc..0000000000
--- a/js/src/components/audience-country-select.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Internal dependencies
- */
-import SupportedCountrySelect from '.~/components/supported-country-select';
-import AppSpinner from '.~/components/app-spinner';
-import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
-
-/**
- * @typedef {import('.~/data/actions').CountryCode} CountryCode
- */
-
-/**
- * Returns a SupportedCountrySelect component with list of countries grouped by continents.
- * And SupportedCountrySelect will be rendered via TreeSelectControl component.
- *
- * This component is for selecting countries under the merchant selected targeting audiences.
- *
- * @param {Object} props React props.
- * @param {Array} [props.additionalCountryCodes] Additional countries that are not in the target audience countries and need to be selectable.
- * @param {Object} props.restProps Props to be forwarded to SupportedCountrySelect.
- */
-const AudienceCountrySelect = ( { additionalCountryCodes, ...restProps } ) => {
- let { data: countryCodes } = useTargetAudienceFinalCountryCodes();
-
- if ( ! countryCodes ) {
- return ;
- }
-
- if ( additionalCountryCodes ) {
- countryCodes = Array.from(
- new Set( countryCodes.concat( additionalCountryCodes ) )
- );
- }
-
- return (
-
- );
-};
-
-export default AudienceCountrySelect;
diff --git a/js/src/components/paid-ads/ads-campaign/ads-campaign.js b/js/src/components/paid-ads/ads-campaign/ads-campaign.js
index 5800d7249b..34324fd40c 100644
--- a/js/src/components/paid-ads/ads-campaign/ads-campaign.js
+++ b/js/src/components/paid-ads/ads-campaign/ads-campaign.js
@@ -13,10 +13,16 @@ import StepContentFooter from '.~/components/stepper/step-content-footer';
import StepContentActions from '.~/components/stepper/step-content-actions';
import AppDocumentationLink from '.~/components/app-documentation-link';
import { useAdaptiveFormContext } from '.~/components/adaptive-form';
-import AudienceSection from '../audience-section';
+import BillingCard from '.~/components/paid-ads/billing-card';
import BudgetSection from '../budget-section';
import { CampaignPreviewCard } from '../campaign-preview';
-import PaidAdsFaqsPanel from '../faqs-panel';
+import PaidAdsFaqsPanel from './faqs-panel';
+import PaidAdsFeaturesSection from './paid-ads-features-section';
+import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
+
+/**
+ * @typedef {import('.~/components/adaptive-form/adaptive-form-context').AdaptiveFormContext} AdaptiveFormContext
+ */
/**
* @typedef {import('.~/data/actions').Campaign} Campaign
@@ -30,79 +36,83 @@ import PaidAdsFaqsPanel from '../faqs-panel';
*
* @fires gla_documentation_link_click with `{ context: 'create-ads' | 'edit-ads' | 'setup-ads', link_id: 'see-what-ads-look-like', href: 'https://support.google.com/google-ads/answer/6275294' }`
* @param {Object} props React props.
- * @param {Campaign} [props.campaign] Campaign data to be edited. If not provided, this component will show campaign creation UI.
- * @param {JSX.Element|Function} props.continueButton Continue button component.
- * @param {'create-ads'|'edit-ads'|'setup-ads'} props.trackingContext A context indicating which page this component is used on. This will be the value of `context` in the track event properties.
+ * @param {Campaign} [props.campaign] Campaign data to be edited. The displayCountries property will be used to fetch budget recommendation data.
+ * @param {string} props.headerTitle The title of the step.
+ * @param {'create-ads'|'edit-ads'|'setup-ads'|'setup-mc'} props.context A context indicating which page this component is used on. This will be the value of `context` in the track event properties.
+ * @param {(formContext: AdaptiveFormContext) => JSX.Element | JSX.Element} [props.skipButton] A React element or function to render the "Skip" button. If a function is passed, it receives the form context and returns the button element.
+ * @param {(formContext: AdaptiveFormContext) => JSX.Element | JSX.Element} [props.continueButton] A React element or function to render the "Continue" button. If a function is passed, it receives the form context and returns the button element.
*/
export default function AdsCampaign( {
campaign,
+ headerTitle,
+ context,
+ skipButton,
continueButton,
- trackingContext,
} ) {
- const isCreation = ! campaign;
const formContext = useAdaptiveFormContext();
+ const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
+ const isOnboardingFlow = context === 'setup-mc';
+ const showCampaignPreviewCard =
+ context === 'setup-ads' ||
+ context === 'create-ads' ||
+ context === 'edit-ads';
+ // only show the billing card during onboarding or setup Ads flow.
+ // For creating/editing a campaign, we assume billing is already set up.
+ const showBillingCard = context === 'setup-mc' || context === 'setup-ads';
- const disabledBudgetSection = ! formContext.values.countryCodes.length;
- const helperText = isCreation
- ? __(
- 'You can only choose from countries you’ve selected during product listings configuration.',
- 'google-listings-and-ads'
- )
- : __(
- 'Once a campaign has been created, you cannot change the target country(s).',
- 'google-listings-and-ads'
- );
+ let description = createInterpolateElement(
+ __(
+ 'Paid Performance Max campaigns are automatically optimized for you by Google. See what your ads will look like.',
+ 'google-listings-and-ads'
+ ),
+ {
+ link: (
+
+ ),
+ }
+ );
+
+ if ( isOnboardingFlow ) {
+ description = __(
+ 'You’re ready to set up a Performance Max campaign to drive more sales with ads. Your products will be included in the campaign after they’re approved.',
+ 'google-listings-and-ads'
+ );
+ }
return (
See what your ads will look like.',
- 'google-listings-and-ads'
- ),
- {
- link: (
-
- ),
- }
- ) }
- />
-
+
+ { isOnboardingFlow && }
+
-
+ { showBillingCard && }
+
+ { showCampaignPreviewCard && }
+ { typeof skipButton === 'function'
+ ? skipButton( formContext )
+ : skipButton }
+
{ typeof continueButton === 'function'
- ? continueButton( {
- formProps: formContext,
- } )
+ ? continueButton( formContext )
: continueButton }
diff --git a/js/src/components/paid-ads/faqs-panel.js b/js/src/components/paid-ads/ads-campaign/faqs-panel.js
similarity index 100%
rename from js/src/components/paid-ads/faqs-panel.js
rename to js/src/components/paid-ads/ads-campaign/faqs-panel.js
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js b/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js
similarity index 100%
rename from js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.js
rename to js/src/components/paid-ads/ads-campaign/paid-ads-features-section.js
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.scss b/js/src/components/paid-ads/ads-campaign/paid-ads-features-section.scss
similarity index 100%
rename from js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-features-section.scss
rename to js/src/components/paid-ads/ads-campaign/paid-ads-features-section.scss
diff --git a/js/src/components/paid-ads/asset-group/asset-group.js b/js/src/components/paid-ads/asset-group/asset-group.js
index bbceda7497..205f79cd29 100644
--- a/js/src/components/paid-ads/asset-group/asset-group.js
+++ b/js/src/components/paid-ads/asset-group/asset-group.js
@@ -16,6 +16,7 @@ import AppButton from '.~/components/app-button';
import AssetGroupFaqsPanel from './faqs-panel';
import AssetGroupSection from './asset-group-section';
import { recordGlaEvent } from '.~/utils/tracks';
+import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
export const ACTION_SUBMIT_CAMPAIGN_AND_ASSETS = 'submit-campaign-and-assets';
export const ACTION_SUBMIT_CAMPAIGN_ONLY = 'submit-campaign-only';
@@ -62,15 +63,17 @@ export default function AssetGroup( { campaign } ) {
const isCreation = ! campaign;
const { isValidForm, handleSubmit, adapter, values } =
useAdaptiveFormContext();
+ const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
const { isValidAssetGroup, isSubmitting, isSubmitted, submitter } = adapter;
const currentAction = submitter?.dataset.action;
function recordSubmissionClickEvent( event ) {
+ const audiences = isCreation ? countryCodes : campaign.displayCountries;
const finalUrl = values[ ASSET_FORM_KEY.FINAL_URL ];
const eventProps = {
context: isCreation ? 'campaign-creation' : 'campaign-editing',
action: event.target.dataset.action,
- audiences: values.countryCodes.join( ',' ),
+ audiences: audiences.join( ',' ),
budget: values.amount.toString(),
assets_validation: isValidAssetGroup ? 'valid' : 'invalid',
};
diff --git a/js/src/components/paid-ads/audience-section.js b/js/src/components/paid-ads/audience-section.js
deleted file mode 100644
index f5ce860dc4..0000000000
--- a/js/src/components/paid-ads/audience-section.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import { SelectControl } from '@wordpress/components';
-
-/**
- * Internal dependencies
- */
-import useCountryKeyNameMap from '.~/hooks/useCountryKeyNameMap';
-import Section from '.~/wcdl/section';
-import AudienceCountrySelect from '.~/components/audience-country-select';
-import './audience-section.scss';
-
-function toCountryOptions( countryCodes, countryNameMap ) {
- return countryCodes.map( ( code ) => ( {
- label: countryNameMap[ code ],
- value: code,
- } ) );
-}
-
-/**
- * Renders and UI with country(s) selector.
- *
- * @param {Object} props React props.
- * @param {Object} props.formProps Form props forwarded from `Form` component.
- * @param {boolean} [props.multiple=true] Whether the selector is multi-selected.
- * @param {boolean} [props.disabled=false] Whether the selector is disabled.
- * @param {JSX.Element} [props.countrySelectHelperText] Helper text to be displayed under the selector.
- */
-const AudienceSection = ( props ) => {
- const {
- formProps: { getInputProps },
- multiple = true,
- disabled = false,
- countrySelectHelperText,
- } = props;
-
- const countryNameMap = useCountryKeyNameMap();
- const inputProps = getInputProps( 'countryCodes' );
-
- const selector = multiple ? (
-
- ) : (
-
- );
-
- return (
-
- { __(
- 'Choose where you want your product ads to appear',
- 'google-listings-and-ads'
- ) }
-
- }
- >
-
- { selector }
-
-
- );
-};
-
-export default AudienceSection;
diff --git a/js/src/components/paid-ads/audience-section.scss b/js/src/components/paid-ads/audience-section.scss
deleted file mode 100644
index 565a4bc70f..0000000000
--- a/js/src/components/paid-ads/audience-section.scss
+++ /dev/null
@@ -1,15 +0,0 @@
-.gla-audience-section {
- // Adjust imported from @wordpress/components.
- // Repeat selector to make it higher priority.
- .components-input-control__container.components-input-control__container {
- .components-select-control__input {
- padding-left: $grid-unit-20;
- }
- }
-
- // Adjust help text of imported from @wordpress/components.
- .components-base-control__help {
- margin: 0;
- font-style: italic;
- }
-}
diff --git a/js/src/components/paid-ads/audienceSection.test.js b/js/src/components/paid-ads/audienceSection.test.js
deleted file mode 100644
index a7eb440a84..0000000000
--- a/js/src/components/paid-ads/audienceSection.test.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * External dependencies
- */
-import '@testing-library/jest-dom';
-import { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-
-/**
- * Internal dependencies
- */
-import AudienceSection from '.~/components/paid-ads/audience-section';
-
-jest.mock( '.~/hooks/useAppSelectDispatch' );
-jest.mock( '.~/hooks/useCountryKeyNameMap' );
-
-jest.mock( '.~/hooks/useTargetAudienceFinalCountryCodes', () =>
- jest.fn( () => ( { data: [ 'GB', 'US', 'ES' ] } ) )
-);
-
-describe( 'AudienceSection with multiple countries selector', () => {
- let defaultProps;
- let onChange;
-
- beforeEach( () => {
- onChange = jest.fn();
- defaultProps = {
- formProps: {
- getInputProps: () => ( { onChange } ),
- },
- };
- } );
-
- test( 'If Audience section is disabled the country field should be disabled', async () => {
- const user = userEvent.setup();
-
- render( );
-
- const dropdown = await screen.findByRole( 'combobox' );
- expect( dropdown ).toBeDisabled();
-
- //Test that input is not editable
- expect( dropdown ).toHaveValue( '' );
- await user.type( dropdown, 'spa' );
- expect( dropdown ).toHaveValue( '' );
-
- const options = screen.queryAllByRole( 'checkbox' );
- expect( options.length ).toBe( 0 );
- expect( onChange ).toHaveBeenCalledTimes( 0 );
- } );
-
- test( 'If Audience section is enable the country field should be enable & editable', async () => {
- const user = userEvent.setup();
-
- render( );
-
- const dropdown = await screen.findByRole( 'combobox' );
- expect( dropdown ).not.toBeDisabled();
-
- //Test that input is editable
- expect( dropdown ).toHaveValue( '' );
- await user.type( dropdown, 'spa' );
- expect( dropdown ).toHaveValue( 'spa' );
-
- const options = await screen.findAllByRole( 'checkbox' );
- expect( options.length ).toBeGreaterThan( 0 );
-
- const firstOption = options[ 0 ];
- await user.click( firstOption );
- expect( onChange ).toHaveBeenCalledTimes( 1 );
- } );
-} );
-
-describe( 'AudienceSection with single country selector', () => {
- let defaultProps;
- let onChange;
-
- beforeEach( () => {
- onChange = jest.fn();
- defaultProps = {
- multiple: false,
- formProps: {
- getInputProps: () => ( {
- value: [ 'US', 'ES', 'GB' ],
- selected: [ 'ES' ],
- onChange,
- } ),
- },
- };
- } );
-
- test( 'When AudienceSection is disabled, the country field should be disabled', () => {
- render( );
- const dropdown = screen.queryByRole( 'combobox' );
-
- expect( dropdown ).toBeDisabled();
- } );
-
- test( 'When AudienceSection is enable, the country field should be enable', () => {
- render( );
- const dropdown = screen.queryByRole( 'combobox' );
-
- expect( dropdown ).not.toBeDisabled();
- } );
-
- test( 'When selecting another option, the country field should trigger `onChange` callback', async () => {
- const user = userEvent.setup();
-
- render( );
-
- const dropdown = screen.queryByRole( 'combobox' );
- await user.selectOptions( dropdown, 'GB' );
-
- expect( onChange ).toHaveBeenCalledTimes( 1 );
- } );
-
- test( 'The country field should have the given options', () => {
- render( );
- const options = screen.queryAllByRole( 'option' );
-
- expect( options.length ).toBe( 3 );
- } );
-
- test( 'The country field should select the option by given value', () => {
- render( );
- const option = screen.queryByRole( 'option', { selected: true } );
-
- expect( option.value ).toBe( 'ES' );
- } );
-} );
diff --git a/js/src/components/types.js b/js/src/components/types.js
index fa7214953f..50259022b1 100644
--- a/js/src/components/types.js
+++ b/js/src/components/types.js
@@ -4,7 +4,6 @@
/**
* @typedef {Object} CampaignFormValues
- * @property {Array} countryCodes Selected country codes for the paid ads campaign.
* @property {number} amount The daily average cost amount.
*/
diff --git a/js/src/pages/create-paid-ads-campaign/index.js b/js/src/pages/create-paid-ads-campaign/index.js
index c93a1cbf68..ae16722d76 100644
--- a/js/src/pages/create-paid-ads-campaign/index.js
+++ b/js/src/pages/create-paid-ads-campaign/index.js
@@ -51,7 +51,7 @@ const CreatePaidAdsCampaign = () => {
const createdCampaignIdRef = useRef( null );
const { createAdsCampaign, updateCampaignAssetGroup } = useAppDispatch();
const { createNotice } = useDispatchCoreNotices();
- const { data: initialCountryCodes } = useTargetAudienceFinalCountryCodes();
+ const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
const handleStepperClick = ( nextStep ) => {
recordStepperChangeEvent(
@@ -76,7 +76,7 @@ const CreatePaidAdsCampaign = () => {
const { action } = enhancer.submitter.dataset;
try {
- const { amount, countryCodes } = values;
+ const { amount } = values;
// Avoid re-creating a new campaign if the subsequent asset group update is failed.
if ( createdCampaignIdRef.current === null ) {
@@ -114,7 +114,7 @@ const CreatePaidAdsCampaign = () => {
getHistory().push( getDashboardUrl( { campaign: 'saved' } ) );
};
- if ( ! initialCountryCodes ) {
+ if ( ! countryCodes ) {
return null;
}
@@ -131,7 +131,6 @@ const CreatePaidAdsCampaign = () => {
@@ -146,15 +145,19 @@ const CreatePaidAdsCampaign = () => {
),
content: (
(
+ headerTitle={ __(
+ 'Create your paid campaign',
+ 'google-listings-and-ads'
+ ) }
+ context={ eventContext }
+ continueButton={ ( formContext ) => (
+ formProps={ formContext }
+ onClick={ () => {
handleContinueClick(
STEP.ASSET_GROUP
- )
- }
+ );
+ } }
/>
) }
/>
diff --git a/js/src/pages/edit-paid-ads-campaign/index.js b/js/src/pages/edit-paid-ads-campaign/index.js
index 952c7f875b..ddc58dc64e 100644
--- a/js/src/pages/edit-paid-ads-campaign/index.js
+++ b/js/src/pages/edit-paid-ads-campaign/index.js
@@ -180,7 +180,6 @@ const EditPaidAdsCampaign = () => {
{
content: (
(
+ context={ eventContext }
+ headerTitle={ __(
+ 'Edit your paid campaign',
+ 'google-listings-and-ads'
+ ) }
+ continueButton={ ( formContext ) => (
handleContinueClick(
STEP.ASSET_GROUP
diff --git a/js/src/setup-ads/ads-stepper/index.js b/js/src/setup-ads/ads-stepper/index.js
index 017ae3193e..51fe990ffb 100644
--- a/js/src/setup-ads/ads-stepper/index.js
+++ b/js/src/setup-ads/ads-stepper/index.js
@@ -9,8 +9,6 @@ import { useState } from '@wordpress/element';
* Internal dependencies
*/
import SetupAccounts from './setup-accounts';
-import AppButton from '.~/components/app-button';
-import AdsCampaign from '.~/components/paid-ads/ads-campaign';
import useEventPropertiesFilter from '.~/hooks/useEventPropertiesFilter';
import {
recordStepperChangeEvent,
@@ -18,15 +16,17 @@ import {
FILTER_ONBOARDING,
CONTEXT_ADS_ONBOARDING,
} from '.~/utils/tracks';
+import SetupPaidAds from './setup-paid-ads';
/**
* @param {Object} props React props
- * @param {Object} props.formProps Form props forwarded from `Form` component.
+ * @param {boolean} props.isSubmitting When the form in the parent component, i.e SetupAdsForm, is currently being submitted via the useAdsSetupCompleteCallback hook.
* @fires gla_setup_ads with `{ triggered_by: 'step1-continue-button', action: 'go-to-step2' }`.
* @fires gla_setup_ads with `{ triggered_by: 'stepper-step1-button', action: 'go-to-step1'}`.
*/
-const AdsStepper = ( { formProps } ) => {
+const AdsStepper = ( { isSubmitting } ) => {
const [ step, setStep ] = useState( '1' );
+
useEventPropertiesFilter( FILTER_ONBOARDING, {
context: CONTEXT_ADS_ONBOARDING,
step,
@@ -57,10 +57,6 @@ const AdsStepper = ( { formProps } ) => {
continueStep( '2' );
};
- // @todo: Add check for billing status once billing setup is moved to step 2.
- // For now, only disable based on the form being valid for testing purposes.
- const isDisabledLaunch = ! formProps.isValidForm;
-
return (
// This Stepper with this class name
// should be refactored into separate shared component.
@@ -88,23 +84,7 @@ const AdsStepper = ( { formProps } ) => {
'Create your paid campaign',
'google-listings-and-ads'
),
- content: (
-
- }
- />
- ),
+ content: ,
onClick: handleStepClick,
},
] }
diff --git a/js/src/setup-ads/ads-stepper/index.test.js b/js/src/setup-ads/ads-stepper/index.test.js
index ebfad27dfc..feae4d028b 100644
--- a/js/src/setup-ads/ads-stepper/index.test.js
+++ b/js/src/setup-ads/ads-stepper/index.test.js
@@ -3,7 +3,6 @@ jest.mock( '@woocommerce/tracks', () => {
recordEvent: jest.fn().mockName( 'recordEvent' ),
};
} );
-
jest.mock( './setup-accounts', () => jest.fn().mockName( 'SetupAccounts' ) );
jest.mock( '.~/components/paid-ads/ads-campaign', () =>
jest.fn().mockName( 'AdsCampaign' )
@@ -12,7 +11,7 @@ jest.mock( '.~/components/paid-ads/ads-campaign', () =>
/**
* External dependencies
*/
-import { screen, render, waitFor } from '@testing-library/react';
+import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { recordEvent } from '@woocommerce/tracks';
@@ -21,54 +20,32 @@ import { recordEvent } from '@woocommerce/tracks';
*/
import AdsStepper from './';
import SetupAccounts from './setup-accounts';
-import AdsCampaign from '.~/components/paid-ads/ads-campaign';
describe( 'AdsStepper', () => {
let continueToStep2;
- let continueToStep3;
beforeEach( () => {
SetupAccounts.mockImplementation( ( { onContinue } ) => {
continueToStep2 = onContinue;
return null;
} );
-
- AdsCampaign.mockImplementation( ( { onContinue } ) => {
- continueToStep3 = onContinue;
- return null;
- } );
} );
afterEach( () => {
jest.clearAllMocks();
} );
- async function continueUntilStep3() {
- continueToStep2();
-
- // Wait for stepper content to be rendered.
- await waitFor( () => {
- expect( continueToStep3 ).toBeDefined();
- } );
-
- continueToStep3();
- }
-
describe( 'tracks', () => {
it( 'Should record events after calling back to `onContinue`', async () => {
render( );
- await continueUntilStep3();
+ await continueToStep2();
- expect( recordEvent ).toHaveBeenCalledTimes( 2 );
+ expect( recordEvent ).toHaveBeenCalledTimes( 1 );
expect( recordEvent ).toHaveBeenNthCalledWith( 1, 'gla_setup_ads', {
action: 'go-to-step2',
triggered_by: 'step1-continue-button',
} );
- expect( recordEvent ).toHaveBeenNthCalledWith( 2, 'gla_setup_ads', {
- action: 'go-to-step3',
- triggered_by: 'step2-continue-button',
- } );
} );
it( 'Should record events after clicking step navigation buttons', async () => {
@@ -77,28 +54,9 @@ describe( 'AdsStepper', () => {
render( );
const step1 = screen.getByRole( 'button', { name: /accounts/ } );
- const step2 = screen.getByRole( 'button', { name: /campaign/ } );
-
- // Step 3 -> Step 2 -> Step 1
- await continueUntilStep3();
- recordEvent.mockClear();
- expect( recordEvent ).toHaveBeenCalledTimes( 0 );
-
- await user.click( step2 );
- await user.click( step1 );
-
- expect( recordEvent ).toHaveBeenCalledTimes( 2 );
- expect( recordEvent ).toHaveBeenNthCalledWith( 1, 'gla_setup_ads', {
- action: 'go-to-step2',
- triggered_by: 'stepper-step2-button',
- } );
- expect( recordEvent ).toHaveBeenNthCalledWith( 2, 'gla_setup_ads', {
- action: 'go-to-step1',
- triggered_by: 'stepper-step1-button',
- } );
- // Step 3 -> Step 1
- await continueUntilStep3();
+ // Step 2 -> Step 1
+ await continueToStep2();
recordEvent.mockClear();
expect( recordEvent ).toHaveBeenCalledTimes( 0 );
diff --git a/js/src/setup-ads/ads-stepper/setup-paid-ads.js b/js/src/setup-ads/ads-stepper/setup-paid-ads.js
new file mode 100644
index 0000000000..6e38e09c92
--- /dev/null
+++ b/js/src/setup-ads/ads-stepper/setup-paid-ads.js
@@ -0,0 +1,51 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import AppButton from '.~/components/app-button';
+import AdsCampaign from '.~/components/paid-ads/ads-campaign';
+import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
+import { GOOGLE_ADS_BILLING_STATUS } from '.~/constants';
+
+const { APPROVED } = GOOGLE_ADS_BILLING_STATUS;
+
+/**
+ * Renders the step to setup paid ads
+ *
+ * @param {Object} props Component props.
+ * @param {boolean} props.isSubmitting Indicates if the form is currently being submitted.
+ */
+const SetupPaidAds = ( { isSubmitting } ) => {
+ const { billingStatus } = useGoogleAdsAccountBillingStatus();
+
+ return (
+ (
+
+ ) }
+ />
+ );
+};
+
+export default SetupPaidAds;
diff --git a/js/src/setup-ads/setup-ads-form.js b/js/src/setup-ads/setup-ads-form.js
index 609c915d93..74b298db71 100644
--- a/js/src/setup-ads/setup-ads-form.js
+++ b/js/src/setup-ads/setup-ads-form.js
@@ -26,11 +26,10 @@ const SetupAdsForm = () => {
const [ isSubmitted, setSubmitted ] = useState( false );
const [ handleSetupComplete, isSubmitting ] = useAdsSetupCompleteCallback();
const adminUrl = useAdminUrl();
- const { data: targetAudience } = useTargetAudienceFinalCountryCodes();
+ const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
const initialValues = {
amount: 0,
- countryCodes: targetAudience,
};
useEffect( () => {
@@ -55,7 +54,7 @@ const SetupAdsForm = () => {
);
const handleSubmit = ( values ) => {
- const { amount, countryCodes } = values;
+ const { amount } = values;
recordGlaEvent( 'gla_launch_paid_campaign_button_click', {
audiences: countryCodes.join( ',' ),
@@ -68,17 +67,10 @@ const SetupAdsForm = () => {
};
const handleChange = ( _, values ) => {
- const args = [ initialValues, values ].map(
- ( { countryCodes, ...v } ) => {
- v.countrySet = new Set( countryCodes );
- return v;
- }
- );
-
- setFormChanged( ! isEqual( ...args ) );
+ setFormChanged( ! isEqual( initialValues, values ) );
};
- if ( ! targetAudience ) {
+ if ( ! countryCodes ) {
return null;
}
@@ -88,19 +80,8 @@ const SetupAdsForm = () => {
onChange={ handleChange }
onSubmit={ handleSubmit }
>
- { ( formProps ) => {
- const mixedFormProps = {
- ...formProps,
- // TODO: maybe move all API calls in useSetupCompleteCallback to ~./data
- isSubmitting,
- };
- return (
- <>
-
-
- >
- );
- } }
+
+
);
};
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/clientSession.js b/js/src/setup-mc/setup-stepper/clientSession.js
similarity index 100%
rename from js/src/setup-mc/setup-stepper/setup-paid-ads/clientSession.js
rename to js/src/setup-mc/setup-stepper/clientSession.js
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/constants.js b/js/src/setup-mc/setup-stepper/constants.js
similarity index 100%
rename from js/src/setup-mc/setup-stepper/setup-paid-ads/constants.js
rename to js/src/setup-mc/setup-stepper/constants.js
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads.js b/js/src/setup-mc/setup-stepper/setup-paid-ads.js
new file mode 100644
index 0000000000..f5911f282d
--- /dev/null
+++ b/js/src/setup-mc/setup-stepper/setup-paid-ads.js
@@ -0,0 +1,150 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import apiFetch from '@wordpress/api-fetch';
+import { useState } from '@wordpress/element';
+import { noop } from 'lodash';
+
+/**
+ * Internal dependencies
+ */
+import useAdminUrl from '.~/hooks/useAdminUrl';
+import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices';
+import useAdsSetupCompleteCallback from '.~/hooks/useAdsSetupCompleteCallback';
+import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
+import AdsCampaign from '.~/components/paid-ads/ads-campaign';
+import CampaignAssetsForm from '.~/components/paid-ads/campaign-assets-form';
+import AppButton from '.~/components/app-button';
+import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
+import { getProductFeedUrl } from '.~/utils/urls';
+import { API_NAMESPACE } from '.~/data/constants';
+import { GUIDE_NAMES, GOOGLE_ADS_BILLING_STATUS } from '.~/constants';
+import { ACTION_COMPLETE, ACTION_SKIP } from './constants';
+import SkipButton from './skip-button';
+import clientSession from './clientSession';
+
+/**
+ * Clicking on the "Complete setup" button to complete the onboarding flow with paid ads.
+ *
+ * @event gla_onboarding_complete_with_paid_ads_button_click
+ * @property {number} budget The budget for the campaign
+ * @property {string} audiences The targeted audiences for the campaign
+ */
+
+/**
+ * Renders the onboarding step for setting up the paid ads (Google Ads account and paid campaign)
+ * or skipping it, and then completing the onboarding flow.
+ * @fires gla_onboarding_complete_with_paid_ads_button_click
+ */
+export default function SetupPaidAds() {
+ const adminUrl = useAdminUrl();
+ const [ completing, setCompleting ] = useState( null );
+ const { createNotice } = useDispatchCoreNotices();
+ const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
+ const [ handleSetupComplete ] = useAdsSetupCompleteCallback();
+ const { billingStatus } = useGoogleAdsAccountBillingStatus();
+
+ const isBillingCompleted =
+ billingStatus?.status === GOOGLE_ADS_BILLING_STATUS.APPROVED;
+
+ const finishOnboardingSetup = async ( onBeforeFinish = noop ) => {
+ try {
+ await onBeforeFinish();
+ await apiFetch( {
+ path: `${ API_NAMESPACE }/mc/settings/sync`,
+ method: 'POST',
+ } );
+ } catch ( e ) {
+ setCompleting( null );
+
+ createNotice(
+ 'error',
+ __(
+ 'Unable to complete your setup.',
+ 'google-listings-and-ads'
+ )
+ );
+ }
+
+ // Force reload WC admin page to initiate the relevant dependencies of the Dashboard page.
+ const query = { guide: GUIDE_NAMES.SUBMISSION_SUCCESS };
+ window.location.href = adminUrl + getProductFeedUrl( query );
+ };
+
+ const handleSkipCreatePaidAds = async () => {
+ setCompleting( ACTION_SKIP );
+ await finishOnboardingSetup();
+ };
+
+ const createSkipButton = ( formContext ) => {
+ const { isValidForm } = formContext;
+
+ return (
+
+ );
+ };
+
+ const createContinueButton = ( formContext ) => {
+ const { isValidForm, values } = formContext;
+ const { amount } = values;
+
+ const disabled =
+ completing === ACTION_SKIP || ! isValidForm || ! isBillingCompleted;
+
+ const handleCompleteClick = async () => {
+ setCompleting( ACTION_COMPLETE );
+ const onBeforeFinish = handleSetupComplete.bind(
+ null,
+ amount,
+ countryCodes
+ );
+
+ await finishOnboardingSetup( onBeforeFinish );
+ };
+
+ return (
+
+ );
+ };
+
+ const paidAds = {
+ amount: 0,
+ ...clientSession.getCampaign(),
+ };
+
+ return (
+ {
+ clientSession.setCampaign( { ...values } );
+ } }
+ >
+
+
+ );
+}
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/index.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/index.js
deleted file mode 100644
index b949d024c1..0000000000
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/index.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default } from './setup-paid-ads';
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js
deleted file mode 100644
index 7072eab023..0000000000
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/paid-ads-setup-sections.js
+++ /dev/null
@@ -1,134 +0,0 @@
-/**
- * External dependencies
- */
-import { useState, useRef, useEffect } from '@wordpress/element';
-import { Form } from '@woocommerce/components';
-
-/**
- * Internal dependencies
- */
-import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
-import BudgetSection from '.~/components/paid-ads/budget-section';
-import BillingCard from '.~/components/paid-ads/billing-card';
-import SpinnerCard from '.~/components/spinner-card';
-import Section from '.~/wcdl/section';
-import validateCampaign from '.~/components/paid-ads/validateCampaign';
-import clientSession from './clientSession';
-import { GOOGLE_ADS_BILLING_STATUS } from '.~/constants';
-
-/**
- * @typedef {import('.~/data/actions').CountryCode} CountryCode
- */
-
-/**
- * @typedef {Object} PaidAdsData
- * @property {number|undefined} amount Daily average cost of the paid ads campaign.
- * @property {boolean} isValid Whether the campaign data are valid values.
- * @property {boolean} isReady Whether the campaign data and the billing setting are ready for completing the paid ads setup.
- */
-
-const defaultPaidAds = {
- amount: 0,
- isValid: false,
- isReady: false,
-};
-
-/**
- * Resolve the initial paid ads data from the given paid ads data.
- * Parts of the resolved data are used in the `initialValues` prop of `Form` component.
- *
- * @param {PaidAdsData} paidAds The paid ads data as the base to be resolved with other states.
- * @return {PaidAdsData} The resolved paid ads data.
- */
-function resolveInitialPaidAds( paidAds ) {
- const nextPaidAds = { ...paidAds };
- nextPaidAds.isValid = ! Object.keys( validateCampaign( nextPaidAds ) )
- .length;
-
- return nextPaidAds;
-}
-
-/**
- * Renders sections of Google Ads account, budget and billing for setting up the paid ads.
- *
- * @param {Object} props React props.
- * @param {(onStatesReceived: PaidAdsData)=>void} props.onStatesReceived Callback to receive the data for setting up paid ads when initial and also when the budget and billing are updated.
- * @param {Array|undefined} props.countryCodes Country codes for the campaign.
- */
-export default function PaidAdsSetupSections( {
- onStatesReceived,
- countryCodes,
-} ) {
- const { billingStatus } = useGoogleAdsAccountBillingStatus();
-
- const onStatesReceivedRef = useRef();
- onStatesReceivedRef.current = onStatesReceived;
-
- const [ paidAds, setPaidAds ] = useState( () => {
- // Resolve the starting paid ads data with the campaign data stored in the client session.
- const startingPaidAds = {
- ...defaultPaidAds,
- ...clientSession.getCampaign(),
- };
- return resolveInitialPaidAds( startingPaidAds );
- } );
-
- const isBillingCompleted =
- billingStatus?.status === GOOGLE_ADS_BILLING_STATUS.APPROVED;
-
- /*
- If a merchant has not yet finished the billing setup, the billing status will be
- updated by `useAutoCheckBillingStatusEffect` hook in `BillingSetupCard` component
- till it gets completed.
-
- Or, if the billing setup is already finished, the loaded `billingStatus.status`
- will already be 'approved' without passing through the above hook and component.
-
- Therefore, in order to ensure the parent component can continue the setup from
- any billing status, it only needs to watch the `isBillingCompleted` eventually
- to wait for the fulfilled 'approved' status, and then propagate it to the parent.
-
- For example, refresh page during onboarding flow after the billing setup is finished.
- */
- useEffect( () => {
- const nextPaidAds = {
- ...paidAds,
- isReady: paidAds.isValid && isBillingCompleted,
- };
- onStatesReceivedRef.current( nextPaidAds );
- clientSession.setCampaign( nextPaidAds );
- }, [ paidAds, isBillingCompleted ] );
-
- if ( ! billingStatus ) {
- return (
-
- );
- }
-
- const initialValues = {
- amount: paidAds.amount,
- };
-
- return (
-
- );
-}
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js b/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js
deleted file mode 100644
index 76b6381fb9..0000000000
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/setup-paid-ads.js
+++ /dev/null
@@ -1,216 +0,0 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-import apiFetch from '@wordpress/api-fetch';
-import { select } from '@wordpress/data';
-import { useState } from '@wordpress/element';
-import { Flex } from '@wordpress/components';
-import { noop } from 'lodash';
-
-/**
- * Internal dependencies
- */
-import useAdminUrl from '.~/hooks/useAdminUrl';
-import useDispatchCoreNotices from '.~/hooks/useDispatchCoreNotices';
-import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
-import useAdsSetupCompleteCallback from '.~/hooks/useAdsSetupCompleteCallback';
-import useTargetAudienceFinalCountryCodes from '.~/hooks/useTargetAudienceFinalCountryCodes';
-import StepContent from '.~/components/stepper/step-content';
-import StepContentHeader from '.~/components/stepper/step-content-header';
-import StepContentFooter from '.~/components/stepper/step-content-footer';
-import StepContentActions from '.~/components/stepper/step-content-actions';
-import AppButton from '.~/components/app-button';
-import PaidAdsFaqsPanel from '.~/components/paid-ads/faqs-panel';
-import PaidAdsFeaturesSection from './paid-ads-features-section';
-import PaidAdsSetupSections from './paid-ads-setup-sections';
-import SkipPaidAdsConfirmationModal from './skip-paid-ads-confirmation-modal';
-import { getProductFeedUrl } from '.~/utils/urls';
-import { API_NAMESPACE, STORE_KEY } from '.~/data/constants';
-import { GUIDE_NAMES } from '.~/constants';
-import { ACTION_COMPLETE, ACTION_SKIP } from './constants';
-import { recordGlaEvent } from '.~/utils/tracks';
-
-/**
- * Clicking on the "Create a paid ad campaign" button to open the paid ads setup in the onboarding flow.
- *
- * @event gla_onboarding_open_paid_ads_setup_button_click
- */
-
-/**
- * Clicking on the "Complete setup" button to complete the onboarding flow with paid ads.
- *
- * @event gla_onboarding_complete_with_paid_ads_button_click
- * @property {number} budget The budget for the campaign
- * @property {string} audiences The targeted audiences for the campaign
- */
-
-/**
- * Clicking on the skip paid ads button to complete the onboarding flow.
- * The 'unknown' value of properties may means:
- * - the final status has not yet been resolved when recording this event
- * - the status is not available, for example, the billing status is unknown if Google Ads account is not yet connected
- *
- * @event gla_onboarding_complete_button_click
- * @property {string} google_ads_account_status The connection status of merchant's Google Ads addcount, e.g. 'connected', 'disconnected', 'incomplete'
- * @property {string} billing_method_status The status of billing method of merchant's Google Ads addcount e.g. 'unknown', 'pending', 'approved', 'cancelled'
- * @property {string} campaign_form_validation Whether the entered paid campaign form data are valid, e.g. 'unknown', 'valid', 'invalid'
- */
-
-/**
- * Renders the onboarding step for setting up the paid ads (Google Ads account and paid campaign)
- * or skipping it, and then completing the onboarding flow.
- *
- * @fires gla_onboarding_open_paid_ads_setup_button_click
- * @fires gla_onboarding_complete_with_paid_ads_button_click
- * @fires gla_onboarding_complete_button_click
- */
-export default function SetupPaidAds() {
- const adminUrl = useAdminUrl();
- const { createNotice } = useDispatchCoreNotices();
- const { data: countryCodes } = useTargetAudienceFinalCountryCodes();
- const { googleAdsAccount, hasGoogleAdsConnection } = useGoogleAdsAccount();
- const [ handleSetupComplete ] = useAdsSetupCompleteCallback();
- const [ paidAds, setPaidAds ] = useState( {} );
- const [ completing, setCompleting ] = useState( null );
- const [
- showSkipPaidAdsConfirmationModal,
- setShowSkipPaidAdsConfirmationModal,
- ] = useState( false );
-
- const finishOnboardingSetup = async ( event, onBeforeFinish = noop ) => {
- setCompleting( event.target.dataset.action );
-
- try {
- await onBeforeFinish();
- await apiFetch( {
- path: `${ API_NAMESPACE }/mc/settings/sync`,
- method: 'POST',
- } );
- } catch ( e ) {
- setCompleting( null );
-
- createNotice(
- 'error',
- __(
- 'Unable to complete your setup.',
- 'google-listings-and-ads'
- )
- );
- }
-
- // Force reload WC admin page to initiate the relevant dependencies of the Dashboard page.
- const query = { guide: GUIDE_NAMES.SUBMISSION_SUCCESS };
- window.location.href = adminUrl + getProductFeedUrl( query );
- };
-
- const handleCompleteClick = async ( event ) => {
- const onBeforeFinish = handleSetupComplete.bind(
- null,
- paidAds.amount,
- countryCodes
- );
- await finishOnboardingSetup( event, onBeforeFinish );
- };
-
- const handleSkipCreatePaidAds = async ( event ) => {
- const selector = select( STORE_KEY );
- const billing = selector.getGoogleAdsAccountBillingStatus();
-
- setShowSkipPaidAdsConfirmationModal( false );
-
- const eventProps = {
- google_ads_account_status: googleAdsAccount?.status,
- billing_method_status: billing?.status || 'unknown',
- campaign_form_validation: paidAds.isValid ? 'valid' : 'invalid',
- };
-
- recordGlaEvent( 'gla_onboarding_complete_button_click', eventProps );
-
- await finishOnboardingSetup( event );
- };
-
- const handleShowSkipPaidAdsConfirmationModal = () => {
- setShowSkipPaidAdsConfirmationModal( true );
- };
-
- const handleCancelSkipPaidAdsClick = () => {
- setShowSkipPaidAdsConfirmationModal( false );
- };
-
- // The status check of Google Ads account connection is included in `paidAds.isReady`,
- // because when there is no connected account, it will disable the budget section and set the `amount` to `undefined`.
- const disabledComplete = completing === ACTION_SKIP || ! paidAds.isReady;
-
- function createSkipButton( text ) {
- const disabledSkip =
- completing === ACTION_COMPLETE || ! hasGoogleAdsConnection;
-
- return (
-
- );
- }
-
- return (
-
-
-
-
-
- { showSkipPaidAdsConfirmationModal && (
-
- ) }
-
-
-
-
- { createSkipButton(
- __(
- 'Skip paid ads creation',
- 'google-listings-and-ads'
- )
- ) }
-
-
-
-
-
-
- );
-}
diff --git a/js/src/setup-mc/setup-stepper/skip-button.js b/js/src/setup-mc/setup-stepper/skip-button.js
new file mode 100644
index 0000000000..c5a62f2d54
--- /dev/null
+++ b/js/src/setup-mc/setup-stepper/skip-button.js
@@ -0,0 +1,84 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+import { useState } from '@wordpress/element';
+import { noop } from 'lodash';
+
+/**
+ * Internal dependencies
+ */
+import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
+import useGoogleAdsAccountBillingStatus from '.~/hooks/useGoogleAdsAccountBillingStatus';
+import AppButton from '.~/components/app-button';
+import SkipPaidAdsConfirmationModal from './skip-paid-ads-confirmation-modal';
+import { recordGlaEvent } from '.~/utils/tracks';
+
+/**
+ * Clicking on the skip paid ads button to complete the onboarding flow.
+ * The 'unknown' value of properties may means:
+ * - the final status has not yet been resolved when recording this event
+ * - the status is not available, for example, the billing status is unknown if Google Ads account is not yet connected
+ *
+ * @event gla_onboarding_complete_button_click
+ * @property {string} google_ads_account_status The connection status of merchant's Google Ads addcount, e.g. 'connected', 'disconnected', 'incomplete'
+ * @property {string} billing_method_status The status of billing method of merchant's Google Ads addcount e.g. 'unknown', 'pending', 'approved', 'cancelled'
+ * @property {string} campaign_form_validation Whether the entered paid campaign form data are valid, e.g. 'unknown', 'valid', 'invalid'
+ */
+
+export default function SkipButton( {
+ isValidForm,
+ onSkipCreatePaidAds = noop,
+ loading,
+ disabled,
+} ) {
+ const [
+ showSkipPaidAdsConfirmationModal,
+ setShowSkipPaidAdsConfirmationModal,
+ ] = useState( false );
+ const { googleAdsAccount } = useGoogleAdsAccount();
+ const { billingStatus } = useGoogleAdsAccountBillingStatus();
+
+ const handleOnSkipClick = () => {
+ setShowSkipPaidAdsConfirmationModal( true );
+ };
+
+ const handleCancelSkipPaidAdsClick = () => {
+ setShowSkipPaidAdsConfirmationModal( false );
+ };
+
+ const handleSkipCreatePaidAds = () => {
+ setShowSkipPaidAdsConfirmationModal( false );
+
+ const eventProps = {
+ google_ads_account_status: googleAdsAccount?.status,
+ billing_method_status: billingStatus?.status || 'unknown',
+ campaign_form_validation: isValidForm ? 'valid' : 'invalid',
+ };
+ recordGlaEvent( 'gla_onboarding_complete_button_click', eventProps );
+
+ onSkipCreatePaidAds();
+ };
+
+ return (
+ <>
+
+
+ { showSkipPaidAdsConfirmationModal && (
+
+ ) }
+ >
+ );
+}
diff --git a/js/src/setup-mc/setup-stepper/setup-paid-ads/skip-paid-ads-confirmation-modal.js b/js/src/setup-mc/setup-stepper/skip-paid-ads-confirmation-modal.js
similarity index 96%
rename from js/src/setup-mc/setup-stepper/setup-paid-ads/skip-paid-ads-confirmation-modal.js
rename to js/src/setup-mc/setup-stepper/skip-paid-ads-confirmation-modal.js
index 18de3c6583..b2ebb0a4de 100644
--- a/js/src/setup-mc/setup-stepper/setup-paid-ads/skip-paid-ads-confirmation-modal.js
+++ b/js/src/setup-mc/setup-stepper/skip-paid-ads-confirmation-modal.js
@@ -9,7 +9,6 @@ import { __ } from '@wordpress/i18n';
import AppModal from '.~/components/app-modal';
import AppButton from '.~/components/app-button';
import AppDocumentationLink from '.~/components/app-documentation-link';
-import { ACTION_SKIP } from './constants';
/**
* @fires gla_documentation_link_click with `{ context: 'skip-paid-ads-modal', link_id: 'paid-ads-with-performance-max-campaigns-learn-more', href: 'https://support.google.com/google-ads/answer/10724817' }`
@@ -37,7 +36,6 @@ const SkipPaidAdsConfirmationModal = ( {
{ __(
diff --git a/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js b/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js
index aa8218b58e..987a7111ae 100644
--- a/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js
+++ b/tests/e2e/specs/add-paid-campaigns/add-paid-campaigns.test.js
@@ -11,8 +11,6 @@ import SetupAdsAccountsPage from '../../utils/pages/setup-ads/setup-ads-accounts
import SetupBudgetPage from '../../utils/pages/setup-ads/setup-budget';
import { LOAD_STATE } from '../../utils/constants';
import {
- getCountryInputSearchBoxContainer,
- getCountryTagsFromInputSearchBoxContainer,
getFAQPanelTitle,
getFAQPanelRow,
checkFAQExpandable,
@@ -69,6 +67,9 @@ test.describe( 'Set up Ads account', () => {
setupBudgetPage = new SetupBudgetPage( page );
await setOnboardedMerchant();
await setupAdsAccounts.mockAdsAccountsResponse( [] );
+ await setupBudgetPage.fulfillBillingStatusRequest( {
+ status: 'approved',
+ } );
await dashboardPage.mockRequests();
await dashboardPage.goto();
} );
@@ -280,26 +281,15 @@ test.describe( 'Set up Ads account', () => {
} );
test.describe( 'Create your paid campaign', () => {
- test.beforeAll( async () => {
- await setupBudgetPage.fulfillBillingStatusRequest( {
- status: 'approved',
- } );
- } );
-
test( 'Continue to create paid ad campaign', async () => {
await setupAdsAccounts.clickContinue();
await page.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
-
await expect(
page.getByRole( 'heading', {
name: 'Create your paid campaign',
} )
).toBeVisible();
- await expect(
- page.getByRole( 'heading', { name: 'Ads audience' } )
- ).toBeVisible();
-
await expect(
page.getByRole( 'heading', { name: 'Set your budget' } )
).toBeVisible();
@@ -357,17 +347,6 @@ test.describe( 'Set up Ads account', () => {
} );
} );
- test( 'Audience should be United States', async () => {
- const countrySearchBoxContainer =
- getCountryInputSearchBoxContainer( page );
- const countryTags =
- getCountryTagsFromInputSearchBoxContainer( page );
- await expect( countryTags ).toHaveCount( 1 );
- await expect( countrySearchBoxContainer ).toContainText(
- 'United States'
- );
- } );
-
test( 'Set the budget', async () => {
budget = '0';
await setupBudgetPage.fillBudget( budget );
diff --git a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js b/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
index 92c3fb3206..adc31ea614 100644
--- a/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
+++ b/tests/e2e/specs/setup-mc/step-4-complete-campaign.test.js
@@ -247,12 +247,21 @@ test.describe( 'Complete your campaign', () => {
} );
test( 'should see billing has been set up successfully when billing status API returns approved', async () => {
+ await setupBudgetPage.mockAdsAccountsResponse( {
+ id: 12345,
+ billing_url: null,
+ } );
await setupBudgetPage.fulfillBillingStatusRequest( {
- status: 'approved',
+ status: 'pending',
} );
await newPage.close();
- await page.reload();
+ // return focus to the page.
+ await setupBudgetPage.focusBudget();
+ await setupBudgetPage.fulfillBillingStatusRequest( {
+ status: 'approved',
+ } );
+ await setupBudgetPage.awaitForBillingStatusRequest();
const billingSetupSuccessSection =
setupBudgetPage.getBillingSetupSuccessSection();
diff --git a/tests/e2e/utils/page.js b/tests/e2e/utils/page.js
index 54912b00f8..51713bd110 100644
--- a/tests/e2e/utils/page.js
+++ b/tests/e2e/utils/page.js
@@ -54,19 +54,6 @@ export function getCountryInputSearchBoxContainer( page ) {
);
}
-/**
- * Get country tags from input search box container.
- *
- * @param {import('@playwright/test').Page} page The current page.
- *
- * @return {import('@playwright/test').Locator} Get country tags from input search box container.
- */
-export function getCountryTagsFromInputSearchBoxContainer( page ) {
- return getCountryInputSearchBoxContainer( page ).locator(
- '.woocommerce-tag'
- );
-}
-
/**
* Get country input search box.
*
diff --git a/tests/e2e/utils/pages/setup-ads/setup-budget.js b/tests/e2e/utils/pages/setup-ads/setup-budget.js
index bf9bab0b71..bb164ff592 100644
--- a/tests/e2e/utils/pages/setup-ads/setup-budget.js
+++ b/tests/e2e/utils/pages/setup-ads/setup-budget.js
@@ -99,6 +99,16 @@ export default class SetupBudget extends MockRequests {
await input.fill( budget );
}
+ /**
+ * Focus the budget input.
+ *
+ * @return {Promise}
+ */
+ async focusBudget() {
+ const input = this.getBudgetInput();
+ await input.focus();
+ }
+
/**
* Click set up billing button.
*