AttributePicker: Set default values if they exist (https://github.com/woocommerce/woocommerce-blocks/pull/4815)
* Update API to add default boolean to attribute values and use this within the component to set the default attributes. * Modify and add unit tests for isObject and getDefaultAttributes * Sanitize attribute name to accommodate for custom attributes being default values. * Comments for sanitized_attribute_name variable * Remove second argument from getAttributes
This commit is contained in:
parent
97c8b7dee5
commit
ee84901ab8
|
@ -11,6 +11,7 @@ import AttributeSelectControl from './attribute-select-control';
|
|||
import {
|
||||
getVariationMatchingSelectedAttributes,
|
||||
getActiveSelectControlOptions,
|
||||
getDefaultAttributes,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
|
@ -27,6 +28,7 @@ const AttributePicker = ( {
|
|||
const currentVariationAttributes = useShallowEqual( variationAttributes );
|
||||
const [ variationId, setVariationId ] = useState( 0 );
|
||||
const [ selectedAttributes, setSelectedAttributes ] = useState( {} );
|
||||
const [ hasSetDefaults, setHasSetDefaults ] = useState( false );
|
||||
|
||||
// Get options for each attribute picker.
|
||||
const filteredAttributeOptions = useMemo( () => {
|
||||
|
@ -37,6 +39,19 @@ const AttributePicker = ( {
|
|||
);
|
||||
}, [ selectedAttributes, currentAttributes, currentVariationAttributes ] );
|
||||
|
||||
// Set default attributes as selected.
|
||||
useEffect( () => {
|
||||
if ( ! hasSetDefaults ) {
|
||||
const defaultAttributes = getDefaultAttributes( attributes );
|
||||
if ( defaultAttributes ) {
|
||||
setSelectedAttributes( {
|
||||
...defaultAttributes,
|
||||
} );
|
||||
}
|
||||
setHasSetDefaults( true );
|
||||
}
|
||||
}, [ selectedAttributes, attributes, hasSetDefaults ] );
|
||||
|
||||
// Select variations when selections are change.
|
||||
useEffect( () => {
|
||||
const hasSelectedAllAttributes =
|
||||
|
|
|
@ -15,7 +15,6 @@ import { getAttributes, getVariationAttributes } from './utils';
|
|||
const VariationAttributes = ( { product, dispatchers } ) => {
|
||||
const attributes = getAttributes( product.attributes );
|
||||
const variationAttributes = getVariationAttributes( product.variations );
|
||||
|
||||
if (
|
||||
Object.keys( attributes ).length === 0 ||
|
||||
variationAttributes.length === 0
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
getVariationsMatchingSelectedAttributes,
|
||||
getVariationMatchingSelectedAttributes,
|
||||
getActiveSelectControlOptions,
|
||||
getDefaultAttributes,
|
||||
} from '../utils';
|
||||
|
||||
const rawAttributeData = [
|
||||
|
@ -20,16 +21,19 @@ const rawAttributeData = [
|
|||
id: 22,
|
||||
name: 'Blue',
|
||||
slug: 'blue',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: 'Green',
|
||||
slug: 'green',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: 'Red',
|
||||
slug: 'red',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -43,11 +47,13 @@ const rawAttributeData = [
|
|||
id: 0,
|
||||
name: 'Yes',
|
||||
slug: 'Yes',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 0,
|
||||
name: 'No',
|
||||
slug: 'No',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -61,11 +67,13 @@ const rawAttributeData = [
|
|||
id: 0,
|
||||
name: 'Test',
|
||||
slug: 'Test',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 0,
|
||||
name: 'Test 2',
|
||||
slug: 'Test 2',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -126,6 +134,61 @@ const rawVariations = [
|
|||
},
|
||||
];
|
||||
|
||||
const formattedAttributes = {
|
||||
Color: {
|
||||
id: 1,
|
||||
name: 'Color',
|
||||
taxonomy: 'pa_color',
|
||||
has_variations: true,
|
||||
terms: [
|
||||
{
|
||||
id: 22,
|
||||
name: 'Blue',
|
||||
slug: 'blue',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: 'Green',
|
||||
slug: 'green',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: 'Red',
|
||||
slug: 'red',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
Size: {
|
||||
id: 2,
|
||||
name: 'Size',
|
||||
taxonomy: 'pa_size',
|
||||
has_variations: true,
|
||||
terms: [
|
||||
{
|
||||
id: 25,
|
||||
name: 'Large',
|
||||
slug: 'large',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 26,
|
||||
name: 'Medium',
|
||||
slug: 'medium',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 27,
|
||||
name: 'Small',
|
||||
slug: 'small',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
describe( 'Testing utils', () => {
|
||||
describe( 'Testing getAttributes()', () => {
|
||||
it( 'returns empty object if there are no attributes', () => {
|
||||
|
@ -145,16 +208,19 @@ describe( 'Testing utils', () => {
|
|||
id: 22,
|
||||
name: 'Blue',
|
||||
slug: 'blue',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: 'Green',
|
||||
slug: 'green',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
id: 24,
|
||||
name: 'Red',
|
||||
slug: 'red',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -168,11 +234,13 @@ describe( 'Testing utils', () => {
|
|||
id: 0,
|
||||
name: 'Yes',
|
||||
slug: 'Yes',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
id: 0,
|
||||
name: 'No',
|
||||
slug: 'No',
|
||||
default: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -392,4 +460,20 @@ describe( 'Testing utils', () => {
|
|||
} );
|
||||
} );
|
||||
} );
|
||||
describe( 'Testing getDefaultAttributes()', () => {
|
||||
const defaultAttributes = getDefaultAttributes( formattedAttributes );
|
||||
|
||||
it( 'should return default attributes in the format that is ready for setting state', () => {
|
||||
expect( defaultAttributes ).toStrictEqual( {
|
||||
Color: 'blue',
|
||||
Size: 'medium',
|
||||
} );
|
||||
} );
|
||||
|
||||
it( 'should return an empty object if given unexpected values', () => {
|
||||
expect( getDefaultAttributes( [] ) ).toStrictEqual( {} );
|
||||
expect( getDefaultAttributes( null ) ).toStrictEqual( {} );
|
||||
expect( getDefaultAttributes( undefined ) ).toStrictEqual( {} );
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*/
|
||||
import { keyBy } from 'lodash';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { isObject } from '@woocommerce/types';
|
||||
|
||||
/**
|
||||
* Key an array of attributes by name,
|
||||
|
@ -206,3 +207,34 @@ export const getActiveSelectControlOptions = (
|
|||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the default values of the given attributes in a format ready to be set in state.
|
||||
*
|
||||
* @param {Object} attributes List of attribute names and terms.
|
||||
* @return {Object} Default attributes.
|
||||
*/
|
||||
export const getDefaultAttributes = ( attributes = {} ) => {
|
||||
if ( ! isObject( attributes ) ) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const attributeNames = Object.keys( attributes );
|
||||
const defaultsToSet = {};
|
||||
|
||||
if ( attributeNames.length === 0 ) {
|
||||
return defaultsToSet;
|
||||
}
|
||||
|
||||
attributeNames.forEach( ( attributeName ) => {
|
||||
const currentAttribute = attributes[ attributeName ];
|
||||
const defaultValue = currentAttribute.terms.filter(
|
||||
( term ) => term.default
|
||||
);
|
||||
if ( defaultValue.length > 0 ) {
|
||||
defaultsToSet[ currentAttribute.name ] = defaultValue[ 0 ]?.slug;
|
||||
}
|
||||
} );
|
||||
|
||||
return defaultsToSet;
|
||||
};
|
||||
|
|
|
@ -13,7 +13,11 @@ export const isString = < U >( term: string | U ): term is string => {
|
|||
export const isObject = < T extends Record< string, unknown >, U >(
|
||||
term: T | U
|
||||
): term is NonNullable< T > => {
|
||||
return ! isNull( term ) && typeof term === 'object';
|
||||
return (
|
||||
! isNull( term ) &&
|
||||
term instanceof Object &&
|
||||
term.constructor === Object
|
||||
);
|
||||
};
|
||||
|
||||
export function objectHasProp< P extends PropertyKey >(
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isObject } from '@woocommerce/types';
|
||||
|
||||
describe( 'type-guards', () => {
|
||||
describe( 'Testing isObject()', () => {
|
||||
it( 'Correctly identifies an object', () => {
|
||||
expect( isObject( {} ) ).toBe( true );
|
||||
expect( isObject( { test: 'object' } ) ).toBe( true );
|
||||
} );
|
||||
it( 'Correctly rejects object-like things', () => {
|
||||
expect( isObject( [] ) ).toBe( false );
|
||||
expect( isObject( null ) ).toBe( false );
|
||||
} );
|
||||
} );
|
||||
} );
|
|
@ -288,24 +288,30 @@ class ProductSchema extends AbstractSchema {
|
|||
'items' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'id' => [
|
||||
'id' => [
|
||||
'description' => __( 'The term ID, or 0 if the attribute is not a global attribute.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'integer',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'name' => [
|
||||
'name' => [
|
||||
'description' => __( 'The term name.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'slug' => [
|
||||
'slug' => [
|
||||
'description' => __( 'The term slug.', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'string',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
'default' => [
|
||||
'description' => __( 'If this is a default attribute', 'woo-gutenberg-products-block' ),
|
||||
'type' => 'boolean',
|
||||
'context' => [ 'view', 'edit' ],
|
||||
'readonly' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -623,20 +629,33 @@ class ProductSchema extends AbstractSchema {
|
|||
* @return array
|
||||
*/
|
||||
protected function get_attributes( \WC_Product $product ) {
|
||||
$attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] );
|
||||
$return = [];
|
||||
$attributes = array_filter( $product->get_attributes(), [ $this, 'filter_valid_attribute' ] );
|
||||
$default_attributes = $product->get_default_attributes();
|
||||
$return = [];
|
||||
|
||||
foreach ( $attributes as $attribute_slug => $attribute ) {
|
||||
// Only visible and variation attributes will be exposed by this API.
|
||||
if ( ! $attribute->get_visible() || ! $attribute->get_variation() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$terms = $attribute->is_taxonomy() ? array_map( [ $this, 'prepare_product_attribute_taxonomy_value' ], $attribute->get_terms() ) : array_map( [ $this, 'prepare_product_attribute_value' ], $attribute->get_options() );
|
||||
// Custom attribute names are sanitized to be the array keys.
|
||||
// So when we do the array_key_exists check below we also need to sanitize the attribute names.
|
||||
$sanitized_attribute_name = sanitize_key( $attribute->get_name() );
|
||||
|
||||
if ( array_key_exists( $sanitized_attribute_name, $default_attributes ) ) {
|
||||
foreach ( $terms as $term ) {
|
||||
$term->default = $term->slug === $default_attributes[ $sanitized_attribute_name ];
|
||||
}
|
||||
}
|
||||
|
||||
$return[] = (object) [
|
||||
'id' => $attribute->get_id(),
|
||||
'name' => wc_attribute_label( $attribute->get_name(), $product ),
|
||||
'taxonomy' => $attribute->is_taxonomy() ? $attribute->get_name() : null,
|
||||
'has_variations' => true === $attribute->get_variation(),
|
||||
'terms' => $attribute->is_taxonomy() ? array_map( [ $this, 'prepare_product_attribute_taxonomy_value' ], $attribute->get_terms() ) : array_map( [ $this, 'prepare_product_attribute_value' ], $attribute->get_options() ),
|
||||
'terms' => $terms,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue