diff --git a/js/src/components/ads-account-select-control/index.js b/js/src/components/ads-account-select-control/index.js
index ccb26c3447..12de497df3 100644
--- a/js/src/components/ads-account-select-control/index.js
+++ b/js/src/components/ads-account-select-control/index.js
@@ -1,8 +1,15 @@
+/**
+ * External dependencies
+ */
+import { __, sprintf } from '@wordpress/i18n';
+import { getSetting } from '@woocommerce/settings'; // eslint-disable-line import/no-unresolved
+
/**
* Internal dependencies
*/
import AppSelectControl from '.~/components/app-select-control';
import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts';
+import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
/**
* @param {Object} props The component props
@@ -10,6 +17,35 @@ import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts'
*/
const AdsAccountSelectControl = ( props ) => {
const { existingAccounts } = useExistingGoogleAdsAccounts();
+ const { googleAdsAccount, hasGoogleAdsConnection } = useGoogleAdsAccount();
+
+ const accountIdExists = existingAccounts?.some(
+ ( existingAccount ) => existingAccount.id === googleAdsAccount?.id
+ );
+
+ // If the account ID is not in the list of existing accounts, fake the select options by displaying the connected account ID only.
+ if ( ! accountIdExists && hasGoogleAdsConnection ) {
+ const domain = new URL( getSetting( 'homeUrl' ) ).host;
+
+ return (
+
+ );
+ }
const options = existingAccounts?.map( ( acc ) => ( {
value: acc.id,
diff --git a/js/src/components/google-account-card/connected-google-account-card.js b/js/src/components/google-account-card/connected-google-account-card.js
index be55f49d0e..4a3b833ea5 100644
--- a/js/src/components/google-account-card/connected-google-account-card.js
+++ b/js/src/components/google-account-card/connected-google-account-card.js
@@ -1,22 +1,10 @@
-/**
- * External dependencies
- */
-import { __ } from '@wordpress/i18n';
-
/**
* Internal dependencies
*/
import AccountCard, { APPEARANCE } from '.~/components/account-card';
-import AppButton from '.~/components/app-button';
import ConnectedIconLabel from '.~/components/connected-icon-label';
import Section from '.~/wcdl/section';
-import useSwitchGoogleAccount from './useSwitchGoogleAccount';
-
-/**
- * Clicking on the "connect to a different Google account" button.
- *
- * @event gla_google_account_connect_different_account_button_click
- */
+import SwitchAccountButton from './switch-account-button';
/**
* Renders a Google account card UI with connected account information.
@@ -26,16 +14,12 @@ import useSwitchGoogleAccount from './useSwitchGoogleAccount';
* @param {{ email: string }} props.googleAccount A data payload object containing the user's Google account email.
* @param {JSX.Element} [props.helper] Helper content below the Google account email.
* @param {boolean} [props.hideAccountSwitch=false] Indicate whether hide the account switch block at the card footer.
- *
- * @fires gla_google_account_connect_different_account_button_click
*/
const ConnectedGoogleAccountCard = ( {
googleAccount,
helper,
hideAccountSwitch = false,
} ) => {
- const [ handleSwitch, { loading } ] = useSwitchGoogleAccount();
-
return (
{ ! hideAccountSwitch && (
-
+
) }
diff --git a/js/src/components/google-account-card/switch-account-button.js b/js/src/components/google-account-card/switch-account-button.js
new file mode 100644
index 0000000000..39838c702a
--- /dev/null
+++ b/js/src/components/google-account-card/switch-account-button.js
@@ -0,0 +1,46 @@
+/**
+ * External dependencies
+ */
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import AppButton from '.~/components/app-button';
+import useSwitchGoogleAccount from './useSwitchGoogleAccount';
+
+/**
+ * Clicking on the "connect to a different Google account" button.
+ *
+ * @event gla_google_account_connect_different_account_button_click
+ */
+
+/**
+ * Renders a switch button that lets user connect with another Google account.
+ *
+ * @fires gla_google_account_connect_different_account_button_click
+ * @param {Object} props React props
+ * @param {string} [props.text="Or, connect to a different Google account"] Text to display on the button
+ */
+const SwitchAccountButton = ( {
+ text = __(
+ 'Or, connect to a different Google account',
+ 'google-listings-and-ads'
+ ),
+ ...restProps
+} ) => {
+ const [ handleSwitch, { loading } ] = useSwitchGoogleAccount();
+
+ return (
+
+ );
+};
+
+export default SwitchAccountButton;
diff --git a/js/src/components/google-account-card/useSwitchGoogleAccount.js b/js/src/components/google-account-card/useSwitchGoogleAccount.js
index 2e38f10f94..c151d86311 100644
--- a/js/src/components/google-account-card/useSwitchGoogleAccount.js
+++ b/js/src/components/google-account-card/useSwitchGoogleAccount.js
@@ -37,6 +37,14 @@ const useSwitchGoogleAccount = () => {
method: 'DELETE',
} );
+ const [
+ fetchGoogleAdsDisconnect,
+ { loading: loadingGoogleAdsDisconnect },
+ ] = useApiFetchCallback( {
+ path: `${ API_NAMESPACE }/ads/connection`,
+ method: 'DELETE',
+ } );
+
/**
* Note: we are manually calling `DELETE /google/connect` instead of using
* `disconnectGoogleAccount` action from wp-data store
@@ -66,6 +74,7 @@ const useSwitchGoogleAccount = () => {
try {
await fetchGoogleMCDisconnect();
+ await fetchGoogleAdsDisconnect();
await fetchGoogleDisconnect();
const { url } = await fetchGoogleConnect();
window.location.href = url;
@@ -83,6 +92,7 @@ const useSwitchGoogleAccount = () => {
const loading =
loadingGoogleMCDisconnect ||
+ loadingGoogleAdsDisconnect ||
loadingGoogleDisconnect ||
loadingGoogleConnect ||
dataGoogleConnect;
diff --git a/js/src/components/google-combo-account-card/claim-ads-account/claim-ads-account.js b/js/src/components/google-combo-account-card/claim-ads-account/claim-ads-account.js
index 298c7bc42a..28e0df8e27 100644
--- a/js/src/components/google-combo-account-card/claim-ads-account/claim-ads-account.js
+++ b/js/src/components/google-combo-account-card/claim-ads-account/claim-ads-account.js
@@ -2,6 +2,8 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
+import { Icon } from '@wordpress/components';
+import { external as externalIcon } from '@wordpress/icons';
/**
* Internal dependencies
@@ -40,13 +42,13 @@ const ClaimAdsAccount = () => {
'google-listings-and-ads'
) }
-
+ { __(
'Claim account in Google Ads',
'google-listings-and-ads'
) }
- isPrimary
- />
+
+
);
};
diff --git a/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js b/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js
index 6c2cd912c7..df8ed23192 100644
--- a/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js
+++ b/js/src/components/google-combo-account-card/connect-ads/connect-ads-footer.js
@@ -8,6 +8,9 @@ import { __ } from '@wordpress/i18n';
*/
import AppButton from '.~/components/app-button';
import DisconnectAccount from '.~/components/google-ads-account-card/disconnect-account';
+import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts';
+import useGoogleAdsAccountStatus from '.~/hooks/useGoogleAdsAccountStatus';
+import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
/**
* Footer component.
@@ -15,20 +18,37 @@ import DisconnectAccount from '.~/components/google-ads-account-card/disconnect-
* @param {Object} props Props.
* @param {boolean} props.isConnected Whether the account is connected.
* @param {Function} props.onCreateNewClick Callback when clicking on the button to create a new account.
+ * @param {boolean} props.disabled Whether to disable the create account button.
* @param {Object} props.restProps Rest props. Passed to AppButton.
* @return {JSX.Element} Footer component.
*/
const ConnectAdsFooter = ( {
isConnected,
onCreateNewClick,
+ disabled,
...restProps
} ) => {
- if ( isConnected ) {
+ const { existingAccounts } = useExistingGoogleAdsAccounts();
+ const { googleAdsAccount } = useGoogleAdsAccount();
+ const { hasAccess } = useGoogleAdsAccountStatus();
+ const shouldClaimGoogleAdsAccount = Boolean(
+ googleAdsAccount?.id && hasAccess === false
+ );
+
+ if ( isConnected && existingAccounts.length > 0 ) {
return ;
}
+ const disabledButton =
+ disabled ||
+ ( shouldClaimGoogleAdsAccount && ! existingAccounts.length );
return (
-
+
{ __(
'Or, create a new Google Ads account',
'google-listings-and-ads'
diff --git a/js/src/components/google-combo-account-card/connect-ads/connect-existing-account.js b/js/src/components/google-combo-account-card/connect-ads/connect-existing-account.js
index a42679f6ff..e074405a00 100644
--- a/js/src/components/google-combo-account-card/connect-ads/connect-existing-account.js
+++ b/js/src/components/google-combo-account-card/connect-ads/connect-existing-account.js
@@ -31,8 +31,12 @@ const ConnectExistingAccount = ( { onCreateClick } ) => {
const { createNotice } = useDispatchCoreNotices();
const { fetchGoogleAdsAccountStatus } = useAppDispatch();
const isConnected = useGoogleAdsAccountReady();
- const { googleAdsAccount, hasFinishedResolution, refetchGoogleAdsAccount } =
- useGoogleAdsAccount();
+ const {
+ googleAdsAccount,
+ hasFinishedResolution,
+ hasGoogleAdsConnection,
+ refetchGoogleAdsAccount,
+ } = useGoogleAdsAccount();
const [ connectGoogleAdsAccount ] = useApiFetchCallback( {
path: '/wc/gla/ads/accounts',
method: 'POST',
@@ -40,10 +44,10 @@ const ConnectExistingAccount = ( { onCreateClick } ) => {
} );
useEffect( () => {
- if ( isConnected ) {
+ if ( hasGoogleAdsConnection ) {
setValue( googleAdsAccount.id );
}
- }, [ googleAdsAccount, isConnected ] );
+ }, [ googleAdsAccount, hasGoogleAdsConnection ] );
const handleConnectClick = async () => {
if ( ! value ) {
@@ -86,7 +90,11 @@ const ConnectExistingAccount = ( { onCreateClick } ) => {
}
return (
-
+
);
};
@@ -108,13 +116,13 @@ const ConnectExistingAccount = ( { onCreateClick } ) => {
value={ value }
onChange={ setValue }
autoSelectFirstOption
- nonInteractive={ isConnected }
+ nonInteractive={ hasGoogleAdsConnection }
/>
}
actions={
}
diff --git a/js/src/components/google-combo-account-card/connect-ads/upserting-account.js b/js/src/components/google-combo-account-card/connect-ads/upserting-account.js
index 61e22ca118..063daac196 100644
--- a/js/src/components/google-combo-account-card/connect-ads/upserting-account.js
+++ b/js/src/components/google-combo-account-card/connect-ads/upserting-account.js
@@ -37,7 +37,7 @@ const UpsertingAccount = ( { upsertingAction } ) => {
className="gla-google-combo-service-account-card--ads"
title={ title }
helper={ __(
- 'This may take a few minutes, please wait a moment…',
+ 'This may take a few moments, please wait…',
'google-listings-and-ads'
) }
indicator={ }
diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js
index cd7864c70b..9acba1c41d 100644
--- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.js
+++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.js
@@ -1,7 +1,8 @@
/**
* External dependencies
*/
-import { useEffect } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { useState, useEffect } from '@wordpress/element';
/**
* Internal dependencies
@@ -20,8 +21,9 @@ import useGoogleMCAccount from '.~/hooks/useGoogleMCAccount';
import useExistingGoogleMCAccounts from '.~/hooks/useExistingGoogleMCAccounts';
import useCreateMCAccount from '.~/hooks/useCreateMCAccount';
import ConnectMC from '.~/components/google-mc-account-card/connect-mc';
-import useGoogleAdsAccountReady from '.~/hooks/useGoogleAdsAccountReady';
import useExistingGoogleAdsAccounts from '.~/hooks/useExistingGoogleAdsAccounts';
+import AppButton from '.~/components/app-button';
+import SwitchAccountButton from '.~/components/google-account-card/switch-account-button';
import useGoogleAdsAccountStatus from '.~/hooks/useGoogleAdsAccountStatus';
import useGoogleAdsAccount from '.~/hooks/useGoogleAdsAccount';
import useUpsertAdsAccount from '.~/hooks/useUpsertAdsAccount';
@@ -33,26 +35,34 @@ import './connected-google-combo-account-card.scss';
* It will also kickoff Ads and Merchant Center account creation if the user does not have accounts.
*/
const ConnectedGoogleComboAccountCard = () => {
+ const [ editMode, setEditMode ] = useState( false );
+
// We use a single instance of the hook to create a MC (Merchant Center) account,
// ensuring consistent results across both the main component (ConnectedGoogleComboAccountCard) and its child component (ConnectMC).
// This approach is especially useful when an MC account is automatically created, and the URL needs to be reclaimed.
// The URL reclaim component is rendered within the ConnectMC component.
const [ createMCAccount, resultCreateMCAccount ] = useCreateMCAccount();
const { data: existingGoogleMCAccounts } = useExistingGoogleMCAccounts();
- const { isReady: isGoogleMCReady } = useGoogleMCAccount();
const { hasDetermined, creatingWhich } =
useAutoCreateAdsMCAccounts( createMCAccount );
const { text, subText } = getAccountCreationTexts( creatingWhich );
const { existingAccounts: existingGoogleAdsAccounts } =
useExistingGoogleAdsAccounts();
- const isConnected = useGoogleAdsAccountReady();
- const { hasGoogleMCConnection, hasFinishedResolution } =
- useGoogleMCAccount();
+ const {
+ isReady: isGoogleMCReady,
+ hasGoogleMCConnection,
+ hasFinishedResolution,
+ } = useGoogleMCAccount();
const { invalidateResolution } = useAppDispatch();
- const { googleAdsAccount } = useGoogleAdsAccount();
+ const { googleAdsAccount, hasGoogleAdsConnection } = useGoogleAdsAccount();
const { hasAccess, step } = useGoogleAdsAccountStatus();
const [ upsertAdsAccount, { action, loading } ] = useUpsertAdsAccount();
+ const hasExistingGoogleMCAccounts = existingGoogleMCAccounts?.length > 0;
+ const hasExistingGoogleAdsAccounts = existingGoogleAdsAccounts?.length > 0;
+ const shouldClaimGoogleAdsAccount = Boolean(
+ ! loading && googleAdsAccount?.id && hasAccess === false
+ );
const finalizeAdsAccountCreation =
hasAccess === true && step === 'conversion_action';
@@ -69,26 +79,95 @@ const ConnectedGoogleComboAccountCard = () => {
upsertAccount();
}, [ finalizeAdsAccountCreation, upsertAdsAccount, invalidateResolution ] );
+ const handleCancelClick = () => {
+ setEditMode( false );
+ };
+
+ const handleEditClick = () => {
+ setEditMode( true );
+ };
+
+ // During MC account creation, we need to show the ConnectMC component
+ // when the account creation needs to show the Switch or Reclaim flow.
+ const googleMCHasError = [ 409, 403 ].includes(
+ resultCreateMCAccount.response?.status
+ );
+
+ // After creating a new account, it may be connected but not ready
+ // (e.g., needing to reclaim the URL). In this case, we show the ConnectMC
+ // component, even if the existing accounts list has not yet updated.
+ //
+ // The last `hasGoogleMCConnection` condition exists for the scenario with these steps:
+ // 1. Automatically creating a Google Merchant Center account and reclaiming URL is required.
+ // 2. The merchant interrupts the onboarding flow and then resumes it.
+ // This is also the case for refreshing webpage.
+ // 3. The newly created account is not yet among the existing accounts.
+ // 4. The condition enables the merchant to resume the Google Merchant Center connection
+ // from the step of connecting the newly created account
+ const canShowConnectMC =
+ googleMCHasError ||
+ hasExistingGoogleMCAccounts ||
+ hasGoogleMCConnection;
+ const showConnectMC = canShowConnectMC && ( editMode || ! isGoogleMCReady );
+
+ // After creating a new account, it may not show up in the existing accounts list
+ // immediately. In this case, we show the ConnectAds component in edit mode unless
+ // we're showing the claim notice in the upper card.
+ const canShowConnectAds =
+ hasGoogleAdsConnection || hasExistingGoogleAdsAccounts;
+ const showConnectAds =
+ canShowConnectAds && ( editMode || ! hasGoogleAdsConnection );
+
+ // When Ads and MC are disconnected in edit mode, exit edit mode.
+ useEffect( () => {
+ if ( editMode && ! hasGoogleMCConnection && ! hasGoogleAdsConnection ) {
+ setEditMode( false );
+ }
+ }, [ editMode, hasGoogleAdsConnection, hasGoogleMCConnection ] );
+
if ( ! hasDetermined ) {
return ;
}
- // @TODO: edit mode implementation in 2605
- const editMode = false;
- const shouldClaimGoogleAdsAccount = Boolean(
- ! loading && googleAdsAccount?.id && hasAccess === false
+ const switchAccountButton = (
+
);
- const hasExistingGoogleMCAccounts = existingGoogleMCAccounts?.length > 0;
- const showConnectMC =
- ( editMode && hasExistingGoogleMCAccounts ) ||
- ( ! isGoogleMCReady && hasExistingGoogleMCAccounts );
-
- const hasExistingGoogleAdsAccounts = existingGoogleAdsAccounts?.length > 0;
- const showConnectAds =
- ( ( editMode && hasExistingGoogleAdsAccounts ) ||
- ( ! isConnected && hasExistingGoogleAdsAccounts ) ) &&
- ! shouldClaimGoogleAdsAccount;
+ const getCardActions = () => {
+ if ( editMode ) {
+ return (
+
+ { switchAccountButton }
+
+ { __( 'Cancel', 'google-listings-and-ads' ) }
+
+
+ );
+ }
+
+ // When not in edit mode, only show the edit button if clicking the
+ // button would change the visibility of the ConnectAds or ConnectMC cards.
+ return (
+
+ { ( showConnectAds || ! canShowConnectAds ) &&
+ ( showConnectMC || ! canShowConnectMC ) ? (
+ switchAccountButton
+ ) : (
+
+ ) }
+
+ );
+ };
// Show the spinner if there's an account creation in progress and account should not be claimed.
// If we are not showing the ConnectMC screen, for e.g when we are creating the first account,
@@ -100,15 +179,16 @@ const ConnectedGoogleComboAccountCard = () => {
const showConversionMeasurementNotice =
showAdsConversionNotice( googleAdsAccount );
- const showAddressCard = hasFinishedResolution && hasGoogleMCConnection;
+ const showAddressCard = hasFinishedResolution && isGoogleMCReady;
return (
-
+
}
+ actions={ getCardActions() }
helper={ subText }
indicator={
}
detail={
diff --git a/js/src/components/google-combo-account-card/connected-google-combo-account-card.scss b/js/src/components/google-combo-account-card/connected-google-combo-account-card.scss
index 9c65e53adf..2fa9639124 100644
--- a/js/src/components/google-combo-account-card/connected-google-combo-account-card.scss
+++ b/js/src/components/google-combo-account-card/connected-google-combo-account-card.scss
@@ -1,6 +1,26 @@
+.gla-google-combo-account-card-wrapper {
+
+ .components-card {
+ &:not(:last-child) {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+
+ &:not(:first-child) {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+ }
+}
+
.gla-google-combo-account-card--connected {
.gla-account-card__description {
gap: 0;
}
+
+ .gla-google-combo-account-card__description-actions {
+ display: flex;
+ gap: var(--large-gap);
+ }
}
diff --git a/js/src/components/google-mc-account-card/connect-mc/actions.js b/js/src/components/google-mc-account-card/connect-mc/actions.js
index 30517e839c..c6932ae911 100644
--- a/js/src/components/google-mc-account-card/connect-mc/actions.js
+++ b/js/src/components/google-mc-account-card/connect-mc/actions.js
@@ -8,6 +8,7 @@ import { __ } from '@wordpress/i18n';
*/
import CreateAccountButton from '../create-account-button';
import DisconnectAccountButton from '../disconnect-account-button';
+import useExistingGoogleMCAccounts from '.~/hooks/useExistingGoogleMCAccounts';
/**
* Actions component.
@@ -27,7 +28,9 @@ const Actions = ( {
resultCreateAccount,
onCreateAccount,
} ) => {
- if ( isConnected ) {
+ const { data: existingGoogleMCAccounts } = useExistingGoogleMCAccounts();
+
+ if ( isConnected && existingGoogleMCAccounts.length > 0 ) {
const handleOnDisconnected = () => {
resultConnectMC.reset();
resultCreateAccount.reset();
diff --git a/js/src/components/google-mc-account-card/connect-mc/index.js b/js/src/components/google-mc-account-card/connect-mc/index.js
index d8eb62d52e..751dbf46c3 100644
--- a/js/src/components/google-mc-account-card/connect-mc/index.js
+++ b/js/src/components/google-mc-account-card/connect-mc/index.js
@@ -52,13 +52,14 @@ const ConnectMC = ( { createAccount, resultCreateAccount, className } ) => {
googleMCAccount,
hasFinishedResolution,
isReady: isGoogleMCReady,
+ hasGoogleMCConnection,
} = useGoogleMCAccount();
useEffect( () => {
- if ( isGoogleMCReady ) {
+ if ( hasGoogleMCConnection ) {
setValue( googleMCAccount.id );
}
- }, [ googleMCAccount, isGoogleMCReady ] );
+ }, [ googleMCAccount, hasGoogleMCConnection ] );
if ( ! isGoogleMCReady ) {
if ( resultConnectMC.response?.status === 409 ) {
@@ -152,14 +153,14 @@ const ConnectMC = ( { createAccount, resultCreateAccount, className } ) => {
indicator={ getIndicator() }
detail={
}
actions={
{
{
data: { id },
} );
const homeUrl = getSetting( 'homeUrl' );
+ const { data: existingGoogleMCAccounts } = useExistingGoogleMCAccounts();
+ const hasExistingGoogleMCAccounts = existingGoogleMCAccounts?.length > 0;
const handleReclaimClick = async () => {
reset();
@@ -65,17 +68,19 @@ const ReclaimUrlCard = ( { id, websiteUrl, onSwitchAccount = noop } ) => {
id
) }
indicator={
-
- { __( 'Switch account', 'google-listings-and-ads' ) }
-
+ hasExistingGoogleMCAccounts ? (
+
+ { __( 'Switch account', 'google-listings-and-ads' ) }
+
+ ) : null
}
>
diff --git a/js/src/hooks/useGoogleMCAccount.js b/js/src/hooks/useGoogleMCAccount.js
index 22a4ecbc24..aabc752315 100644
--- a/js/src/hooks/useGoogleMCAccount.js
+++ b/js/src/hooks/useGoogleMCAccount.js
@@ -59,10 +59,12 @@ const useGoogleMCAccount = () => {
googleMCAccountSelector
);
- const hasGoogleMCConnection = [
- GOOGLE_MC_ACCOUNT_STATUS.CONNECTED,
- GOOGLE_MC_ACCOUNT_STATUS.INCOMPLETE,
- ].includes( acc?.status );
+ const hasGoogleMCConnection =
+ Boolean( acc?.id ) &&
+ [
+ GOOGLE_MC_ACCOUNT_STATUS.CONNECTED,
+ GOOGLE_MC_ACCOUNT_STATUS.INCOMPLETE,
+ ].includes( acc?.status );
const isReady =
acc?.status === GOOGLE_MC_ACCOUNT_STATUS.CONNECTED ||
diff --git a/tests/e2e/global-setup.js b/tests/e2e/global-setup.js
index 26870e5aaa..6cf36e5a4a 100644
--- a/tests/e2e/global-setup.js
+++ b/tests/e2e/global-setup.js
@@ -55,7 +55,7 @@ module.exports = async ( config ) => {
await adminPage
.locator( 'input[name="pwd"]' )
.fill( admin.password );
- await adminPage.locator( 'text=Log In' ).click();
+ await adminPage.getByRole( 'button', { name: 'Log In' } ).click();
await adminPage.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
await adminPage.goto( `/wp-admin` );
await adminPage.waitForLoadState( LOAD_STATE.DOM_CONTENT_LOADED );
diff --git a/tests/e2e/specs/setup-mc/step-1-accounts.test.js b/tests/e2e/specs/setup-mc/step-1-accounts.test.js
index a8bc132a12..0abda36ce5 100644
--- a/tests/e2e/specs/setup-mc/step-1-accounts.test.js
+++ b/tests/e2e/specs/setup-mc/step-1-accounts.test.js
@@ -233,9 +233,11 @@ test.describe( 'Set up accounts', () => {
await once.mockAdsHasNoAccounts();
await once.mockMCHasNoAccounts();
await once.mockAdsAccountDisconnected();
+ await once.mockAdsStatusDisconnected();
await once.mockMCNotConnected();
await setUpAccountsPage.goto();
+
const googleAccountCard = setUpAccountsPage.getGoogleAccountCard();
await expect(
@@ -248,6 +250,37 @@ test.describe( 'Set up accounts', () => {
).toBeVisible();
} );
+ test( 'should show Ads claim and MC Reclaim after auto-creation, when appropriate', async () => {
+ await setUpAccountsPage.mockJetpackConnected();
+ await setUpAccountsPage.mockGoogleConnected();
+ await setUpAccountsPage.mockMCCreateAccountWebsiteClaimed();
+ await setUpAccountsPage.mockAdsCreateAccount();
+ await setUpAccountsPage.mockAdsAccountIncomplete( 'claim_account' );
+ await setUpAccountsPage.mockAdsStatusNotClaimed();
+
+ const once = setUpAccountsPage.fulfillTimes( 1 );
+
+ await once.mockAdsHasNoAccounts();
+ await once.mockMCHasNoAccounts();
+ await once.mockAdsAccountDisconnected();
+ await once.mockAdsStatusDisconnected();
+ await once.mockMCNotConnected();
+
+ await setUpAccountsPage.goto();
+
+ const googleAccountCard = setUpAccountsPage.getGoogleAccountCard();
+ const googleMCReclaimButton =
+ setUpAccountsPage.getReclaimMyURLButton();
+
+ await expect(
+ googleAccountCard.getByRole( 'button', {
+ name: 'Claim account in Google Ads',
+ } )
+ ).toBeVisible();
+
+ await expect( googleMCReclaimButton ).toBeVisible();
+ } );
+
test.describe( 'After connecting Google account', () => {
test.beforeEach( async () => {
await setUpAccountsPage.mockJetpackConnected();
@@ -886,6 +919,7 @@ test.describe( 'Set up accounts', () => {
test.beforeAll( async () => {
await setUpAccountsPage.mockAdsAccountConnected();
await setUpAccountsPage.mockMCConnected();
+ await setUpAccountsPage.mockAdsStatusClaimed();
await setUpAccountsPage.mockContactInformation( {} );
await setUpAccountsPage.goto();
@@ -921,6 +955,142 @@ test.describe( 'Set up accounts', () => {
} );
} );
+ test.describe( 'Edit button', () => {
+ test.beforeAll( async () => {
+ await setUpAccountsPage.mockJetpackConnected();
+ await setUpAccountsPage.mockGoogleConnected();
+ await setUpAccountsPage.mockMCConnected();
+ await setUpAccountsPage.mockMCHasAccounts();
+ await setUpAccountsPage.mockContactInformation();
+ await setUpAccountsPage.mockAdsAccountConnected();
+ await setUpAccountsPage.fulfillAdsAccounts( ADS_ACCOUNTS );
+ await setUpAccountsPage.mockAdsStatusClaimed();
+ await setUpAccountsPage.goto();
+ } );
+
+ test( 'should display the Edit button and the Ads and MC account cards are not visible', async () => {
+ const editButton = setUpAccountsPage.getEditButton();
+ await expect( editButton ).toBeVisible();
+
+ const googleMcAccountCard = setUpAccountsPage.getMCAccountCard();
+ await expect( googleMcAccountCard ).not.toBeVisible();
+
+ const googleAdsAccountCard =
+ setUpAccountsPage.getGoogleAdsAccountCard();
+ await expect( googleAdsAccountCard ).not.toBeVisible();
+ } );
+
+ test.describe( 'clicking the Edit button', async () => {
+ test( 'the "Or, connect to a different Google account" button is visible', async () => {
+ const editButton = setUpAccountsPage.getEditButton();
+ await editButton.click();
+
+ const connectDifferentGoogleAccountButton =
+ setUpAccountsPage.getConnectDifferentGoogleAccountButton();
+ await expect(
+ connectDifferentGoogleAccountButton
+ ).toBeVisible();
+ } );
+
+ test( 'the "Cancel" button is visible', async () => {
+ const cancelButton = setUpAccountsPage.getCancelButton();
+ await expect( cancelButton ).toBeVisible();
+ } );
+
+ test( 'MC and Ads account cards are visible', async () => {
+ const googleMcAccountCard =
+ setUpAccountsPage.getMCAccountCard();
+ await expect( googleMcAccountCard ).toBeVisible();
+
+ const googleAdsAccountCard =
+ setUpAccountsPage.getGoogleAdsAccountCard();
+ await expect( googleAdsAccountCard ).toBeVisible();
+ } );
+ } );
+
+ test.describe( 'clicking the Cancel button', async () => {
+ test( 'the "Edit" button is visible', async () => {
+ const cancelButton = setUpAccountsPage.getCancelButton();
+ await cancelButton.click();
+
+ const editButton = setUpAccountsPage.getEditButton();
+ await expect( editButton ).toBeVisible();
+ } );
+
+ test( 'MC and Ads account cards are not visible', async () => {
+ const googleMcAccountCard =
+ setUpAccountsPage.getMCAccountCard();
+ await expect( googleMcAccountCard ).not.toBeVisible();
+
+ const googleAdsAccountCard =
+ setUpAccountsPage.getGoogleAdsAccountCard();
+ await expect( googleAdsAccountCard ).not.toBeVisible();
+ } );
+ } );
+
+ test.describe( 'clicking "Edit" when an Ads account is being claimed', async () => {
+ test( 'should let you connect to a different account', async () => {
+ await setUpAccountsPage.mockAdsStatusNotClaimed();
+ await setUpAccountsPage.mockAdsAccountIncomplete(
+ 'claim_account'
+ );
+
+ await setUpAccountsPage.goto();
+
+ const editButton = setUpAccountsPage.getEditButton();
+ await editButton.click();
+
+ const connectDifferentGoogleAdsAccountButton =
+ setUpAccountsPage.getConnectDifferentAdsAccountButton();
+
+ await expect(
+ connectDifferentGoogleAdsAccountButton
+ ).toBeVisible();
+ } );
+
+ test( 'should disable the create new account button if there are no other existing accounts', async () => {
+ await setUpAccountsPage.mockAdsStatusNotClaimed();
+ await setUpAccountsPage.mockAdsHasNoAccounts();
+ await setUpAccountsPage.mockAdsAccountIncomplete(
+ 'claim_account'
+ );
+
+ await setUpAccountsPage.goto();
+
+ const editButton = setUpAccountsPage.getEditButton();
+ await editButton.click();
+
+ const createNewAdsAccountButton =
+ setUpAccountsPage.getCreateNewAdsAccountButton();
+
+ await expect( createNewAdsAccountButton ).toBeDisabled();
+ } );
+ } );
+
+ test.describe( 'clicking "Edit" when there are no other existing accounts', async () => {
+ test( 'should let you create new accounts', async () => {
+ await setUpAccountsPage.mockAdsStatusClaimed();
+ await setUpAccountsPage.mockAdsAccountConnected();
+ await setUpAccountsPage.mockAdsHasNoAccounts();
+ await setUpAccountsPage.mockMCHasNoAccounts();
+ await setUpAccountsPage.mockMCConnected();
+
+ await setUpAccountsPage.goto();
+
+ const editButton = setUpAccountsPage.getEditButton();
+ await editButton.click();
+
+ const createNewAdsAccountButton =
+ setUpAccountsPage.getCreateNewAdsAccountButton();
+ const createNewMCAccountButton =
+ setUpAccountsPage.getCreateNewMCAccountButton();
+
+ await expect( createNewAdsAccountButton ).toBeVisible();
+ await expect( createNewMCAccountButton ).toBeVisible();
+ } );
+ } );
+ } );
+
test.describe( 'Links', () => {
test( 'should contain the correct URL for "Google Merchant Center Help" link', async () => {
await setUpAccountsPage.goto();
diff --git a/tests/e2e/utils/mock-requests.js b/tests/e2e/utils/mock-requests.js
index 29306ad059..a1f7d52869 100644
--- a/tests/e2e/utils/mock-requests.js
+++ b/tests/e2e/utils/mock-requests.js
@@ -241,10 +241,17 @@ export default class MockRequests {
* Fulfill the Ads Account request.
*
* @param {Object} payload
+ * @param {number} [status=200]
+ * @param {string[]} [methods]
* @return {Promise}
*/
- async fulfillAdsAccounts( payload ) {
- await this.fulfillRequest( /\/wc\/gla\/ads\/accounts\b/, payload );
+ async fulfillAdsAccounts( payload, status = 200, methods ) {
+ await this.fulfillRequest(
+ /\/wc\/gla\/ads\/accounts\b/,
+ payload,
+ status,
+ methods
+ );
}
/**
@@ -525,13 +532,13 @@ export default class MockRequests {
*
* @return {Promise}
*/
- async mockAdsAccountIncomplete() {
+ async mockAdsAccountIncomplete( step = 'billing' ) {
await this.fulfillAdsConnection( {
id: 12345,
currency: 'TWD',
symbol: 'NT$',
status: 'incomplete',
- step: 'billing',
+ step,
} );
}
@@ -550,6 +557,23 @@ export default class MockRequests {
} );
}
+ /**
+ * Mock Ads create account.
+ *
+ * @return {Promise}
+ */
+ async mockAdsCreateAccount() {
+ await this.fulfillAdsAccounts(
+ {
+ has_access: false,
+ invite_link: 'https://test.com',
+ step: 'claim_account',
+ },
+ 200,
+ [ 'POST' ]
+ );
+ }
+
/**
* Mock the Ads accounts response.
*
diff --git a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js
index 4411e96f63..c11ea2caf3 100644
--- a/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js
+++ b/tests/e2e/utils/pages/setup-mc/step-1-set-up-accounts.js
@@ -299,6 +299,18 @@ export default class SetUpAccountsPage extends MockRequests {
return this.getMCCardFooter().getByRole( 'button' );
}
+ /**
+ * Get create a new Merchant Center account button.
+ *
+ * @return {import('@playwright/test').Locator} Get create a new Merchant Center account button.
+ */
+ getCreateNewMCAccountButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Or, create a new Merchant Center account',
+ exact: true,
+ } );
+ }
+
/**
* Get "Continue" button.
*
@@ -383,6 +395,30 @@ export default class SetUpAccountsPage extends MockRequests {
);
}
+ /**
+ * Get connect to a different Google Ads account button.
+ *
+ * @return {import('@playwright/test').Locator} Get connect to a different Google Ads account button.
+ */
+ getConnectDifferentAdsAccountButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Or, connect to a different Google Ads account',
+ exact: true,
+ } );
+ }
+
+ /**
+ * Get create a new Google Ads account button.
+ *
+ * @return {import('@playwright/test').Locator} Get create a new Google Ads account button.
+ */
+ getCreateNewAdsAccountButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Or, create a new Google Ads account',
+ exact: true,
+ } );
+ }
+
/**
* Get terms checkbox.
*
@@ -392,6 +428,18 @@ export default class SetUpAccountsPage extends MockRequests {
return this.page.getByLabel( /I accept the terms and conditions/ );
}
+ /**
+ * Get "Edit" button.
+ *
+ * @return {import('@playwright/test').Locator} Get "Edit" button.
+ */
+ getEditButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Edit',
+ exact: true,
+ } );
+ }
+
/**
* Get store address card.
*
@@ -414,6 +462,30 @@ export default class SetUpAccountsPage extends MockRequests {
}
/**
+ * Get "Or, connect to a different Google account" button.
+ *
+ * @return {import('@playwright/test').Locator} Get "Or, connect to a different Google account" button.
+ */
+ getConnectDifferentGoogleAccountButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Or, connect to a different Google account',
+ exact: true,
+ } );
+ }
+
+ /**
+ * Get "Cancel" button.
+ *
+ * @return {import('@playwright/test').Locator} Get "Cancel" button.
+ */
+ getCancelButton() {
+ return this.page.getByRole( 'button', {
+ name: 'Cancel',
+ exact: true,
+ } );
+ }
+
+ /*
* Register the response when connecting an Ads account.
*
* @return {Promise} The response.