merge conflicts

This commit is contained in:
Paul Sealock 2022-06-13 12:47:00 +12:00
commit 0efd480581
22 changed files with 297 additions and 162 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add update product actions to product data store #33282

View File

@ -7,6 +7,8 @@ export enum TYPES {
GET_PRODUCTS_ERROR = 'GET_PRODUCTS_ERROR',
GET_PRODUCTS_TOTAL_COUNT_SUCCESS = 'GET_PRODUCTS_TOTAL_COUNT_SUCCESS',
GET_PRODUCTS_TOTAL_COUNT_ERROR = 'GET_PRODUCTS_TOTAL_COUNT_ERROR',
UPDATE_PRODUCT_ERROR = 'UPDATE_PRODUCT_ERROR',
UPDATE_PRODUCT_SUCCESS = 'UPDATE_PRODUCT_SUCCESS',
}
export default TYPES;

View File

@ -9,7 +9,7 @@ import { DispatchFromMap } from '@automattic/data-stores';
*/
import TYPES from './action-types';
import {
MutableProperties,
ReadOnlyProperties,
PartialProduct,
Product,
ProductQuery,
@ -35,7 +35,7 @@ export function getProductError(
};
}
function createProductSuccess( id: number, product: PartialProduct ) {
function createProductSuccess( id: number, product: Partial< Product > ) {
return {
type: TYPES.CREATE_PRODUCT_SUCCESS as const,
id,
@ -54,6 +54,22 @@ export function createProductError(
};
}
function updateProductSuccess( id: number, product: Partial< Product > ) {
return {
type: TYPES.UPDATE_PRODUCT_SUCCESS as const,
id,
product,
};
}
export function updateProductError( id: number, error: unknown ) {
return {
type: TYPES.UPDATE_PRODUCT_ERROR as const,
id,
error,
};
}
export function getProductsSuccess(
query: Partial< ProductQuery >,
products: PartialProduct[],
@ -100,7 +116,7 @@ export function getProductsTotalCountError(
};
}
export function* createProduct( data: Pick< Product, MutableProperties > ) {
export function* createProduct( data: Omit< Product, ReadOnlyProperties > ) {
try {
const product: Product = yield apiFetch( {
path: WC_PRODUCT_NAMESPACE,
@ -116,6 +132,25 @@ export function* createProduct( data: Pick< Product, MutableProperties > ) {
}
}
export function* updateProduct(
id: number,
data: Omit< Product, ReadOnlyProperties >
) {
try {
const product: Product = yield apiFetch( {
path: `${ WC_PRODUCT_NAMESPACE }/${ id }`,
method: 'PUT',
data,
} );
yield updateProductSuccess( product.id, product );
return product;
} catch ( error ) {
yield updateProductError( id, error );
throw error;
}
}
export type Actions = ReturnType<
| typeof createProductError
| typeof createProductSuccess
@ -125,8 +160,11 @@ export type Actions = ReturnType<
| typeof getProductsError
| typeof getProductsTotalCountSuccess
| typeof getProductsTotalCountError
| typeof updateProductError
| typeof updateProductSuccess
>;
export type ActionDispatchers = DispatchFromMap< {
createProduct: typeof createProduct;
updateProduct: typeof updateProduct;
} >;

View File

@ -39,6 +39,7 @@ const reducer: Reducer< ProductState, Actions > = (
switch ( payload.type ) {
case TYPES.CREATE_PRODUCT_SUCCESS:
case TYPES.GET_PRODUCT_SUCCESS:
case TYPES.UPDATE_PRODUCT_SUCCESS:
const productData = state.data || {};
return {
...state,
@ -99,6 +100,14 @@ const reducer: Reducer< ProductState, Actions > = (
) ]: payload.error,
},
};
case TYPES.UPDATE_PRODUCT_ERROR:
return {
...state,
errors: {
...state.errors,
[ `update/${ payload.id }` ]: payload.error,
},
};
default:
return state;
}

View File

@ -85,6 +85,15 @@ export const getCreateProductError = (
return state.errors[ resourceName ];
};
export const getUpdateProductError = (
state: ProductState,
id: number,
query: ProductQuery
) => {
const resourceName = getProductResourceName( query );
return state.errors[ `update/${ id }/${ resourceName }` ];
};
export type ProductsSelectors = {
getCreateProductError: WPDataSelector< typeof getCreateProductError >;
getProducts: WPDataSelector< typeof getProducts >;

View File

@ -210,4 +210,54 @@ describe( 'products reducer', () => {
expect( state.errors[ resourceName ] ).toBe( error );
} );
it( 'should handle UPDATE_PRODUCT_SUCCESS', () => {
const itemType = 'guyisms';
const initialState: ProductState = {
products: {
[ itemType ]: {
data: [ 1, 2 ],
},
},
productsCount: {
'total-guyisms:{}': 2,
},
errors: {},
data: {
1: { id: 1, name: 'Donkey', status: 'draft' },
2: { id: 2, name: 'Sauce', status: 'publish' },
},
};
const product: PartialProduct = {
id: 2,
name: 'Holy smokes!',
status: 'draft',
};
const state = reducer( initialState, {
type: TYPES.UPDATE_PRODUCT_SUCCESS,
id: product.id,
product,
} );
expect( state.products ).toEqual( initialState.products );
expect( state.errors ).toEqual( initialState.errors );
expect( state.data[ 1 ] ).toEqual( initialState.data[ 1 ] );
expect( state.data[ 2 ].id ).toEqual( initialState.data[ 2 ].id );
expect( state.data[ 2 ].title ).toEqual( initialState.data[ 2 ].title );
expect( state.data[ 2 ].name ).toEqual( product.name );
} );
it( 'should handle UPDATE_PRODUCT_ERROR', () => {
const id = 1;
const error = 'Baaam!';
const state = reducer( defaultState, {
type: TYPES.UPDATE_PRODUCT_ERROR,
id,
error,
} );
expect( state.errors[ `update/${ id }` ] ).toBe( error );
} );
} );

View File

@ -40,17 +40,27 @@ export type Product<
sale_price: string;
};
export type MutableProperties =
| 'name'
| 'slug'
| 'type'
| 'status'
| 'featured'
| 'description'
| 'short_description'
| 'sku'
| 'regular_price'
| 'sale_price';
export type ReadOnlyProperties =
| 'id'
| 'permalink'
| 'date_created'
| 'date_created_gmt'
| 'date_modified'
| 'date_modified_gmt'
| 'price'
| 'price_html'
| 'on_sale'
| 'purchasable'
| 'total_sales'
| 'backorders_allowed'
| 'backordered'
| 'shipping_required'
| 'shipping_taxable'
| 'shipping_class_id'
| 'average_rating'
| 'rating_count'
| 'related_ids'
| 'variations';
export type PartialProduct = Partial< Product > & Pick< Product, 'id' >;

View File

@ -0,0 +1,30 @@
/nbproject/private/
node_modules
project.xml
project.properties
.DS_Store
Thumbs.db
.buildpath
.project
.settings*
.vscode
sftp-config.json
/deploy/
/wc-apidocs/
/languages/
screenshots/
.idea
# Ignore all log files except for .htaccess
/logs/*
!/logs/.htaccess
tests/e2e/config/local-*
.eslintcache
/vendor/
dist
build
{{slug}}.zip

View File

@ -21,6 +21,29 @@
.components-navigation__menu-title {
overflow: visible;
}
.components-navigation__menu {
scrollbar-color: $gray-700 $gray-900;
scrollbar-width: thin;
&::-webkit-scrollbar-thumb {
border-radius: 10px;
background-color: $gray-700;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $gray-700;
width: 8px;
height: 8px;
}
&::-webkit-scrollbar {
width: 8px;
height: 8px;
}
}
}
.woocommerce-navigation__wrapper {

View File

@ -231,6 +231,16 @@ export class StoreDetails extends Component {
errors.storeEmail = __( 'Invalid email address', 'woocommerce' );
}
if (
values.isAgreeMarketing &&
( ! values.storeEmail || ! values.storeEmail.trim().length )
) {
errors.storeEmail = __(
'Please enter your email address to subscribe',
'woocommerce'
);
}
return errors;
}
@ -373,16 +383,6 @@ export class StoreDetails extends Component {
autoComplete="email"
{ ...getInputProps( 'storeEmail' ) }
/>
{ values.isAgreeMarketing &&
( ! values.storeEmail ||
! values.storeEmail.trim().length ) && (
<div className="woocommerce-profile-wizard__store-details-error">
{ __(
'Please enter your email address to subscribe',
'woocommerce'
) }
</div>
) }
<FlexItem>
<div className="woocommerce-profile-wizard__newsletter-signup">
<CheckboxControl

View File

@ -40,6 +40,37 @@ describe( 'StoreDetails', () => {
} );
} );
describe( 'Email validation test cases', () => {
test( 'should fail email validation and disable continue button when isAgreeMarketing is true and email is empty', async () => {
const container = render(
<StoreDetails
{ ...testProps }
initialValues={ {
addressLine1: 'address1',
addressLine2: 'address2',
city: 'city',
countryState: 'state',
postCode: '123',
isAgreeMarketing: true,
storeEmail: 'wordpress@example.com',
} }
/>
);
const emailInput = container.getByLabelText( 'Email address' );
await userEvent.clear( emailInput );
userEvent.tab();
expect(
container.queryByText(
'Please enter your email address to subscribe'
)
).toBeInTheDocument();
expect(
container.queryByRole( 'button', {
name: 'Continue',
} ).disabled
).toBe( true );
} );
// test cases taken from wordpress php is_email test cases
// https://github.com/WordPress/wordpress-develop/blob/2648a5f984b8abf06872151898e3a61d3458a628/tests/phpunit/tests/formatting/isEmail.php
test.each( [

View File

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Improve Twenty Twenty One notice styles

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Fix continue button is enabled even when email is null in setup wizard

View File

@ -0,0 +1,4 @@
Significance: minor
Type: enhancement
Swap info and message notice colors in Twenty-Twenty

View File

@ -0,0 +1,4 @@
Significance: minor
Type: tweak
Make the option 'woocommerce_tracker_ua' load on demand.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Update Webpack to 5.x

View File

@ -862,7 +862,7 @@ function wc_maybe_store_user_agent( $user_login, $user ) {
if ( 'yes' === get_option( 'woocommerce_allow_tracking', 'no' ) && user_can( $user, 'manage_woocommerce' ) ) {
$admin_user_agents = array_filter( (array) get_option( 'woocommerce_tracker_ua', array() ) );
$admin_user_agents[] = wc_get_user_agent();
update_option( 'woocommerce_tracker_ua', array_unique( $admin_user_agents ) );
update_option( 'woocommerce_tracker_ua', array_unique( $admin_user_agents ), false );
}
}
add_action( 'wp_login', 'wc_maybe_store_user_agent', 10, 2 );

View File

@ -213,30 +213,16 @@ a.button {
.woocommerce-message,
.woocommerce-error,
.woocommerce-info {
color: #000;
border-top: 3px solid $highlights-color;
margin-bottom: 2rem;
padding: 0;
margin-left: 0;
background: var(--global--color-background);
background: #eee;
font-size: 0.88889em;
font-family: $headings;
list-style: none;
overflow: hidden;
}
.woocommerce-message,
.woocommerce-error li,
.woocommerce-info {
padding: 1.5rem 3rem;
justify-content: space-between;
align-items: center;
.button {
order: 2;
}
}
.woocommerce-error {
color: #fff;
background: #b22222;
a {
color: #fff;
@ -247,39 +233,34 @@ a.button {
&.button {
background: #111;
color: #fff;
}
}
> li {
margin: 0;
}
}
#main {
.woocommerce-error,
.woocommerce-info {
font-family: $headings;
}
}
.woocommerce-message,
.woocommerce-error li,
.woocommerce-info {
background: #eee;
color: #000;
border-top: 2px solid $highlights-color;
padding: 1.5rem 3rem;
display: flex;
justify-content: space-between;
align-items: center;
a {
color: #444;
.button {
order: 2;
}
}
&:hover {
color: #000;
}
.woocommerce-info {
border-top-color: var( --wc-blue );
}
&.button {
background: $highlights-color;
color: #f5efe0;
}
.woocommerce-error {
border-top-color: #b22222;
> li {
margin: 0;
}
}
@ -619,10 +600,6 @@ dl.variation,
}
}
.woocommerce-message {
flex-direction: row-reverse;
}
.woocommerce-Tabs-panel--additional_information,
.woocommerce-Tabs-panel--reviews {
@ -2938,14 +2915,6 @@ a.reset_variations {
.woocommerce {
.woocommerce-notices-wrapper {
& > * {
padding: 15px;
list-style: none;
}
}
.return-to-shop,
.wc-proceed-to-checkout {

View File

@ -224,7 +224,7 @@ a.button {
}
}
.woocommerce-message {
.woocommerce-info {
border-color: var( --wc-blue );
}

View File

@ -75,7 +75,7 @@
"prettier": "npm:wp-prettier@2.0.5",
"stylelint": "^13.8.0",
"typescript": "3.9.7",
"webpack": "4.44.2",
"webpack": "5.70.0",
"webpack-cli": "3.3.12",
"wp-textdomain": "1.0.1"
},

View File

@ -1226,7 +1226,7 @@ importers:
prettier: npm:wp-prettier@2.0.5
stylelint: ^13.8.0
typescript: 3.9.7
webpack: 4.44.2
webpack: 5.70.0
webpack-cli: 3.3.12
wp-textdomain: 1.0.1
devDependencies:
@ -1267,8 +1267,8 @@ importers:
prettier: /wp-prettier/2.0.5
stylelint: 13.13.1
typescript: 3.9.7
webpack: 4.44.2_webpack-cli@3.3.12
webpack-cli: 3.3.12_webpack@4.44.2
webpack: 5.70.0_webpack-cli@3.3.12
webpack-cli: 3.3.12_webpack@5.70.0
wp-textdomain: 1.0.1
plugins/woocommerce-admin:
@ -36257,7 +36257,7 @@ packages:
engines: {node: '>=0.8.0'}
/supports-color/3.2.3:
resolution: {integrity: sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=}
resolution: {integrity: sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==}
engines: {node: '>=0.8.0'}
dependencies:
has-flag: 1.0.0
@ -36504,24 +36504,6 @@ packages:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.2.0
/terser-webpack-plugin/1.4.5_webpack@4.44.2:
resolution: {integrity: sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==}
engines: {node: '>= 6.9.0'}
peerDependencies:
webpack: ^4.0.0
dependencies:
cacache: 12.0.4
find-cache-dir: 2.1.0
is-wsl: 1.1.0
schema-utils: 1.0.0
serialize-javascript: 4.0.0
source-map: 0.6.1
terser: 4.8.0
webpack: 4.44.2_webpack-cli@3.3.12
webpack-sources: 1.4.3
worker-farm: 1.7.0
dev: true
/terser-webpack-plugin/1.4.5_webpack@4.46.0:
resolution: {integrity: sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==}
engines: {node: '>= 6.9.0'}
@ -38394,6 +38376,7 @@ packages:
ws: 6.2.2
dev: true
<<<<<<< HEAD
/webpack-bundle-analyzer/4.5.0:
resolution: {integrity: sha512-GUMZlM3SKwS8Z+CKeIFx7CVoHn3dXFcUAjT/dcZQQmfSZGvitPfMob2ipjai7ovFFqPvTqkEZ/leL4O0YOdAYQ==}
engines: {node: '>= 10.13.0'}
@ -38434,6 +38417,8 @@ packages:
yargs: 13.3.2
dev: true
=======
>>>>>>> trunk
/webpack-cli/3.3.12_webpack@4.46.0:
resolution: {integrity: sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==}
engines: {node: '>=6.11.5'}
@ -38682,45 +38667,6 @@ packages:
resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==}
dev: true
/webpack/4.44.2_webpack-cli@3.3.12:
resolution: {integrity: sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==}
engines: {node: '>=6.11.5'}
hasBin: true
peerDependencies:
webpack-cli: '*'
webpack-command: '*'
peerDependenciesMeta:
webpack-cli:
optional: true
webpack-command:
optional: true
dependencies:
'@webassemblyjs/ast': 1.9.0
'@webassemblyjs/helper-module-context': 1.9.0
'@webassemblyjs/wasm-edit': 1.9.0
'@webassemblyjs/wasm-parser': 1.9.0
acorn: 6.4.2
ajv: 6.12.6
ajv-keywords: 3.5.2_ajv@6.12.6
chrome-trace-event: 1.0.3
enhanced-resolve: 4.5.0
eslint-scope: 4.0.3
json-parse-better-errors: 1.0.2
loader-runner: 2.4.0
loader-utils: 1.4.0
memory-fs: 0.4.1
micromatch: 3.1.10
mkdirp: 0.5.5
neo-async: 2.6.2
node-libs-browser: 2.2.1
schema-utils: 1.0.0
tapable: 1.1.3
terser-webpack-plugin: 1.4.5_webpack@4.44.2
watchpack: 1.7.5
webpack-cli: 3.3.12_webpack@4.44.2
webpack-sources: 1.4.3
dev: true
/webpack/4.46.0:
resolution: {integrity: sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==}
engines: {node: '>=6.11.5'}
@ -38815,7 +38761,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.1
acorn: 8.7.0
acorn-import-assertions: 1.8.0_acorn@8.7.0
browserslist: 4.20.2
browserslist: 4.20.4
chrome-trace-event: 1.0.3
enhanced-resolve: 5.9.2
es-module-lexer: 0.9.3
@ -38896,7 +38842,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.1
acorn: 8.7.0
acorn-import-assertions: 1.8.0_acorn@8.7.0
browserslist: 4.20.2
browserslist: 4.20.4
chrome-trace-event: 1.0.3
enhanced-resolve: 5.9.2
es-module-lexer: 0.9.3
@ -38937,7 +38883,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.1
acorn: 8.7.0
acorn-import-assertions: 1.8.0_acorn@8.7.0
browserslist: 4.20.2
browserslist: 4.20.4
chrome-trace-event: 1.0.3
enhanced-resolve: 5.9.2
es-module-lexer: 0.9.3

View File

@ -86,26 +86,20 @@ if ( $verbose ) {
$base_path = dirname( dirname( __DIR__ ) );
// Read workspace.json file to find potential composer files.
try {
$workspace = json_decode( file_get_contents( $base_path . '/workspace.json' ), true, 10, JSON_THROW_ON_ERROR );
} catch ( Exception $e ) {
$workspace = false;
}
if ( ! $workspace || ! is_array( $workspace['projects'] ) ) {
debug( 'Unable to parse workspace file' );
exit( 1 );
$workspace_paths = array();
$workspace_yaml = file_get_contents( $base_path . '/pnpm-workspace.yaml' );
if ( preg_match( '/^packages:((\n\s+.+)+)/', $workspace_yaml, $matches ) ) {
$packages_config = $matches[1];
if ( preg_match_all( "/^\s+-\s?'([^']+)'/m", $packages_config, $matches ) ) {
$workspace_paths = $matches[1];
}
}
$composer_projects = array();
foreach( $workspace['projects'] as $project => $directory ) {
if ( $path && $directory !== $path ) {
continue;
}
if ( file_exists( $base_path . '/' . $directory . '/composer.json' ) ) {
$composer_projects[] = $directory;
}
}
$composer_files = array_map( function( $path ) {
return glob( $path . '/composer.json' );
}, $workspace_paths );
$composer_files = array_merge( ...$composer_files );
$composer_projects = array_map( 'dirname', $composer_files );
if ( $path && ! count( $composer_projects ) ) {
debug( sprintf( 'The provided project path, %s, did not contain a composer file.', $path ) );