Skip to content
This repository has been archived by the owner on Jul 12, 2024. It is now read-only.

Commit

Permalink
Fix php notice when selecting paid theme (#8493)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
louwie17 authored Mar 18, 2022
1 parent 661dd8d commit 4ed0fd6
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 39 deletions.
4 changes: 4 additions & 0 deletions changelogs/fix-8478_php_notice_purchase_task
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: Fix

Fix handling of paid themes in purchase task. #8493
10 changes: 4 additions & 6 deletions client/profile-wizard/steps/theme/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,10 @@ class Theme extends Component {
location,
} );

if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) {
if ( isInstalled ) {
this.activateTheme( slug );
} else {
this.installTheme( slug );
}
if ( slug !== activeTheme && isInstalled ) {
this.activateTheme( slug );
} else if ( slug !== activeTheme && getPriceValue( price ) <= 0 ) {
this.installTheme( slug );
} else {
updateProfileItems( { theme: slug } );
}
Expand Down
77 changes: 76 additions & 1 deletion packages/admin-e2e-tests/src/pages/OnboardingWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import { Page } from 'puppeteer';
import { BusinessSection } from '../sections/onboarding/BusinessSection';
import { IndustrySection } from '../sections/onboarding/IndustrySection';
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 { BasePage } from './BasePage';

/* eslint-disable @typescript-eslint/no-var-requires */
const { expect } = require( '@jest/globals' );
const config = require( 'config' );

export class OnboardingWizard extends BasePage {
url = 'wp-admin/admin.php?page=wc-admin&path=/setup-wizard';
Expand Down Expand Up @@ -79,4 +83,75 @@ export class OnboardingWizard extends BasePage {
async goToOBWStep( step: string ): Promise< void > {
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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const {
const config = require( 'config' );
/* eslint-enable @typescript-eslint/no-var-requires */

interface StoreDetails {
export interface StoreDetails {
addressLine1?: string;
addressLine2?: string;
countryRegionSubstring?: string;
Expand Down
13 changes: 13 additions & 0 deletions packages/admin-e2e-tests/src/sections/onboarding/ThemeSection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,17 @@ export class ThemeSection extends BasePage {
async continueWithActiveTheme(): Promise< void > {
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();
}
}
}
1 change: 1 addition & 0 deletions packages/admin-e2e-tests/src/specs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export * from './analytics/analytics';
export * from './analytics/analytics-overview';
export * from './marketing/coupons';
export * from './tasks/payment';
export * from './tasks/purchase';
export * from './homescreen/task-list';
export * from './homescreen/activity-panel';
100 changes: 100 additions & 0 deletions packages/admin-e2e-tests/src/specs/tasks/purchase.ts
Original file line number Diff line number Diff line change
@@ -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 };
11 changes: 8 additions & 3 deletions src-internal/Admin/Onboarding/OnboardingThemes.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static function get_paid_theme_by_slug( $slug ) {
$themes = self::get_themes();
$theme_key = array_search( $slug, array_column( $themes, 'slug' ), true );
$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'] );
if ( $price && $price > 0 ) {
return $themes[ $theme_key ];
Expand Down Expand Up @@ -123,8 +123,13 @@ public static function get_themes() {
$active_theme = get_option( 'stylesheet' );

foreach ( $installed_themes as $slug => $theme ) {
$theme_data = self::get_theme_data( $theme );
$themes[ $slug ] = $theme_data;
$theme_data = self::get_theme_data( $theme );
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.
Expand Down
37 changes: 18 additions & 19 deletions src/Features/OnboardingTasks/Tasks/Purchase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
use Automattic\WooCommerce\Admin\PluginsHelper;

/**
* Purchase Task
Expand Down Expand Up @@ -55,7 +54,7 @@ public function get_id() {
* @return string
*/
public function get_title() {
$products = $this->get_products();
$products = $this->get_paid_products_and_themes();

return count( $products['remaining'] ) === 1
? sprintf(
Expand All @@ -78,19 +77,20 @@ public function get_title() {
* @return string
*/
public function get_content() {
$products = $this->get_products();
$products = $this->get_paid_products_and_themes();

return count( $products['remaining'] ) === 1
? $products['purchaseable'][0]['description']
: 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'
),
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
end( $products['remaining'] )
);
if ( count( $products['remaining'] ) === 1 ) {
return isset( $products['purchaseable'][0]['description'] ) ? $products['purchaseable'][0]['description'] : $products['purchaseable'][0]['excerpt'];
}
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'
),
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
end( $products['remaining'] )
);
}

/**
Expand Down Expand Up @@ -118,7 +118,7 @@ public function get_time() {
* @return bool
*/
public function is_complete() {
$products = $this->get_products();
$products = $this->get_paid_products_and_themes();
return count( $products['remaining'] ) === 0;
}

Expand All @@ -137,7 +137,7 @@ public function is_dismissable() {
* @return bool
*/
public function can_view() {
$products = $this->get_products();
$products = $this->get_paid_products_and_themes();
return count( $products['purchaseable'] ) > 0;
}

Expand All @@ -146,18 +146,17 @@ public function can_view() {
*
* @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();

$profiler_data = get_option( OnboardingProfile::DATA_OPTION, array() );
$theme = isset( $profiler_data['theme'] ) ? $profiler_data['theme'] : null;
$paid_theme = $theme ? OnboardingThemes::get_paid_theme_by_slug( $theme ) : null;
$installed = PluginsHelper::get_installed_plugin_slugs();
if ( $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'];
}
}
Expand Down
Loading

0 comments on commit 4ed0fd6

Please sign in to comment.