Merge branch 'trunk' into add/wooexpress-rin-rule
This commit is contained in:
commit
53d7388269
|
@ -0,0 +1,26 @@
|
|||
name: Milestone Manager
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [milestoned]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
remove-milestone-from-unmerged-prs:
|
||||
name: "Remove Milestone from Unmerged PRs"
|
||||
if: github.event.pull_request.merged != true
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
milestone: null,
|
||||
});
|
|
@ -38,9 +38,7 @@ jobs:
|
|||
with:
|
||||
php-version: '7.4'
|
||||
- name: 'Run the script to assign a milestone'
|
||||
if: |
|
||||
!github.event.pull_request.milestone &&
|
||||
github.event.pull_request.base.ref == 'trunk'
|
||||
if: github.event.pull_request.base.ref == 'trunk'
|
||||
run: php assign-milestone-to-merged-pr.php
|
||||
env:
|
||||
PULL_REQUEST_ID: ${{ github.event.pull_request.node_id }}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Set the stalebot start date given a cron schedule.
|
||||
*/
|
||||
|
||||
// You need to install this dependency as part of your workflow.
|
||||
const core = require( '@actions/core' );
|
||||
|
||||
const ScheduleStartDate = () => {
|
||||
let scheduleStartDate;
|
||||
|
||||
switch ( process.env.CRON_SCHEDULE ) {
|
||||
case '21 1 * * *':
|
||||
scheduleStartDate = '2022-01-01';
|
||||
break;
|
||||
case '31 2 * * *':
|
||||
scheduleStartDate = '2023-01-01';
|
||||
break;
|
||||
case '41 3 * * *':
|
||||
scheduleStartDate = '2023-08-01';
|
||||
break;
|
||||
default:
|
||||
scheduleStartDate = '2018-01-01';
|
||||
break;
|
||||
}
|
||||
|
||||
core.setOutput( 'stale-start-date', scheduleStartDate );
|
||||
};
|
||||
|
||||
ScheduleStartDate();
|
|
@ -1,7 +1,10 @@
|
|||
name: 'Close stale needs-feedback issues'
|
||||
name: 'Process stale needs-feedback issues'
|
||||
on:
|
||||
schedule:
|
||||
- cron: '21 0 * * *'
|
||||
- cron: '11 0 * * *'
|
||||
- cron: '21 1 * * *'
|
||||
- cron: '31 2 * * *'
|
||||
- cron: '41 3 * * *'
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
@ -13,10 +16,20 @@ jobs:
|
|||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v8
|
||||
- name: Install Actions Core
|
||||
run: npm --prefix .github/workflows/scripts install @actions/core
|
||||
|
||||
- name: Get start date
|
||||
id: startdate
|
||||
run: node .github/workflows/scripts/stalebot.js
|
||||
env:
|
||||
CRON_SCHEDULE: ${{ github.event.schedule }}
|
||||
- name: Scan issues
|
||||
uses: actions/stale@v8
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
operations-per-run: 40
|
||||
operations-per-run: 8
|
||||
start-date: steps.startdate.outputs.stale-start-date
|
||||
stale-issue-message: "As a part of this repository's maintenance, this issue is being marked as stale due to inactivity. Please feel free to comment on it in case we missed something.\n\n###### After 7 days with no activity this issue will be automatically be closed."
|
||||
close-issue-message: 'This issue was closed because it has been 14 days with no activity.'
|
||||
days-before-issue-stale: 7
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: dev
|
||||
|
||||
Add preview and replace button to downloads edit #40835
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix selection in currency and number fields to only select if field still has focus.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Allow plugins to access PostTypeContext and blocks (through core/block-editor data store).
|
|
@ -1,13 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { ChangeEvent } from 'react';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { createElement, useState } from '@wordpress/element';
|
||||
import { trash } from '@wordpress/icons';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { ImageGallery, ImageGalleryItem } from '@woocommerce/components';
|
||||
import { uploadMedia } from '@wordpress/media-utils';
|
||||
import {
|
||||
Button,
|
||||
FormFileUpload,
|
||||
Modal,
|
||||
BaseControl,
|
||||
// @ts-expect-error `__experimentalInputControl` does exist.
|
||||
|
@ -20,18 +24,39 @@ import {
|
|||
import { EditDownloadsModalProps } from './types';
|
||||
import { UnionIcon } from './union-icon';
|
||||
|
||||
export interface Image {
|
||||
id: number;
|
||||
src: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export const EditDownloadsModal: React.FC< EditDownloadsModalProps > = ( {
|
||||
downloableItem,
|
||||
maxUploadFileSize = 10000000,
|
||||
onCancel,
|
||||
onChange,
|
||||
onRemove,
|
||||
onSave,
|
||||
onUploadSuccess,
|
||||
onUploadError,
|
||||
} ) => {
|
||||
const { createNotice } = useDispatch( 'core/notices' );
|
||||
const [ isCopingToClipboard, setIsCopingToClipboard ] =
|
||||
useState< boolean >( false );
|
||||
const [ isFileUploading, setIsFileUploading ] =
|
||||
useState< boolean >( false );
|
||||
|
||||
const { file = '', name = '' } = downloableItem;
|
||||
const { allowedMimeTypes } = useSelect( ( select ) => {
|
||||
const { getEditorSettings } = select( 'core/editor' );
|
||||
return getEditorSettings();
|
||||
} );
|
||||
|
||||
const allowedTypes = allowedMimeTypes
|
||||
? Object.values( allowedMimeTypes )
|
||||
: [];
|
||||
|
||||
const { id = 0, file = '', name = '' } = downloableItem;
|
||||
|
||||
const onCopySuccess = () => {
|
||||
createNotice(
|
||||
|
@ -40,6 +65,15 @@ export const EditDownloadsModal: React.FC< EditDownloadsModalProps > = ( {
|
|||
);
|
||||
};
|
||||
|
||||
const isImage = ( filename = '' ) => {
|
||||
if ( ! filename ) return;
|
||||
const imageExtensions = [ 'jpg', 'jpeg', 'png', 'gif', 'webp' ];
|
||||
const fileExtension = (
|
||||
filename.split( '.' ).pop() || ''
|
||||
).toLowerCase();
|
||||
return imageExtensions.includes( fileExtension );
|
||||
};
|
||||
|
||||
async function copyTextToClipboard( text: string ) {
|
||||
if ( 'clipboard' in navigator ) {
|
||||
await navigator.clipboard.writeText( text );
|
||||
|
@ -61,6 +95,21 @@ export const EditDownloadsModal: React.FC< EditDownloadsModalProps > = ( {
|
|||
setIsCopingToClipboard( false );
|
||||
}
|
||||
|
||||
async function handleFormFileUploadChange(
|
||||
event: ChangeEvent< HTMLInputElement >
|
||||
) {
|
||||
setIsFileUploading( true );
|
||||
const filesList = event.currentTarget.files as FileList;
|
||||
await uploadMedia( {
|
||||
allowedTypes,
|
||||
filesList,
|
||||
maxUploadFileSize,
|
||||
onFileChange: onUploadSuccess,
|
||||
onError: onUploadError,
|
||||
} );
|
||||
setIsFileUploading( false );
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={ sprintf(
|
||||
|
@ -81,6 +130,34 @@ export const EditDownloadsModal: React.FC< EditDownloadsModalProps > = ( {
|
|||
} }
|
||||
className="woocommerce-edit-downloads-modal"
|
||||
>
|
||||
<div className="woocommerce-edit-downloads-modal__preview">
|
||||
{ isImage( file ) && (
|
||||
<ImageGallery allowDragging={ false } columns={ 1 }>
|
||||
<ImageGalleryItem
|
||||
key={ id }
|
||||
alt={ name }
|
||||
src={ file }
|
||||
id={ `${ id }` }
|
||||
isCover={ false }
|
||||
/>
|
||||
</ImageGallery>
|
||||
) }
|
||||
<FormFileUpload
|
||||
onChange={ handleFormFileUploadChange }
|
||||
render={ ( { openFileDialog } ) => (
|
||||
<div>
|
||||
<p>{ name }</p>
|
||||
<Button
|
||||
onClick={ openFileDialog }
|
||||
isBusy={ isFileUploading }
|
||||
disabled={ isFileUploading }
|
||||
>
|
||||
{ __( 'Replace', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
<BaseControl
|
||||
id={ 'file-name-help' }
|
||||
className="woocommerce-edit-downloads-modal__file-name"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
$gutenberg-blue: var(--wp-admin-theme-color);
|
||||
|
||||
.woocommerce-edit-downloads-modal {
|
||||
&__buttons {
|
||||
display: flex;
|
||||
|
@ -15,7 +17,37 @@
|
|||
padding-bottom: $gap-large;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
margin-bottom: $gap;
|
||||
}
|
||||
|
||||
.components-input-control__suffix {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.woocommerce-image-gallery,
|
||||
.components-form-file-upload {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding-right: $gap;
|
||||
p {
|
||||
margin-bottom: $gap-smallest;
|
||||
}
|
||||
button {
|
||||
color: $gutenberg-blue;
|
||||
padding: 0;
|
||||
}
|
||||
button:hover:not(.is-busy) {
|
||||
background: rgba(var(--wp-admin-theme-color--rgb), 0.04);
|
||||
}
|
||||
.woocommerce-image-gallery__item {
|
||||
width: $gap-larger * 2;
|
||||
height: $gap-larger * 2;
|
||||
img {
|
||||
width: $gap-larger * 2;
|
||||
height: $gap-larger * 2;
|
||||
border-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { ProductDownload } from '@woocommerce/data';
|
||||
import { MediaItem } from '@wordpress/media-utils';
|
||||
|
||||
export type EditDownloadsModalProps = {
|
||||
downloableItem: ProductDownload;
|
||||
maxUploadFileSize?: number;
|
||||
onCancel: () => void;
|
||||
onRemove: () => void;
|
||||
onSave: () => void;
|
||||
onChange: ( name: string ) => void;
|
||||
onUploadSuccess( files: MediaItem[] ): void;
|
||||
onUploadError( error: unknown ): void;
|
||||
};
|
||||
|
|
|
@ -143,6 +143,39 @@ export function Edit( {
|
|||
}
|
||||
}
|
||||
|
||||
function handleFileReplace( files: MediaItem | MediaItem[] ) {
|
||||
if (
|
||||
! Array.isArray( files ) ||
|
||||
! files?.length ||
|
||||
files[ 0 ]?.id === undefined
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! downloads.length ) {
|
||||
setDownloadable( true );
|
||||
}
|
||||
|
||||
const uploadedFile = {
|
||||
id: stringifyId( files[ 0 ].id ),
|
||||
file: files[ 0 ].url,
|
||||
name:
|
||||
files[ 0 ].title ||
|
||||
files[ 0 ].alt ||
|
||||
files[ 0 ].caption ||
|
||||
getFileName( files[ 0 ].url ),
|
||||
};
|
||||
const stringifyIds = downloads.map( ( download ) => {
|
||||
if ( download.file === selectedDownload?.file ) {
|
||||
return stringifyEntityId( uploadedFile );
|
||||
}
|
||||
return stringifyEntityId( download );
|
||||
} );
|
||||
|
||||
setDownloads( stringifyIds );
|
||||
setSelectedDownload( uploadedFile );
|
||||
}
|
||||
|
||||
function removeDownload( download: ProductDownload ) {
|
||||
const otherDownloads = downloads.reduce< ProductDownload[] >(
|
||||
function removeDownloadElement(
|
||||
|
@ -336,6 +369,8 @@ export function Edit( {
|
|||
setDownloads( newDownloads );
|
||||
setSelectedDownload( null );
|
||||
} }
|
||||
onUploadSuccess={ handleFileReplace }
|
||||
onUploadError={ handleUploadError }
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,7 @@ import { synchronizeBlocksWithTemplate, Template } from '@wordpress/blocks';
|
|||
import { createElement, useMemo, useLayoutEffect } from '@wordpress/element';
|
||||
import { useDispatch, useSelect, select as WPSelect } from '@wordpress/data';
|
||||
import { uploadMedia } from '@wordpress/media-utils';
|
||||
import { PluginArea } from '@wordpress/plugins';
|
||||
import {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore No types for this exist yet.
|
||||
|
@ -32,6 +33,7 @@ import {
|
|||
*/
|
||||
import { useConfirmUnsavedProductChanges } from '../../hooks/use-confirm-unsaved-product-changes';
|
||||
import { ProductEditorContext } from '../../types';
|
||||
import { PostTypeContext } from '../../contexts/post-type-context';
|
||||
|
||||
type BlockEditorProps = {
|
||||
context: Partial< ProductEditorContext >;
|
||||
|
@ -120,6 +122,11 @@ export function BlockEditor( {
|
|||
<BlockList className="woocommerce-product-block-editor__block-list" />
|
||||
</ObserveTyping>
|
||||
</BlockTools>
|
||||
{ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ }
|
||||
<PostTypeContext.Provider value={ context.postType! }>
|
||||
{ /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ }
|
||||
<PluginArea scope="woocommerce-product-block-editor" />
|
||||
</PostTypeContext.Provider>
|
||||
</BlockEditorProvider>
|
||||
</BlockContextProvider>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
Fragment,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
import { PluginArea } from '@wordpress/plugins';
|
||||
import {
|
||||
LayoutContextProvider,
|
||||
useExtendLayout,
|
||||
|
@ -90,8 +89,6 @@ export function Editor( {
|
|||
postId: product.id,
|
||||
} }
|
||||
/>
|
||||
{ /* @ts-expect-error 'scope' does exist. @types/wordpress__plugins is outdated. */ }
|
||||
<PluginArea scope="woocommerce-product-block-editor" />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createContext } from '@wordpress/element';
|
||||
|
||||
export const PostTypeContext = createContext( 'product' );
|
|
@ -8,7 +8,7 @@ import { useContext } from '@wordpress/element';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { useProductHelper } from './use-product-helper';
|
||||
import { formatCurrencyDisplayValue } from '../utils';
|
||||
import { deferSelectInFocus, formatCurrencyDisplayValue } from '../utils';
|
||||
|
||||
export type CurrencyInputProps = {
|
||||
prefix: string;
|
||||
|
@ -51,18 +51,7 @@ export const useCurrencyInputProps = ( {
|
|||
return sanitizePrice( String( val ) );
|
||||
},
|
||||
onFocus( event: React.FocusEvent< HTMLInputElement > ) {
|
||||
// In some browsers like safari .select() function inside
|
||||
// the onFocus event doesn't work as expected because it
|
||||
// conflicts with onClick the first time user click the
|
||||
// input. Using setTimeout defers the text selection and
|
||||
// avoid the unexpected behaviour.
|
||||
setTimeout(
|
||||
function deferSelection( element: HTMLInputElement ) {
|
||||
element.select();
|
||||
},
|
||||
0,
|
||||
event.currentTarget
|
||||
);
|
||||
deferSelectInFocus( event.currentTarget );
|
||||
if ( onFocus ) {
|
||||
onFocus( event );
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { useProductHelper } from './use-product-helper';
|
||||
import { deferSelectInFocus } from '../utils';
|
||||
|
||||
export type NumberInputProps = {
|
||||
value: string;
|
||||
|
@ -28,18 +29,7 @@ export const useNumberInputProps = ( {
|
|||
const numberInputProps: NumberInputProps = {
|
||||
value: formatNumber( value ),
|
||||
onFocus( event: React.FocusEvent< HTMLInputElement > ) {
|
||||
// In some browsers like safari .select() function inside
|
||||
// the onFocus event doesn't work as expected because it
|
||||
// conflicts with onClick the first time user click the
|
||||
// input. Using setTimeout defers the text selection and
|
||||
// avoid the unexpected behaviour.
|
||||
setTimeout(
|
||||
function deferSelection( element: HTMLInputElement ) {
|
||||
element.select();
|
||||
},
|
||||
0,
|
||||
event.currentTarget
|
||||
);
|
||||
deferSelectInFocus( event.currentTarget );
|
||||
if ( onFocus ) {
|
||||
onFocus( event );
|
||||
}
|
||||
|
|
|
@ -20,5 +20,6 @@ export * from './utils';
|
|||
* Hooks
|
||||
*/
|
||||
export * from './hooks';
|
||||
export { PostTypeContext } from './contexts/post-type-context';
|
||||
export { useValidation, useValidations } from './contexts/validation-context';
|
||||
export * from './contexts/validation-context/types';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
export function deferSelectInFocus( element: HTMLInputElement ) {
|
||||
// In some browsers like safari .select() function inside
|
||||
// the onFocus event doesn't work as expected because it
|
||||
// conflicts with onClick the first time user click the
|
||||
// input. Using setTimeout defers the text selection and
|
||||
// avoid the unexpected behaviour.
|
||||
setTimeout(
|
||||
function deferSelection( originalElement: HTMLInputElement ) {
|
||||
if ( element.ownerDocument.activeElement === originalElement ) {
|
||||
// We still have focus, so select the content.
|
||||
originalElement.select();
|
||||
}
|
||||
},
|
||||
0,
|
||||
element
|
||||
);
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { AUTO_DRAFT_NAME } from './constants';
|
||||
import { deferSelectInFocus } from './defer-select-in-focus';
|
||||
import { formatCurrencyDisplayValue } from './format-currency-display-value';
|
||||
import { getCheckboxTracks } from './get-checkbox-tracks';
|
||||
import { getCurrencySymbolProps } from './get-currency-symbol-props';
|
||||
|
@ -30,6 +31,7 @@ export * from './sift';
|
|||
|
||||
export {
|
||||
AUTO_DRAFT_NAME,
|
||||
deferSelectInFocus,
|
||||
formatCurrencyDisplayValue,
|
||||
getCheckboxTracks,
|
||||
getCurrencySymbolProps,
|
||||
|
|
|
@ -66,7 +66,7 @@ const MAX_PAGE_COUNT = 100;
|
|||
export const BlockEditor = ( {} ) => {
|
||||
const history = useHistory();
|
||||
const settings = useSiteEditorSettings();
|
||||
const [ blocks, onChange ] = useEditorBlocks();
|
||||
const [ blocks, , onChange ] = useEditorBlocks();
|
||||
const urlParams = useQuery();
|
||||
const { currentState } = useContext( CustomizeStoreContext );
|
||||
|
||||
|
|
|
@ -145,35 +145,15 @@ export const Layout = () => {
|
|||
</NavigableRegion>
|
||||
|
||||
{ ! isMobileViewport && (
|
||||
<div
|
||||
className={ classnames(
|
||||
'edit-site-layout__canvas-container'
|
||||
) }
|
||||
>
|
||||
<div className="edit-site-layout__canvas-container">
|
||||
{ canvasResizer }
|
||||
{ !! canvasSize.width && (
|
||||
<motion.div
|
||||
whileHover={ {
|
||||
scale: 1.005,
|
||||
transition: {
|
||||
duration: disableMotion
|
||||
? 0
|
||||
: 0.5,
|
||||
ease: 'easeOut',
|
||||
},
|
||||
} }
|
||||
initial={ false }
|
||||
layout="position"
|
||||
className={ classnames(
|
||||
'edit-site-layout__canvas'
|
||||
) }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration: disableMotion
|
||||
? 0
|
||||
: ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
>
|
||||
<ErrorBoundary>
|
||||
<ResizableFrame
|
||||
|
|
|
@ -158,7 +158,7 @@ export const OnboardingTour = ( {
|
|||
[ key: string ]: unknown;
|
||||
} ) => {
|
||||
if ( placement === 'left' ) {
|
||||
return [ -15, 35 ];
|
||||
return [ 0, 20 ];
|
||||
}
|
||||
return [ 52, 16 ];
|
||||
},
|
||||
|
|
|
@ -189,12 +189,10 @@ function ResizableFrame( {
|
|||
},
|
||||
};
|
||||
const currentResizeHandleVariant = ( () => {
|
||||
if ( isResizing ) {
|
||||
if ( isResizing || isHandleVisibleByDefault ) {
|
||||
return 'active';
|
||||
}
|
||||
return shouldShowHandle || isHandleVisibleByDefault
|
||||
? 'visible'
|
||||
: 'hidden';
|
||||
return shouldShowHandle ? 'visible' : 'hidden';
|
||||
} )();
|
||||
|
||||
const resizeHandler = (
|
||||
|
@ -246,6 +244,13 @@ function ResizableFrame( {
|
|||
if ( definition === 'fullWidth' )
|
||||
setFrameSize( { width: '100%', height: '100%' } );
|
||||
} }
|
||||
whileHover={ {
|
||||
scale: 1.005,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: 'easeOut',
|
||||
},
|
||||
} }
|
||||
transition={ frameTransition }
|
||||
size={ frameSize }
|
||||
enable={ {
|
||||
|
|
|
@ -1187,6 +1187,13 @@ export const COLOR_PALETTES = [
|
|||
text: 'var(--wp--preset--color--background)',
|
||||
},
|
||||
},
|
||||
':visited': {
|
||||
color: {
|
||||
text: color.styles.elements?.button
|
||||
? color.styles.elements.button.color
|
||||
: 'var(--wp--preset--color--background)',
|
||||
},
|
||||
},
|
||||
color: {
|
||||
background: 'var(--wp--preset--color--primary)',
|
||||
text: 'var(--wp--preset--color--background)',
|
||||
|
|
|
@ -26,8 +26,17 @@ export const ColorPanel = () => {
|
|||
const [ rawSettings ] = useGlobalSetting( '' );
|
||||
const settings = useSettingsForBlockElement( rawSettings );
|
||||
|
||||
const onChange = ( ...props ) => {
|
||||
setStyle( ...props );
|
||||
const onChange = ( _style ) => {
|
||||
setStyle( {
|
||||
..._style,
|
||||
blocks: {
|
||||
..._style.blocks,
|
||||
// Reset the "core/button" color that may have been set via predefined color palette to ensure it uses the custom button color.
|
||||
'core/button': {
|
||||
color: {},
|
||||
},
|
||||
},
|
||||
} );
|
||||
setUserConfig( ( currentConfig ) => ( {
|
||||
...currentConfig,
|
||||
settings: mergeBaseAndUserConfigs( currentConfig.settings, {
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createInterpolateElement } from '@wordpress/element';
|
||||
import { createInterpolateElement, useContext } from '@wordpress/element';
|
||||
import { Link } from '@woocommerce/components';
|
||||
import { PanelBody } from '@wordpress/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { privateApis as blockEditorPrivateApis } from '@wordpress/block-editor';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { unlock } from '@wordpress/edit-site/build-module/lock-unlock';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -16,7 +20,14 @@ import { SidebarNavigationScreen } from './sidebar-navigation-screen';
|
|||
import { ADMIN_URL } from '~/utils/admin-settings';
|
||||
import { ColorPalette, ColorPanel } from './global-styles';
|
||||
|
||||
const { GlobalStylesContext } = unlock( blockEditorPrivateApis );
|
||||
|
||||
const SidebarNavigationScreenColorPaletteContent = () => {
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { user } = useContext( GlobalStylesContext );
|
||||
const hasCreatedOwnColors = !! (
|
||||
user.settings.color && user.settings.color.palette.hasCreatedOwnColors
|
||||
);
|
||||
// Wrap in a BlockEditorProvider to ensure that the Iframe's dependencies are
|
||||
// loaded. This is necessary because the Iframe component waits until
|
||||
// the block editor store's `__internalIsInitialized` is true before
|
||||
|
@ -34,7 +45,7 @@ const SidebarNavigationScreenColorPaletteContent = () => {
|
|||
<PanelBody
|
||||
className="woocommerce-customize-store__color-panel-container"
|
||||
title={ __( 'or create your own', 'woocommerce' ) }
|
||||
initialOpen={ false }
|
||||
initialOpen={ hasCreatedOwnColors }
|
||||
>
|
||||
<ColorPanel />
|
||||
</PanelBody>
|
||||
|
|
|
@ -29,7 +29,7 @@ import { findPatternByBlock } from './utils';
|
|||
import BlockPatternList from '../block-pattern-list';
|
||||
|
||||
const SUPPORTED_FOOTER_PATTERNS = [
|
||||
'woocommerce-blocks/footer-simple-menu-and-cart',
|
||||
'woocommerce-blocks/footer-simple-menu',
|
||||
'woocommerce-blocks/footer-with-3-menus',
|
||||
'woocommerce-blocks/footer-large',
|
||||
];
|
||||
|
|
|
@ -415,6 +415,11 @@
|
|||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.color-block-support-panel {
|
||||
border-top: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-customize-store_color-palette-container {
|
||||
|
|
|
@ -116,8 +116,7 @@ export const ApiCallLoader = () => {
|
|||
return (
|
||||
<Loader>
|
||||
<Loader.Sequence
|
||||
/* divide all frames equally over 1m. */
|
||||
interval={ ( 60 * 1000 ) / ( loaderSteps.length - 1 ) }
|
||||
interval={ ( 40 * 1000 ) / ( loaderSteps.length - 1 ) }
|
||||
shouldLoop={ false }
|
||||
>
|
||||
{ loaderSteps.slice( 0, -1 ).map( ( step, index ) => (
|
||||
|
|
|
@ -5,8 +5,8 @@ import { z } from 'zod';
|
|||
|
||||
const footerChoices = [
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-simple-menu-and-cart',
|
||||
label: 'Footer with Simple Menu and Cart',
|
||||
slug: 'woocommerce-blocks/footer-simple-menu',
|
||||
label: 'Footer with Simple Menu',
|
||||
},
|
||||
{
|
||||
slug: 'woocommerce-blocks/footer-with-3-menus',
|
||||
|
|
|
@ -218,11 +218,11 @@ export const updateStorePatterns = async (
|
|||
woocommerce_blocks_allow_ai_connection: true,
|
||||
} );
|
||||
|
||||
const response: {
|
||||
const { images } = await apiFetch< {
|
||||
ai_content_generated: boolean;
|
||||
additional_errors?: unknown[];
|
||||
} = await apiFetch( {
|
||||
path: '/wc/store/patterns',
|
||||
images: Array< unknown >;
|
||||
} >( {
|
||||
path: '/wc/private/ai/images',
|
||||
method: 'POST',
|
||||
data: {
|
||||
business_description:
|
||||
|
@ -230,6 +230,63 @@ export const updateStorePatterns = async (
|
|||
},
|
||||
} );
|
||||
|
||||
const [ response ] = await Promise.all< {
|
||||
ai_content_generated: boolean;
|
||||
product_content: Array< {
|
||||
title: string;
|
||||
description: string;
|
||||
image: {
|
||||
src: string;
|
||||
alt: string;
|
||||
};
|
||||
} >;
|
||||
additional_errors?: unknown[];
|
||||
} >( [
|
||||
apiFetch( {
|
||||
path: '/wc/private/ai/products',
|
||||
method: 'POST',
|
||||
data: {
|
||||
business_description:
|
||||
context.businessInfoDescription.descriptionText,
|
||||
images,
|
||||
},
|
||||
} ),
|
||||
apiFetch( {
|
||||
path: '/wc/private/ai/patterns',
|
||||
method: 'POST',
|
||||
data: {
|
||||
business_description:
|
||||
context.businessInfoDescription.descriptionText,
|
||||
images,
|
||||
},
|
||||
} ),
|
||||
] );
|
||||
|
||||
const productContents = response.product_content.map(
|
||||
( product, index ) => {
|
||||
return apiFetch( {
|
||||
path: '/wc/private/ai/product',
|
||||
method: 'POST',
|
||||
data: {
|
||||
products_information: product,
|
||||
index,
|
||||
},
|
||||
} );
|
||||
}
|
||||
);
|
||||
|
||||
await Promise.all( [
|
||||
...productContents,
|
||||
apiFetch( {
|
||||
path: '/wc/private/ai/business-description',
|
||||
method: 'POST',
|
||||
data: {
|
||||
business_description:
|
||||
context.businessInfoDescription.descriptionText,
|
||||
},
|
||||
} ),
|
||||
] );
|
||||
|
||||
if ( ! response.ai_content_generated ) {
|
||||
throw new Error(
|
||||
'AI content not generated: ' + response.additional_errors
|
||||
|
@ -260,6 +317,12 @@ const updateGlobalStyles = async ( {
|
|||
( pairing ) => pairing.title === fontPairingName
|
||||
);
|
||||
|
||||
// @ts-ignore No types for this exist yet.
|
||||
const { invalidateResolutionForStoreSelector } = dispatch( coreStore );
|
||||
invalidateResolutionForStoreSelector(
|
||||
'__experimentalGetCurrentGlobalStylesId'
|
||||
);
|
||||
|
||||
const globalStylesId = await resolveSelect(
|
||||
coreStore
|
||||
// @ts-ignore No types for this exist yet.
|
||||
|
@ -299,6 +362,7 @@ const updateTemplate = async ( {
|
|||
|
||||
// Ensure that the patterns are up to date because we populate images and content in previous step.
|
||||
invalidateResolutionForStoreSelector( 'getBlockPatterns' );
|
||||
invalidateResolutionForStoreSelector( '__experimentalGetTemplateForLink' );
|
||||
|
||||
const patterns = ( await resolveSelect(
|
||||
coreStore
|
||||
|
@ -349,7 +413,6 @@ export const assembleSite = async (
|
|||
} );
|
||||
recordEvent( 'customize_your_store_ai_update_global_styles_success' );
|
||||
} catch ( error ) {
|
||||
// TODO handle error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( error );
|
||||
recordEvent(
|
||||
|
@ -358,6 +421,7 @@ export const assembleSite = async (
|
|||
error: error instanceof Error ? error.message : 'unknown',
|
||||
}
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -368,12 +432,12 @@ export const assembleSite = async (
|
|||
} );
|
||||
recordEvent( 'customize_your_store_ai_update_template_success' );
|
||||
} catch ( error ) {
|
||||
// TODO handle error
|
||||
// eslint-disable-next-line no-console
|
||||
console.error( error );
|
||||
recordEvent( 'customize_your_store_ai_update_template_response_error', {
|
||||
error: error instanceof Error ? error.message : 'unknown',
|
||||
} );
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ export type Campaign = {
|
|||
cost: {
|
||||
value: string;
|
||||
currency: string;
|
||||
};
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type CampaignsPage = {
|
||||
|
|
|
@ -46,11 +46,15 @@ export const useCampaigns = ( page = 1, perPage = 5 ): UseCampaignsType => {
|
|||
( el ) => el.slug === campaign.channel
|
||||
);
|
||||
|
||||
const cost = campaign.cost
|
||||
? `${ campaign.cost.currency } ${ campaign.cost.value }`
|
||||
: '';
|
||||
|
||||
return {
|
||||
id: `${ campaign.channel }|${ campaign.id }`,
|
||||
title: campaign.title,
|
||||
description: '',
|
||||
cost: `${ campaign.cost.currency } ${ campaign.cost.value }`,
|
||||
cost,
|
||||
manageUrl: campaign.manage_url,
|
||||
icon: channel?.icon || '',
|
||||
channelName: channel?.title || '',
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
TablePlaceholder,
|
||||
Link,
|
||||
} from '@woocommerce/components';
|
||||
import { isWCAdmin } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -138,7 +139,16 @@ export const Campaigns = () => {
|
|||
<FlexBlock>
|
||||
<Flex direction="column" gap={ 1 }>
|
||||
<FlexItem className="woocommerce-marketing-campaigns-card__campaign-title">
|
||||
<Link href={ el.manageUrl }>
|
||||
<Link
|
||||
type={
|
||||
isWCAdmin(
|
||||
el.manageUrl
|
||||
)
|
||||
? 'wc-admin'
|
||||
: 'external'
|
||||
}
|
||||
href={ el.manageUrl }
|
||||
>
|
||||
{ el.title }
|
||||
</Link>
|
||||
</FlexItem>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix save button is still disabled after updating logo settings
|
|
@ -1,4 +0,0 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Preload Jetpack-related data from the Jetpack Connection package
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Rename the reference to the 'Footer with Simple Menu and Cart' pattern
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: fix
|
||||
|
||||
Fix marketing campaign link not navigating to the right page.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: update
|
||||
|
||||
Add stalebot schedules to allow processing of all issues
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: dev
|
||||
|
||||
Adds e2e tests for tax display in store, cart and checkout
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Avoid the PHP error with an undefined property on the WooCommerce > Extensions page.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix CYS initial pattern population bug
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix cys ui issues
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fix for PR tests and daily tests
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Allow null value in cost field for multichannel campaign.
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: performance
|
||||
|
||||
use multiple endpoints to improve performance
|
|
@ -0,0 +1,4 @@
|
|||
Significance: patch
|
||||
Type: fix
|
||||
|
||||
Fixed warning on wc_get_product_variation_attributes when product does not exist
|
|
@ -1263,7 +1263,7 @@ class WC_Admin_Addons {
|
|||
'title' => $locale->title,
|
||||
'description' => $locale->description,
|
||||
'image' => ( 'http' === substr( $locale->image, 0, 4 ) ) ? $locale->image : WC()->plugin_url() . $locale->image,
|
||||
'image_alt' => $locale->image_alt,
|
||||
'image_alt' => $locale->image_alt ?? '',
|
||||
'actions' => $promotion_actions,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -685,7 +685,7 @@ function wc_get_product_id_by_sku( $sku ) {
|
|||
*/
|
||||
function wc_get_product_variation_attributes( $variation_id ) {
|
||||
// Build variation data from meta.
|
||||
$all_meta = get_post_meta( $variation_id );
|
||||
$all_meta = is_array( get_post_meta( $variation_id ) ) ? get_post_meta( $variation_id ) : array();
|
||||
$parent_id = wp_get_post_parent_id( $variation_id );
|
||||
$parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) );
|
||||
$found_parent_attributes = array();
|
||||
|
|
|
@ -224,7 +224,7 @@ module.exports = async ( config ) => {
|
|||
}
|
||||
}
|
||||
|
||||
!process.env.BASE_URL || process.env.BASE_URL === 'localhost' && await site.useCartCheckoutShortcodes( baseURL, userAgent, admin );
|
||||
await site.useCartCheckoutShortcodes( baseURL, userAgent, admin );
|
||||
|
||||
await adminContext.close();
|
||||
await customerContext.close();
|
||||
|
|
|
@ -0,0 +1,695 @@
|
|||
const { test, expect } = require( '@playwright/test' );
|
||||
const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default;
|
||||
const { admin, customer } = require( '../../test-data/data' );
|
||||
|
||||
const productName = 'Taxed products are awesome';
|
||||
const productPrice = '100.00';
|
||||
const messyProductPrice = '13.47';
|
||||
const secondProductName = 'Other products are also awesome';
|
||||
|
||||
let productId, productId2, nastyTaxId, seventeenTaxId, sixTaxId, countryTaxId, stateTaxId, cityTaxId, zipTaxId, shippingTaxId, shippingZoneId, shippingMethodId;
|
||||
|
||||
test.describe( 'Shopper Tax Display Tests', () => {
|
||||
|
||||
test.beforeAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'yes',
|
||||
} );
|
||||
await api.post( 'products', {
|
||||
name: productName,
|
||||
type: 'simple',
|
||||
regular_price: productPrice,
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
productId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "US",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "25",
|
||||
"name": "Nasty Tax",
|
||||
"shipping": false
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
nastyTaxId = response.data.id;
|
||||
} );
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { page, context } ) => {
|
||||
// Shopping cart is very sensitive to cookies, so be explicit
|
||||
await context.clearCookies();
|
||||
|
||||
// all tests use the first product
|
||||
await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } );
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl'
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_price_display_suffix', {
|
||||
value: '',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'no',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized',
|
||||
} );
|
||||
await api.delete( `products/${ productId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ nastyTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks that taxes are calculated properly on totals, inclusive tax displayed properly', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'incl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'incl'
|
||||
} );
|
||||
|
||||
await test.step( 'Load shop page and confirm price display', async() => {
|
||||
await page.goto( '/shop/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible();
|
||||
await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $125.00' }).first() ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$125.00 (incl. tax)' } ) ).toHaveCount(2);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $125.00 (incl. tax)'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $125.00 (includes $25.00 Nasty Tax)' } ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load checkout page and confirm price display', async() => {
|
||||
await page.goto( '/checkout/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $125.00 (incl. tax)' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $125.00 (incl. tax)' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $125.00 (includes $25.00 Nasty Tax)'} ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks that taxes are calculated and displayed correctly exclusive on shop, cart and checkout', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl'
|
||||
} );
|
||||
|
||||
await test.step( 'Load shop page and confirm price display', async() => {
|
||||
await page.goto( '/shop/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible();
|
||||
await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $100.00' }).first() ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Tax $25.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $125.00' } ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load checkout page and confirm price display', async() => {
|
||||
await page.goto( '/checkout/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible();
|
||||
|
||||
await page.locator( '#billing_first_name' ).fill( customer.billing.us.first_name );
|
||||
await page.locator( '#billing_last_name' ).fill( customer.billing.us.last_name );
|
||||
await page.locator( '#billing_address_1' ).fill( customer.billing.us.address );
|
||||
await page.locator( '#billing_city' ).fill( customer.billing.us.city );
|
||||
await page.locator( '#billing_country' ).selectOption( customer.billing.us.country );
|
||||
await page.locator( '#billing_state' ).selectOption( customer.billing.us.state );
|
||||
await page.locator( '#billing_postcode' ).fill( customer.billing.us.zip );
|
||||
await page.locator( '#billing_phone' ).fill( customer.billing.us.phone );
|
||||
await page.locator( '#billing_email' ).fill( customer.billing.us.email );
|
||||
|
||||
await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $100.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Tax $25.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $125.00' } ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks that display suffix is shown', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_price_display_suffix', {
|
||||
value: 'excluding VAT',
|
||||
} );
|
||||
|
||||
await test.step( 'Load shop page and confirm price suffix display', async() => {
|
||||
await page.goto( '/shop/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible();
|
||||
await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $100.00 excluding VAT' }).first() ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'Shopper Tax Rounding', () => {
|
||||
|
||||
test.beforeAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'yes',
|
||||
} );
|
||||
await api.post( 'products', {
|
||||
name: productName,
|
||||
type: 'simple',
|
||||
regular_price: messyProductPrice,
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
productId = response.data.id;
|
||||
} );
|
||||
await api.post( 'products', {
|
||||
name: secondProductName,
|
||||
type: 'simple',
|
||||
regular_price: messyProductPrice,
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
productId2 = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "US",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "17",
|
||||
"name": "Seventeen Tax",
|
||||
"shipping": false,
|
||||
"compound": true,
|
||||
"priority": 1
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
seventeenTaxId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "US",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "6",
|
||||
"name": "Six Tax",
|
||||
"shipping": false,
|
||||
"compound": true,
|
||||
"priority": 2
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
sixTaxId = response.data.id;
|
||||
} );
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { page, context } ) => {
|
||||
// Shopping cart is very sensitive to cookies, so be explicit
|
||||
await context.clearCookies();
|
||||
|
||||
// all tests use the first product
|
||||
await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } );
|
||||
await page.goto( `/shop/?add-to-cart=${ productId2 }`, { waitUntil: 'networkidle' } );
|
||||
await page.goto( `/shop/?add-to-cart=${ productId2 }`, { waitUntil: 'networkidle' } );
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl'
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', {
|
||||
value: 'no',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'no',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized'
|
||||
} );
|
||||
await api.delete( `products/${ productId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `products/${ productId2 }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ seventeenTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ sixTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks rounding at subtotal level', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', {
|
||||
value: 'yes',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'single',
|
||||
} );
|
||||
|
||||
await test.step( 'Load shop page and confirm price display', async() => {
|
||||
await page.goto( '/shop/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible();
|
||||
await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $13.47' }).first() ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$13.47' } ) ).toHaveCount(3);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $40.41'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Tax $9.71 ' } ) ).toBeVisible()
|
||||
await expect( page.getByRole( 'row', { name: 'Total $50.12 ' } ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks rounding off at subtotal level', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_shop', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_round_at_subtotal', {
|
||||
value: 'no',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized',
|
||||
} );
|
||||
|
||||
await test.step( 'Load shop page and confirm price display', async() => {
|
||||
await page.goto( '/shop/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Shop' } ) ).toBeVisible();
|
||||
await expect( page.getByRole('link', { name: 'Placeholder Taxed products are awesome $13.47' }).first() ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$13.47' } ) ).toHaveCount(3);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $40.41'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Seventeen Tax $6.87 ' } ) ).toBeVisible()
|
||||
await expect( page.getByRole( 'row', { name: 'Six Tax $2.84 ' } ) ).toBeVisible()
|
||||
await expect( page.getByRole( 'row', { name: 'Total $50.12 ' } ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'Shopper Tax Levels', () => {
|
||||
|
||||
test.beforeAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'yes',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.post( 'products', {
|
||||
name: productName,
|
||||
type: 'simple',
|
||||
regular_price: productPrice,
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
productId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "US",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "10",
|
||||
"name": "Country Tax",
|
||||
"shipping": false,
|
||||
"priority": 1
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
countryTaxId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "*",
|
||||
"state": "CA",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "5",
|
||||
"name": "State Tax",
|
||||
"shipping": false,
|
||||
"priority": 2
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
stateTaxId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "*",
|
||||
"state": "*",
|
||||
"cities": "Sacramento",
|
||||
"postcodes": "*",
|
||||
"rate": "2.5",
|
||||
"name": "City Tax",
|
||||
"shipping": false,
|
||||
"priority": 3
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
cityTaxId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "*",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "55555",
|
||||
"rate": "1.25",
|
||||
"name": "Zip Tax",
|
||||
"shipping": false,
|
||||
"priority": 4
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
zipTaxId = response.data.id;
|
||||
} );
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { page, context } ) => {
|
||||
// Shopping cart is very sensitive to cookies, so be explicit
|
||||
await context.clearCookies();
|
||||
|
||||
// all tests use the first product
|
||||
await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } );
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized'
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'excl',
|
||||
} );
|
||||
await api.delete( `products/${ productId }`, {
|
||||
force: true,
|
||||
} );
|
||||
|
||||
await api.delete( `taxes/${ countryTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ stateTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ cityTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ zipTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks applying taxes of 4 different levels', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized',
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Country Tax $10.00 ' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'State Tax $5.00 ' } ) ).toBeVisible()
|
||||
await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load checkout page and confirm taxes displayed', async() => {
|
||||
await page.goto( '/checkout/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Checkout', exact: true } ) ).toBeVisible();
|
||||
|
||||
await page.getByLabel('First name *').first().fill( customer.billing.us.first_name );
|
||||
await page.getByLabel('Last name *').first().fill( customer.billing.us.last_name );
|
||||
await page.getByPlaceholder('House number and street name').first().fill( customer.billing.us.address );
|
||||
await page.getByLabel('Town / City *').first().pressSequentially( 'Sacramento' );
|
||||
await page.getByLabel('ZIP Code *').first().pressSequentially( '55555' );
|
||||
await page.getByLabel('Phone *').first().fill( customer.billing.us.phone );
|
||||
await page.getByLabel('Email address *').first().fill( customer.billing.us.email );
|
||||
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Country Tax $10.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'State Tax $5.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'City Tax $2.50' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Zip Tax $1.25' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $118.75 ' } ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks applying taxes of 2 different levels (2 excluded)', async ( { page, baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_total_display', {
|
||||
value: 'itemized',
|
||||
} );
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$100.00' } ) ).toHaveCount(3);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Country Tax $10.00 ' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'State Tax $5.00 ' } ) ).toBeVisible()
|
||||
await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load checkout page and confirm taxes displayed', async() => {
|
||||
await page.goto( '/checkout/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Checkout', exact: true } ) ).toBeVisible();
|
||||
|
||||
await page.getByLabel('First name *').first().fill( customer.billing.us.first_name );
|
||||
await page.getByLabel('Last name *').first().fill( customer.billing.us.last_name );
|
||||
await page.getByPlaceholder('House number and street name').first().fill( customer.billing.us.address );
|
||||
await page.getByLabel('Town / City *').first().pressSequentially( customer.billing.us.city );
|
||||
await page.getByLabel('ZIP Code *').first().pressSequentially( customer.billing.us.zip );
|
||||
await page.getByLabel('Phone *').first().fill( customer.billing.us.phone );
|
||||
await page.getByLabel('Email address *').first().fill( customer.billing.us.email );
|
||||
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $100.00'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Country Tax $10.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'State Tax $5.00' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'City Tax $2.50' } ) ).not.toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Zip Tax $1.25' } ) ).not.toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $115.00 ' } ) ).toBeVisible();
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
test.describe( 'Shipping Tax', () => {
|
||||
|
||||
test.beforeAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'yes',
|
||||
} );
|
||||
await api.post( 'products', {
|
||||
name: productName,
|
||||
type: 'simple',
|
||||
regular_price: productPrice,
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
productId = response.data.id;
|
||||
} );
|
||||
await api.post( 'taxes', {
|
||||
"country": "US",
|
||||
"state": "*",
|
||||
"cities": "*",
|
||||
"postcodes": "*",
|
||||
"rate": "15",
|
||||
"name": "Shipping Tax",
|
||||
"shipping": true
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
shippingTaxId = response.data.id;
|
||||
} );
|
||||
await api.post( 'shipping/zones', {
|
||||
name: 'All',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
shippingZoneId = response.data.id;
|
||||
} );
|
||||
await api.post( `shipping/zones/${ shippingZoneId }/methods`, {
|
||||
method_id: 'flat_rate',
|
||||
} )
|
||||
.then( ( response ) => {
|
||||
shippingMethodId = response.data.id;
|
||||
} );
|
||||
await api.put( `shipping/zones/${ shippingZoneId }/methods/${ shippingMethodId }`, {
|
||||
settings: {
|
||||
cost: '20.00',
|
||||
}
|
||||
} );
|
||||
await api.put( 'payment_gateways/cod' , {
|
||||
enabled: true
|
||||
} );
|
||||
await api.put( 'settings/tax/woocommerce_tax_display_cart', {
|
||||
value: 'incl',
|
||||
} );
|
||||
} );
|
||||
|
||||
test.beforeEach( async ( { page, context } ) => {
|
||||
// Shopping cart is very sensitive to cookies, so be explicit
|
||||
await context.clearCookies();
|
||||
|
||||
// all tests use the first product
|
||||
await page.goto( `/shop/?add-to-cart=${ productId }`, { waitUntil: 'networkidle' } );
|
||||
} );
|
||||
|
||||
test.afterAll( async ( { baseURL } ) => {
|
||||
const api = new wcApi( {
|
||||
url: baseURL,
|
||||
consumerKey: process.env.CONSUMER_KEY,
|
||||
consumerSecret: process.env.CONSUMER_SECRET,
|
||||
version: 'wc/v3',
|
||||
} );
|
||||
await api.put( 'settings/general/woocommerce_calc_taxes', {
|
||||
value: 'no',
|
||||
} );
|
||||
await api.delete( `products/${ productId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.delete( `taxes/${ shippingTaxId }`, {
|
||||
force: true,
|
||||
} );
|
||||
await api.put( 'payment_gateways/cod' , {
|
||||
enabled: false
|
||||
} );
|
||||
await api.delete( `shipping/zones/${ shippingZoneId }`, {
|
||||
force: true,
|
||||
} );
|
||||
} );
|
||||
|
||||
test( 'checks that tax is applied to shipping as well as order', async ( { page, baseURL } ) => {
|
||||
|
||||
await test.step( 'Load cart page and confirm price display', async() => {
|
||||
await page.goto( '/cart/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Cart', exact: true } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'cell', { name: '$115.00 (incl. tax)' } ) ).toHaveCount(2);
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $115.00 (incl. tax)'} ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Shipping Flat rate: $23.00 (incl. tax) Shipping to CA.' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $138.00 (includes $18.00 Shipping Tax)' } ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
await test.step( 'Load checkout page and confirm price display', async() => {
|
||||
await page.goto( '/checkout/' );
|
||||
await expect( page.getByRole( 'heading', { name: 'Checkout' } ) ).toBeVisible();
|
||||
|
||||
await page.getByRole('textbox', { name: 'First name *' }).fill( customer.billing.us.first_name );
|
||||
await page.getByRole('textbox', { name: 'Last name *' }).fill( customer.billing.us.last_name );
|
||||
await page.getByRole('textbox', { name: 'Street address *' }).fill( customer.billing.us.address );
|
||||
await page.getByRole('textbox', { name: 'Town / City *' }).type( customer.billing.us.city );
|
||||
await page.getByRole('textbox', { name: 'ZIP Code *' }).type( customer.billing.us.zip );
|
||||
await page.getByLabel('Phone *').fill( customer.billing.us.phone );
|
||||
await page.getByLabel('Email address *').fill( customer.billing.us.email );
|
||||
|
||||
await expect( page.getByRole( 'row', { name: 'Taxed products are awesome × 1 $115.00 (incl. tax)' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Subtotal $115.00 (incl. tax)' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Shipping Flat rate: $23.00 (incl. tax)' } ) ).toBeVisible();
|
||||
await expect( page.getByRole( 'row', { name: 'Total $138.00 (includes $18.00 Shipping Tax)'} ) ).toBeVisible();
|
||||
} );
|
||||
|
||||
} );
|
||||
|
||||
} );
|
Loading…
Reference in New Issue