Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write unit tests for new useColors hook. #18672

Closed
epiqueras opened this issue Nov 21, 2019 · 3 comments
Closed

Write unit tests for new useColors hook. #18672

epiqueras opened this issue Nov 21, 2019 · 3 comments
Labels
[Package] Block editor /packages/block-editor [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. [Type] Task Issues or PRs that have been broken down into an individual action to take
Milestone

Comments

@epiqueras
Copy link
Contributor

/**
* External dependencies
*/
import memoize from 'memize';
import classnames from 'classnames';
import { map, kebabCase, camelCase, startCase } from 'lodash';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { useSelect, useDispatch } from '@wordpress/data';
import {
useCallback,
useMemo,
Children,
cloneElement,
useRef,
} from '@wordpress/element';
import { withFallbackStyles } from '@wordpress/components';
/**
* Internal dependencies
*/
import PanelColorSettings from '../panel-color-settings';
import ContrastChecker from '../contrast-checker';
import InspectorControls from '../inspector-controls';
import { useBlockEditContext } from '../block-edit';
/**
* Browser dependencies
*/
const { getComputedStyle } = window;
const DEFAULT_COLORS = [];
const resolveContrastCheckerColor = ( color, colorSettings, detectedColor ) => {
if ( typeof color === 'function' ) {
return color( colorSettings );
} else if ( color === true ) {
return detectedColor;
}
return color;
};
const ColorPanel = ( {
title,
colorSettings,
colorPanelProps,
contrastCheckers,
detectedBackgroundColor,
panelChildren,
} ) => (
<PanelColorSettings
title={ title }
initialOpen={ false }
colorSettings={ Object.values( colorSettings ) }
{ ...colorPanelProps }
>
{ contrastCheckers &&
( Array.isArray( contrastCheckers ) ?
contrastCheckers.map( ( { backgroundColor, textColor, ...rest } ) => {
backgroundColor = resolveContrastCheckerColor(
backgroundColor,
colorSettings,
detectedBackgroundColor
);
textColor = resolveContrastCheckerColor( textColor, colorSettings );
return (
<ContrastChecker
key={ `${ backgroundColor }-${ textColor }` }
backgroundColor={ backgroundColor }
textColor={ textColor }
{ ...rest }
/>
);
} ) :
map( colorSettings, ( { value } ) => {
let { backgroundColor, textColor } = contrastCheckers;
backgroundColor = resolveContrastCheckerColor(
backgroundColor || value,
colorSettings,
detectedBackgroundColor
);
textColor = resolveContrastCheckerColor(
textColor || value,
colorSettings
);
return (
<ContrastChecker
{ ...contrastCheckers }
key={ `${ backgroundColor }-${ textColor }` }
backgroundColor={ backgroundColor }
textColor={ textColor }
/>
);
} ) ) }
{ typeof panelChildren === 'function' ?
panelChildren( colorSettings ) :
panelChildren }
</PanelColorSettings>
);
const InspectorControlsColorPanel = ( props ) => (
<InspectorControls>
<ColorPanel { ...props } />
</InspectorControls>
);
export default function __experimentalUseColors(
colorConfigs,
{
panelTitle = __( 'Color Settings' ),
colorPanelProps,
contrastCheckers,
panelChildren,
} = {
panelTitle: __( 'Color Settings' ),
},
deps = []
) {
const { clientId } = useBlockEditContext();
const { attributes, settingsColors } = useSelect(
( select ) => {
const { getBlockAttributes, getSettings } = select( 'core/block-editor' );
const colors = getSettings().colors;
return {
attributes: getBlockAttributes( clientId ),
settingsColors: ! colors || colors === true ? DEFAULT_COLORS : colors,
};
},
[ clientId ]
);
const { updateBlockAttributes } = useDispatch( 'core/block-editor' );
const setAttributes = useCallback(
( newAttributes ) => updateBlockAttributes( clientId, newAttributes ),
[ updateBlockAttributes, clientId ]
);
const createComponent = useMemo(
() =>
memoize(
( name, property, className, color, colorValue, customColor ) => ( {
children,
className: componentClassName = '',
style: componentStyle = {},
} ) =>
// Clone children, setting the style property from the color configuration,
// if not already set explicitly through props.
Children.map( children, ( child ) => {
let colorStyle = {};
if ( color ) {
colorStyle = { [ property ]: colorValue };
} else if ( customColor ) {
colorStyle = { [ property ]: customColor };
}
return cloneElement( child, {
className: classnames( componentClassName, child.props.className, {
[ `has-${ kebabCase( color ) }-${ kebabCase( property ) }` ]: color,
[ className || `has-${ kebabCase( name ) }` ]: color || customColor,
} ),
style: {
...colorStyle,
...componentStyle,
...( child.props.style || {} ),
},
} );
} ),
{ maxSize: colorConfigs.length }
),
[ colorConfigs.length ]
);
const createSetColor = useMemo(
() =>
memoize(
( name, colors ) => ( newColor ) => {
const color = colors.find( ( _color ) => _color.color === newColor );
setAttributes( {
[ color ? camelCase( `custom ${ name }` ) : name ]: undefined,
} );
setAttributes( {
[ color ? name : camelCase( `custom ${ name }` ) ]: color ?
color.slug :
newColor,
} );
},
{
maxSize: colorConfigs.length,
}
),
[ setAttributes, colorConfigs.length ]
);
const detectedBackgroundColorRef = useRef();
const BackgroundColorDetector = useMemo(
() =>
contrastCheckers &&
( Array.isArray( contrastCheckers ) ?
contrastCheckers.some(
( { backgroundColor } ) => backgroundColor === true
) :
contrastCheckers.backgroundColor === true ) &&
withFallbackStyles( ( node, { querySelector } ) => {
if ( querySelector ) {
node = node.parentNode.querySelector( querySelector );
}
let backgroundColor = getComputedStyle( node ).backgroundColor;
while ( backgroundColor === 'rgba(0, 0, 0, 0)' && node.parentNode ) {
node = node.parentNode;
backgroundColor = getComputedStyle( node ).backgroundColor;
}
detectedBackgroundColorRef.current = backgroundColor;
return { backgroundColor };
} )( () => <></> ),
[
colorConfigs.reduce(
( acc, colorConfig ) =>
`${ acc } | ${ attributes[ colorConfig.name ] } | ${
attributes[ camelCase( `custom ${ colorConfig.name }` ) ]
}`,
''
),
...deps,
]
);
return useMemo( () => {
const colorSettings = {};
const components = colorConfigs.reduce( ( acc, colorConfig ) => {
if ( typeof colorConfig === 'string' ) {
colorConfig = { name: colorConfig };
}
const {
name, // E.g. 'backgroundColor'.
property = name, // E.g. 'backgroundColor'.
className,
panelLabel = startCase( name ), // E.g. 'Background Color'.
componentName = panelLabel.replace( /\s/g, '' ), // E.g. 'BackgroundColor'.
color = colorConfig.color,
colors = settingsColors,
} = {
...colorConfig,
color: attributes[ colorConfig.name ],
};
const customColor = attributes[ camelCase( `custom ${ name }` ) ];
// We memoize the non-primitives to avoid unnecessary updates
// when they are used as props for other components.
const _color = ! customColor ?
colors.find( ( __color ) => __color.slug === color ) :
undefined;
acc[ componentName ] = createComponent(
name,
property,
className,
color,
_color && _color.color,
customColor
);
acc[ componentName ].displayName = componentName;
acc[ componentName ].color = customColor ? customColor : ( _color && _color.color );
acc[ componentName ].slug = color;
acc[ componentName ].setColor = createSetColor( name, colors );
colorSettings[ componentName ] = {
value: _color ? _color.color : attributes[ camelCase( `custom ${ name }` ) ],
onChange: acc[ componentName ].setColor,
label: panelLabel,
colors,
};
// These settings will be spread over the `colors` in
// `colorPanelProps`, so we need to unset the key here,
// if not set to an actual value, to avoid overwriting
// an actual value in `colorPanelProps`.
if ( ! colors ) {
delete colorSettings[ componentName ].colors;
}
return acc;
}, {} );
const wrappedColorPanelProps = {
title: panelTitle,
colorSettings,
colorPanelProps,
contrastCheckers,
detectedBackgroundColor: detectedBackgroundColorRef.current,
panelChildren,
};
return {
...components,
ColorPanel: <ColorPanel { ...wrappedColorPanelProps } />,
InspectorControlsColorPanel: (
<InspectorControlsColorPanel { ...wrappedColorPanelProps } />
),
BackgroundColorDetector,
};
}, [ attributes, setAttributes, detectedBackgroundColorRef.current, ...deps ] );
}

@epiqueras epiqueras added [Type] Task Issues or PRs that have been broken down into an individual action to take [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. [Package] Block editor /packages/block-editor labels Nov 21, 2019
@epiqueras epiqueras added this to the Future milestone Nov 21, 2019
@gziolo
Copy link
Member

gziolo commented May 18, 2020

@youknowriad, do we still use useColors in the codebase? My understanding was that we are trying the approach with WordPress hooks like in #21021.

@youknowriad
Copy link
Contributor

We might still be using it but Ideally, we should sunset it yes.

@epiqueras
Copy link
Contributor Author

Haha, I always thought supports was a better place for this. I'll close this then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Package] Block editor /packages/block-editor [Type] Automated Testing Testing infrastructure changes impacting the execution of end-to-end (E2E) and/or unit tests. [Type] Task Issues or PRs that have been broken down into an individual action to take
Projects
None yet
Development

No branches or pull requests

3 participants