Merge branch 'trunk' into try/add-settings-refresh-next
This commit is contained in:
commit
2a6750ca31
|
@ -132,7 +132,7 @@ jobs:
|
||||||
install: '${{ matrix.projectName }}...'
|
install: '${{ matrix.projectName }}...'
|
||||||
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
|
build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }}
|
||||||
build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }}
|
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 }}'
|
pull-package-deps: '${{ matrix.projectName }}'
|
||||||
|
|
||||||
- name: 'Update wp-env config'
|
- name: 'Update wp-env config'
|
||||||
|
|
|
@ -29,6 +29,9 @@ concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Check Asset Sizes
|
name: Check Asset Sizes
|
||||||
|
@ -42,6 +45,8 @@ jobs:
|
||||||
uses: ./.github/actions/setup-woocommerce-monorepo
|
uses: ./.github/actions/setup-woocommerce-monorepo
|
||||||
with:
|
with:
|
||||||
php-version: false
|
php-version: false
|
||||||
|
install: '@woocommerce/plugin-woocommerce...'
|
||||||
|
build: '@woocommerce/plugin-woocommerce'
|
||||||
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
pull-package-deps: '@woocommerce/plugin-woocommerce'
|
||||||
|
|
||||||
- uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c
|
- uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c
|
||||||
|
@ -49,9 +54,9 @@ jobs:
|
||||||
BROWSERSLIST_IGNORE_OLD_DATA: true
|
BROWSERSLIST_IGNORE_OLD_DATA: true
|
||||||
with:
|
with:
|
||||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
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'
|
install-script: 'pnpm install --filter="@woocommerce/plugin-woocommerce..." --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts'
|
||||||
build-script: '--filter="@woocommerce/plugin-woocommerce" build'
|
build-script: '--filter="@woocommerce/plugin-woocommerce" build'
|
||||||
clean-script: '--if-present buildclean'
|
clean-script: '--if-present clean:build'
|
||||||
minimum-change-threshold: 100
|
minimum-change-threshold: 100
|
||||||
omit-unchanged: true
|
omit-unchanged: true
|
||||||
|
|
|
@ -16,6 +16,9 @@ concurrency:
|
||||||
group: build-${{ github.event_name == 'push' && github.run_id || 'pr' }}-${{ github.ref }}
|
group: build-${{ github.event_name == 'push' && github.run_id || 'pr' }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: 1
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
@ -2,31 +2,79 @@
|
||||||
|
|
||||||
set -eo pipefail
|
set -eo pipefail
|
||||||
|
|
||||||
function title() {
|
# The commented variables are for troubleshooting locally. The commented commands below are also for local troubleshooting.
|
||||||
echo -e "\n\033[1m$1\033[0m"
|
# 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
|
if [[ -z "$GITHUB_EVENT_NAME" ]]; then
|
||||||
echo "::error::GITHUB_EVENT_NAME must be set"
|
echo "::error::GITHUB_EVENT_NAME must be set"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
title "Installing NVM"
|
function title() {
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash > /dev/null
|
echo -e "\n\033[1m$1\033[0m"
|
||||||
export NVM_DIR="$HOME/.nvm"
|
}
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
||||||
echo "Installed version: $(nvm -v)"
|
|
||||||
|
|
||||||
title "Installing dependencies"
|
if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
|
||||||
pnpm install --frozen-lockfile --filter="compare-perf" > /dev/null
|
mkdir -p $ARTIFACTS_PATH && export WP_ARTIFACTS_PATH=$ARTIFACTS_PATH
|
||||||
|
|
||||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
# It should be 3d7d7f02017383937f1a4158d433d0e5d44b3dc9, but we pick 55f855a2e6d769b5ae44305b2772eb30d3e721df
|
||||||
title "Comparing performance with trunk"
|
# where compare-perf reporting mode was introduced for processing the provided reports.
|
||||||
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA
|
BASE_SHA=55f855a2e6d769b5ae44305b2772eb30d3e721df
|
||||||
|
HEAD_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||||
elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
|
|
||||||
title "Comparing performance with base branch"
|
|
||||||
WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt)
|
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
|
# Updating the WP version used for performance jobs means there’s a high
|
||||||
# chance that the reference commit used for performance test stability
|
# chance that the reference commit used for performance test stability
|
||||||
# becomes incompatible with the WP version. So, every time the "Tested up
|
# 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 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
|
# - Be tracked on https://www.codevitals.run/project/woo for all existing
|
||||||
# metrics.
|
# metrics.
|
||||||
BASE_SHA=3d7d7f02017383937f1a4158d433d0e5d44b3dc9
|
|
||||||
echo "WP_VERSION: $WP_VERSION"
|
|
||||||
IFS=. read -ra WP_VERSION_ARRAY <<< "$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_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" --ci --skip-benchmarking
|
||||||
pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR"
|
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")
|
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
|
pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT
|
||||||
|
echo '##[endgroup]'
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
echo "Unsupported event: $GITHUB_EVENT_NAME"
|
echo "Unsupported event: $GITHUB_EVENT_NAME"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -13,7 +13,7 @@ To get up and running within the WooCommerce Monorepo, you will need to make sur
|
||||||
### Prerequisites
|
### 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.
|
- [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.
|
- [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.
|
- [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",
|
"lint": "pnpm -r lint",
|
||||||
"cherry-pick": "node ./tools/cherry-pick/bin/run",
|
"cherry-pick": "node ./tools/cherry-pick/bin/run",
|
||||||
"clean": "rimraf -g '**/node_modules' '**/.wireit' && pnpm store prune",
|
"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",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"postinstall": "husky",
|
"postinstall": "husky",
|
||||||
"sync-dependencies": "pnpm exec syncpack -- fix-mismatches",
|
"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
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { ColorPalette } from './types';
|
import { ColorPalette } from './types';
|
||||||
|
|
||||||
|
const MAX_COLOR_PALETTES = 4;
|
||||||
|
|
||||||
export const ColorPalettes = ( {
|
export const ColorPalettes = ( {
|
||||||
colorPalettes,
|
colorPalettes,
|
||||||
totalPalettes,
|
totalPalettes,
|
||||||
|
@ -10,17 +18,52 @@ export const ColorPalettes = ( {
|
||||||
colorPalettes: ColorPalette[];
|
colorPalettes: ColorPalette[];
|
||||||
totalPalettes: number;
|
totalPalettes: number;
|
||||||
} ) => {
|
} ) => {
|
||||||
let extra = null;
|
const canFit = totalPalettes <= MAX_COLOR_PALETTES;
|
||||||
|
|
||||||
if ( totalPalettes > 4 ) {
|
const descriptionId = useInstanceId(
|
||||||
extra = <li className="more_palettes">+{ totalPalettes - 4 }</li>;
|
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 (
|
return (
|
||||||
<ul className="theme-card__color-palettes">
|
<>
|
||||||
|
<ul
|
||||||
|
className="theme-card__color-palettes"
|
||||||
|
aria-label={ __( 'Color palettes', 'woocommerce' ) }
|
||||||
|
aria-describedby={ descriptionId }
|
||||||
|
>
|
||||||
{ colorPalettes.map( ( colorPalette ) => (
|
{ colorPalettes.map( ( colorPalette ) => (
|
||||||
<li
|
<li
|
||||||
key={ colorPalette.title }
|
key={ colorPalette.title }
|
||||||
|
aria-label={ colorPalette.title }
|
||||||
style={ {
|
style={ {
|
||||||
background:
|
background:
|
||||||
'linear-gradient(to right, ' +
|
'linear-gradient(to right, ' +
|
||||||
|
@ -34,9 +77,12 @@ export const ColorPalettes = ( {
|
||||||
' 100%' +
|
' 100%' +
|
||||||
')',
|
')',
|
||||||
} }
|
} }
|
||||||
></li>
|
/>
|
||||||
) ) }
|
) ) }
|
||||||
{ extra }
|
{ renderMore() }
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
{ renderDescription() }
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -314,6 +314,10 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-description {
|
||||||
|
@include visually-hidden();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-card__free {
|
.theme-card__free {
|
||||||
|
|
|
@ -355,9 +355,16 @@ export function subscriptionStatus(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return subscription.autorenew
|
let status;
|
||||||
? __( 'Active', 'woocommerce' )
|
if ( subscription.lifetime ) {
|
||||||
: __( 'Cancelled', 'woocommerce' );
|
status = __( 'Lifetime', 'woocommerce' );
|
||||||
|
} else if ( subscription.autorenew ) {
|
||||||
|
status = __( 'Active', 'woocommerce' );
|
||||||
|
} else {
|
||||||
|
status = __( 'Cancelled', 'woocommerce' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
display: getStatus(),
|
display: getStatus(),
|
||||||
|
@ -377,7 +384,7 @@ export function actions( subscription: Subscription ): TableRow {
|
||||||
let actionButton = null;
|
let actionButton = null;
|
||||||
if ( subscription.product_key === '' ) {
|
if ( subscription.product_key === '' ) {
|
||||||
actionButton = <SubscribeButton subscription={ subscription } />;
|
actionButton = <SubscribeButton subscription={ subscription } />;
|
||||||
} else if ( subscription.expired ) {
|
} else if ( subscription.expired && ! subscription.lifetime ) {
|
||||||
actionButton = <RenewButton subscription={ subscription } />;
|
actionButton = <RenewButton subscription={ subscription } />;
|
||||||
} else if (
|
} else if (
|
||||||
subscription.local.installed === false &&
|
subscription.local.installed === false &&
|
||||||
|
@ -391,7 +398,7 @@ export function actions( subscription: Subscription ): TableRow {
|
||||||
actionButton = (
|
actionButton = (
|
||||||
<ConnectButton subscription={ subscription } variant="link" />
|
<ConnectButton subscription={ subscription } variant="link" />
|
||||||
);
|
);
|
||||||
} else if ( ! subscription.autorenew ) {
|
} else if ( ! subscription.autorenew && ! subscription.lifetime ) {
|
||||||
actionButton = <AutoRenewButton subscription={ subscription } />;
|
actionButton = <AutoRenewButton subscription={ subscription } />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,4 +11,8 @@
|
||||||
.wc-block-components-totals-footer-item-tax {
|
.wc-block-components-totals-footer-item-tax {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wc-block-components-totals-item__value {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,7 @@ export const TotalsShipping = ( {
|
||||||
<ShippingPlaceholder
|
<ShippingPlaceholder
|
||||||
showCalculator={ showCalculator }
|
showCalculator={ showCalculator }
|
||||||
isCheckout={ isCheckout }
|
isCheckout={ isCheckout }
|
||||||
|
addressProvided={ addressComplete }
|
||||||
isShippingCalculatorOpen={
|
isShippingCalculatorOpen={
|
||||||
isShippingCalculatorOpen
|
isShippingCalculatorOpen
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,22 +12,27 @@ export interface ShippingPlaceholderProps {
|
||||||
showCalculator: boolean;
|
showCalculator: boolean;
|
||||||
isShippingCalculatorOpen: boolean;
|
isShippingCalculatorOpen: boolean;
|
||||||
isCheckout?: boolean;
|
isCheckout?: boolean;
|
||||||
|
addressProvided: boolean;
|
||||||
setIsShippingCalculatorOpen: CalculatorButtonProps[ 'setIsShippingCalculatorOpen' ];
|
setIsShippingCalculatorOpen: CalculatorButtonProps[ 'setIsShippingCalculatorOpen' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShippingPlaceholder = ( {
|
export const ShippingPlaceholder = ( {
|
||||||
showCalculator,
|
showCalculator,
|
||||||
|
addressProvided,
|
||||||
isShippingCalculatorOpen,
|
isShippingCalculatorOpen,
|
||||||
setIsShippingCalculatorOpen,
|
setIsShippingCalculatorOpen,
|
||||||
isCheckout = false,
|
isCheckout = false,
|
||||||
}: ShippingPlaceholderProps ): JSX.Element => {
|
}: ShippingPlaceholderProps ): JSX.Element => {
|
||||||
if ( ! showCalculator ) {
|
if ( ! showCalculator ) {
|
||||||
|
const label = addressProvided
|
||||||
|
? __( 'No available delivery option', 'woocommerce' )
|
||||||
|
: __( 'Enter address to calculate', 'woocommerce' );
|
||||||
return (
|
return (
|
||||||
<em>
|
<span className="wc-block-components-shipping-placeholder__value">
|
||||||
{ isCheckout
|
{ isCheckout
|
||||||
? __( 'No shipping options available', 'woocommerce' )
|
? label
|
||||||
: __( 'Calculated during checkout', 'woocommerce' ) }
|
: __( 'Calculated during checkout', 'woocommerce' ) }
|
||||||
</em>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.wc-block-components-shipping-address {
|
.wc-block-components-shipping-address {
|
||||||
margin-top: $gap;
|
margin-top: $gap;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -50,6 +49,10 @@
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wc-block-components-shipping-placeholder__value {
|
||||||
|
@include font-size(small);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra classes for specificity.
|
// Extra classes for specificity.
|
||||||
|
|
|
@ -9,22 +9,24 @@ import { screen, render } from '@testing-library/react';
|
||||||
import ShippingPlaceholder from '../shipping-placeholder';
|
import ShippingPlaceholder from '../shipping-placeholder';
|
||||||
|
|
||||||
describe( 'ShippingPlaceholder', () => {
|
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(
|
const { rerender } = render(
|
||||||
<ShippingPlaceholder
|
<ShippingPlaceholder
|
||||||
showCalculator={ false }
|
showCalculator={ false }
|
||||||
|
addressProvided={ false }
|
||||||
isCheckout={ true }
|
isCheckout={ true }
|
||||||
isShippingCalculatorOpen={ false }
|
isShippingCalculatorOpen={ false }
|
||||||
setIsShippingCalculatorOpen={ jest.fn() }
|
setIsShippingCalculatorOpen={ jest.fn() }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
screen.getByText( 'No shipping options available' )
|
screen.getByText( 'Enter address to calculate' )
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
rerender(
|
rerender(
|
||||||
<ShippingPlaceholder
|
<ShippingPlaceholder
|
||||||
showCalculator={ false }
|
showCalculator={ false }
|
||||||
isCheckout={ false }
|
isCheckout={ false }
|
||||||
|
addressProvided={ false }
|
||||||
isShippingCalculatorOpen={ false }
|
isShippingCalculatorOpen={ false }
|
||||||
setIsShippingCalculatorOpen={ jest.fn() }
|
setIsShippingCalculatorOpen={ jest.fn() }
|
||||||
/>
|
/>
|
||||||
|
@ -33,4 +35,19 @@ describe( 'ShippingPlaceholder', () => {
|
||||||
screen.getByText( 'Calculated during checkout' )
|
screen.getByText( 'Calculated during checkout' )
|
||||||
).toBeInTheDocument();
|
).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 (
|
return (
|
||||||
<FormTokenField
|
<FormTokenField
|
||||||
displayTransform={ transformTokenIntoProductName }
|
displayTransform={ transformTokenIntoProductName }
|
||||||
label={ __( 'Hand-Picked Products', 'woocommerce' ) }
|
label={ __( 'Hand-Picked', 'woocommerce' ) }
|
||||||
onChange={ onTokenChange }
|
onChange={ onTokenChange }
|
||||||
onInputChange={ isLargeCatalog ? handleSearch : undefined }
|
onInputChange={ isLargeCatalog ? handleSearch : undefined }
|
||||||
suggestions={ suggestions }
|
suggestions={ suggestions }
|
||||||
|
@ -186,7 +186,7 @@ const HandPickedProductsControl = ( {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolsPanelItem
|
<ToolsPanelItem
|
||||||
label={ __( 'Hand-Picked Products', 'woocommerce' ) }
|
label={ __( 'Hand-Picked', 'woocommerce' ) }
|
||||||
hasValue={ () => !! selectedProductIds?.length }
|
hasValue={ () => !! selectedProductIds?.length }
|
||||||
onDeselect={ deselectCallback }
|
onDeselect={ deselectCallback }
|
||||||
resetAllFilter={ deselectCallback }
|
resetAllFilter={ deselectCallback }
|
||||||
|
|
|
@ -49,7 +49,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToolsPanelItem
|
<ToolsPanelItem
|
||||||
label={ __( 'Stock status', 'woocommerce' ) }
|
label={ __( 'Stock Status', 'woocommerce' ) }
|
||||||
hasValue={ () =>
|
hasValue={ () =>
|
||||||
! fastDeepEqual(
|
! fastDeepEqual(
|
||||||
query.woocommerceStockStatus,
|
query.woocommerceStockStatus,
|
||||||
|
@ -61,7 +61,7 @@ const StockStatusControl = ( props: QueryControlProps ) => {
|
||||||
isShownByDefault
|
isShownByDefault
|
||||||
>
|
>
|
||||||
<FormTokenField
|
<FormTokenField
|
||||||
label={ __( 'Stock status', 'woocommerce' ) }
|
label={ __( 'Stock Status', 'woocommerce' ) }
|
||||||
onChange={ ( statusLabels ) => {
|
onChange={ ( statusLabels ) => {
|
||||||
const woocommerceStockStatus = statusLabels
|
const woocommerceStockStatus = statusLabels
|
||||||
.map( getStockStatusIdByLabel )
|
.map( getStockStatusIdByLabel )
|
||||||
|
|
|
@ -48,6 +48,20 @@ function TaxonomyControls( {
|
||||||
return null;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{ taxonomies.map( ( taxonomy: Taxonomy ) => {
|
{ taxonomies.map( ( taxonomy: Taxonomy ) => {
|
||||||
|
@ -75,7 +89,7 @@ function TaxonomyControls( {
|
||||||
return (
|
return (
|
||||||
<ToolsPanelItem
|
<ToolsPanelItem
|
||||||
key={ slug }
|
key={ slug }
|
||||||
label={ name }
|
label={ normalizeName( name ) }
|
||||||
hasValue={ () => termIds.length }
|
hasValue={ () => termIds.length }
|
||||||
onDeselect={ deselectCallback }
|
onDeselect={ deselectCallback }
|
||||||
resetAllFilter={ deselectCallback }
|
resetAllFilter={ deselectCallback }
|
||||||
|
|
|
@ -9,10 +9,6 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wc-block-components-totals-item__value {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wc-block-components-totals-item__description {
|
.wc-block-components-totals-item__description {
|
||||||
@include font-size(small);
|
@include font-size(small);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin Name: Register Product Collection Tester
|
* Plugin Name: WooCommerce Blocks Test Register Product Collection
|
||||||
* Description: A plugin to test the registerProductCollection function from WooCommerce Blocks.
|
* Description: Used to tests the registerProductCollection function.
|
||||||
* Plugin URI: https://github.com/woocommerce/woocommerce
|
* Plugin URI: https://github.com/woocommerce/woocommerce
|
||||||
* Author: WooCommerce
|
* Author: WooCommerce
|
||||||
*
|
*
|
||||||
*
|
* @package woocommerce-blocks-test-register-product-collection
|
||||||
* @package register-product-collection-tester
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Exit if accessed directly.
|
// Exit if accessed directly.
|
||||||
|
@ -20,9 +18,9 @@ function register_product_collections_script()
|
||||||
{
|
{
|
||||||
wp_enqueue_script(
|
wp_enqueue_script(
|
||||||
'rpc_register_product_collections',
|
'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'),
|
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
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -19,8 +19,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Product Collection', () => {
|
test.describe( 'Product Collection: Collections', () => {
|
||||||
test.describe( 'Collections', () => {
|
|
||||||
test( 'New Arrivals Collection can be added and displays proper products', async ( {
|
test( 'New Arrivals Collection can be added and displays proper products', async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
} ) => {
|
} ) => {
|
||||||
|
@ -53,9 +52,7 @@ test.describe( 'Product Collection', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
await expect( pageObject.products ).toHaveCount( 5 );
|
await expect( pageObject.products ).toHaveCount( 5 );
|
||||||
await expect( pageObject.productTitles ).toHaveText(
|
await expect( pageObject.productTitles ).toHaveText( topRatedProducts );
|
||||||
topRatedProducts
|
|
||||||
);
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
|
@ -102,9 +99,7 @@ test.describe( 'Product Collection', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
await expect( pageObject.products ).toHaveCount( 5 );
|
await expect( pageObject.products ).toHaveCount( 5 );
|
||||||
await expect( pageObject.productTitles ).toHaveText(
|
await expect( pageObject.productTitles ).toHaveText( onSaleProducts );
|
||||||
onSaleProducts
|
|
||||||
);
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
|
@ -124,9 +119,7 @@ test.describe( 'Product Collection', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
await expect( pageObject.products ).toHaveCount( 4 );
|
await expect( pageObject.products ).toHaveCount( 4 );
|
||||||
await expect( pageObject.productTitles ).toHaveText(
|
await expect( pageObject.productTitles ).toHaveText( featuredProducts );
|
||||||
featuredProducts
|
|
||||||
);
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
|
@ -217,5 +210,4 @@ test.describe( 'Product Collection', () => {
|
||||||
await expect( input ).toBeHidden();
|
await expect( input ).toBeHidden();
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -86,8 +86,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Compatibility Layer with Product Collection block', () => {
|
test.describe( 'Product Collection: Compatibility Layer', () => {
|
||||||
test.describe( 'Product Archive with Product Collection block', () => {
|
|
||||||
test.beforeEach( async ( { pageObject, requestUtils } ) => {
|
test.beforeEach( async ( { pageObject, requestUtils } ) => {
|
||||||
await requestUtils.activatePlugin(
|
await requestUtils.activatePlugin(
|
||||||
'woocommerce-blocks-test-product-collection-compatibility-layer'
|
'woocommerce-blocks-test-product-collection-compatibility-layer'
|
||||||
|
@ -116,5 +115,4 @@ test.describe( 'Compatibility Layer with Product Collection block', () => {
|
||||||
await expect( hooks.first() ).toHaveText( scenario.content );
|
await expect( hooks.first() ).toHaveText( scenario.content );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -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 ( {
|
test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
page,
|
page,
|
||||||
|
|
|
@ -19,28 +19,21 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( {
|
||||||
},
|
},
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.describe( 'Product Collection', () => {
|
test.describe( 'Product Collection: Inspector Controls', () => {
|
||||||
test.describe( 'Inspector Controls', () => {
|
|
||||||
test( 'Reflects the correct number of columns according to sidebar settings', async ( {
|
test( 'Reflects the correct number of columns according to sidebar settings', async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
} ) => {
|
} ) => {
|
||||||
await pageObject.createNewPostAndInsertBlock();
|
await pageObject.createNewPostAndInsertBlock();
|
||||||
|
|
||||||
await pageObject.setNumberOfColumns( 2 );
|
await pageObject.setNumberOfColumns( 2 );
|
||||||
await expect( pageObject.productTemplate ).toHaveClass(
|
await expect( pageObject.productTemplate ).toHaveClass( /columns-2/ );
|
||||||
/columns-2/
|
|
||||||
);
|
|
||||||
|
|
||||||
await pageObject.setNumberOfColumns( 4 );
|
await pageObject.setNumberOfColumns( 4 );
|
||||||
await expect( pageObject.productTemplate ).toHaveClass(
|
await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ );
|
||||||
/columns-4/
|
|
||||||
);
|
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
|
||||||
await expect( pageObject.productTemplate ).toHaveClass(
|
await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ );
|
||||||
/columns-4/
|
|
||||||
);
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test( 'Order By - sort products by title in descending order correctly', async ( {
|
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.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 pageObject.setFilterComboboxValue( filterName, [ 'Album' ] );
|
||||||
await expect( pageObject.products ).toHaveCount( 1 );
|
await expect( pageObject.products ).toHaveCount( 1 );
|
||||||
|
|
||||||
|
@ -144,9 +137,7 @@ test.describe( 'Product Collection', () => {
|
||||||
|
|
||||||
const filterName = 'Product categories';
|
const filterName = 'Product categories';
|
||||||
await pageObject.addFilter( 'Show product categories' );
|
await pageObject.addFilter( 'Show product categories' );
|
||||||
await pageObject.setFilterComboboxValue( filterName, [
|
await pageObject.setFilterComboboxValue( filterName, [ 'Clothing' ] );
|
||||||
'Clothing',
|
|
||||||
] );
|
|
||||||
await expect( pageObject.productTitles ).toHaveText( [
|
await expect( pageObject.productTitles ).toHaveText( [
|
||||||
'Logo Collection',
|
'Logo Collection',
|
||||||
] );
|
] );
|
||||||
|
@ -560,9 +551,7 @@ test.describe( 'Product Collection', () => {
|
||||||
|
|
||||||
await page.getByLabel( 'Toggle block inserter' ).click();
|
await page.getByLabel( 'Toggle block inserter' ).click();
|
||||||
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
||||||
await page
|
await page.getByPlaceholder( 'Search' ).fill( 'product filters' );
|
||||||
.getByPlaceholder( 'Search' )
|
|
||||||
.fill( 'product filters' );
|
|
||||||
await page.getByLabel( 'Product Filters' ).click();
|
await page.getByLabel( 'Product Filters' ).click();
|
||||||
|
|
||||||
const postId = await editor.publishPost();
|
const postId = await editor.publishPost();
|
||||||
|
@ -611,9 +600,7 @@ test.describe( 'Product Collection', () => {
|
||||||
|
|
||||||
await page.getByLabel( 'Toggle block inserter' ).click();
|
await page.getByLabel( 'Toggle block inserter' ).click();
|
||||||
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
await page.getByRole( 'tab', { name: 'Patterns' } ).click();
|
||||||
await page
|
await page.getByPlaceholder( 'Search' ).fill( 'product filters' );
|
||||||
.getByPlaceholder( 'Search' )
|
|
||||||
.fill( 'product filters' );
|
|
||||||
await page.getByLabel( 'Product Filters' ).click();
|
await page.getByLabel( 'Product Filters' ).click();
|
||||||
|
|
||||||
await expect( pageObject.products ).toHaveCount( 2 );
|
await expect( pageObject.products ).toHaveCount( 2 );
|
||||||
|
@ -635,5 +622,4 @@ test.describe( 'Product Collection', () => {
|
||||||
await expect( pageObject.products ).toHaveCount( 1 );
|
await expect( pageObject.products ).toHaveCount( 1 );
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
} );
|
|
||||||
} );
|
} );
|
||||||
|
|
|
@ -9,7 +9,6 @@ import { test as base, expect } from '@woocommerce/e2e-utils';
|
||||||
*/
|
*/
|
||||||
import ProductCollectionPage, {
|
import ProductCollectionPage, {
|
||||||
BLOCK_LABELS,
|
BLOCK_LABELS,
|
||||||
Collections,
|
|
||||||
SELECTORS,
|
SELECTORS,
|
||||||
} from './product-collection.page';
|
} from './product-collection.page';
|
||||||
|
|
||||||
|
@ -87,11 +86,7 @@ test.describe( 'Product Collection', () => {
|
||||||
await admin.createNewPost();
|
await admin.createNewPost();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
test.skip( 'does not render', async ( {
|
test( 'does not render', async ( { page, editor, pageObject } ) => {
|
||||||
page,
|
|
||||||
editor,
|
|
||||||
pageObject,
|
|
||||||
} ) => {
|
|
||||||
await pageObject.insertProductCollection();
|
await pageObject.insertProductCollection();
|
||||||
await pageObject.chooseCollectionInPost( 'featured' );
|
await pageObject.chooseCollectionInPost( 'featured' );
|
||||||
await pageObject.addFilter( 'Price Range' );
|
await pageObject.addFilter( 'Price Range' );
|
||||||
|
@ -106,7 +101,7 @@ test.describe( 'Product Collection', () => {
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
// The "No results found" info is rendered in editor for all collections.
|
// The "No results found" info is rendered in editor for all collections.
|
||||||
await expect(
|
await expect(
|
||||||
featuredBlock.getByText( 'No results found' )
|
featuredBlock.getByText( 'No products to display' )
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
|
|
||||||
await pageObject.publishAndGoToFrontend();
|
await pageObject.publishAndGoToFrontend();
|
||||||
|
@ -114,7 +109,9 @@ test.describe( 'Product Collection', () => {
|
||||||
const content = page.locator( 'main' );
|
const content = page.locator( 'main' );
|
||||||
|
|
||||||
await expect( content ).not.toContainText( 'Featured products' );
|
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
|
// 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.
|
// This test is disabled because archives are disabled for attributes by default. This can be uncommented when this is toggled on.
|
||||||
//'taxonomy-product_attribute': {
|
//'taxonomy-product_attribute': {
|
||||||
// templateTitle: 'Product Attribute',
|
// templateTitle: 'Product Attribute',
|
||||||
|
@ -747,43 +744,44 @@ test.describe( 'Product Collection', () => {
|
||||||
// frontendPage: '/product-attribute/color/',
|
// frontendPage: '/product-attribute/color/',
|
||||||
// legacyBlockName: 'woocommerce/legacy-template',
|
// legacyBlockName: 'woocommerce/legacy-template',
|
||||||
//},
|
//},
|
||||||
'taxonomy-product_cat': {
|
{
|
||||||
templateTitle: 'Product Category',
|
templateTitle: 'Product Category',
|
||||||
slug: 'taxonomy-product_cat',
|
slug: 'taxonomy-product_cat',
|
||||||
frontendPage: '/product-category/music/',
|
frontendPage: '/product-category/music/',
|
||||||
legacyBlockName: 'woocommerce/legacy-template',
|
legacyBlockName: 'woocommerce/legacy-template',
|
||||||
expectedProductsCount: 2,
|
expectedProductsCount: 2,
|
||||||
},
|
},
|
||||||
'taxonomy-product_tag': {
|
{
|
||||||
templateTitle: 'Product Tag',
|
templateTitle: 'Product Tag',
|
||||||
slug: 'taxonomy-product_tag',
|
slug: 'taxonomy-product_tag',
|
||||||
frontendPage: '/product-tag/recommended/',
|
frontendPage: '/product-tag/recommended/',
|
||||||
legacyBlockName: 'woocommerce/legacy-template',
|
legacyBlockName: 'woocommerce/legacy-template',
|
||||||
expectedProductsCount: 2,
|
expectedProductsCount: 2,
|
||||||
},
|
},
|
||||||
'archive-product': {
|
{
|
||||||
templateTitle: 'Product Catalog',
|
templateTitle: 'Product Catalog',
|
||||||
slug: 'archive-product',
|
slug: 'archive-product',
|
||||||
frontendPage: '/shop/',
|
frontendPage: '/shop/',
|
||||||
legacyBlockName: 'woocommerce/legacy-template',
|
legacyBlockName: 'woocommerce/legacy-template',
|
||||||
expectedProductsCount: 16,
|
expectedProductsCount: 16,
|
||||||
},
|
},
|
||||||
'product-search-results': {
|
{
|
||||||
templateTitle: 'Product Search Results',
|
templateTitle: 'Product Search Results',
|
||||||
slug: 'product-search-results',
|
slug: 'product-search-results',
|
||||||
frontendPage: '/?s=shirt&post_type=product',
|
frontendPage: '/?s=shirt&post_type=product',
|
||||||
legacyBlockName: 'woocommerce/legacy-template',
|
legacyBlockName: 'woocommerce/legacy-template',
|
||||||
expectedProductsCount: 3,
|
expectedProductsCount: 3,
|
||||||
},
|
},
|
||||||
};
|
];
|
||||||
|
|
||||||
for ( const {
|
templates.forEach(
|
||||||
|
( {
|
||||||
templateTitle,
|
templateTitle,
|
||||||
slug,
|
slug,
|
||||||
frontendPage,
|
frontendPage,
|
||||||
legacyBlockName,
|
legacyBlockName,
|
||||||
expectedProductsCount,
|
expectedProductsCount,
|
||||||
} of Object.values( templates ) ) {
|
} ) => {
|
||||||
test.describe( `${ templateTitle } template`, () => {
|
test.describe( `${ templateTitle } template`, () => {
|
||||||
test( 'Product Collection block matches with classic template block', async ( {
|
test( 'Product Collection block matches with classic template block', async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
|
@ -843,6 +841,7 @@ test.describe( 'Product Collection', () => {
|
||||||
} );
|
} );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
);
|
||||||
test.describe( 'Editor: In taxonomies templates', () => {
|
test.describe( 'Editor: In taxonomies templates', () => {
|
||||||
test( 'Products by specific category template displays products from this category', async ( {
|
test( 'Products by specific category template displays products from this category', async ( {
|
||||||
admin,
|
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(
|
async addFilter(
|
||||||
name:
|
name:
|
||||||
| 'Show Hand-picked Products'
|
| 'Show Hand-picked'
|
||||||
| 'Keyword'
|
| 'Keyword'
|
||||||
| 'Show product categories'
|
| 'Show product categories'
|
||||||
| 'Show product tags'
|
| '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
|
* These E2E tests are for `registerProductCollection` which we are exposing
|
||||||
* for 3PDs to register new product collections.
|
* for 3PDs to register new product collections.
|
||||||
*/
|
*/
|
||||||
test.describe( 'Product Collection registration', () => {
|
test.describe( 'Product Collection: Register Product Collection', () => {
|
||||||
const MY_REGISTERED_COLLECTIONS = {
|
const MY_REGISTERED_COLLECTIONS = {
|
||||||
myCustomCollection: {
|
myCustomCollection: {
|
||||||
name: 'My Custom Collection',
|
name: 'My Custom Collection',
|
||||||
|
@ -56,7 +56,7 @@ test.describe( 'Product Collection registration', () => {
|
||||||
// Activate plugin which registers custom product collections
|
// Activate plugin which registers custom product collections
|
||||||
test.beforeEach( async ( { requestUtils } ) => {
|
test.beforeEach( async ( { requestUtils } ) => {
|
||||||
await requestUtils.activatePlugin(
|
await requestUtils.activatePlugin(
|
||||||
'register-product-collection-tester'
|
'woocommerce-blocks-test-register-product-collection'
|
||||||
);
|
);
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ test.describe( 'Product Collection registration', () => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
].forEach( ( collection ) => {
|
].forEach( ( collection ) => {
|
||||||
for ( const template of collection.previewLabelTemplate ) {
|
collection.previewLabelTemplate.forEach( ( template ) => {
|
||||||
test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( {
|
test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
editor,
|
editor,
|
||||||
|
@ -341,7 +341,7 @@ test.describe( 'Product Collection registration', () => {
|
||||||
|
|
||||||
await expect( previewButtonLocator ).toBeVisible();
|
await expect( previewButtonLocator ).toBeVisible();
|
||||||
} );
|
} );
|
||||||
}
|
} );
|
||||||
|
|
||||||
test( `Collection "${ collection.name }" should not show preview label in a post`, async ( {
|
test( `Collection "${ collection.name }" should not show preview label in a post`, async ( {
|
||||||
pageObject,
|
pageObject,
|
||||||
|
@ -429,7 +429,7 @@ test.describe( 'Product Collection registration', () => {
|
||||||
} );
|
} );
|
||||||
|
|
||||||
// Product picker should be shown in Editor
|
// Product picker should be shown in Editor
|
||||||
await admin.page.reload();
|
await page.reload();
|
||||||
const deletedProductPicker = editor.canvas.getByText(
|
const deletedProductPicker = editor.canvas.getByText(
|
||||||
'Previously selected product'
|
'Previously selected product'
|
||||||
);
|
);
|
||||||
|
@ -465,4 +465,127 @@ test.describe( 'Product Collection registration', () => {
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await expect( deletedProductPicker ).toBeVisible();
|
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 './performance';
|
||||||
export * from './request-utils';
|
export * from './request-utils';
|
||||||
export * from './shipping';
|
export * from './shipping';
|
||||||
export * from './storeApi';
|
|
||||||
|
|
||||||
export * from './test';
|
export * from './test';
|
||||||
|
|
||||||
|
|
|
@ -15,33 +15,6 @@ import {
|
||||||
} from './templates';
|
} from './templates';
|
||||||
|
|
||||||
export class RequestUtils extends CoreRequestUtils {
|
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 */
|
/** @borrows getTemplates as this.getTemplates */
|
||||||
getTemplates: typeof getTemplates = getTemplates.bind( this );
|
getTemplates: typeof getTemplates = getTemplates.bind( this );
|
||||||
/** @borrows revertTemplate as this.revertTemplate */
|
/** @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,
|
PerformanceUtils,
|
||||||
RequestUtils,
|
RequestUtils,
|
||||||
ShippingUtils,
|
ShippingUtils,
|
||||||
StoreApiUtils,
|
|
||||||
} from '@woocommerce/e2e-utils';
|
} from '@woocommerce/e2e-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -108,7 +107,6 @@ const test = base.extend<
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
pageUtils: PageUtils;
|
pageUtils: PageUtils;
|
||||||
frontendUtils: FrontendUtils;
|
frontendUtils: FrontendUtils;
|
||||||
storeApiUtils: StoreApiUtils;
|
|
||||||
performanceUtils: PerformanceUtils;
|
performanceUtils: PerformanceUtils;
|
||||||
snapshotConfig: void;
|
snapshotConfig: void;
|
||||||
shippingUtils: ShippingUtils;
|
shippingUtils: ShippingUtils;
|
||||||
|
@ -135,6 +133,10 @@ const test = base.extend<
|
||||||
window.localStorage.clear();
|
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 }` );
|
await wpCLI( `db import ${ DB_EXPORT_FILE }` );
|
||||||
},
|
},
|
||||||
pageUtils: async ( { page }, use ) => {
|
pageUtils: async ( { page }, use ) => {
|
||||||
|
@ -146,9 +148,6 @@ const test = base.extend<
|
||||||
performanceUtils: async ( { page }, use ) => {
|
performanceUtils: async ( { page }, use ) => {
|
||||||
await use( new PerformanceUtils( page ) );
|
await use( new PerformanceUtils( page ) );
|
||||||
},
|
},
|
||||||
storeApiUtils: async ( { requestUtils }, use ) => {
|
|
||||||
await use( new StoreApiUtils( requestUtils ) );
|
|
||||||
},
|
|
||||||
shippingUtils: async ( { page, admin }, use ) => {
|
shippingUtils: async ( { page, admin }, use ) => {
|
||||||
await use( new ShippingUtils( page, admin ) );
|
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){
|
html:has(#status-table-templates){
|
||||||
scroll-padding-top: 80px;
|
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 {
|
class WC_Admin_Marketplace_Promotions {
|
||||||
|
|
||||||
|
const CRON_NAME = 'woocommerce_marketplace_cron_fetch_promotions';
|
||||||
const TRANSIENT_NAME = 'woocommerce_marketplace_promotions_v2';
|
const TRANSIENT_NAME = 'woocommerce_marketplace_promotions_v2';
|
||||||
const TRANSIENT_LIFE_SPAN = DAY_IN_SECONDS;
|
const TRANSIENT_LIFE_SPAN = DAY_IN_SECONDS;
|
||||||
const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions';
|
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() {
|
public static function init() {
|
||||||
// A legacy hook that can be triggered by action scheduler.
|
// 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', 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 (
|
if (
|
||||||
defined( 'DOING_AJAX' ) && DOING_AJAX
|
defined( 'DOING_AJAX' ) && DOING_AJAX
|
||||||
|
@ -53,24 +63,33 @@ class WC_Admin_Marketplace_Promotions {
|
||||||
return;
|
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::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US';
|
||||||
self::maybe_show_bubble_promotions();
|
self::maybe_show_bubble_promotions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch promotions from the API and store them in a transient.
|
* Schedule a daily cron event to fetch promotions.
|
||||||
* Fetching can be suppressed by the `woocommerce_marketplace_suppress_promotions` filter.
|
*
|
||||||
|
* @version 9.5.0
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
private static function maybe_update_promotions() {
|
private static function schedule_cron_event() {
|
||||||
// Fetch promotions if they're not in the transient.
|
if ( ! wp_next_scheduled( self::CRON_NAME ) ) {
|
||||||
if ( false !== get_transient( self::TRANSIENT_NAME ) ) {
|
wp_schedule_event( time(), 'daily', self::CRON_NAME );
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch promotions from the API and store them in a transient.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function update_promotions() {
|
||||||
// Fetch promotions from the API.
|
// Fetch promotions from the API.
|
||||||
$promotions = self::fetch_marketplace_promotions();
|
$promotions = self::fetch_marketplace_promotions();
|
||||||
set_transient( self::TRANSIENT_NAME, $promotions, self::TRANSIENT_LIFE_SPAN );
|
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.
|
* When WooCommerce is disabled, clear the WP Cron event we use to fetch promotions.
|
||||||
* It's no longer needed as a transient is used to store the data.
|
*
|
||||||
|
* @version 9.5.0
|
||||||
*
|
*
|
||||||
* @return void
|
* @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' ) ) {
|
if ( function_exists( 'as_unschedule_all_actions' ) ) {
|
||||||
as_unschedule_all_actions( 'woocommerce_marketplace_fetch_promotions' );
|
as_unschedule_all_actions( 'woocommerce_marketplace_fetch_promotions' );
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class WC_Brands {
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->template_url = apply_filters( 'woocommerce_template_url', 'woocommerce/' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
|
$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();
|
$this->register_shortcodes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@
|
||||||
"license": "GPL-3.0+",
|
"license": "GPL-3.0+",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
|
"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": "pnpm --if-present '/^build:project:.*$/'",
|
||||||
"build:project:copy-assets:legacy": "wireit",
|
"build:project:copy-assets:legacy": "wireit",
|
||||||
"build:project:copy-assets:admin": "wireit",
|
"build:project:copy-assets:admin": "wireit",
|
||||||
|
@ -74,6 +78,9 @@
|
||||||
"test:unit:env:watch": "pnpm test:php:env:watch",
|
"test:unit:env:watch": "pnpm test:php:env:watch",
|
||||||
"update-wp-env": "php ./tests/e2e-pw/bin/update-wp-env.php",
|
"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": "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": "pnpm --if-present run '/^watch:build:project:.*$/'",
|
||||||
"watch:build:project:copy-assets": "wireit",
|
"watch:build:project:copy-assets": "wireit",
|
||||||
"wp-env": "wp-env"
|
"wp-env": "wp-env"
|
||||||
|
@ -483,7 +490,6 @@
|
||||||
"start": "test:perf:ci-setup"
|
"start": "test:perf:ci-setup"
|
||||||
},
|
},
|
||||||
"events": [
|
"events": [
|
||||||
"pull_request",
|
|
||||||
"push"
|
"push"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -504,6 +510,9 @@
|
||||||
"tests/metrics/**",
|
"tests/metrics/**",
|
||||||
".wp-env.json"
|
".wp-env.json"
|
||||||
],
|
],
|
||||||
|
"testEnv": {
|
||||||
|
"start": "env:test"
|
||||||
|
},
|
||||||
"events": [
|
"events": [
|
||||||
"push"
|
"push"
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
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 );
|
$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 );
|
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||||
$product_classname = $is_descendent_of_single_product_block ? 'product' : '';
|
$product_classname = $is_descendent_of_single_product_block ? 'product' : '';
|
||||||
|
|
||||||
$form = sprintf(
|
$classes = implode(
|
||||||
'<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>',
|
' ',
|
||||||
|
array_filter(
|
||||||
|
array(
|
||||||
|
'wp-block-add-to-cart-form wc-block-add-to-cart-form',
|
||||||
esc_attr( $classes_and_styles['classes'] ),
|
esc_attr( $classes_and_styles['classes'] ),
|
||||||
esc_attr( $classname ),
|
|
||||||
esc_attr( $product_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
|
$product
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
|
||||||
'font_size',
|
'font_size',
|
||||||
'padding',
|
'padding',
|
||||||
'text_color',
|
'text_color',
|
||||||
|
'extra_classes',
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -272,10 +273,6 @@ abstract class FeaturedItem extends AbstractDynamicBlock {
|
||||||
$classes[] = "has-{$attributes['contentAlign']}-content";
|
$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 );
|
$global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper );
|
||||||
|
|
||||||
$classes[] = $global_style_classes;
|
$classes[] = $global_style_classes;
|
||||||
|
|
|
@ -414,11 +414,8 @@ class MiniCart extends AbstractBlock {
|
||||||
return '';
|
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'] );
|
$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'];
|
$wrapper_styles = $classes_styles['styles'];
|
||||||
|
|
||||||
$icon_color = array_key_exists( 'iconColor', $attributes ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor';
|
$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();
|
$order = $this->get_order();
|
||||||
$permission = $this->get_view_order_permissions( $order );
|
$permission = $this->get_view_order_permissions( $order );
|
||||||
$block_content = $order ? $this->render_content( $order, $permission, $attributes, $content ) : $this->render_content_fallback();
|
$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 );
|
$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(
|
return $block_content ? sprintf(
|
||||||
'<div class="wp-block-%5$s-%4$s wc-block-%4$s %1$s" style="%2$s">%3$s</div>',
|
'<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'] ),
|
esc_attr( $classes_and_styles['styles'] ),
|
||||||
$block_content,
|
$block_content,
|
||||||
esc_attr( $this->block_name ),
|
esc_attr( $this->block_name ),
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status class.
|
* Status class.
|
||||||
*/
|
*/
|
||||||
|
@ -26,7 +28,7 @@ class Status extends AbstractOrderConfirmationBlock {
|
||||||
*/
|
*/
|
||||||
protected function render( $attributes, $content, $block ) {
|
protected function render( $attributes, $content, $block ) {
|
||||||
$order = $this->get_order();
|
$order = $this->get_order();
|
||||||
$classname = $attributes['className'] ?? '';
|
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||||
|
|
||||||
if ( isset( $attributes['align'] ) ) {
|
if ( isset( $attributes['align'] ) ) {
|
||||||
$classname .= " align{$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';
|
$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();
|
$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';
|
$html_element = $is_ajax_button ? 'button' : 'a';
|
||||||
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
$styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
|
||||||
$classname = $attributes['className'] ?? '';
|
$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_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : '';
|
||||||
$custom_align_classes = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : '';
|
$custom_align_classes = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : '';
|
||||||
$html_classes = implode(
|
$html_classes = implode(
|
||||||
|
|
|
@ -89,7 +89,7 @@ class ProductCategories extends AbstractDynamicBlock {
|
||||||
|
|
||||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes(
|
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes(
|
||||||
$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'];
|
$classes = $this->get_container_classes( $attributes ) . ' ' . $classes_and_styles['classes'];
|
||||||
|
@ -116,10 +116,6 @@ class ProductCategories extends AbstractDynamicBlock {
|
||||||
$classes[] = "align{$attributes['align']}";
|
$classes[] = "align{$attributes['align']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! empty( $attributes['className'] ) ) {
|
|
||||||
$classes[] = $attributes['className'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $attributes['isDropdown'] ) {
|
if ( $attributes['isDropdown'] ) {
|
||||||
$classes[] = 'is-dropdown';
|
$classes[] = 'is-dropdown';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -76,18 +76,15 @@ class ProductDetails extends AbstractBlock {
|
||||||
$tabs = $tabs_html->get_updated_html();
|
$tabs = $tabs_html->get_updated_html();
|
||||||
}
|
}
|
||||||
|
|
||||||
$classname = $attributes['className'] ?? '';
|
|
||||||
|
|
||||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div class="wp-block-woocommerce-product-details %1$s %2$s">
|
'<div class="wp-block-woocommerce-product-details %1$s">
|
||||||
<div style="%3$s">
|
<div style="%2$s">
|
||||||
%4$s
|
%3$s
|
||||||
</div>
|
</div>
|
||||||
</div>',
|
</div>',
|
||||||
esc_attr( $classes_and_styles['classes'] ),
|
esc_attr( $classes_and_styles['classes'] ),
|
||||||
esc_attr( $classname ),
|
|
||||||
esc_attr( $classes_and_styles['styles'] ),
|
esc_attr( $classes_and_styles['styles'] ),
|
||||||
$tabs
|
$tabs
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils;
|
||||||
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProductGallery class.
|
* ProductGallery class.
|
||||||
|
@ -125,7 +126,7 @@ class ProductGallery extends AbstractBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
$number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0;
|
$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() : '';
|
$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 = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 );
|
||||||
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
$product_gallery_first_image_id = reset( $product_gallery_first_image );
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils;
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProductGalleryPager class.
|
* ProductGalleryPager class.
|
||||||
|
@ -55,7 +56,7 @@ class ProductGalleryPager extends AbstractBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
$number_of_thumbnails = $block->context['thumbnailsNumberOfThumbnails'] ?? 0;
|
$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 ) ) );
|
$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classname ) ) );
|
||||||
$post_id = $block->context['postId'] ?? '';
|
$post_id = $block->context['postId'] ?? '';
|
||||||
$product = wc_get_product( $post_id );
|
$product = wc_get_product( $post_id );
|
||||||
|
|
|
@ -208,13 +208,29 @@ class ProductImage extends AbstractBlock {
|
||||||
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
|
$post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : '';
|
||||||
$product = wc_get_product( $post_id );
|
$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 ) {
|
if ( $product ) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div class="wc-block-components-product-image wc-block-grid__product-image %1$s" style="%2$s">
|
'<div %1$s>
|
||||||
%3$s
|
%2$s
|
||||||
</div>',
|
</div>',
|
||||||
esc_attr( $classes_and_styles['classes'] ),
|
$wrapper_attributes,
|
||||||
esc_attr( $classes_and_styles['styles'] ),
|
|
||||||
$this->render_anchor(
|
$this->render_anchor(
|
||||||
$product,
|
$product,
|
||||||
$this->render_on_sale_badge( $product, $parsed_attributes ),
|
$this->render_on_sale_badge( $product, $parsed_attributes ),
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProductImageGallery class.
|
* ProductImageGallery class.
|
||||||
*/
|
*/
|
||||||
|
@ -67,7 +69,7 @@ class ProductImageGallery extends AbstractBlock {
|
||||||
$product_image_gallery_html = ob_get_clean();
|
$product_image_gallery_html = ob_get_clean();
|
||||||
|
|
||||||
$product = $previous_product;
|
$product = $previous_product;
|
||||||
$classname = $attributes['className'] ?? '';
|
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s %3$s</div>',
|
'<div class="wp-block-woocommerce-product-image-gallery %1$s">%2$s %3$s</div>',
|
||||||
esc_attr( $classname ),
|
esc_attr( $classname ),
|
||||||
|
|
|
@ -202,13 +202,29 @@ class ProductRating extends AbstractBlock {
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
return sprintf(
|
$classes = implode(
|
||||||
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
|
' ',
|
||||||
%4$s
|
array_filter(
|
||||||
</div>',
|
array(
|
||||||
|
'wc-block-components-product-rating wc-block-grid__product-rating',
|
||||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||||
esc_attr( $styles_and_classes['classes'] ),
|
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
|
$rating_html
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ class ProductRatingCounter extends AbstractBlock {
|
||||||
* @param int $count Total number of ratings.
|
* @param int $count Total number of ratings.
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
$filter_rating_html = function( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
|
$filter_rating_html = function ( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) {
|
||||||
$product_permalink = get_permalink( $post_id );
|
$product_permalink = get_permalink( $post_id );
|
||||||
$reviews_count = $count;
|
$reviews_count = $count;
|
||||||
$average_rating = $rating;
|
$average_rating = $rating;
|
||||||
|
@ -193,17 +193,32 @@ class ProductRatingCounter extends AbstractBlock {
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
return sprintf(
|
$classes = implode(
|
||||||
'<div class="wc-block-components-product-rating-counter wc-block-grid__product-rating-counter %1$s %2$s" style="%3$s">
|
' ',
|
||||||
%4$s
|
array_filter(
|
||||||
</div>',
|
array(
|
||||||
|
'wc-block-components-product-rating-counter wc-block-grid__product-rating-counter',
|
||||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||||
esc_attr( $styles_and_classes['classes'] ),
|
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
|
$rating_html
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,13 +149,29 @@ class ProductRatingStars extends AbstractBlock {
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
|
||||||
return sprintf(
|
$classes = implode(
|
||||||
'<div class="wc-block-components-product-rating wc-block-grid__product-rating %1$s %2$s" style="%3$s">
|
' ',
|
||||||
%4$s
|
array_filter(
|
||||||
</div>',
|
array(
|
||||||
|
'wc-block-components-product-rating wc-block-grid__product-rating',
|
||||||
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
esc_attr( $text_align_styles_and_classes['class'] ?? '' ),
|
||||||
esc_attr( $styles_and_classes['classes'] ),
|
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
|
$rating_html
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,6 @@ class ProductResultsCount extends AbstractBlock {
|
||||||
'wc-block-product-results-count',
|
'wc-block-product-results-count',
|
||||||
'wp-block-woocommerce-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( 'class', implode( ' ', $classes ) );
|
||||||
$p->set_attribute( 'style', $parsed_style_attributes['styles'] );
|
$p->set_attribute( 'style', $parsed_style_attributes['styles'] );
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
namespace Automattic\WooCommerce\Blocks\BlockTypes;
|
||||||
|
|
||||||
|
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ProductReviews class.
|
* ProductReviews class.
|
||||||
*/
|
*/
|
||||||
|
@ -40,13 +42,11 @@ class ProductReviews extends AbstractBlock {
|
||||||
|
|
||||||
$reviews = ob_get_clean();
|
$reviews = ob_get_clean();
|
||||||
|
|
||||||
$classname = $attributes['className'] ?? '';
|
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div class="wp-block-woocommerce-product-reviews %1$s">
|
'<div class="wp-block-woocommerce-product-reviews %1$s">
|
||||||
%2$s
|
%2$s
|
||||||
</div>',
|
</div>',
|
||||||
esc_attr( $classname ),
|
StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ),
|
||||||
$reviews
|
$reviews
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,8 +110,9 @@ class ProductSaleBadge extends AbstractBlock {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) );
|
||||||
$classname = isset( $attributes['className'] ) ? $attributes['className'] : '';
|
|
||||||
|
$classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) );
|
||||||
|
|
||||||
$align = isset( $attributes['align'] ) ? $attributes['align'] : '';
|
$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 );
|
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
|
||||||
|
|
||||||
$classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : '';
|
$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--out-of-stock ' : '';
|
||||||
$classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-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 ' : '';
|
$classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : '';
|
||||||
|
|
|
@ -687,6 +687,25 @@ class StyleAttributesUtils {
|
||||||
return self::EMPTY_STYLE;
|
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.
|
* Get classes and styles from attributes.
|
||||||
*
|
*
|
||||||
|
@ -717,6 +736,7 @@ class StyleAttributesUtils {
|
||||||
'text_color' => self::get_text_color_class_and_style( $attributes ),
|
'text_color' => self::get_text_color_class_and_style( $attributes ),
|
||||||
'text_decoration' => self::get_text_decoration_class_and_style( $attributes ),
|
'text_decoration' => self::get_text_decoration_class_and_style( $attributes ),
|
||||||
'text_transform' => self::get_text_transform_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 ) ) {
|
if ( ! empty( $properties ) ) {
|
||||||
|
|
|
@ -25,11 +25,6 @@ class Brands {
|
||||||
return;
|
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.php';
|
||||||
include_once WC_ABSPATH . 'includes/class-wc-brands-coupons.php';
|
include_once WC_ABSPATH . 'includes/class-wc-brands-coupons.php';
|
||||||
include_once WC_ABSPATH . 'includes/class-wc-brands-brand-settings-manager.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.
|
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;
|
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.
|
* 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 ) {
|
public function site_visibility_badge( $wp_admin_bar ) {
|
||||||
// Early exit if LYS feature is disabled.
|
// Early exit if LYS feature is disabled.
|
||||||
if ( ! Features::is_enabled( 'launch-your-store' ) ) {
|
if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ class ComingSoonAdminBarBadge {
|
||||||
*/
|
*/
|
||||||
public function output_css() {
|
public function output_css() {
|
||||||
// Early exit if LYS feature is disabled.
|
// Early exit if LYS feature is disabled.
|
||||||
if ( ! Features::is_enabled( 'launch-your-store' ) ) {
|
if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class ComingSoonRequestHandler {
|
||||||
* @internal
|
* @internal
|
||||||
*
|
*
|
||||||
* @param string $template The path to the previously determined template.
|
* @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 ) {
|
public function handle_template_include( $template ) {
|
||||||
global $wp;
|
global $wp;
|
||||||
|
@ -91,8 +91,8 @@ class ComingSoonRequestHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $is_fse_theme ) {
|
if ( $is_fse_theme ) {
|
||||||
// Since we've already rendered a template, return null to ensure no other template is rendered.
|
// Since we've already rendered a template, return empty string to ensure no other template is rendered.
|
||||||
return null;
|
return '';
|
||||||
} else {
|
} else {
|
||||||
// In non-FSE themes, other templates will still be rendered.
|
// In non-FSE themes, other templates will still be rendered.
|
||||||
// We need to exit to prevent further processing.
|
// We need to exit to prevent further processing.
|
||||||
|
|
|
@ -224,6 +224,18 @@ class FeaturesController {
|
||||||
'is_legacy' => true,
|
'is_legacy' => true,
|
||||||
'is_experimental' => false,
|
'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(
|
'hpos_fts_indexes' => array(
|
||||||
'name' => __( 'HPOS Full text search indexes', 'woocommerce' ),
|
'name' => __( 'HPOS Full text search indexes', 'woocommerce' ),
|
||||||
'description' => __(
|
'description' => __(
|
||||||
|
|
|
@ -66,7 +66,8 @@ class Packages {
|
||||||
* @since 3.7.0
|
* @since 3.7.0
|
||||||
*/
|
*/
|
||||||
public static function init() {
|
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.
|
// Prevent plugins already merged into WooCommerce core from getting activated as standalone plugins.
|
||||||
add_action( 'activate_plugin', array( __CLASS__, 'deactivate_merged_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() );
|
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.
|
* Deactivates merged feature plugins.
|
||||||
*
|
*
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
*
|
*
|
||||||
* @see https://docs.woocommerce.com/document/template-structure/
|
* @see https://docs.woocommerce.com/document/template-structure/
|
||||||
* @package WooCommerce\Templates
|
* @package WooCommerce\Templates
|
||||||
* @version 8.6.0
|
* @version 9.5.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if ( ! defined( 'ABSPATH' ) ) {
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
@ -26,7 +26,7 @@ if ( ! $notices ) {
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php foreach ( $notices as $notice ) : ?>
|
<?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">
|
<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>
|
<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>
|
</svg>
|
||||||
|
|
|
@ -33,6 +33,18 @@ config = {
|
||||||
'**/merchant/launch-your-store.spec.js',
|
'**/merchant/launch-your-store.spec.js',
|
||||||
'**/merchant/lost-password.spec.js',
|
'**/merchant/lost-password.spec.js',
|
||||||
'**/merchant/order-bulk-edit.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/,
|
grepInvert: /@skip-on-default-wpcom/,
|
||||||
},
|
},
|
||||||
|
|
|
@ -246,6 +246,14 @@ test.describe(
|
||||||
'wp-admin/edit.php?post_type=product&page=product-reviews'
|
'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 }` );
|
const reviewRow = page.locator( `#comment-${ review.id }` );
|
||||||
await reviewRow.hover();
|
await reviewRow.hover();
|
||||||
await reviewRow.getByRole( 'button', { name: 'Reply' } ).click();
|
await reviewRow.getByRole( 'button', { name: 'Reply' } ).click();
|
||||||
|
@ -256,7 +264,12 @@ test.describe(
|
||||||
const replyText = `Thank you for your feedback! (replied ${ Date.now() })`;
|
const replyText = `Thank you for your feedback! (replied ${ Date.now() })`;
|
||||||
await replyTextArea.fill( replyText );
|
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
|
const productLink = await reviewRow
|
||||||
.locator( 'a.comments-view-item-link' )
|
.locator( 'a.comments-view-item-link' )
|
||||||
|
|
|
@ -3,7 +3,13 @@ const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
|
||||||
|
|
||||||
test.describe(
|
test.describe(
|
||||||
'WooCommerce woo.com Settings',
|
'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 } );
|
test.use( { storageState: process.env.ADMINSTATE } );
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,11 @@ async function userDeletionTest( page, username ) {
|
||||||
page.getByRole( 'heading', { name: 'Delete Users' } )
|
page.getByRole( 'heading', { name: 'Delete Users' } )
|
||||||
).toBeVisible();
|
).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();
|
await page.getByRole( 'button', { name: 'Confirm Deletion' } ).click();
|
||||||
} );
|
} );
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue