Fix php notice when selecting paid theme (https://github.com/woocommerce/woocommerce-admin/pull/8493)
* Add initial E2E tests for purchase task * Update paid theme logic to remove PHP warning and keep the correct price * Fix php unit tests * Address some PR feedback * Add changelog * Include the purchase task e2e test * Disable test * Delete purchase E2E test file
This commit is contained in:
parent
c05605fddf
commit
4bff4d1302
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: Fix
|
||||||
|
|
||||||
|
Fix handling of paid themes in purchase task. #8493
|
|
@ -92,12 +92,10 @@ class Theme extends Component {
|
||||||
location,
|
location,
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) {
|
if ( slug !== activeTheme && isInstalled ) {
|
||||||
if ( isInstalled ) {
|
this.activateTheme( slug );
|
||||||
this.activateTheme( slug );
|
} else if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) {
|
||||||
} else {
|
this.installTheme( slug );
|
||||||
this.installTheme( slug );
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updateProfileItems( { theme: slug } );
|
updateProfileItems( { theme: slug } );
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,16 @@ import { Page } from 'puppeteer';
|
||||||
import { BusinessSection } from '../sections/onboarding/BusinessSection';
|
import { BusinessSection } from '../sections/onboarding/BusinessSection';
|
||||||
import { IndustrySection } from '../sections/onboarding/IndustrySection';
|
import { IndustrySection } from '../sections/onboarding/IndustrySection';
|
||||||
import { ProductTypeSection } from '../sections/onboarding/ProductTypesSection';
|
import { ProductTypeSection } from '../sections/onboarding/ProductTypesSection';
|
||||||
import { StoreDetailsSection } from '../sections/onboarding/StoreDetailsSection';
|
import {
|
||||||
|
StoreDetails,
|
||||||
|
StoreDetailsSection,
|
||||||
|
} from '../sections/onboarding/StoreDetailsSection';
|
||||||
import { ThemeSection } from '../sections/onboarding/ThemeSection';
|
import { ThemeSection } from '../sections/onboarding/ThemeSection';
|
||||||
import { BasePage } from './BasePage';
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
const { expect } = require( '@jest/globals' );
|
const { expect } = require( '@jest/globals' );
|
||||||
|
const config = require( 'config' );
|
||||||
|
|
||||||
export class OnboardingWizard extends BasePage {
|
export class OnboardingWizard extends BasePage {
|
||||||
url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
|
url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
|
||||||
|
@ -79,4 +83,75 @@ export class OnboardingWizard extends BasePage {
|
||||||
async goToOBWStep( step: string ): Promise< void > {
|
async goToOBWStep( step: string ): Promise< void > {
|
||||||
await this.clickElementWithText( 'span', step );
|
await this.clickElementWithText( 'span', step );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async walkThroughAndCompleteOnboardingWizard(
|
||||||
|
options: {
|
||||||
|
storeDetails?: StoreDetails;
|
||||||
|
industries?: string[];
|
||||||
|
products?: string[];
|
||||||
|
businessDetails?: {
|
||||||
|
productNumber: string;
|
||||||
|
currentlySelling: string;
|
||||||
|
};
|
||||||
|
themeTitle?: string;
|
||||||
|
} = {}
|
||||||
|
): Promise< void > {
|
||||||
|
await this.navigate();
|
||||||
|
await this.storeDetails.completeStoreDetailsSection(
|
||||||
|
options.storeDetails
|
||||||
|
);
|
||||||
|
|
||||||
|
// Wait for "Continue" button to become active
|
||||||
|
await this.continue();
|
||||||
|
|
||||||
|
// Wait for usage tracking pop-up window to appear
|
||||||
|
await this.optionallySelectUsageTracking();
|
||||||
|
// Query for the industries checkboxes
|
||||||
|
await this.industry.isDisplayed();
|
||||||
|
const industries = options.industries || [ 'Other' ];
|
||||||
|
for ( const industry of industries ) {
|
||||||
|
await this.industry.selectIndustry( industry );
|
||||||
|
}
|
||||||
|
await this.continue();
|
||||||
|
await this.productTypes.isDisplayed( 7 );
|
||||||
|
const products = options.products || [
|
||||||
|
'Physical products',
|
||||||
|
'Downloads',
|
||||||
|
];
|
||||||
|
for ( const product of products ) {
|
||||||
|
await this.productTypes.selectProduct( product );
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.continue();
|
||||||
|
await page.waitForNavigation( {
|
||||||
|
waitUntil: 'networkidle0',
|
||||||
|
} );
|
||||||
|
await this.business.isDisplayed();
|
||||||
|
|
||||||
|
const businessDetails = options.businessDetails || {
|
||||||
|
productNumber: config.get( 'onboardingwizard.numberofproducts' ),
|
||||||
|
currentlySelling: config.get( 'onboardingwizard.sellingelsewhere' ),
|
||||||
|
};
|
||||||
|
await this.business.selectProductNumber(
|
||||||
|
businessDetails.productNumber
|
||||||
|
);
|
||||||
|
await this.business.selectCurrentlySelling(
|
||||||
|
businessDetails.currentlySelling
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.continue();
|
||||||
|
await this.business.freeFeaturesIsDisplayed();
|
||||||
|
await this.business.expandRecommendedBusinessFeatures();
|
||||||
|
await this.business.uncheckAllRecommendedBusinessFeatures();
|
||||||
|
|
||||||
|
await this.continue();
|
||||||
|
await this.themes.isDisplayed();
|
||||||
|
|
||||||
|
// This navigates to the home screen
|
||||||
|
if ( options.themeTitle ) {
|
||||||
|
await this.themes.continueWithTheme( options.themeTitle );
|
||||||
|
} else {
|
||||||
|
await this.themes.continueWithActiveTheme();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const {
|
||||||
const config = require( 'config' );
|
const config = require( 'config' );
|
||||||
/* eslint-enable @typescript-eslint/no-var-requires */
|
/* eslint-enable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
interface StoreDetails {
|
export interface StoreDetails {
|
||||||
addressLine1?: string;
|
addressLine1?: string;
|
||||||
addressLine2?: string;
|
addressLine2?: string;
|
||||||
countryRegionSubstring?: string;
|
countryRegionSubstring?: string;
|
||||||
|
|
|
@ -13,4 +13,17 @@ export class ThemeSection extends BasePage {
|
||||||
async continueWithActiveTheme(): Promise< void > {
|
async continueWithActiveTheme(): Promise< void > {
|
||||||
await this.clickButtonWithText( 'Continue with my active theme' );
|
await this.clickButtonWithText( 'Continue with my active theme' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async continueWithTheme( themeTitle: string ): Promise< void > {
|
||||||
|
const title = await waitForElementByText( 'h2', themeTitle );
|
||||||
|
const card = await title?.evaluateHandle( ( element ) => {
|
||||||
|
return element.closest( '.components-card' );
|
||||||
|
} );
|
||||||
|
const chooseButton = await card
|
||||||
|
?.asElement()
|
||||||
|
?.$x( `//button[contains(text(), "Choose")]` );
|
||||||
|
if ( chooseButton && chooseButton.length > 0 ) {
|
||||||
|
await chooseButton[ 0 ].click();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,6 @@ export * from './analytics/analytics';
|
||||||
export * from './analytics/analytics-overview';
|
export * from './analytics/analytics-overview';
|
||||||
export * from './marketing/coupons';
|
export * from './marketing/coupons';
|
||||||
export * from './tasks/payment';
|
export * from './tasks/payment';
|
||||||
|
export * from './tasks/purchase';
|
||||||
export * from './homescreen/task-list';
|
export * from './homescreen/task-list';
|
||||||
export * from './homescreen/activity-panel';
|
export * from './homescreen/activity-panel';
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { resetWooCommerceState } from '../../fixtures';
|
||||||
|
import { Login } from '../../pages/Login';
|
||||||
|
import { OnboardingWizard } from '../../pages/OnboardingWizard';
|
||||||
|
import { WcHomescreen } from '../../pages/WcHomescreen';
|
||||||
|
import { getElementByText, waitForElementByText } from '../../utils/actions';
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
const { afterAll, beforeAll, describe, it } = require( '@jest/globals' );
|
||||||
|
/* eslint-enable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
|
const testAdminPurchaseSetupTask = () => {
|
||||||
|
describe( 'Purchase setup task', () => {
|
||||||
|
const profileWizard = new OnboardingWizard( page );
|
||||||
|
const homeScreen = new WcHomescreen( page );
|
||||||
|
const login = new Login( page );
|
||||||
|
|
||||||
|
beforeAll( async () => {
|
||||||
|
await login.login();
|
||||||
|
} );
|
||||||
|
|
||||||
|
afterAll( async () => {
|
||||||
|
await login.logout();
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'selecting paid product', () => {
|
||||||
|
beforeAll( async () => {
|
||||||
|
await resetWooCommerceState();
|
||||||
|
|
||||||
|
await profileWizard.navigate();
|
||||||
|
await profileWizard.walkThroughAndCompleteOnboardingWizard( {
|
||||||
|
products: [ 'Memberships' ],
|
||||||
|
} );
|
||||||
|
|
||||||
|
await homeScreen.isDisplayed();
|
||||||
|
await homeScreen.possiblyDismissWelcomeModal();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should display add <product name> to my store task', async () => {
|
||||||
|
expect(
|
||||||
|
await getElementByText( '*', 'Add Memberships to my store' )
|
||||||
|
).toBeDefined();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should show paid features modal with option to buy now', async () => {
|
||||||
|
const task = await getElementByText(
|
||||||
|
'*',
|
||||||
|
'Add Memberships to my store'
|
||||||
|
);
|
||||||
|
await task?.click();
|
||||||
|
await waitForElementByText(
|
||||||
|
'h1',
|
||||||
|
'Would you like to add the following paid features to your store now?'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await getElementByText( 'button', 'Buy now' )
|
||||||
|
).toBeDefined();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
describe( 'selecting paid theme', () => {
|
||||||
|
beforeAll( async () => {
|
||||||
|
await resetWooCommerceState();
|
||||||
|
|
||||||
|
await profileWizard.navigate();
|
||||||
|
await profileWizard.walkThroughAndCompleteOnboardingWizard( {
|
||||||
|
themeTitle: 'Blooms',
|
||||||
|
} );
|
||||||
|
|
||||||
|
await homeScreen.isDisplayed();
|
||||||
|
await homeScreen.possiblyDismissWelcomeModal();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should display add <theme name> to my store task', async () => {
|
||||||
|
expect(
|
||||||
|
await getElementByText( '*', 'Add Blooms to my store' )
|
||||||
|
).toBeDefined();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should show paid features modal with option to buy now', async () => {
|
||||||
|
const task = await getElementByText(
|
||||||
|
'*',
|
||||||
|
'Add Blooms to my store'
|
||||||
|
);
|
||||||
|
await task?.click();
|
||||||
|
await waitForElementByText(
|
||||||
|
'h1',
|
||||||
|
'Would you like to add the following paid features to your store now?'
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
await getElementByText( 'button', 'Buy now' )
|
||||||
|
).toBeDefined();
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { testAdminPurchaseSetupTask };
|
|
@ -60,7 +60,7 @@ class OnboardingThemes {
|
||||||
$themes = self::get_themes();
|
$themes = self::get_themes();
|
||||||
$theme_key = array_search( $slug, array_column( $themes, 'slug' ), true );
|
$theme_key = array_search( $slug, array_column( $themes, 'slug' ), true );
|
||||||
$theme = false !== $theme_key ? $themes[ $theme_key ] : null;
|
$theme = false !== $theme_key ? $themes[ $theme_key ] : null;
|
||||||
if ( $theme && isset( $theme['id'] ) && isset( $theme['price'] ) && ( ! isset( $theme['is_installed'] ) || ! $theme['is_installed'] ) ) {
|
if ( $theme && isset( $theme['id'] ) && isset( $theme['price'] ) ) {
|
||||||
$price = self::get_price_from_string( $theme['price'] );
|
$price = self::get_price_from_string( $theme['price'] );
|
||||||
if ( $price && $price > 0 ) {
|
if ( $price && $price > 0 ) {
|
||||||
return $themes[ $theme_key ];
|
return $themes[ $theme_key ];
|
||||||
|
@ -123,8 +123,13 @@ class OnboardingThemes {
|
||||||
$active_theme = get_option( 'stylesheet' );
|
$active_theme = get_option( 'stylesheet' );
|
||||||
|
|
||||||
foreach ( $installed_themes as $slug => $theme ) {
|
foreach ( $installed_themes as $slug => $theme ) {
|
||||||
$theme_data = self::get_theme_data( $theme );
|
$theme_data = self::get_theme_data( $theme );
|
||||||
$themes[ $slug ] = $theme_data;
|
if ( isset( $themes[ $slug ] ) ) {
|
||||||
|
$themes[ $slug ]['is_installed'] = true;
|
||||||
|
$themes[ $slug ]['image'] = $theme_data['image'];
|
||||||
|
} else {
|
||||||
|
$themes[ $slug ] = $theme_data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the WooCommerce support tag for default themes that don't explicitly declare support.
|
// Add the WooCommerce support tag for default themes that don't explicitly declare support.
|
||||||
|
|
|
@ -6,7 +6,6 @@ use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
|
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
|
||||||
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
|
||||||
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
|
||||||
use Automattic\WooCommerce\Admin\PluginsHelper;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Purchase Task
|
* Purchase Task
|
||||||
|
@ -55,7 +54,7 @@ class Purchase extends Task {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_title() {
|
public function get_title() {
|
||||||
$products = $this->get_products();
|
$products = $this->get_paid_products_and_themes();
|
||||||
|
|
||||||
return count( $products['remaining'] ) === 1
|
return count( $products['remaining'] ) === 1
|
||||||
? sprintf(
|
? sprintf(
|
||||||
|
@ -78,19 +77,20 @@ class Purchase extends Task {
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function get_content() {
|
public function get_content() {
|
||||||
$products = $this->get_products();
|
$products = $this->get_paid_products_and_themes();
|
||||||
|
|
||||||
return count( $products['remaining'] ) === 1
|
if ( count( $products['remaining'] ) === 1 ) {
|
||||||
? $products['purchaseable'][0]['description']
|
return isset( $products['purchaseable'][0]['description'] ) ? $products['purchaseable'][0]['description'] : $products['purchaseable'][0]['excerpt'];
|
||||||
: sprintf(
|
}
|
||||||
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
|
return sprintf(
|
||||||
__(
|
/* translators: %1$s: list of product names comma separated, %2%s the last product name */
|
||||||
'Good choice! You chose to add %1$s and %2$s to your store.',
|
__(
|
||||||
'woocommerce-admin'
|
'Good choice! You chose to add %1$s and %2$s to your store.',
|
||||||
),
|
'woocommerce-admin'
|
||||||
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
|
),
|
||||||
end( $products['remaining'] )
|
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
|
||||||
);
|
end( $products['remaining'] )
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,7 +118,7 @@ class Purchase extends Task {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function is_complete() {
|
public function is_complete() {
|
||||||
$products = $this->get_products();
|
$products = $this->get_paid_products_and_themes();
|
||||||
return count( $products['remaining'] ) === 0;
|
return count( $products['remaining'] ) === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class Purchase extends Task {
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function can_view() {
|
public function can_view() {
|
||||||
$products = $this->get_products();
|
$products = $this->get_paid_products_and_themes();
|
||||||
return count( $products['purchaseable'] ) > 0;
|
return count( $products['purchaseable'] ) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,18 +146,17 @@ class Purchase extends Task {
|
||||||
*
|
*
|
||||||
* @return array purchaseable and remaining products and themes.
|
* @return array purchaseable and remaining products and themes.
|
||||||
*/
|
*/
|
||||||
public static function get_products() {
|
public static function get_paid_products_and_themes() {
|
||||||
$relevant_products = OnboardingProducts::get_relevant_products();
|
$relevant_products = OnboardingProducts::get_relevant_products();
|
||||||
|
|
||||||
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
|
||||||
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
|
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
|
||||||
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
|
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
|
||||||
$installed = PluginsHelper::get_installed_plugin_slugs();
|
|
||||||
if ( $paid_theme ) {
|
if ( $paid_theme ) {
|
||||||
|
|
||||||
$relevant_products['purchaseable'][] = $paid_theme;
|
$relevant_products['purchaseable'][] = $paid_theme;
|
||||||
|
|
||||||
if ( ! in_array( $paid_theme['slug'], $installed, true ) ) {
|
if ( isset( $paid_theme['is_installed'] ) && false === $paid_theme['is_installed'] ) {
|
||||||
$relevant_products['remaining'][] = $paid_theme['title'];
|
$relevant_products['remaining'][] = $paid_theme['title'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,16 @@ class WC_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
||||||
set_transient(
|
set_transient(
|
||||||
OnboardingThemes::THEMES_TRANSIENT,
|
OnboardingThemes::THEMES_TRANSIENT,
|
||||||
array(
|
array(
|
||||||
'free' => array( 'slug' => 'free' ),
|
'free' => array(
|
||||||
|
'slug' => 'free',
|
||||||
|
'is_installed' => false,
|
||||||
|
),
|
||||||
'paid' => array(
|
'paid' => array(
|
||||||
'slug' => 'paid',
|
'slug' => 'paid',
|
||||||
'id' => 12312,
|
'id' => 12312,
|
||||||
'price' => '$79.00',
|
'price' => '$79.00',
|
||||||
'title' => 'theme title',
|
'title' => 'theme title',
|
||||||
|
'is_installed' => false,
|
||||||
),
|
),
|
||||||
'paid_installed' => array(
|
'paid_installed' => array(
|
||||||
'slug' => 'paid_installed',
|
'slug' => 'paid_installed',
|
||||||
|
@ -47,10 +51,11 @@ class WC_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
|
||||||
'is_installed' => true,
|
'is_installed' => true,
|
||||||
),
|
),
|
||||||
'free_with_price' => array(
|
'free_with_price' => array(
|
||||||
'slug' => 'free_with_price',
|
'slug' => 'free_with_price',
|
||||||
'id' => 12312,
|
'id' => 12312,
|
||||||
'price' => '$0.00',
|
'price' => '$0.00',
|
||||||
'title' => 'theme title',
|
'title' => 'theme title',
|
||||||
|
'is_installed' => false,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue