Merge branch 'feature/in-app-search' into add/wccom-21568-load-more-products-in-app

This commit is contained in:
Boro Sitnikovski 2024-09-11 13:04:20 +02:00 committed by GitHub
commit e51e2d2b0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 290 additions and 483 deletions

View File

@ -23,6 +23,8 @@ import MySubscriptions from '../my-subscriptions/my-subscriptions';
import { MarketplaceContext } from '../../contexts/marketplace-context'; import { MarketplaceContext } from '../../contexts/marketplace-context';
import { fetchSearchResults } from '../../utils/functions'; import { fetchSearchResults } from '../../utils/functions';
import { SubscriptionsContextProvider } from '../../contexts/subscriptions-context'; import { SubscriptionsContextProvider } from '../../contexts/subscriptions-context';
import { SearchResultsCountType } from '../../contexts/types';
import { import {
recordMarketplaceView, recordMarketplaceView,
recordLegacyTabView, recordLegacyTabView,
@ -39,8 +41,12 @@ export default function Content(): JSX.Element {
const [ products, setProducts ] = useState< Product[] >( [] ); const [ products, setProducts ] = useState< Product[] >( [] );
const [ hasMore, setHasMore ] = useState( false ); const [ hasMore, setHasMore ] = useState( false );
const [ page, setPage ] = useState( 1 ); const [ page, setPage ] = useState( 1 );
const { setIsLoading, selectedTab, setHasBusinessServices } = const {
marketplaceContextValue; setIsLoading,
selectedTab,
setHasBusinessServices,
setSearchResultsCount,
} = marketplaceContextValue;
const query = useQuery(); const query = useQuery();
const lastProductRef = useRef< HTMLDivElement >( null ); const lastProductRef = useRef< HTMLDivElement >( null );
@ -51,35 +57,42 @@ export default function Content(): JSX.Element {
// On initial load of the in-app marketplace, fetch extensions, themes and business services // On initial load of the in-app marketplace, fetch extensions, themes and business services
// and check if there are any business services available on WCCOM // and check if there are any business services available on WCCOM
useEffect( () => { useEffect( () => {
const categories = [ '', 'themes', 'business-services' ]; const categories: Array< keyof SearchResultsCountType > = [
'extensions',
'themes',
'business-services',
];
const abortControllers = categories.map( () => new AbortController() ); const abortControllers = categories.map( () => new AbortController() );
categories.forEach( ( category: string, index ) => { categories.forEach(
const params = new URLSearchParams(); ( category: keyof SearchResultsCountType, index ) => {
if ( category !== '' ) { const params = new URLSearchParams();
params.append( 'category', category ); if ( category !== 'extensions' ) {
} params.append( 'category', category );
}
const wccomSettings = getAdminSetting( 'wccomHelper', false ); const wccomSettings = getAdminSetting( 'wccomHelper', false );
if ( wccomSettings.storeCountry ) { if ( wccomSettings.storeCountry ) {
params.append( 'country', wccomSettings.storeCountry ); params.append( 'country', wccomSettings.storeCountry );
} }
fetchSearchResults( params, abortControllers[ index ].signal ).then( fetchSearchResults(
( productList ) => { params,
abortControllers[ index ].signal
).then( ( productList ) => {
if ( category === 'business-services' ) { if ( category === 'business-services' ) {
setHasBusinessServices( setHasBusinessServices(
productList.products.length > 0 productList.products.length > 0
); );
} }
}
);
return () => {
abortControllers.forEach( ( controller ) => {
controller.abort();
} ); } );
}; return () => {
} ); abortControllers.forEach( ( controller ) => {
controller.abort();
} );
};
}
);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [] ); }, [] );
@ -144,6 +157,20 @@ export default function Content(): JSX.Element {
return newProducts; return newProducts;
} ); } );
setHasMore( productList.hasMore ); setHasMore( productList.hasMore );
if ( query.term ) {
setSearchResultsCount( {
extensions: productList.filter(
( p ) => p.type === 'extension'
).length,
themes: productList.filter(
( p ) => p.type === 'theme'
).length,
'business-services': productList.filter(
( p ) => p.type === 'business-service'
).length,
} );
}
} ) } )
.catch( () => { .catch( () => {
if ( page === 1 ) { if ( page === 1 ) {

View File

@ -45,6 +45,10 @@
text-align: center; text-align: center;
z-index: 26; z-index: 26;
} }
&__update-count {
background-color: #000;
}
} }
@media (width <= $breakpoint-medium) { @media (width <= $breakpoint-medium) {

View File

@ -35,45 +35,6 @@ interface Tabs {
const wccomSettings = getAdminSetting( 'wccomHelper', {} ); const wccomSettings = getAdminSetting( 'wccomHelper', {} );
const wooUpdateCount = wccomSettings?.wooUpdateCount ?? 0; const wooUpdateCount = wccomSettings?.wooUpdateCount ?? 0;
const tabs: Tabs = {
search: {
name: 'search',
title: __( 'Search results', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
discover: {
name: 'discover',
title: __( 'Discover', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
extensions: {
name: 'extensions',
title: __( 'Extensions', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
themes: {
name: 'themes',
title: __( 'Themes', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
'business-services': {
name: 'business-services',
title: __( 'Business services', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
'my-subscriptions': {
name: 'my-subscriptions',
title: __( 'My subscriptions', 'woocommerce' ),
showUpdateCount: true,
updateCount: wooUpdateCount,
},
};
const setUrlTabParam = ( tabKey: string ) => { const setUrlTabParam = ( tabKey: string ) => {
navigateTo( { navigateTo( {
url: getNewPath( url: getNewPath(
@ -84,7 +45,11 @@ const setUrlTabParam = ( tabKey: string ) => {
} ); } );
}; };
const getVisibleTabs = ( selectedTab: string, hasBusinessServices = false ) => { const getVisibleTabs = (
selectedTab: string,
hasBusinessServices = false,
tabs: Tabs
) => {
if ( selectedTab === '' ) { if ( selectedTab === '' ) {
return tabs; return tabs;
} }
@ -101,7 +66,8 @@ const getVisibleTabs = ( selectedTab: string, hasBusinessServices = false ) => {
const renderTabs = ( const renderTabs = (
marketplaceContextValue: MarketplaceContextType, marketplaceContextValue: MarketplaceContextType,
visibleTabs: Tabs visibleTabs: Tabs,
tabs: Tabs
) => { ) => {
const { selectedTab, setSelectedTab } = marketplaceContextValue; const { selectedTab, setSelectedTab } = marketplaceContextValue;
@ -141,12 +107,13 @@ const renderTabs = (
key={ tabKey } key={ tabKey }
> >
{ tabs[ tabKey ]?.title } { tabs[ tabKey ]?.title }
{ tabs[ tabKey ]?.showUpdateCount && { tabs[ tabKey ]?.showUpdateCount && (
tabs[ tabKey ]?.updateCount > 0 && ( <span
<span className="woocommerce-marketplace__update-count"> className={ `woocommerce-marketplace__update-count woocommerce-marketplace__update-count-${ tabKey }` }
<span> { tabs[ tabKey ]?.updateCount } </span> >
</span> <span> { tabs[ tabKey ]?.updateCount } </span>
) } </span>
) }
</Button> </Button>
) )
); );
@ -159,10 +126,53 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
const marketplaceContextValue = useContext( MarketplaceContext ); const marketplaceContextValue = useContext( MarketplaceContext );
const { selectedTab, setSelectedTab, hasBusinessServices } = const { selectedTab, setSelectedTab, hasBusinessServices } =
marketplaceContextValue; marketplaceContextValue;
const [ visibleTabs, setVisibleTabs ] = useState( getVisibleTabs( '' ) ); const { searchResultsCount } = marketplaceContextValue;
const query: Record< string, string > = useQuery(); const query: Record< string, string > = useQuery();
const tabs: Tabs = {
search: {
name: 'search',
title: __( 'Search results', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
discover: {
name: 'discover',
title: __( 'Discover', 'woocommerce' ),
showUpdateCount: false,
updateCount: 0,
},
extensions: {
name: 'extensions',
title: __( 'Extensions', 'woocommerce' ),
showUpdateCount: !! query.term,
updateCount: searchResultsCount.extensions,
},
themes: {
name: 'themes',
title: __( 'Themes', 'woocommerce' ),
showUpdateCount: !! query.term,
updateCount: searchResultsCount.themes,
},
'business-services': {
name: 'business-services',
title: __( 'Business services', 'woocommerce' ),
showUpdateCount: !! query.term,
updateCount: searchResultsCount[ 'business-services' ],
},
'my-subscriptions': {
name: 'my-subscriptions',
title: __( 'My subscriptions', 'woocommerce' ),
showUpdateCount: true,
updateCount: wooUpdateCount,
},
};
const [ visibleTabs, setVisibleTabs ] = useState(
getVisibleTabs( '', false, tabs )
);
useEffect( () => { useEffect( () => {
if ( query?.tab && tabs[ query.tab ] ) { if ( query?.tab && tabs[ query.tab ] ) {
setSelectedTab( query.tab ); setSelectedTab( query.tab );
@ -172,8 +182,11 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
}, [ query, setSelectedTab ] ); }, [ query, setSelectedTab ] );
useEffect( () => { useEffect( () => {
setVisibleTabs( getVisibleTabs( selectedTab, hasBusinessServices ) ); setVisibleTabs(
getVisibleTabs( selectedTab, hasBusinessServices, tabs )
);
}, [ selectedTab, hasBusinessServices ] ); }, [ selectedTab, hasBusinessServices ] );
return ( return (
<nav <nav
className={ clsx( className={ clsx(
@ -181,7 +194,7 @@ const Tabs = ( props: TabsProps ): JSX.Element => {
additionalClassNames || [] additionalClassNames || []
) } ) }
> >
{ renderTabs( marketplaceContextValue, visibleTabs ) } { renderTabs( marketplaceContextValue, visibleTabs, tabs ) }
</nav> </nav>
); );
}; };

View File

@ -1,12 +1,17 @@
/** /**
* External dependencies * External dependencies
*/ */
import { useState, useEffect, createContext } from '@wordpress/element'; import {
useState,
useEffect,
useCallback,
createContext,
} from '@wordpress/element';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { MarketplaceContextType } from './types'; import { SearchResultsCountType, MarketplaceContextType } from './types';
import { getAdminSetting } from '../../utils/admin-settings'; import { getAdminSetting } from '../../utils/admin-settings';
export const MarketplaceContext = createContext< MarketplaceContextType >( { export const MarketplaceContext = createContext< MarketplaceContextType >( {
@ -18,6 +23,12 @@ export const MarketplaceContext = createContext< MarketplaceContextType >( {
addInstalledProduct: () => {}, addInstalledProduct: () => {},
hasBusinessServices: false, hasBusinessServices: false,
setHasBusinessServices: () => {}, setHasBusinessServices: () => {},
searchResultsCount: {
extensions: 0,
themes: 0,
'business-services': 0,
},
setSearchResultsCount: () => {},
} ); } );
export function MarketplaceContextProvider( props: { export function MarketplaceContextProvider( props: {
@ -29,6 +40,22 @@ export function MarketplaceContextProvider( props: {
[] []
); );
const [ hasBusinessServices, setHasBusinessServices ] = useState( false ); const [ hasBusinessServices, setHasBusinessServices ] = useState( false );
const [ searchResultsCount, setSearchResultsCountState ] =
useState< SearchResultsCountType >( {
extensions: 0,
themes: 0,
'business-services': 0,
} );
const setSearchResultsCount = useCallback(
( updatedCounts: Partial< SearchResultsCountType > ) => {
setSearchResultsCountState( ( prev ) => ( {
...prev,
...updatedCounts,
} ) );
},
[]
);
/** /**
* Knowing installed products will help us to determine which products * Knowing installed products will help us to determine which products
@ -59,6 +86,8 @@ export function MarketplaceContextProvider( props: {
addInstalledProduct, addInstalledProduct,
hasBusinessServices, hasBusinessServices,
setHasBusinessServices, setHasBusinessServices,
searchResultsCount,
setSearchResultsCount,
}; };
return ( return (

View File

@ -8,6 +8,12 @@ import { Options } from '@wordpress/notices';
*/ */
import { Subscription } from '../components/my-subscriptions/types'; import { Subscription } from '../components/my-subscriptions/types';
export interface SearchResultsCountType {
extensions: number;
themes: number;
'business-services': number;
}
export type MarketplaceContextType = { export type MarketplaceContextType = {
isLoading: boolean; isLoading: boolean;
setIsLoading: ( isLoading: boolean ) => void; setIsLoading: ( isLoading: boolean ) => void;
@ -17,6 +23,10 @@ export type MarketplaceContextType = {
addInstalledProduct: ( slug: string ) => void; addInstalledProduct: ( slug: string ) => void;
hasBusinessServices: boolean; hasBusinessServices: boolean;
setHasBusinessServices: ( hasBusinessServices: boolean ) => void; setHasBusinessServices: ( hasBusinessServices: boolean ) => void;
searchResultsCount: SearchResultsCountType;
setSearchResultsCount: (
updatedCounts: Partial< SearchResultsCountType >
) => void;
}; };
export type SubscriptionsContextType = { export type SubscriptionsContextType = {

View File

@ -73,22 +73,20 @@ body:has(.woocommerce-coming-soon-banner) {
} }
.wp-block-loginout { .wp-block-loginout {
background-color: #000;
border-radius: 6px;
display: flex; display: flex;
height: 40px;
width: 74px;
justify-content: center;
align-items: center;
gap: 10px;
box-sizing: border-box;
a { a {
box-sizing: border-box;
background-color: #000;
border-radius: 6px;
color: #fff; color: #fff;
text-decoration: none; gap: 10px;
line-height: 17px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-style: normal;
line-height: normal;
text-align: center;
text-decoration: none;
padding: 17px 16px;
} }
} }

View File

@ -0,0 +1,5 @@
Significance: patch
Type: fix
Comment: Fix size for coming soon banner button

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix duplicate spec evaluation in evaluate_specs()

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add search result counts to the in-app marketplace header tabs (Extensions area)

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Clean up Purchase task

View File

@ -1,203 +0,0 @@
<?php
namespace Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProducts;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Task;
/**
* Purchase Task
*/
class Purchase extends Task {
/**
* Constructor
*
* @param TaskList $task_list Parent task list.
*/
public function __construct( $task_list ) {
parent::__construct( $task_list );
add_action( 'update_option_woocommerce_onboarding_profile', array( $this, 'clear_dismissal' ), 10, 2 );
}
/**
* Clear dismissal on onboarding product type changes.
*
* @param array $old_value Old value.
* @param array $new_value New value.
*/
public function clear_dismissal( $old_value, $new_value ) {
$product_types = isset( $new_value['product_types'] ) ? (array) $new_value['product_types'] : array();
$previous_product_types = isset( $old_value['product_types'] ) ? (array) $old_value['product_types'] : array();
if ( empty( array_diff( $product_types, $previous_product_types ) ) ) {
return;
}
$this->undo_dismiss();
}
/**
* Get the task arguments.
* ID.
*
* @return string
*/
public function get_id() {
return 'purchase';
}
/**
* Title.
*
* @return string
*/
public function get_title() {
$products = $this->get_paid_products_and_themes();
$first_product = count( $products['purchaseable'] ) >= 1 ? $products['purchaseable'][0] : false;
if ( ! $first_product ) {
return null;
}
$product_label = isset( $first_product['label'] ) ? $first_product['label'] : $first_product['title'];
$additional_count = count( $products['purchaseable'] ) - 1;
if ( $this->get_parent_option( 'use_completed_title' ) && $this->is_complete() ) {
return count( $products['purchaseable'] ) === 1
? sprintf(
/* translators: %1$s: a purchased product name */
__(
'You added %1$s',
'woocommerce'
),
$product_label
)
: sprintf(
/* translators: %1$s: a purchased product name, %2$d the number of other products purchased */
_n(
'You added %1$s and %2$d other product',
'You added %1$s and %2$d other products',
$additional_count,
'woocommerce'
),
$product_label,
$additional_count
);
}
return count( $products['purchaseable'] ) === 1
? sprintf(
/* translators: %1$s: a purchaseable product name */
__(
'Add %s to my store',
'woocommerce'
),
$product_label
)
: sprintf(
/* translators: %1$s: a purchaseable product name, %2$d the number of other products to purchase */
_n(
'Add %1$s and %2$d more product to my store',
'Add %1$s and %2$d more products to my store',
$additional_count,
'woocommerce'
),
$product_label,
$additional_count
);
}
/**
* Content.
*
* @return string
*/
public function get_content() {
$products = $this->get_paid_products_and_themes();
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'
),
implode( ', ', array_slice( $products['remaining'], 0, -1 ) ) . ( count( $products['remaining'] ) > 2 ? ',' : '' ),
end( $products['remaining'] )
);
}
/**
* Action label.
*
* @return string
*/
public function get_action_label() {
return __( 'Purchase & install now', 'woocommerce' );
}
/**
* Time.
*
* @return string
*/
public function get_time() {
return __( '2 minutes', 'woocommerce' );
}
/**
* Task completion.
*
* @return bool
*/
public function is_complete() {
$products = $this->get_paid_products_and_themes();
return count( $products['remaining'] ) === 0;
}
/**
* Dismissable.
*
* @return bool
*/
public function is_dismissable() {
return true;
}
/**
* Task visibility.
*
* @return bool
*/
public function can_view() {
$products = $this->get_paid_products_and_themes();
return count( $products['purchaseable'] ) > 0;
}
/**
* Get purchaseable and remaining products.
*
* @return array purchaseable and remaining products and themes.
*/
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;
if ( $paid_theme ) {
$relevant_products['purchaseable'][] = $paid_theme;
if ( isset( $paid_theme['is_installed'] ) && false === $paid_theme['is_installed'] ) {
$relevant_products['remaining'][] = $paid_theme['title'];
}
}
return $relevant_products;
}
}

View File

@ -13,6 +13,13 @@ use Automattic\WooCommerce\Admin\RemoteSpecs\RuleProcessors\RuleEvaluator;
* Evaluates the spec and returns the evaluated suggestion. * Evaluates the spec and returns the evaluated suggestion.
*/ */
class EvaluateSuggestion { class EvaluateSuggestion {
/**
* Stores memoized results of evaluate_specs.
*
* @var array
*/
protected static $memo = array();
/** /**
* Evaluates the spec and returns the suggestion. * Evaluates the spec and returns the suggestion.
* *
@ -58,6 +65,12 @@ class EvaluateSuggestion {
* @return array The visible suggestions and errors. * @return array The visible suggestions and errors.
*/ */
public static function evaluate_specs( $specs, $logger_args = array() ) { public static function evaluate_specs( $specs, $logger_args = array() ) {
$specs_key = self::get_memo_key( $specs );
if ( isset( self::$memo[ $specs_key ] ) ) {
return self::$memo[ $specs_key ];
}
$suggestions = array(); $suggestions = array();
$errors = array(); $errors = array();
@ -72,9 +85,43 @@ class EvaluateSuggestion {
} }
} }
return array( $result = array(
'suggestions' => $suggestions, 'suggestions' => $suggestions,
'errors' => $errors, 'errors' => $errors,
); );
// Memoize results, with a fail safe to prevent unbounded memory growth.
// This limit is unlikely to be reached under normal circumstances.
if ( count( self::$memo ) > 50 ) {
self::reset_memo();
}
self::$memo[ $specs_key ] = $result;
return $result;
}
/**
* Resets the memoized results. Useful for testing.
*/
public static function reset_memo() {
self::$memo = array();
}
/**
* Returns a memoization key for the given specs.
*
* @param array $specs The specs to generate a key for.
*
* @return string The memoization key.
*/
private static function get_memo_key( $specs ) {
$data = wp_json_encode( $specs );
if ( function_exists( 'hash' ) && in_array( 'xxh3', hash_algos(), true ) ) {
// Use xxHash (xxh3) if available.
return hash( 'xxh3', $data );
}
// Fall back to CRC32.
return (string) crc32( $data );
} }
} }

View File

@ -1,186 +0,0 @@
<?php
/**
* Test the TaskList class.
*
* @package WooCommerce\Admin\Tests\OnboardingTasks
*/
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingThemes;
use Automattic\WooCommerce\Internal\Admin\Onboarding\OnboardingProfile;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\TaskList;
use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Purchase;
/**
* class WC_Admin_Tests_OnboardingTasks_TaskList
*/
class WC_Admin_Tests_OnboardingTasks_Task_Purchase extends WC_Unit_Test_Case {
/**
* Task list.
*
* @var Task|null
*/
protected $task = null;
/**
* Setup test data. Called before every test.
*/
public function setUp(): void {
parent::setUp();
$this->task = new Purchase( new TaskList() );
set_transient(
OnboardingThemes::THEMES_TRANSIENT,
array(
'free' => array(
'slug' => 'free',
'is_installed' => false,
),
'paid' => array(
'slug' => 'paid',
'id' => 12312,
'price' => '&#36;79.00',
'title' => 'theme title',
'is_installed' => false,
),
'paid_installed' => array(
'slug' => 'paid_installed',
'id' => 12312,
'price' => '&#36;79.00',
'title' => 'theme title',
'is_installed' => true,
),
'free_with_price' => array(
'slug' => 'free_with_price',
'id' => 12312,
'price' => '&#36;0.00',
'title' => 'theme title',
'is_installed' => false,
),
)
);
}
/**
* Tear down.
*/
public function tearDown(): void {
parent::tearDown();
delete_transient( OnboardingThemes::THEMES_TRANSIENT );
delete_option( OnboardingProfile::DATA_OPTION );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_complete_if_no_remaining_products() {
update_option( OnboardingProfile::DATA_OPTION, array( 'product_types' => array( 'physical' ) ) );
$this->assertEquals( true, $this->task->is_complete() );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_not_complete_if_remaining_paid_products() {
update_option( OnboardingProfile::DATA_OPTION, array( 'product_types' => array( 'memberships' ) ) );
$this->assertEquals( false, $this->task->is_complete() );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_complete_if_no_paid_themes() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array(),
'theme' => 'free',
)
);
$this->assertEquals( true, $this->task->is_complete() );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_not_complete_if_paid_theme_that_is_not_installed() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array(),
'theme' => 'paid',
)
);
$this->assertEquals( false, $this->task->is_complete() );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_complete_if_paid_theme_that_is_installed() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array(),
'theme' => 'paid_installed',
)
);
$this->assertEquals( true, $this->task->is_complete() );
}
/**
* Test is_complete function of Purchase task.
*/
public function test_is_complete_if_free_theme_with_set_price() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array(),
'theme' => 'free_with_price',
)
);
$this->assertEquals( true, $this->task->is_complete() );
}
/**
* Test the task title for a single paid item.
*/
public function test_get_title_if_single_paid_item() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array(),
'theme' => 'paid',
)
);
$this->assertEquals( 'Add theme title to my store', $this->task->get_title() );
}
/**
* Test the task title if 2 paid items exist.
*/
public function test_get_title_if_multiple_paid_themes() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array( 'memberships' ),
'theme' => 'paid',
)
);
$this->assertEquals( 'Add Memberships and 1 more product to my store', $this->task->get_title() );
}
/**
* Test the task title if multiple additional paid items exist.
*/
public function test_get_title_if_multiple_paid_products() {
update_option(
OnboardingProfile::DATA_OPTION,
array(
'product_types' => array( 'memberships', 'bookings' ),
'theme' => 'paid',
)
);
$this->assertEquals( 'Add Memberships and 2 more products to my store', $this->task->get_title() );
}
}

View File

@ -323,6 +323,31 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_EvaluateSuggestion extends WC_Uni
remove_filter( 'woocommerce_admin_remote_specs_evaluator_should_log', '__return_true' ); remove_filter( 'woocommerce_admin_remote_specs_evaluator_should_log', '__return_true' );
} }
/**
* Test that the memo is set correctly.
*/
public function test_memo_set_correctly() {
$specs = array(
array(
'id' => 'test-gateway-1',
'is_visible' => true,
),
array(
'id' => 'test-gateway-2',
'is_visible' => false,
),
);
$result = TestableEvaluateSuggestion::evaluate_specs( $specs );
$memo = TestableEvaluateSuggestion::get_memo_for_tests();
$this->assertCount( 1, $memo );
$memo_key = array_keys( $memo )[0];
$this->assertEquals( $result, $memo[ $memo_key ] );
$this->assertCount( 1, $result['suggestions'] );
$this->assertEquals( 'test-gateway-1', $result['suggestions'][0]->id );
}
/** /**
* Overrides the WC logger. * Overrides the WC logger.
* *
@ -359,3 +384,19 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_EvaluateSuggestion extends WC_Uni
); );
} }
} }
//phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Classes.ClassFileName.NoMatch, Suin.Classes.PSR4.IncorrectClassName
/**
* TestableEvaluateSuggestion class.
*/
class TestableEvaluateSuggestion extends EvaluateSuggestion {
/**
* Get the memo for testing.
*
* @return array
*/
public static function get_memo_for_tests() {
return self::$memo;
}
}
//phpcs:enable Generic.Files.OneObjectStructurePerFile.MultipleFound, Squiz.Classes.ClassFileName.NoMatch, Suin.Classes.PSR4.IncorrectClassName

View File

@ -25,7 +25,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
delete_option( 'woocommerce_show_marketplace_suggestions' ); delete_option( 'woocommerce_show_marketplace_suggestions' );
add_filter( add_filter(
'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs', 'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs',
function( $value ) { function ( $value ) {
if ( $value ) { if ( $value ) {
return $value; return $value;
} }
@ -37,6 +37,8 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
); );
} }
); );
EvaluateSuggestion::reset_memo();
} }
/** /**
@ -57,7 +59,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
remove_all_filters( 'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs' ); remove_all_filters( 'transient_woocommerce_admin_' . PaymentGatewaySuggestionsDataSourcePoller::ID . '_specs' );
add_filter( add_filter(
DataSourcePoller::FILTER_NAME, DataSourcePoller::FILTER_NAME,
function() { function () {
return array(); return array();
} }
); );
@ -242,7 +244,7 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
add_filter( add_filter(
'locale', 'locale',
function( $_locale ) { function () {
return 'zh_TW'; return 'zh_TW';
} }
); );
@ -364,5 +366,4 @@ class WC_Admin_Tests_PaymentGatewaySuggestions_Init extends WC_Unit_Test_Case {
// Clean up. // Clean up.
delete_option( PaymentGatewaySuggestions::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION ); delete_option( PaymentGatewaySuggestions::RECOMMENDED_PAYMENT_PLUGINS_DISMISS_OPTION );
} }
} }

View File

@ -1,7 +1,7 @@
<?php <?php
declare( strict_types = 1 ); declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Admin\ShippingPartnerSuggestions; namespace Automattic\WooCommerce\Tests\Admin\Features\ShippingPartnerSuggestions;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion; use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners; use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners;
@ -31,6 +31,8 @@ class DefaultShippingPartnersTest extends WC_Unit_Test_Case {
update_option( 'woocommerce_store_address', 'foo' ); update_option( 'woocommerce_store_address', 'foo' );
update_option( 'active_plugins', array( 'foo/foo.php' ) ); update_option( 'active_plugins', array( 'foo/foo.php' ) );
EvaluateSuggestion::reset_memo();
} }
/** /**

View File

@ -7,6 +7,7 @@ use Automattic\WooCommerce\Admin\Features\OnboardingTasks\Tasks\Shipping;
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners; use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\DefaultShippingPartners;
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestions; use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestions;
use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestionsDataSourcePoller; use Automattic\WooCommerce\Admin\Features\ShippingPartnerSuggestions\ShippingPartnerSuggestionsDataSourcePoller;
use Automattic\WooCommerce\Admin\Features\PaymentGatewaySuggestions\EvaluateSuggestion;
use WC_Unit_Test_Case; use WC_Unit_Test_Case;
/** /**
@ -37,7 +38,7 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
delete_option( 'woocommerce_show_marketplace_suggestions' ); delete_option( 'woocommerce_show_marketplace_suggestions' );
add_filter( add_filter(
'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs', 'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs',
function( $value ) { function ( $value ) {
if ( $value ) { if ( $value ) {
return $value; return $value;
} }
@ -70,6 +71,8 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
// Have a mock logger used by the suggestions rule evaluator. // Have a mock logger used by the suggestions rule evaluator.
$this->mock_logger = $this->getMockBuilder( 'WC_Logger_Interface' )->getMock(); $this->mock_logger = $this->getMockBuilder( 'WC_Logger_Interface' )->getMock();
add_filter( 'woocommerce_logging_class', array( $this, 'override_wc_logger' ) ); add_filter( 'woocommerce_logging_class', array( $this, 'override_wc_logger' ) );
EvaluateSuggestion::reset_memo();
} }
/** /**
@ -91,7 +94,7 @@ class ShippingPartnerSuggestionsTest extends WC_Unit_Test_Case {
remove_all_filters( 'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs' ); remove_all_filters( 'transient_woocommerce_admin_' . ShippingPartnerSuggestionsDataSourcePoller::ID . '_specs' );
add_filter( add_filter(
DataSourcePoller::FILTER_NAME, DataSourcePoller::FILTER_NAME,
function() { function () {
return array(); return array();
} }
); );

View File

@ -29,6 +29,8 @@ class DefaultPromotionsTest extends WC_Unit_Test_Case {
update_option( 'woocommerce_store_address', 'foo' ); update_option( 'woocommerce_store_address', 'foo' );
update_option( 'active_plugins', array( 'foo/foo.php' ) ); update_option( 'active_plugins', array( 'foo/foo.php' ) );
EvaluateSuggestion::reset_memo();
} }
/** /**

View File

@ -26,7 +26,7 @@ class InitTest extends WC_Unit_Test_Case {
delete_option( 'woocommerce_show_marketplace_suggestions' ); delete_option( 'woocommerce_show_marketplace_suggestions' );
add_filter( add_filter(
'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs', 'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs',
function( $value ) { function ( $value ) {
if ( $value ) { if ( $value ) {
return $value; return $value;
} }
@ -38,6 +38,8 @@ class InitTest extends WC_Unit_Test_Case {
); );
} }
); );
EvaluateSuggestion::reset_memo();
} }
/** /**
@ -59,7 +61,7 @@ class InitTest extends WC_Unit_Test_Case {
remove_all_filters( 'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs' ); remove_all_filters( 'transient_woocommerce_admin_' . WCPayPromotionDataSourcePoller::ID . '_specs' );
add_filter( add_filter(
DataSourcePoller::FILTER_NAME, DataSourcePoller::FILTER_NAME,
function() { function () {
return array(); return array();
} }
); );