Merge branch 'trunk' into try/add-settings-refresh-next
This commit is contained in:
commit
2a6750ca31
|
@ -132,7 +132,7 @@ jobs:
|
|||
install: '${{ matrix.projectName }}...'
|
||||
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
|
||||
build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }}
|
||||
pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }}
|
||||
pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && ( matrix.testType == 'e2e' || matrix.testType == 'performance' ) }}
|
||||
pull-package-deps: '${{ matrix.projectName }}'
|
||||
|
||||
- name: 'Update wp-env config'
|
||||
|
|
|
@ -29,6 +29,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Check Asset Sizes
|
||||
|
@ -42,6 +45,8 @@ jobs:
|
|||
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||
with:
|
||||
php-version: false
|
||||
install: '@woocommerce/plugin-woocommerce...'
|
||||
build: '@woocommerce/plugin-woocommerce'
|
||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||
|
||||
- uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c
|
||||
|
@ -49,9 +54,9 @@ jobs:
|
|||
BROWSERSLIST_IGNORE_OLD_DATA: true
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks}/{build,build-style}/**/*.{js,css}'
|
||||
pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks,plugins/woocommerce-admin,plugins/woocommerce/client/legacy}/{build,build-style}/**/*.{js,css}'
|
||||
install-script: 'pnpm install --filter="@woocommerce/plugin-woocommerce..." --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts'
|
||||
build-script: '--filter="@woocommerce/plugin-woocommerce" build'
|
||||
clean-script: '--if-present buildclean'
|
||||
clean-script: '--if-present clean:build'
|
||||
minimum-change-threshold: 100
|
||||
omit-unchanged: true
|
||||
|
|
|
@ -16,6 +16,9 @@ concurrency:
|
|||
group: build-${{ github.event_name == 'push' && github.run_id || 'pr' }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
|
|
|
@ -2,31 +2,79 @@
|
|||
|
||||
set -eo pipefail
|
||||
|
||||
function title() {
|
||||
echo -e "\n\033[1m$1\033[0m"
|
||||
}
|
||||
# The commented variables are for troubleshooting locally. The commented commands below are also for local troubleshooting.
|
||||
# GITHUB_EVENT_NAME='pull_request'
|
||||
# GITHUB_SHA=$(git rev-parse HEAD)
|
||||
# ARTIFACTS_PATH="$(realpath $(dirname -- ${BASH_SOURCE[0]})/../../../tools/compare-perf)/artifacts"
|
||||
|
||||
if [[ -z "$GITHUB_EVENT_NAME" ]]; then
|
||||
echo "::error::GITHUB_EVENT_NAME must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
title "Installing NVM"
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash > /dev/null
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
echo "Installed version: $(nvm -v)"
|
||||
function title() {
|
||||
echo -e "\n\033[1m$1\033[0m"
|
||||
}
|
||||
|
||||
title "Installing dependencies"
|
||||
pnpm install --frozen-lockfile --filter="compare-perf" > /dev/null
|
||||
if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
|
||||
mkdir -p $ARTIFACTS_PATH && export WP_ARTIFACTS_PATH=$ARTIFACTS_PATH
|
||||
|
||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
||||
title "Comparing performance with trunk"
|
||||
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA
|
||||
|
||||
elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
|
||||
title "Comparing performance with base branch"
|
||||
# It should be 3d7d7f02017383937f1a4158d433d0e5d44b3dc9, but we pick 55f855a2e6d769b5ae44305b2772eb30d3e721df
|
||||
# where compare-perf reporting mode was introduced for processing the provided reports.
|
||||
BASE_SHA=55f855a2e6d769b5ae44305b2772eb30d3e721df
|
||||
HEAD_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt)
|
||||
title "Comparing performance between: $BASE_SHA@trunk (base) and $GITHUB_SHA@$HEAD_BRANCH (head) on WordPress v$WP_VERSION"
|
||||
|
||||
title "##[group]Setting up necessary tooling"
|
||||
pnpm --filter="@woocommerce/plugin-woocommerce" test:e2e:install > /dev/null &
|
||||
pnpm install --filter='compare-perf...' --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts
|
||||
echo '##[endgroup]'
|
||||
|
||||
if test -n "$(find $ARTIFACTS_PATH -maxdepth 1 -name "*_${GITHUB_SHA}_*" -print -quit)"; then
|
||||
title "Skipping benchmarking head as benchmarking results already available under $ARTIFACTS_PATH"
|
||||
else
|
||||
# title "##[group]Building head"
|
||||
# git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD)
|
||||
# pnpm run --if-present clean:build
|
||||
# pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false
|
||||
# pnpm --filter='@woocommerce/plugin-woocommerce' build
|
||||
# echo '##[endgroup]'
|
||||
|
||||
title "##[group]Benchmarking head"
|
||||
RESULTS_ID="editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor
|
||||
RESULTS_ID="product-editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor
|
||||
echo '##[endgroup]'
|
||||
fi
|
||||
|
||||
if test -n "$(find $ARTIFACTS_PATH -maxdepth 1 -name "*_${BASE_SHA}_*" -print -quit)"; then
|
||||
title "Skipping benchmarking baseline as benchmarking results already available under $ARTIFACTS_PATH"
|
||||
else
|
||||
title "##[group]Checkout baseline"
|
||||
git fetch --no-tags --quiet --unshallow origin trunk
|
||||
echo '##[endgroup]'
|
||||
|
||||
title "##[group]Building baseline"
|
||||
( git -c core.hooksPath=/dev/null checkout --quiet $BASE_SHA > /dev/null || git reset --hard $BASE_SHA ) && echo 'On' $(git rev-parse HEAD)
|
||||
pnpm run --if-present clean:build &
|
||||
pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false
|
||||
pnpm --filter='@woocommerce/plugin-woocommerce' build
|
||||
echo '##[endgroup]'
|
||||
|
||||
title "##[group]Benchmarking baseline"
|
||||
RESULTS_ID="editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor
|
||||
RESULTS_ID="product-editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor
|
||||
echo '##[endgroup]'
|
||||
|
||||
# title "##[group]Restoring codebase state back to head"
|
||||
# git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD)
|
||||
# pnpm install --frozen-lockfile > /dev/null &
|
||||
# pnpm run --if-present clean:build
|
||||
# echo '##[endgroup]'
|
||||
fi
|
||||
|
||||
title "##[group]Processing reports under $ARTIFACTS_PATH"
|
||||
ls -l $ARTIFACTS_PATH
|
||||
# Updating the WP version used for performance jobs means there’s a high
|
||||
# chance that the reference commit used for performance test stability
|
||||
# becomes incompatible with the WP version. So, every time the "Tested up
|
||||
|
@ -36,15 +84,16 @@ elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
|
|||
# - Be compatible with the new WP version used in the “Tested up to” flag.
|
||||
# - Be tracked on https://www.codevitals.run/project/woo for all existing
|
||||
# metrics.
|
||||
BASE_SHA=3d7d7f02017383937f1a4158d433d0e5d44b3dc9
|
||||
echo "WP_VERSION: $WP_VERSION"
|
||||
IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION"
|
||||
WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}"
|
||||
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
|
||||
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" --ci --skip-benchmarking
|
||||
echo '##[endgroup]'
|
||||
|
||||
title "Publish results to CodeVitals"
|
||||
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
|
||||
title "##[group]Publish results to CodeVitals"
|
||||
COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI")
|
||||
pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT
|
||||
echo '##[endgroup]'
|
||||
fi
|
||||
else
|
||||
echo "Unsupported event: $GITHUB_EVENT_NAME"
|
||||
fi
|
||||
|
|
|
@ -13,7 +13,7 @@ To get up and running within the WooCommerce Monorepo, you will need to make sur
|
|||
### Prerequisites
|
||||
|
||||
- [NVM](https://github.com/nvm-sh/nvm#installing-and-updating): While you can always install Node through other means, we recommend using NVM to ensure you're aligned with the version used by our development teams. Our repository contains [an `.nvmrc` file](.nvmrc) which helps ensure you are using the correct version of Node.
|
||||
- [PNPM](https://pnpm.io/installation): Our repository utilizes PNPM to manage project dependencies and run various scripts involved in building and testing projects.
|
||||
- [PNPM](https://pnpm.io/installation): Our repository utilizes PNPM version 9.1.3 to manage project dependencies and run various scripts involved in building and testing projects.
|
||||
- [PHP 7.4+](https://www.php.net/manual/en/install.php): WooCommerce Core currently features a minimum PHP version of 7.4. It is also needed to run Composer and various project build scripts. See [troubleshooting](DEVELOPMENT.md#troubleshooting) for troubleshooting problems installing PHP.
|
||||
- [Composer](https://getcomposer.org/doc/00-intro.md): We use Composer to manage all of the dependencies for PHP packages and plugins.
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"lint": "pnpm -r lint",
|
||||
"cherry-pick": "node ./tools/cherry-pick/bin/run",
|
||||
"clean": "rimraf -g '**/node_modules' '**/.wireit' && pnpm store prune",
|
||||
"buildclean": "git clean --force -d -X ./packages ./plugins ./tools",
|
||||
"clean:build": "rimraf -g 'packages/js/*/build' 'packages/js/*/build-*' 'packages/js/*/dist' 'plugins/*/build' 'plugins/woocommerce/client/legacy/build' && git clean --force -d -X --quiet ./plugins/woocommerce/assets",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"postinstall": "husky",
|
||||
"sync-dependencies": "pnpm exec syncpack -- fix-mismatches",
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useInstanceId } from '@wordpress/compose';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ColorPalette } from './types';
|
||||
|
||||
const MAX_COLOR_PALETTES = 4;
|
||||
|
||||
export const ColorPalettes = ( {
|
||||
colorPalettes,
|
||||
totalPalettes,
|
||||
|
@ -10,17 +18,52 @@ export const ColorPalettes = ( {
|
|||
colorPalettes: ColorPalette[];
|
||||
totalPalettes: number;
|
||||
} ) => {
|
||||
let extra = null;
|
||||
const canFit = totalPalettes <= MAX_COLOR_PALETTES;
|
||||
|
||||
if ( totalPalettes > 4 ) {
|
||||
extra = <li className="more_palettes">+{ totalPalettes - 4 }</li>;
|
||||
const descriptionId = useInstanceId(
|
||||
ColorPalettes,
|
||||
'color-palettes-description'
|
||||
) as string;
|
||||
|
||||
function renderMore() {
|
||||
if ( canFit ) return null;
|
||||
return (
|
||||
<li aria-hidden="true" className="more_palettes">
|
||||
+{ totalPalettes - 4 }
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function renderDescription() {
|
||||
if ( canFit ) return null;
|
||||
return (
|
||||
<p
|
||||
id={ descriptionId }
|
||||
className="theme-card__color-palettes-description"
|
||||
>
|
||||
{ sprintf(
|
||||
/* translators: $d is the total amount of color palettes */
|
||||
__(
|
||||
'There are a total of %d color palettes',
|
||||
'woocommerce'
|
||||
),
|
||||
totalPalettes
|
||||
) }
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="theme-card__color-palettes">
|
||||
<>
|
||||
<ul
|
||||
className="theme-card__color-palettes"
|
||||
aria-label={ __( 'Color palettes', 'woocommerce' ) }
|
||||
aria-describedby={ descriptionId }
|
||||
>
|
||||
{ colorPalettes.map( ( colorPalette ) => (
|
||||
<li
|
||||
key={ colorPalette.title }
|
||||
aria-label={ colorPalette.title }
|
||||
style={ {
|
||||
background:
|
||||
'linear-gradient(to right, ' +
|
||||
|
@ -34,9 +77,12 @@ export const ColorPalettes = ( {
|
|||
' 100%' +
|
||||
')',
|
||||
} }
|
||||
></li>
|
||||
/>
|
||||
) ) }
|
||||
{ extra }
|
||||
{ renderMore() }
|
||||
</ul>
|
||||
|
||||
{ renderDescription() }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -314,6 +314,10 @@
|
|||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&-description {
|
||||
@include visually-hidden();
|
||||
}
|
||||
}
|
||||
|
||||
.theme-card__free {
|
||||
|
|
|
@ -355,9 +355,16 @@ export function subscriptionStatus(
|
|||
);
|
||||
}
|
||||
|
||||
return subscription.autorenew
|
||||
? __( 'Active', 'woocommerce' )
|
||||
: __( 'Cancelled', 'woocommerce' );
|
||||
let status;
|
||||
if ( subscription.lifetime ) {
|
||||
status = __( 'Lifetime', 'woocommerce' );
|
||||
} else if ( subscription.autorenew ) {
|
||||
status = __( 'Active', 'woocommerce' );
|
||||
} else {
|
||||
status = __( 'Cancelled', 'woocommerce' );
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
return {
|
||||
display: getStatus(),
|
||||
|
@ -377,7 +384,7 @@ export function actions( subscription: Subscription ): TableRow {
|
|||
let actionButton = null;
|
||||
if ( subscription.product_key === '' ) {
|
||||
actionButton = <SubscribeButton subscription={ subscription } />;
|
||||
} else if ( subscription.expired ) {
|
||||
} else if ( subscription.expired && ! subscription.lifetime ) {
|
||||
actionButton = <RenewButton subscription={ subscription } />;
|
||||
} else if (
|
||||
subscription.local.installed === false &&
|
||||
|
@ -391,7 +398,7 @@ export function actions( subscription: Subscription ): TableRow {
|
|||
actionButton = (
|
||||
<ConnectButton subscription={ subscription } variant="link" />
|
||||
);
|
||||
} else if ( ! subscription.autorenew ) {
|
||||
} else if ( ! subscription.autorenew && ! subscription.lifetime ) {
|
||||
actionButton = <AutoRenewButton subscription={ subscription } />;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,4 +11,8 @@
|
|||
.wc-block-components-totals-footer-item-tax {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-item__value {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,7 @@ export const TotalsShipping = ( {
|
|||
<ShippingPlaceholder
|
||||
showCalculator={ showCalculator }
|
||||
isCheckout={ isCheckout }
|
||||
addressProvided={ addressComplete }
|
||||
isShippingCalculatorOpen={
|
||||
isShippingCalculatorOpen
|
||||
}
|
||||
|
|
|
@ -12,22 +12,27 @@ export interface ShippingPlaceholderProps {
|
|||
showCalculator: boolean;
|
||||
isShippingCalculatorOpen: boolean;
|
||||
isCheckout?: boolean;
|
||||
addressProvided: boolean;
|
||||
setIsShippingCalculatorOpen: CalculatorButtonProps[ 'setIsShippingCalculatorOpen' ];
|
||||
}
|
||||
|
||||
export const ShippingPlaceholder = ( {
|
||||
showCalculator,
|
||||
addressProvided,
|
||||
isShippingCalculatorOpen,
|
||||
setIsShippingCalculatorOpen,
|
||||
isCheckout = false,
|
||||
}: ShippingPlaceholderProps ): JSX.Element => {
|
||||
if ( ! showCalculator ) {
|
||||
const label = addressProvided
|
||||
? __( 'No available delivery option', 'woocommerce' )
|
||||
: __( 'Enter address to calculate', 'woocommerce' );
|
||||
return (
|
||||
<em>
|
||||
<span className="wc-block-components-shipping-placeholder__value">
|
||||
{ isCheckout
|
||||
? __( 'No shipping options available', 'woocommerce' )
|
||||
? label
|
||||
: __( 'Calculated during checkout', 'woocommerce' ) }
|
||||
</em>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
.wc-block-components-shipping-address {
|
||||
margin-top: $gap;
|
||||
display: block;
|
||||
|
@ -50,6 +49,10 @@
|
|||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-block-components-shipping-placeholder__value {
|
||||
@include font-size(small);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra classes for specificity.
|
||||
|
|
|
@ -9,22 +9,24 @@ import { screen, render } from '@testing-library/react';
|
|||
import ShippingPlaceholder from '../shipping-placeholder';
|
||||
|
||||
describe( 'ShippingPlaceholder', () => {
|
||||
it( 'should show correct text if showCalculator is false', () => {
|
||||
it( 'should show correct text if showCalculator is false and addressProvided is false', () => {
|
||||
const { rerender } = render(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
addressProvided={ false }
|
||||
isCheckout={ true }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText( 'No shipping options available' )
|
||||
screen.getByText( 'Enter address to calculate' )
|
||||
).toBeInTheDocument();
|
||||
rerender(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
isCheckout={ false }
|
||||
addressProvided={ false }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
/>
|
||||
|
@ -33,4 +35,19 @@ describe( 'ShippingPlaceholder', () => {
|
|||
screen.getByText( 'Calculated during checkout' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'should show correct text if showCalculator is false and addressProvided is true', () => {
|
||||
render(
|
||||
<ShippingPlaceholder
|
||||
showCalculator={ false }
|
||||
addressProvided={ true }
|
||||
isCheckout={ true }
|
||||
isShippingCalculatorOpen={ false }
|
||||
setIsShippingCalculatorOpen={ jest.fn() }
|
||||
/>
|
||||
);
|
||||
expect(
|
||||
screen.getByText( 'No available delivery option' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -151,7 +151,7 @@ export const HandPickedProductsControlField = ( {
|
|||
return (
|
||||
<FormTokenField
|
||||
displayTransform={ transformTokenIntoProductName }
|
||||
label={ __( 'Hand-Picked Products', 'woocommerce' ) }
|
||||
label={ __( 'Hand-Picked', 'woocommerce' ) }
|
||||
onChange={ onTokenChange }
|
||||
onInputChange={ isLargeCatalog ? handleSearch : undefined }
|
||||
suggestions={ suggestions }
|
||||
|
@ -186,7 +186,7 @@ const HandPickedProductsControl = ( {
|
|||
|
||||
return (
|
||||
<ToolsPanelItem
|
||||
label={ __( 'Hand-Picked Products', 'woocommerce' ) }
|
||||
label={ __( 'Hand-Picked', 'woocommerce' ) }
|
||||
hasValue={ () => !! selectedProductIds?.length }
|
||||
onDeselect={ deselectCallback }
|
||||
resetAllFilter={ deselectCallback }
|
||||
|
|
|
@ -49,7 +49,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
|
|||
|
||||
return (
|
||||
<ToolsPanelItem
|
||||
label={ __( 'Stock status', 'woocommerce' ) }
|
||||
label={ __( 'Stock Status', 'woocommerce' ) }
|
||||
hasValue={ () =>
|
||||
! fastDeepEqual(
|
||||
query.woocommerceStockStatus,
|
||||
|
@ -61,7 +61,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
|
|||
isShownByDefault
|
||||
>
|
||||
<FormTokenField
|
||||
label={ __( 'Stock status', 'woocommerce' ) }
|
||||
label={ __( 'Stock Status', 'woocommerce' ) }
|
||||
onChange={ ( statusLabels ) => {
|
||||
const woocommerceStockStatus = statusLabels
|
||||
.map( getStockStatusIdByLabel )
|
||||
|
|
|
@ -48,6 +48,20 @@ function TaxonomyControls( {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the name so first letter of every word is capitalized.
|
||||
*/
|
||||
const normalizeName = ( name: string | undefined | null ) => {
|
||||
if ( ! name ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return name
|
||||
.split( ' ' )
|
||||
.map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) )
|
||||
.join( ' ' );
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{ taxonomies.map( ( taxonomy: Taxonomy ) => {
|
||||
|
@ -75,7 +89,7 @@ function TaxonomyControls( {
|
|||
return (
|
||||
<ToolsPanelItem
|
||||
key={ slug }
|
||||
label={ name }
|
||||
label={ normalizeName( name ) }
|
||||
hasValue={ () => termIds.length }
|
||||
onDeselect={ deselectCallback }
|
||||
resetAllFilter={ deselectCallback }
|
||||
|
|
|
@ -9,10 +9,6 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-item__value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.wc-block-components-totals-item__description {
|
||||
@include font-size(small);
|
||||
width: 100%;
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: Register Product Collection Tester
|
||||
* Description: A plugin to test the registerProductCollection function from WooCommerce Blocks.
|
||||
* Plugin Name: WooCommerce Blocks Test Register Product Collection
|
||||
* Description: Used to tests the registerProductCollection function.
|
||||
* Plugin URI: https://github.com/woocommerce/woocommerce
|
||||
* Author: WooCommerce
|
||||
*
|
||||
*
|
||||
* @package register-product-collection-tester
|
||||
* @package woocommerce-blocks-test-register-product-collection
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
|
@ -20,9 +18,9 @@ function register_product_collections_script()
|
|||
{
|
||||
wp_enqueue_script(
|
||||
'rpc_register_product_collections',
|
||||
plugins_url('register-product-collection-tester/index.js', __FILE__),
|
||||
plugins_url('register-product-collection.js', __FILE__),
|
||||
array('wp-element', 'wp-blocks', 'wp-i18n', 'wp-components', 'wp-editor', 'wc-blocks', 'wc-blocks-registry'),
|
||||
filemtime(plugin_dir_path(__FILE__) . 'register-product-collection-tester/index.js'),
|
||||
filemtime(plugin_dir_path(__FILE__) . 'register-product-collection.js'),
|
||||
true
|
||||
);
|
||||
}
|
|
@ -19,8 +19,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
|||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Product Collection', () => {
|
||||
test.describe( 'Collections', () => {
|
||||
test.describe( 'Product Collection: Collections', () => {
|
||||
test( 'New Arrivals Collection can be added and displays proper products', async ( {
|
||||
pageObject,
|
||||
} ) => {
|
||||
|
@ -53,9 +52,7 @@ test.describe( 'Product Collection', () => {
|
|||
];
|
||||
|
||||
await expect( pageObject.products ).toHaveCount( 5 );
|
||||
await expect( pageObject.productTitles ).toHaveText(
|
||||
topRatedProducts
|
||||
);
|
||||
await expect( pageObject.productTitles ).toHaveText( topRatedProducts );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
|
||||
|
@ -102,9 +99,7 @@ test.describe( 'Product Collection', () => {
|
|||
];
|
||||
|
||||
await expect( pageObject.products ).toHaveCount( 5 );
|
||||
await expect( pageObject.productTitles ).toHaveText(
|
||||
onSaleProducts
|
||||
);
|
||||
await expect( pageObject.productTitles ).toHaveText( onSaleProducts );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
|
||||
|
@ -124,9 +119,7 @@ test.describe( 'Product Collection', () => {
|
|||
];
|
||||
|
||||
await expect( pageObject.products ).toHaveCount( 4 );
|
||||
await expect( pageObject.productTitles ).toHaveText(
|
||||
featuredProducts
|
||||
);
|
||||
await expect( pageObject.productTitles ).toHaveText( featuredProducts );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
|
||||
|
@ -218,4 +211,3 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -86,8 +86,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
|||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Compatibility Layer with Product Collection block', () => {
|
||||
test.describe( 'Product Archive with Product Collection block', () => {
|
||||
test.describe( 'Product Collection: Compatibility Layer', () => {
|
||||
test.beforeEach( async ( { pageObject, requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-product-collection-compatibility-layer'
|
||||
|
@ -117,4 +116,3 @@ test.describe( 'Compatibility Layer with Product Collection block', () => {
|
|||
} );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -19,7 +19,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
|||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Product Collection - extensibility JS events', () => {
|
||||
test.describe( 'Product Collection: Extensibility Events', () => {
|
||||
test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( {
|
||||
pageObject,
|
||||
page,
|
||||
|
|
|
@ -19,28 +19,21 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
|||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Product Collection', () => {
|
||||
test.describe( 'Inspector Controls', () => {
|
||||
test.describe( 'Product Collection: Inspector Controls', () => {
|
||||
test( 'Reflects the correct number of columns according to sidebar settings', async ( {
|
||||
pageObject,
|
||||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock();
|
||||
|
||||
await pageObject.setNumberOfColumns( 2 );
|
||||
await expect( pageObject.productTemplate ).toHaveClass(
|
||||
/columns-2/
|
||||
);
|
||||
await expect( pageObject.productTemplate ).toHaveClass( /columns-2/ );
|
||||
|
||||
await pageObject.setNumberOfColumns( 4 );
|
||||
await expect( pageObject.productTemplate ).toHaveClass(
|
||||
/columns-4/
|
||||
);
|
||||
await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ );
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
|
||||
await expect( pageObject.productTemplate ).toHaveClass(
|
||||
/columns-4/
|
||||
);
|
||||
await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ );
|
||||
} );
|
||||
|
||||
test( 'Order By - sort products by title in descending order correctly', async ( {
|
||||
|
@ -104,9 +97,9 @@ test.describe( 'Product Collection', () => {
|
|||
} ) => {
|
||||
await pageObject.createNewPostAndInsertBlock();
|
||||
|
||||
await pageObject.addFilter( 'Show Hand-picked Products' );
|
||||
await pageObject.addFilter( 'Show Hand-picked' );
|
||||
|
||||
const filterName = 'Hand-picked Products';
|
||||
const filterName = 'Hand-picked';
|
||||
await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] );
|
||||
await expect( pageObject.products ).toHaveCount( 1 );
|
||||
|
||||
|
@ -144,9 +137,7 @@ test.describe( 'Product Collection', () => {
|
|||
|
||||
const filterName = 'Product categories';
|
||||
await pageObject.addFilter( 'Show product categories' );
|
||||
await pageObject.setFilterComboboxValue( filterName, [
|
||||
'Clothing',
|
||||
] );
|
||||
await pageObject.setFilterComboboxValue( filterName, [ 'Clothing' ] );
|
||||
await expect( pageObject.productTitles ).toHaveText( [
|
||||
'Logo Collection',
|
||||
] );
|
||||
|
@ -560,9 +551,7 @@ test.describe( 'Product Collection', () => {
|
|||
|
||||
await page.getByLabel( 'Toggle block inserter' ).click();
|
||||
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
||||
await page
|
||||
.getByPlaceholder( 'Search' )
|
||||
.fill( 'product filters' );
|
||||
await page.getByPlaceholder( 'Search' ).fill( 'product filters' );
|
||||
await page.getByLabel( 'Product Filters' ).click();
|
||||
|
||||
const postId = await editor.publishPost();
|
||||
|
@ -611,9 +600,7 @@ test.describe( 'Product Collection', () => {
|
|||
|
||||
await page.getByLabel( 'Toggle block inserter' ).click();
|
||||
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
||||
await page
|
||||
.getByPlaceholder( 'Search' )
|
||||
.fill( 'product filters' );
|
||||
await page.getByPlaceholder( 'Search' ).fill( 'product filters' );
|
||||
await page.getByLabel( 'Product Filters' ).click();
|
||||
|
||||
await expect( pageObject.products ).toHaveCount( 2 );
|
||||
|
@ -636,4 +623,3 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -9,7 +9,6 @@ import { test as base, expect } from '@woocommerce/e2e-utils';
|
|||
*/
|
||||
import ProductCollectionPage, {
|
||||
BLOCK_LABELS,
|
||||
Collections,
|
||||
SELECTORS,
|
||||
} from './product-collection.page';
|
||||
|
||||
|
@ -87,11 +86,7 @@ test.describe( 'Product Collection', () => {
|
|||
await admin.createNewPost();
|
||||
} );
|
||||
|
||||
test.skip( 'does not render', async ( {
|
||||
page,
|
||||
editor,
|
||||
pageObject,
|
||||
} ) => {
|
||||
test( 'does not render', async ( { page, editor, pageObject } ) => {
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost( 'featured' );
|
||||
await pageObject.addFilter( 'Price Range' );
|
||||
|
@ -106,7 +101,7 @@ test.describe( 'Product Collection', () => {
|
|||
).toBeVisible();
|
||||
// The "No results found" info is rendered in editor for all collections.
|
||||
await expect(
|
||||
featuredBlock.getByText( 'No results found' )
|
||||
featuredBlock.getByText( 'No products to display' )
|
||||
).toBeVisible();
|
||||
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
|
@ -114,7 +109,9 @@ test.describe( 'Product Collection', () => {
|
|||
const content = page.locator( 'main' );
|
||||
|
||||
await expect( content ).not.toContainText( 'Featured products' );
|
||||
await expect( content ).not.toContainText( 'No results found' );
|
||||
await expect( content ).not.toContainText(
|
||||
'No products to display'
|
||||
);
|
||||
} );
|
||||
|
||||
// This test ensures the runtime render state is correctly reset for
|
||||
|
@ -739,7 +736,7 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
} );
|
||||
|
||||
const templates = {
|
||||
const templates = [
|
||||
// This test is disabled because archives are disabled for attributes by default. This can be uncommented when this is toggled on.
|
||||
//'taxonomy-product_attribute': {
|
||||
// templateTitle: 'Product Attribute',
|
||||
|
@ -747,43 +744,44 @@ test.describe( 'Product Collection', () => {
|
|||
// frontendPage: '/product-attribute/color/',
|
||||
// legacyBlockName: 'woocommerce/legacy-template',
|
||||
//},
|
||||
'taxonomy-product_cat': {
|
||||
{
|
||||
templateTitle: 'Product Category',
|
||||
slug: 'taxonomy-product_cat',
|
||||
frontendPage: '/product-category/music/',
|
||||
legacyBlockName: 'woocommerce/legacy-template',
|
||||
expectedProductsCount: 2,
|
||||
},
|
||||
'taxonomy-product_tag': {
|
||||
{
|
||||
templateTitle: 'Product Tag',
|
||||
slug: 'taxonomy-product_tag',
|
||||
frontendPage: '/product-tag/recommended/',
|
||||
legacyBlockName: 'woocommerce/legacy-template',
|
||||
expectedProductsCount: 2,
|
||||
},
|
||||
'archive-product': {
|
||||
{
|
||||
templateTitle: 'Product Catalog',
|
||||
slug: 'archive-product',
|
||||
frontendPage: '/shop/',
|
||||
legacyBlockName: 'woocommerce/legacy-template',
|
||||
expectedProductsCount: 16,
|
||||
},
|
||||
'product-search-results': {
|
||||
{
|
||||
templateTitle: 'Product Search Results',
|
||||
slug: 'product-search-results',
|
||||
frontendPage: '/?s=shirt&post_type=product',
|
||||
legacyBlockName: 'woocommerce/legacy-template',
|
||||
expectedProductsCount: 3,
|
||||
},
|
||||
};
|
||||
];
|
||||
|
||||
for ( const {
|
||||
templates.forEach(
|
||||
( {
|
||||
templateTitle,
|
||||
slug,
|
||||
frontendPage,
|
||||
legacyBlockName,
|
||||
expectedProductsCount,
|
||||
} of Object.values( templates ) ) {
|
||||
} ) => {
|
||||
test.describe( `${ templateTitle } template`, () => {
|
||||
test( 'Product Collection block matches with classic template block', async ( {
|
||||
pageObject,
|
||||
|
@ -843,6 +841,7 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
} );
|
||||
}
|
||||
);
|
||||
test.describe( 'Editor: In taxonomies templates', () => {
|
||||
test( 'Products by specific category template displays products from this category', async ( {
|
||||
admin,
|
||||
|
@ -906,309 +905,3 @@ test.describe( 'Product Collection', () => {
|
|||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'Testing "usesReference" argument in "registerProductCollection"', () => {
|
||||
const MY_REGISTERED_COLLECTIONS = {
|
||||
myCustomCollectionWithProductContext: {
|
||||
name: 'My Custom Collection - Product Context',
|
||||
label: 'Block: My Custom Collection - Product Context',
|
||||
previewLabelTemplate: [ 'woocommerce/woocommerce//single-product' ],
|
||||
shouldShowProductPicker: true,
|
||||
},
|
||||
myCustomCollectionWithCartContext: {
|
||||
name: 'My Custom Collection - Cart Context',
|
||||
label: 'Block: My Custom Collection - Cart Context',
|
||||
previewLabelTemplate: [ 'woocommerce/woocommerce//page-cart' ],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
myCustomCollectionWithOrderContext: {
|
||||
name: 'My Custom Collection - Order Context',
|
||||
label: 'Block: My Custom Collection - Order Context',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//order-confirmation',
|
||||
],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
myCustomCollectionWithArchiveContext: {
|
||||
name: 'My Custom Collection - Archive Context',
|
||||
label: 'Block: My Custom Collection - Archive Context',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//taxonomy-product_cat',
|
||||
],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
myCustomCollectionMultipleContexts: {
|
||||
name: 'My Custom Collection - Multiple Contexts',
|
||||
label: 'Block: My Custom Collection - Multiple Contexts',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//single-product',
|
||||
'woocommerce/woocommerce//order-confirmation',
|
||||
],
|
||||
shouldShowProductPicker: true,
|
||||
},
|
||||
};
|
||||
|
||||
// Activate plugin which registers custom product collections
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'register-product-collection-tester'
|
||||
);
|
||||
} );
|
||||
|
||||
Object.entries( MY_REGISTERED_COLLECTIONS ).forEach(
|
||||
( [ key, collection ] ) => {
|
||||
for ( const template of collection.previewLabelTemplate ) {
|
||||
test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToEditorTemplate( template );
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInTemplate(
|
||||
key as Collections
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
} );
|
||||
}
|
||||
|
||||
test( `Collection "${ collection.name }" should not show preview label in a post`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
admin,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost( key as Collections );
|
||||
|
||||
// Check visibility of product picker
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
const expectedVisibility = collection.shouldShowProductPicker
|
||||
? 'toBeVisible'
|
||||
: 'toBeHidden';
|
||||
await expect( editorProductPicker )[ expectedVisibility ]();
|
||||
|
||||
if ( collection.shouldShowProductPicker ) {
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
}
|
||||
|
||||
// At this point, the product picker should be hidden
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Check visibility of preview label
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( `Collection "${ collection.name }" should not show preview label in Product Catalog template`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToProductCatalogAndInsertCollection(
|
||||
key as Collections
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
test.describe( 'Product picker', () => {
|
||||
const MY_REGISTERED_COLLECTIONS_THAT_NEEDS_PRODUCT = {
|
||||
myCustomCollectionWithProductContext: {
|
||||
name: 'My Custom Collection - Product Context',
|
||||
label: 'Block: My Custom Collection - Product Context',
|
||||
collection:
|
||||
'woocommerce/product-collection/my-custom-collection-product-context',
|
||||
},
|
||||
myCustomCollectionMultipleContexts: {
|
||||
name: 'My Custom Collection - Multiple Contexts',
|
||||
label: 'Block: My Custom Collection - Multiple Contexts',
|
||||
collection:
|
||||
'woocommerce/product-collection/my-custom-collection-multiple-contexts',
|
||||
},
|
||||
};
|
||||
|
||||
// Activate plugin which registers custom product collections
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'register-product-collection-tester'
|
||||
);
|
||||
} );
|
||||
|
||||
Object.entries( MY_REGISTERED_COLLECTIONS_THAT_NEEDS_PRODUCT ).forEach(
|
||||
( [ key, collection ] ) => {
|
||||
test( `For collection "${ collection.name }" - manually selected product reference should be available on Frontend in a post`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
page,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost( key as Collections );
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// On Frontend, verify that product reference is a number
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
const collectionWithProductContext = page.locator(
|
||||
`[data-collection="${ collection.collection }"]`
|
||||
);
|
||||
const queryAttribute = JSON.parse(
|
||||
( await collectionWithProductContext.getAttribute(
|
||||
'data-query'
|
||||
) ) || '{}'
|
||||
);
|
||||
expect( typeof queryAttribute?.productReference ).toBe(
|
||||
'number'
|
||||
);
|
||||
} );
|
||||
|
||||
test( `For collection "${ collection.name }" - changing product using inspector control`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
page,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost( key as Collections );
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Verify that Album is selected
|
||||
await expect(
|
||||
admin.page.locator( SELECTORS.linkedProductControl.button )
|
||||
).toContainText( 'Album' );
|
||||
|
||||
// Change product using inspector control to Beanie
|
||||
await admin.page
|
||||
.locator( SELECTORS.linkedProductControl.button )
|
||||
.click();
|
||||
await admin.page
|
||||
.locator( SELECTORS.linkedProductControl.popoverContent )
|
||||
.getByLabel( 'Beanie', { exact: true } )
|
||||
.click();
|
||||
await expect(
|
||||
admin.page.locator( SELECTORS.linkedProductControl.button )
|
||||
).toContainText( 'Beanie' );
|
||||
|
||||
// On Frontend, verify that product reference is a number
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
const collectionWithProductContext = page.locator(
|
||||
`[data-collection="${ collection.collection }"]`
|
||||
);
|
||||
const queryAttribute = JSON.parse(
|
||||
( await collectionWithProductContext.getAttribute(
|
||||
'data-query'
|
||||
) ) || '{}'
|
||||
);
|
||||
expect( typeof queryAttribute?.productReference ).toBe(
|
||||
'number'
|
||||
);
|
||||
} );
|
||||
|
||||
test( `For collection "${ collection.name }" - "From current product" is chosen by default`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//single-product`,
|
||||
postType: 'wp_template',
|
||||
canvas: 'edit',
|
||||
} );
|
||||
await editor.canvas.locator( 'body' ).click();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInTemplate(
|
||||
key as Collections
|
||||
);
|
||||
|
||||
const productToShowControl = admin.page.getByText(
|
||||
'From the current product'
|
||||
);
|
||||
await expect( productToShowControl ).toBeChecked();
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
test( 'Product picker should work as expected while changing collection using "Choose collection" button from Toolbar', async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost(
|
||||
'myCustomCollectionWithProductContext'
|
||||
);
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Change collection using Toolbar
|
||||
await pageObject.changeCollectionUsingToolbar(
|
||||
'myCustomCollectionMultipleContexts'
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Product picker should be hidden for collections that don't need product
|
||||
await pageObject.changeCollectionUsingToolbar( 'featured' );
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -396,7 +396,7 @@ class ProductCollectionPage {
|
|||
|
||||
async addFilter(
|
||||
name:
|
||||
| 'Show Hand-picked Products'
|
||||
| 'Show Hand-picked'
|
||||
| 'Keyword'
|
||||
| 'Show product categories'
|
||||
| 'Show product tags'
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { test as base, expect } from '@woocommerce/e2e-utils';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import ProductCollectionPage, {
|
||||
Collections,
|
||||
SELECTORS,
|
||||
} from './product-collection.page';
|
||||
|
||||
const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
||||
pageObject: async ( { page, admin, editor }, use ) => {
|
||||
const pageObject = new ProductCollectionPage( {
|
||||
page,
|
||||
admin,
|
||||
editor,
|
||||
} );
|
||||
await use( pageObject );
|
||||
},
|
||||
} );
|
||||
|
||||
test.describe( 'Product Collection: Product Picker', () => {
|
||||
const CUSTOM_COLLECTIONS = [
|
||||
{
|
||||
id: 'myCustomCollectionWithProductContext',
|
||||
name: 'My Custom Collection - Product Context',
|
||||
label: 'Block: My Custom Collection - Product Context',
|
||||
collection:
|
||||
'woocommerce/product-collection/my-custom-collection-product-context',
|
||||
},
|
||||
{
|
||||
id: 'myCustomCollectionMultipleContexts',
|
||||
name: 'My Custom Collection - Multiple Contexts',
|
||||
label: 'Block: My Custom Collection - Multiple Contexts',
|
||||
collection:
|
||||
'woocommerce/product-collection/my-custom-collection-multiple-contexts',
|
||||
},
|
||||
];
|
||||
|
||||
// Activate plugin which registers custom product collections
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'woocommerce-blocks-test-register-product-collection'
|
||||
);
|
||||
} );
|
||||
|
||||
CUSTOM_COLLECTIONS.forEach( ( collection ) => {
|
||||
test( `For collection "${ collection.name }" - manually selected product reference should be available on Frontend in a post`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
page,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// On Frontend, verify that product reference is a number
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
const collectionWithProductContext = page.locator(
|
||||
`[data-collection="${ collection.collection }"]`
|
||||
);
|
||||
const queryAttribute = JSON.parse(
|
||||
( await collectionWithProductContext.getAttribute(
|
||||
'data-query'
|
||||
) ) || '{}'
|
||||
);
|
||||
expect( typeof queryAttribute?.productReference ).toBe( 'number' );
|
||||
} );
|
||||
|
||||
test( `For collection "${ collection.name }" - changing product using inspector control`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
page,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Verify that Album is selected
|
||||
await expect(
|
||||
admin.page.locator( SELECTORS.linkedProductControl.button )
|
||||
).toContainText( 'Album' );
|
||||
|
||||
// Change product using inspector control to Beanie
|
||||
await admin.page
|
||||
.locator( SELECTORS.linkedProductControl.button )
|
||||
.click();
|
||||
await admin.page
|
||||
.locator( SELECTORS.linkedProductControl.popoverContent )
|
||||
.getByLabel( 'Beanie', { exact: true } )
|
||||
.click();
|
||||
await expect(
|
||||
admin.page.locator( SELECTORS.linkedProductControl.button )
|
||||
).toContainText( 'Beanie' );
|
||||
|
||||
// On Frontend, verify that product reference is a number
|
||||
await pageObject.publishAndGoToFrontend();
|
||||
const collectionWithProductContext = page.locator(
|
||||
`[data-collection="${ collection.collection }"]`
|
||||
);
|
||||
const queryAttribute = JSON.parse(
|
||||
( await collectionWithProductContext.getAttribute(
|
||||
'data-query'
|
||||
) ) || '{}'
|
||||
);
|
||||
expect( typeof queryAttribute?.productReference ).toBe( 'number' );
|
||||
} );
|
||||
|
||||
test( `For collection "${ collection.name }" - "From current product" is chosen by default`, async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.visitSiteEditor( {
|
||||
postId: `woocommerce/woocommerce//single-product`,
|
||||
postType: 'wp_template',
|
||||
canvas: 'edit',
|
||||
} );
|
||||
await editor.canvas.locator( 'body' ).click();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInTemplate(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
const productToShowControl = admin.page.getByText(
|
||||
'From the current product'
|
||||
);
|
||||
await expect( productToShowControl ).toBeChecked();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'Product picker should work as expected while changing collection using "Choose collection" button from Toolbar', async ( {
|
||||
pageObject,
|
||||
admin,
|
||||
editor,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost(
|
||||
'myCustomCollectionWithProductContext'
|
||||
);
|
||||
|
||||
// Verify that product picker is shown in Editor
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Change collection using Toolbar
|
||||
await pageObject.changeCollectionUsingToolbar(
|
||||
'myCustomCollectionMultipleContexts'
|
||||
);
|
||||
await expect( editorProductPicker ).toBeVisible();
|
||||
|
||||
// Once a product is selected, the product picker should be hidden
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Product picker should be hidden for collections that don't need product
|
||||
await pageObject.changeCollectionUsingToolbar( 'featured' );
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
} );
|
||||
} );
|
|
@ -33,7 +33,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
|||
* These E2E tests are for `registerProductCollection` which we are exposing
|
||||
* for 3PDs to register new product collections.
|
||||
*/
|
||||
test.describe( 'Product Collection registration', () => {
|
||||
test.describe( 'Product Collection: Register Product Collection', () => {
|
||||
const MY_REGISTERED_COLLECTIONS = {
|
||||
myCustomCollection: {
|
||||
name: 'My Custom Collection',
|
||||
|
@ -56,7 +56,7 @@ test.describe( 'Product Collection registration', () => {
|
|||
// Activate plugin which registers custom product collections
|
||||
test.beforeEach( async ( { requestUtils } ) => {
|
||||
await requestUtils.activatePlugin(
|
||||
'register-product-collection-tester'
|
||||
'woocommerce-blocks-test-register-product-collection'
|
||||
);
|
||||
} );
|
||||
|
||||
|
@ -323,7 +323,7 @@ test.describe( 'Product Collection registration', () => {
|
|||
],
|
||||
},
|
||||
].forEach( ( collection ) => {
|
||||
for ( const template of collection.previewLabelTemplate ) {
|
||||
collection.previewLabelTemplate.forEach( ( template ) => {
|
||||
test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
|
@ -341,7 +341,7 @@ test.describe( 'Product Collection registration', () => {
|
|||
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
test( `Collection "${ collection.name }" should not show preview label in a post`, async ( {
|
||||
pageObject,
|
||||
|
@ -429,7 +429,7 @@ test.describe( 'Product Collection registration', () => {
|
|||
} );
|
||||
|
||||
// Product picker should be shown in Editor
|
||||
await admin.page.reload();
|
||||
await page.reload();
|
||||
const deletedProductPicker = editor.canvas.getByText(
|
||||
'Previously selected product'
|
||||
);
|
||||
|
@ -465,4 +465,127 @@ test.describe( 'Product Collection registration', () => {
|
|||
await page.reload();
|
||||
await expect( deletedProductPicker ).toBeVisible();
|
||||
} );
|
||||
|
||||
test.describe( 'with "usesReference" argument', () => {
|
||||
[
|
||||
{
|
||||
id: 'myCustomCollectionWithProductContext',
|
||||
name: 'My Custom Collection - Product Context',
|
||||
label: 'Block: My Custom Collection - Product Context',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//single-product',
|
||||
],
|
||||
shouldShowProductPicker: true,
|
||||
},
|
||||
{
|
||||
id: 'myCustomCollectionWithCartContext',
|
||||
name: 'My Custom Collection - Cart Context',
|
||||
label: 'Block: My Custom Collection - Cart Context',
|
||||
previewLabelTemplate: [ 'woocommerce/woocommerce//page-cart' ],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
{
|
||||
id: 'myCustomCollectionWithOrderContext',
|
||||
name: 'My Custom Collection - Order Context',
|
||||
label: 'Block: My Custom Collection - Order Context',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//order-confirmation',
|
||||
],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
{
|
||||
id: 'myCustomCollectionWithArchiveContext',
|
||||
name: 'My Custom Collection - Archive Context',
|
||||
label: 'Block: My Custom Collection - Archive Context',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//taxonomy-product_cat',
|
||||
],
|
||||
shouldShowProductPicker: false,
|
||||
},
|
||||
{
|
||||
id: 'myCustomCollectionMultipleContexts',
|
||||
name: 'My Custom Collection - Multiple Contexts',
|
||||
label: 'Block: My Custom Collection - Multiple Contexts',
|
||||
previewLabelTemplate: [
|
||||
'woocommerce/woocommerce//single-product',
|
||||
'woocommerce/woocommerce//order-confirmation',
|
||||
],
|
||||
shouldShowProductPicker: true,
|
||||
},
|
||||
].forEach( ( collection ) => {
|
||||
collection.previewLabelTemplate.forEach( ( template ) => {
|
||||
test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToEditorTemplate( template );
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInTemplate(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( `Collection "${ collection.name }" should not show preview label in a post`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
admin,
|
||||
} ) => {
|
||||
await admin.createNewPost();
|
||||
await pageObject.insertProductCollection();
|
||||
await pageObject.chooseCollectionInPost(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
// Check visibility of product picker
|
||||
const editorProductPicker = editor.canvas.locator(
|
||||
SELECTORS.productPicker
|
||||
);
|
||||
const expectedVisibility = collection.shouldShowProductPicker
|
||||
? 'toBeVisible'
|
||||
: 'toBeHidden';
|
||||
await expect( editorProductPicker )[ expectedVisibility ]();
|
||||
|
||||
if ( collection.shouldShowProductPicker ) {
|
||||
await pageObject.chooseProductInEditorProductPickerIfAvailable(
|
||||
editor.canvas
|
||||
);
|
||||
}
|
||||
|
||||
// At this point, the product picker should be hidden
|
||||
await expect( editorProductPicker ).toBeHidden();
|
||||
|
||||
// Check visibility of preview label
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
|
||||
test( `Collection "${ collection.name }" should not show preview label in Product Catalog template`, async ( {
|
||||
pageObject,
|
||||
editor,
|
||||
} ) => {
|
||||
await pageObject.goToProductCatalogAndInsertCollection(
|
||||
collection.id as Collections
|
||||
);
|
||||
|
||||
const block = editor.canvas.getByLabel( collection.label );
|
||||
const previewButtonLocator = block.getByTestId(
|
||||
SELECTORS.previewButtonTestID
|
||||
);
|
||||
|
||||
await expect( previewButtonLocator ).toBeHidden();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -10,7 +10,6 @@ export * from './mini-cart';
|
|||
export * from './performance';
|
||||
export * from './request-utils';
|
||||
export * from './shipping';
|
||||
export * from './storeApi';
|
||||
|
||||
export * from './test';
|
||||
|
||||
|
|
|
@ -15,33 +15,6 @@ import {
|
|||
} from './templates';
|
||||
|
||||
export class RequestUtils extends CoreRequestUtils {
|
||||
// The `setup` override is necessary only until
|
||||
// https://github.com/WordPress/gutenberg/pull/59362 is merged.
|
||||
static async setup( ...args: Parameters< typeof CoreRequestUtils.setup > ) {
|
||||
const { request, user, storageState, storageStatePath, baseURL } =
|
||||
await CoreRequestUtils.setup( ...args );
|
||||
|
||||
// We need those checks to satisfy TypeScript.
|
||||
if ( ! storageState ) {
|
||||
throw new Error( 'Storage state is required' );
|
||||
}
|
||||
|
||||
if ( ! storageStatePath ) {
|
||||
throw new Error( 'Storage state path is required' );
|
||||
}
|
||||
|
||||
if ( ! baseURL ) {
|
||||
throw new Error( 'Base URL is required' );
|
||||
}
|
||||
|
||||
return new this( request, {
|
||||
user,
|
||||
storageState,
|
||||
storageStatePath,
|
||||
baseURL,
|
||||
} );
|
||||
}
|
||||
|
||||
/** @borrows getTemplates as this.getTemplates */
|
||||
getTemplates: typeof getTemplates = getTemplates.bind( this );
|
||||
/** @borrows revertTemplate as this.revertTemplate */
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
export * from './store-api-utils.page';
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { RequestUtils } from '@wordpress/e2e-test-utils-playwright';
|
||||
|
||||
export class StoreApiUtils {
|
||||
private requestUtils: RequestUtils;
|
||||
|
||||
constructor( requestUtils: RequestUtils ) {
|
||||
this.requestUtils = requestUtils;
|
||||
}
|
||||
|
||||
// @todo: It is necessary work to a middleware to avoid this kind of code.
|
||||
async cleanCart() {
|
||||
const response = await this.requestUtils.request.get(
|
||||
'/wp-json/wc/store/cart'
|
||||
);
|
||||
|
||||
const { nonce } = response.headers();
|
||||
|
||||
await this.requestUtils.request.delete(
|
||||
`/wp-json/wc/store/v1/cart/items`,
|
||||
{
|
||||
headers: {
|
||||
nonce,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,6 @@ import {
|
|||
PerformanceUtils,
|
||||
RequestUtils,
|
||||
ShippingUtils,
|
||||
StoreApiUtils,
|
||||
} from '@woocommerce/e2e-utils';
|
||||
|
||||
/**
|
||||
|
@ -108,7 +107,6 @@ const test = base.extend<
|
|||
editor: Editor;
|
||||
pageUtils: PageUtils;
|
||||
frontendUtils: FrontendUtils;
|
||||
storeApiUtils: StoreApiUtils;
|
||||
performanceUtils: PerformanceUtils;
|
||||
snapshotConfig: void;
|
||||
shippingUtils: ShippingUtils;
|
||||
|
@ -135,6 +133,10 @@ const test = base.extend<
|
|||
window.localStorage.clear();
|
||||
} );
|
||||
|
||||
// Dispose the current APIRequestContext to free up resources.
|
||||
await page.request.dispose();
|
||||
|
||||
// Reset the database to the initial state via snapshot import.
|
||||
await wpCLI( `db import ${ DB_EXPORT_FILE }` );
|
||||
},
|
||||
pageUtils: async ( { page }, use ) => {
|
||||
|
@ -146,9 +148,6 @@ const test = base.extend<
|
|||
performanceUtils: async ( { page }, use ) => {
|
||||
await use( new PerformanceUtils( page ) );
|
||||
},
|
||||
storeApiUtils: async ( { requestUtils }, use ) => {
|
||||
await use( new StoreApiUtils( requestUtils ) );
|
||||
},
|
||||
shippingUtils: async ( { page, admin }, use ) => {
|
||||
await use( new ShippingUtils( page, admin ) );
|
||||
},
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
Comment: These are changes to build commands.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Add missing `wp-block-woocommerce-{name}` class to Add to Cart with Options, Product Image, Product Rating, Product Rating Stars, Product Rating Counter and Product Image blocks.
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Resolved fatal error when applying Brands-restricted coupon
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: tweak
|
||||
|
||||
Changes to copy and styling of delivery summary in checkout block.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Expand the e2e suite we're running on WPCOM part #5.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Return an empty string from `template_include` filter instead of null to avoid PHP fatal error with conflicting plugins using strict types.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: tweak
|
||||
|
||||
Ensure Product Collection filter names are consistently capitalized.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: add
|
||||
|
||||
Add a11y to the color swatches
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add core feature for site visibility badge
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Render Advanced CSS Classes in the Product Image block
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Fix an issue where database is randomly disconnected while running Blocks E2E tests.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Blocks E2E: fix plugin namespace
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Remove alert role from informational notices
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix subscription status and action items for free (lifetime) subscriptions
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
CI: update performance metrics job benchmarking.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
CI: omit caching baseline results in perfromance metrics job.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
CI: update perfromance metrics job and improve execution time.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Changed how we fetch WooCommerce promotions. We're doing it async so as not to affect the loading of wp-admin.
|
|
@ -8795,3 +8795,8 @@ table.bar_chart {
|
|||
html:has(#status-table-templates){
|
||||
scroll-padding-top: 80px;
|
||||
}
|
||||
|
||||
// Fix for Safari bug: https://bugs.webkit.org/show_bug.cgi?id=280063.
|
||||
.woocommerce-admin-page #postbox-container-2 {
|
||||
clear: left;
|
||||
}
|
|
@ -15,6 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
*/
|
||||
class WC_Admin_Marketplace_Promotions {
|
||||
|
||||
const CRON_NAME = 'woocommerce_marketplace_cron_fetch_promotions';
|
||||
const TRANSIENT_NAME = 'woocommerce_marketplace_promotions_v2';
|
||||
const TRANSIENT_LIFE_SPAN = DAY_IN_SECONDS;
|
||||
const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions';
|
||||
|
@ -39,7 +40,16 @@ class WC_Admin_Marketplace_Promotions {
|
|||
public static function init() {
|
||||
// A legacy hook that can be triggered by action scheduler.
|
||||
add_action( 'woocommerce_marketplace_fetch_promotions', array( __CLASS__, 'clear_deprecated_action' ) );
|
||||
add_action( 'woocommerce_marketplace_fetch_promotions_clear', array( __CLASS__, 'clear_scheduled_event' ) );
|
||||
add_action(
|
||||
'woocommerce_marketplace_fetch_promotions_clear',
|
||||
array(
|
||||
__CLASS__,
|
||||
'clear_deprecated_scheduled_event',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch promotions from the API and store them in a transient.
|
||||
add_action( self::CRON_NAME, array( __CLASS__, 'update_promotions' ) );
|
||||
|
||||
if (
|
||||
defined( 'DOING_AJAX' ) && DOING_AJAX
|
||||
|
@ -53,24 +63,33 @@ class WC_Admin_Marketplace_Promotions {
|
|||
return;
|
||||
}
|
||||
|
||||
self::maybe_update_promotions();
|
||||
self::schedule_cron_event();
|
||||
|
||||
register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_cron_event' ) );
|
||||
|
||||
self::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US';
|
||||
self::maybe_show_bubble_promotions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch promotions from the API and store them in a transient.
|
||||
* Fetching can be suppressed by the `woocommerce_marketplace_suppress_promotions` filter.
|
||||
* Schedule a daily cron event to fetch promotions.
|
||||
*
|
||||
* @version 9.5.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function maybe_update_promotions() {
|
||||
// Fetch promotions if they're not in the transient.
|
||||
if ( false !== get_transient( self::TRANSIENT_NAME ) ) {
|
||||
return;
|
||||
private static function schedule_cron_event() {
|
||||
if ( ! wp_next_scheduled( self::CRON_NAME ) ) {
|
||||
wp_schedule_event( time(), 'daily', self::CRON_NAME );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch promotions from the API and store them in a transient.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_promotions() {
|
||||
// Fetch promotions from the API.
|
||||
$promotions = self::fetch_marketplace_promotions();
|
||||
set_transient( self::TRANSIENT_NAME, $promotions, self::TRANSIENT_LIFE_SPAN );
|
||||
|
@ -326,12 +345,24 @@ class WC_Admin_Marketplace_Promotions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clear the scheduled action that was used to fetch promotions in WooCommerce 8.8.
|
||||
* It's no longer needed as a transient is used to store the data.
|
||||
* When WooCommerce is disabled, clear the WP Cron event we use to fetch promotions.
|
||||
*
|
||||
* @version 9.5.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_scheduled_event() {
|
||||
public static function clear_cron_event() {
|
||||
$timestamp = wp_next_scheduled( self::CRON_NAME );
|
||||
wp_unschedule_event( $timestamp, self::CRON_NAME );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear deprecated scheduled action that was used to fetch promotions in WooCommerce 8.8.
|
||||
* Replaced with a transient in WooCommerce 9.0.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clear_deprecated_scheduled_event() {
|
||||
if ( function_exists( 'as_unschedule_all_actions' ) ) {
|
||||
as_unschedule_all_actions( 'woocommerce_marketplace_fetch_promotions' );
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ class WC_Brands {
|
|||
public function __construct() {
|
||||
$this->template_url = apply_filters( 'woocommerce_template_url', 'woocommerce/' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
||||
|
||||
add_action( 'plugins_loaded', array( $this, 'register_hooks' ), 2 );
|
||||
add_action( 'plugins_loaded', array( $this, 'register_hooks' ), 11 );
|
||||
|
||||
$this->register_shortcodes();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
"license": "GPL-3.0+",
|
||||
"scripts": {
|
||||
"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
|
||||
"build:admin": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
|
||||
"build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
|
||||
"build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'",
|
||||
"build:zip": "./bin/build-zip.sh",
|
||||
"build:project": "pnpm --if-present '/^build:project:.*$/'",
|
||||
"build:project:copy-assets:legacy": "wireit",
|
||||
"build:project:copy-assets:admin": "wireit",
|
||||
|
@ -74,6 +78,9 @@
|
|||
"test:unit:env:watch": "pnpm test:php:env:watch",
|
||||
"update-wp-env": "php ./tests/e2e-pw/bin/update-wp-env.php",
|
||||
"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
|
||||
"watch:build:admin": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
|
||||
"watch:build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
|
||||
"watch:build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'",
|
||||
"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
|
||||
"watch:build:project:copy-assets": "wireit",
|
||||
"wp-env": "wp-env"
|
||||
|
@ -483,7 +490,6 @@
|
|||
"start": "test:perf:ci-setup"
|
||||
},
|
||||
"events": [
|
||||
"pull_request",
|
||||
"push"
|
||||
]
|
||||
},
|
||||
|
@ -504,6 +510,9 @@
|
|||
"tests/metrics/**",
|
||||
".wp-env.json"
|
||||
],
|
||||
"testEnv": {
|
||||
"start": "env:test"
|
||||
},
|
||||
"events": [
|
||||
"push"
|
||||
],
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
|
@ -99,16 +100,30 @@ class AddToCartForm extends AbstractBlock {
|
|||
$product = $this->add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block );
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$product_classname = $is_descendent_of_single_product_block ? 'product' : '';
|
||||
|
||||
$form = sprintf(
|
||||
'<div class="wp-block-add-to-cart-form wc-block-add-to-cart-form %1$s %2$s %3$s" style="%4$s">%5$s</div>',
|
||||
$classes = implode(
|
||||
' ',
|
||||
array_filter(
|
||||
array(
|
||||
'wp-block-add-to-cart-form wc-block-add-to-cart-form',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
esc_attr( $product_classname ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $classes,
|
||||
'style' => esc_attr( $classes_and_styles['styles'] ),
|
||||
)
|
||||
);
|
||||
|
||||
$form = sprintf(
|
||||
'<div %1$s>%2$s</div>',
|
||||
$wrapper_attributes,
|
||||
$product
|
||||
);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
|
|||
'font_size',
|
||||
'padding',
|
||||
'text_color',
|
||||
'extra_classes',
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -272,10 +273,6 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
|
|||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
||||
}
|
||||
|
||||
if ( isset( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
$global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper );
|
||||
|
||||
$classes[] = $global_style_classes;
|
||||
|
|
|
@ -414,11 +414,8 @@ class MiniCart extends AbstractBlock {
|
|||
return '';
|
||||
}
|
||||
|
||||
$classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family' ) );
|
||||
$classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family', 'extra_classes' ) );
|
||||
$wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] );
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$wrapper_classes .= ' ' . $attributes['className'];
|
||||
}
|
||||
$wrapper_styles = $classes_styles['styles'];
|
||||
|
||||
$icon_color = array_key_exists( 'iconColor', $attributes ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
|
||||
|
|
|
@ -36,16 +36,11 @@ abstract class AbstractOrderConfirmationBlock extends AbstractBlock {
|
|||
$order = $this->get_order();
|
||||
$permission = $this->get_view_order_permissions( $order );
|
||||
$block_content = $order ? $this->render_content( $order, $permission, $attributes, $content ) : $this->render_content_fallback();
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
if ( ! empty( $classes_and_styles['classes'] ) ) {
|
||||
$classname .= ' ' . $classes_and_styles['classes'];
|
||||
}
|
||||
|
||||
return $block_content ? sprintf(
|
||||
'<div class="wp-block-%5$s-%4$s wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
|
||||
esc_attr( trim( $classname ) ),
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$block_content,
|
||||
esc_attr( $this->block_name ),
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* Status class.
|
||||
*/
|
||||
|
@ -26,7 +28,7 @@ class Status extends AbstractOrderConfirmationBlock {
|
|||
*/
|
||||
protected function render( $attributes, $content, $block ) {
|
||||
$order = $this->get_order();
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
|
||||
if ( isset( $attributes['align'] ) ) {
|
||||
$classname .= " align{$attributes['align']}";
|
||||
|
|
|
@ -100,8 +100,8 @@ class ProductButton extends AbstractBlock {
|
|||
$ajax_add_to_cart_enabled = get_option( 'woocommerce_enable_ajax_add_to_cart' ) === 'yes';
|
||||
$is_ajax_button = $ajax_add_to_cart_enabled && ! $cart_redirect_after_add && $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && $product->is_in_stock();
|
||||
$html_element = $is_ajax_button ? 'button' : 'a';
|
||||
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
$custom_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
|
||||
$custom_align_classes = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : '';
|
||||
$html_classes = implode(
|
||||
|
|
|
@ -89,7 +89,7 @@ class ProductCategories extends AbstractDynamicBlock {
|
|||
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes(
|
||||
$attributes,
|
||||
array( 'line_height', 'text_color', 'font_size' )
|
||||
array( 'line_height', 'text_color', 'font_size', 'extra_classes' )
|
||||
);
|
||||
|
||||
$classes = $this->get_container_classes( $attributes ) . ' ' . $classes_and_styles['classes'];
|
||||
|
@ -116,10 +116,6 @@ class ProductCategories extends AbstractDynamicBlock {
|
|||
$classes[] = "align{$attributes['align']}";
|
||||
}
|
||||
|
||||
if ( ! empty( $attributes['className'] ) ) {
|
||||
$classes[] = $attributes['className'];
|
||||
}
|
||||
|
||||
if ( $attributes['isDropdown'] ) {
|
||||
$classes[] = 'is-dropdown';
|
||||
} else {
|
||||
|
|
|
@ -76,18 +76,15 @@ class ProductDetails extends AbstractBlock {
|
|||
$tabs = $tabs_html->get_updated_html();
|
||||
}
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
return sprintf(
|
||||
'<div class="wp-block-woocommerce-product-details %1$s %2$s">
|
||||
<div style="%3$s">
|
||||
%4$s
|
||||
'<div class="wp-block-woocommerce-product-details %1$s">
|
||||
<div style="%2$s">
|
||||
%3$s
|
||||
</div>
|
||||
</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classname ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$tabs
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
|||
|
||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* ProductGallery class.
|
||||
|
@ -125,7 +126,7 @@ class ProductGallery extends AbstractBlock {
|
|||
}
|
||||
|
||||
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
$dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : '';
|
||||
$product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
|
||||
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* ProductGalleryPager class.
|
||||
|
@ -55,7 +56,7 @@ class ProductGalleryPager extends AbstractBlock {
|
|||
}
|
||||
|
||||
$number_of_thumbnails = $block->context['thumbnailsNumberOfThumbnails'] ?? 0;
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classname ) ) );
|
||||
$post_id = $block->context['postId'] ?? '';
|
||||
$product = wc_get_product( $post_id );
|
||||
|
|
|
@ -208,13 +208,29 @@ class ProductImage extends AbstractBlock {
|
|||
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
|
||||
$product = wc_get_product( $post_id );
|
||||
|
||||
$classes = implode(
|
||||
' ',
|
||||
array_filter(
|
||||
array(
|
||||
'wc-block-components-product-image wc-block-grid__product-image',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $classes,
|
||||
'style' => esc_attr( $classes_and_styles['styles'] ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( $product ) {
|
||||
return sprintf(
|
||||
'<div class="wc-block-components-product-image wc-block-grid__product-image %1$s" style="%2$s">
|
||||
%3$s
|
||||
'<div %1$s>
|
||||
%2$s
|
||||
</div>',
|
||||
esc_attr( $classes_and_styles['classes'] ),
|
||||
esc_attr( $classes_and_styles['styles'] ),
|
||||
$wrapper_attributes,
|
||||
$this->render_anchor(
|
||||
$product,
|
||||
$this->render_on_sale_badge( $product, $parsed_attributes ),
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* ProductImageGallery class.
|
||||
*/
|
||||
|
@ -67,7 +69,7 @@ class ProductImageGallery extends AbstractBlock {
|
|||
$product_image_gallery_html = ob_get_clean();
|
||||
|
||||
$product = $previous_product;
|
||||
$classname = $attributes['className'] ?? '';
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
return sprintf(
|
||||
'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s %3$s</div>',
|
||||
esc_attr( $classname ),
|
||||
|
|
|
@ -202,13 +202,29 @@ class ProductRating extends AbstractBlock {
|
|||
10
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
|
||||
%4$s
|
||||
</div>',
|
||||
$classes = implode(
|
||||
' ',
|
||||
array_filter(
|
||||
array(
|
||||
'wc-block-components-product-rating wc-block-grid__product-rating',
|
||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||
esc_attr( $styles_and_classes['classes'] ),
|
||||
esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $classes,
|
||||
'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>
|
||||
%2$s
|
||||
</div>',
|
||||
$wrapper_attributes,
|
||||
$rating_html
|
||||
);
|
||||
}
|
||||
|
|
|
@ -193,17 +193,32 @@ class ProductRatingCounter extends AbstractBlock {
|
|||
10
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="wc-block-components-product-rating-counter wc-block-grid__product-rating-counter %1$s %2$s" style="%3$s">
|
||||
%4$s
|
||||
</div>',
|
||||
$classes = implode(
|
||||
' ',
|
||||
array_filter(
|
||||
array(
|
||||
'wc-block-components-product-rating-counter wc-block-grid__product-rating-counter',
|
||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||
esc_attr( $styles_and_classes['classes'] ),
|
||||
esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $classes,
|
||||
'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>
|
||||
%2$s
|
||||
</div>',
|
||||
$wrapper_attributes,
|
||||
$rating_html
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -149,13 +149,29 @@ class ProductRatingStars extends AbstractBlock {
|
|||
10
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
|
||||
%4$s
|
||||
</div>',
|
||||
$classes = implode(
|
||||
' ',
|
||||
array_filter(
|
||||
array(
|
||||
'wc-block-components-product-rating wc-block-grid__product-rating',
|
||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||
esc_attr( $styles_and_classes['classes'] ),
|
||||
esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$wrapper_attributes = get_block_wrapper_attributes(
|
||||
array(
|
||||
'class' => $classes,
|
||||
'style' => esc_attr( $styles_and_classes['styles'] ?? '' ),
|
||||
)
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'<div %1$s>
|
||||
%2$s
|
||||
</div>',
|
||||
$wrapper_attributes,
|
||||
$rating_html
|
||||
);
|
||||
}
|
||||
|
|
|
@ -54,7 +54,6 @@ class ProductResultsCount extends AbstractBlock {
|
|||
'wc-block-product-results-count',
|
||||
'wp-block-woocommerce-product-results-count',
|
||||
),
|
||||
isset( $attributes['className'] ) ? array( $attributes['className'] ) : array(),
|
||||
);
|
||||
$p->set_attribute( 'class', implode( ' ', $classes ) );
|
||||
$p->set_attribute( 'style', $parsed_style_attributes['styles'] );
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||
|
||||
/**
|
||||
* ProductReviews class.
|
||||
*/
|
||||
|
@ -40,13 +42,11 @@ class ProductReviews extends AbstractBlock {
|
|||
|
||||
$reviews = ob_get_clean();
|
||||
|
||||
$classname = $attributes['className'] ?? '';
|
||||
|
||||
return sprintf(
|
||||
'<div class="wp-block-woocommerce-product-reviews %1$s">
|
||||
%2$s
|
||||
</div>',
|
||||
esc_attr( $classname ),
|
||||
StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ),
|
||||
$reviews
|
||||
);
|
||||
}
|
||||
|
|
|
@ -110,8 +110,9 @@ class ProductSaleBadge extends AbstractBlock {
|
|||
return null;
|
||||
}
|
||||
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
|
||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
|
||||
|
||||
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||
|
||||
$align = isset( $attributes['align'] ) ? $attributes['align'] : '';
|
||||
|
||||
|
|
|
@ -101,7 +101,6 @@ class ProductStockIndicator extends AbstractBlock {
|
|||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||
|
||||
$classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : '';
|
||||
$classnames .= isset( $attributes['className'] ) ? ' ' . $attributes['className'] . ' ' : '';
|
||||
$classnames .= ! $is_in_stock ? ' wc-block-components-product-stock-indicator--out-of-stock ' : '';
|
||||
$classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-stock ' : '';
|
||||
$classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : '';
|
||||
|
|
|
@ -687,6 +687,25 @@ class StyleAttributesUtils {
|
|||
return self::EMPTY_STYLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra CSS classes from attributes.
|
||||
*
|
||||
* @param array $attributes Block attributes.
|
||||
* @return array
|
||||
*/
|
||||
public static function get_classes_from_attributes( $attributes ) {
|
||||
|
||||
$extra_css_classes = $attributes['className'] ?? '';
|
||||
|
||||
if ( '' !== $extra_css_classes ) {
|
||||
return array(
|
||||
'class' => $extra_css_classes,
|
||||
'style' => null,
|
||||
);
|
||||
}
|
||||
return self::EMPTY_STYLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get classes and styles from attributes.
|
||||
*
|
||||
|
@ -717,6 +736,7 @@ class StyleAttributesUtils {
|
|||
'text_color' => self::get_text_color_class_and_style( $attributes ),
|
||||
'text_decoration' => self::get_text_decoration_class_and_style( $attributes ),
|
||||
'text_transform' => self::get_text_transform_class_and_style( $attributes ),
|
||||
'extra_classes' => self::get_classes_from_attributes( $attributes ),
|
||||
);
|
||||
|
||||
if ( ! empty( $properties ) ) {
|
||||
|
|
|
@ -25,11 +25,6 @@ class Brands {
|
|||
return;
|
||||
}
|
||||
|
||||
// If the WooCommerce Brands plugin is activated via the WP CLI using the '--skip-plugins' flag, deactivate it here.
|
||||
if ( function_exists( 'wc_brands_init' ) ) {
|
||||
remove_action( 'plugins_loaded', 'wc_brands_init', 1 );
|
||||
}
|
||||
|
||||
include_once WC_ABSPATH . 'includes/class-wc-brands.php';
|
||||
include_once WC_ABSPATH . 'includes/class-wc-brands-coupons.php';
|
||||
include_once WC_ABSPATH . 'includes/class-wc-brands-brand-settings-manager.php';
|
||||
|
@ -58,4 +53,19 @@ class Brands {
|
|||
}
|
||||
return ( $assignment <= 6 ); // Considering 5% of the 0-120 range.
|
||||
}
|
||||
|
||||
/**
|
||||
* If WooCommerce Brands gets activated forcibly, without WooCommerce active (e.g. via '--skip-plugins'),
|
||||
* remove WooCommerce Brands initialization functions early on in the 'plugins_loaded' timeline.
|
||||
*/
|
||||
public static function prepare() {
|
||||
|
||||
if ( ! self::is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( function_exists( 'wc_brands_init' ) ) {
|
||||
remove_action( 'plugins_loaded', 'wc_brands_init', 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ declare( strict_types = 1 );
|
|||
|
||||
namespace Automattic\WooCommerce\Internal\ComingSoon;
|
||||
|
||||
use Automattic\WooCommerce\Admin\Features\Features;
|
||||
use Automattic\WooCommerce\Utilities\FeaturesUtil;
|
||||
|
||||
|
||||
/**
|
||||
* Adds hooks to add a badge to the WordPress admin bar showing site visibility.
|
||||
|
@ -30,7 +31,7 @@ class ComingSoonAdminBarBadge {
|
|||
*/
|
||||
public function site_visibility_badge( $wp_admin_bar ) {
|
||||
// Early exit if LYS feature is disabled.
|
||||
if ( ! Features::is_enabled( 'launch-your-store' ) ) {
|
||||
if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -68,7 +69,7 @@ class ComingSoonAdminBarBadge {
|
|||
*/
|
||||
public function output_css() {
|
||||
// Early exit if LYS feature is disabled.
|
||||
if ( ! Features::is_enabled( 'launch-your-store' ) ) {
|
||||
if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class ComingSoonRequestHandler {
|
|||
* @internal
|
||||
*
|
||||
* @param string $template The path to the previously determined template.
|
||||
* @return string|null The path to the 'coming soon' template or null to prevent further template loading in FSE themes.
|
||||
* @return string The path to the 'coming soon' template or any empty string to prevent further template loading in FSE themes.
|
||||
*/
|
||||
public function handle_template_include( $template ) {
|
||||
global $wp;
|
||||
|
@ -91,8 +91,8 @@ class ComingSoonRequestHandler {
|
|||
}
|
||||
|
||||
if ( $is_fse_theme ) {
|
||||
// Since we've already rendered a template, return null to ensure no other template is rendered.
|
||||
return null;
|
||||
// Since we've already rendered a template, return empty string to ensure no other template is rendered.
|
||||
return '';
|
||||
} else {
|
||||
// In non-FSE themes, other templates will still be rendered.
|
||||
// We need to exit to prevent further processing.
|
||||
|
|
|
@ -224,6 +224,18 @@ class FeaturesController {
|
|||
'is_legacy' => true,
|
||||
'is_experimental' => false,
|
||||
),
|
||||
'site_visibility_badge' => array(
|
||||
'name' => __( 'Site visibility badge', 'woocommerce' ),
|
||||
'description' => __(
|
||||
'Enable the site visibility badge in the WordPress admin bar',
|
||||
'woocommerce'
|
||||
),
|
||||
'enabled_by_default' => true,
|
||||
'disable_ui' => false,
|
||||
'is_legacy' => true,
|
||||
'is_experimental' => false,
|
||||
'disabled' => false,
|
||||
),
|
||||
'hpos_fts_indexes' => array(
|
||||
'name' => __( 'HPOS Full text search indexes', 'woocommerce' ),
|
||||
'description' => __(
|
||||
|
|
|
@ -66,7 +66,8 @@ class Packages {
|
|||
* @since 3.7.0
|
||||
*/
|
||||
public static function init() {
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'on_init' ), 0 );
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'prepare_packages' ), -100 );
|
||||
add_action( 'plugins_loaded', array( __CLASS__, 'on_init' ), 10 );
|
||||
|
||||
// Prevent plugins already merged into WooCommerce core from getting activated as standalone plugins.
|
||||
add_action( 'activate_plugin', array( __CLASS__, 'deactivate_merged_plugins' ) );
|
||||
|
@ -149,6 +150,18 @@ class Packages {
|
|||
return array_key_exists( $package, self::get_enabled_packages() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare merged packages for initialization.
|
||||
* Especially useful when running actions early in the 'plugins_loaded' timeline.
|
||||
*/
|
||||
public static function prepare_packages() {
|
||||
foreach ( self::get_enabled_packages() as $package_name => $package_class ) {
|
||||
if ( method_exists( $package_class, 'prepare' ) ) {
|
||||
call_user_func( array( $package_class, 'prepare' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates merged feature plugins.
|
||||
*
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
*
|
||||
* @see https://docs.woocommerce.com/document/template-structure/
|
||||
* @package WooCommerce\Templates
|
||||
* @version 8.6.0
|
||||
* @version 9.5.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
@ -26,7 +26,7 @@ if ( ! $notices ) {
|
|||
?>
|
||||
|
||||
<?php foreach ( $notices as $notice ) : ?>
|
||||
<div class="wc-block-components-notice-banner is-info"<?php echo wc_get_notice_data_attr( $notice ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> role="alert">
|
||||
<div class="wc-block-components-notice-banner is-info"<?php echo wc_get_notice_data_attr( $notice ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> role="status">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" aria-hidden="true" focusable="false">
|
||||
<path d="M12 3.2c-4.8 0-8.8 3.9-8.8 8.8 0 4.8 3.9 8.8 8.8 8.8 4.8 0 8.8-3.9 8.8-8.8 0-4.8-4-8.8-8.8-8.8zm0 16c-4 0-7.2-3.3-7.2-7.2C4.8 8 8 4.8 12 4.8s7.2 3.3 7.2 7.2c0 4-3.2 7.2-7.2 7.2zM11 17h2v-6h-2v6zm0-8h2V7h-2v2z"></path>
|
||||
</svg>
|
||||
|
|
|
@ -33,6 +33,18 @@ config = {
|
|||
'**/merchant/launch-your-store.spec.js',
|
||||
'**/merchant/lost-password.spec.js',
|
||||
'**/merchant/order-bulk-edit.spec.js',
|
||||
'**/merchant/product-images.spec.js',
|
||||
'**/merchant/product-import-csv.spec.js',
|
||||
'**/merchant/product-linked-products.spec.js',
|
||||
'**/merchant/product-reviews.spec.js',
|
||||
'**/merchant/product-search.spec.js',
|
||||
'**/merchant/product-settings.spec.js',
|
||||
'**/merchant/settings-general.spec.js',
|
||||
'**/merchant/settings-shipping.spec.js',
|
||||
'**/merchant/settings-tax.spec.js',
|
||||
'**/merchant/settings-woo-com.spec.js',
|
||||
'**/merchant/users-create.spec.js',
|
||||
'**/merchant/users-manage.spec.js',
|
||||
],
|
||||
grepInvert: /@skip-on-default-wpcom/,
|
||||
},
|
||||
|
|
|
@ -246,6 +246,14 @@ test.describe(
|
|||
'wp-admin/edit.php?post_type=product&page=product-reviews'
|
||||
);
|
||||
|
||||
// Handle notice if present
|
||||
await page.addLocatorHandler(
|
||||
page.getByRole( 'link', { name: 'Dismiss' } ),
|
||||
async () => {
|
||||
await page.getByRole( 'link', { name: 'Dismiss' } ).click();
|
||||
}
|
||||
);
|
||||
|
||||
const reviewRow = page.locator( `#comment-${ review.id }` );
|
||||
await reviewRow.hover();
|
||||
await reviewRow.getByRole( 'button', { name: 'Reply' } ).click();
|
||||
|
@ -256,7 +264,12 @@ test.describe(
|
|||
const replyText = `Thank you for your feedback! (replied ${ Date.now() })`;
|
||||
await replyTextArea.fill( replyText );
|
||||
|
||||
await page.locator( 'button.save.button.button-primary' ).click();
|
||||
await page
|
||||
.getByRole( 'cell', { name: 'Reply to Comment' } )
|
||||
.getByRole( 'button', { name: 'Reply', exact: true } )
|
||||
.click();
|
||||
|
||||
await expect( replyTextArea ).toBeHidden();
|
||||
|
||||
const productLink = await reviewRow
|
||||
.locator( 'a.comments-view-item-link' )
|
||||
|
|
|
@ -3,7 +3,13 @@ const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
|
|||
|
||||
test.describe(
|
||||
'WooCommerce woo.com Settings',
|
||||
{ tag: [ '@services', '@skip-on-default-pressable' ] },
|
||||
{
|
||||
tag: [
|
||||
'@services',
|
||||
'@skip-on-default-pressable',
|
||||
'@skip-on-default-wpcom',
|
||||
],
|
||||
},
|
||||
() => {
|
||||
test.use( { storageState: process.env.ADMINSTATE } );
|
||||
|
||||
|
|
|
@ -65,7 +65,11 @@ async function userDeletionTest( page, username ) {
|
|||
page.getByRole( 'heading', { name: 'Delete Users' } )
|
||||
).toBeVisible();
|
||||
|
||||
await expect( page.getByText( `${ username }` ) ).toBeVisible();
|
||||
await expect(
|
||||
page
|
||||
.getByText( 'Delete Users You have' )
|
||||
.getByText( `${ username }` )
|
||||
).toBeVisible();
|
||||
await page.getByRole( 'button', { name: 'Confirm Deletion' } ).click();
|
||||
} );
|
||||
|
||||
|
|
Loading…
Reference in New Issue