From dc4b1838f85bdd03d5e9c55330049fd205c8f299 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 12 Oct 2022 16:37:23 +1100 Subject: [PATCH 01/26] FontSizePicker: Use components instead of helper functions Simplify how FontSizePicker is implemented by using nested components instead of helper functions. Components are easier to reason about and much easier to type. --- .../components/src/font-size-picker/index.tsx | 154 ++++++------------ .../src/font-size-picker/select.tsx | 77 +++++++++ .../src/font-size-picker/test/utils.ts | 77 +-------- .../src/font-size-picker/toggle-group.tsx | 58 +++++++ .../components/src/font-size-picker/types.ts | 32 ++-- .../components/src/font-size-picker/utils.ts | 111 +------------ 6 files changed, 202 insertions(+), 307 deletions(-) create mode 100644 packages/components/src/font-size-picker/select.tsx create mode 100644 packages/components/src/font-size-picker/toggle-group.tsx diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index bc5154801880d2..b733639c9c03ba 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -7,7 +7,7 @@ import type { ReactNode, ForwardedRef } from 'react'; * WordPress dependencies */ import deprecated from '@wordpress/deprecated'; -import { __, sprintf } from '@wordpress/i18n'; +import { __ } from '@wordpress/i18n'; import { settings } from '@wordpress/icons'; import { useState, useMemo, forwardRef } from '@wordpress/element'; @@ -18,26 +18,11 @@ import Button from '../button'; import RangeControl from '../range-control'; import { Flex, FlexItem } from '../flex'; import { default as UnitControl, useCustomUnits } from '../unit-control'; -import CustomSelectControl from '../custom-select-control'; import { VisuallyHidden } from '../visually-hidden'; -import { - ToggleGroupControl, - ToggleGroupControlOption, -} from '../toggle-group-control'; -import { - getFontSizeOptions, - getSelectedOption, - splitValueAndUnitFromSize, - isSimpleCssValue, - CUSTOM_FONT_SIZE, -} from './utils'; +import { splitValueAndUnitFromSize, isSimpleCssValue } from './utils'; import { VStack } from '../v-stack'; import { HStack } from '../h-stack'; -import type { - FontSizePickerProps, - FontSizeSelectOption, - FontSizeToggleGroupOption, -} from './types'; +import type { FontSizePickerProps } from './types'; import { Container, HeaderHint, @@ -46,6 +31,8 @@ import { ResetButton, } from './styles'; import { Spacer } from '../spacer'; +import FontSizePickerSelect from './select'; +import FontSizePickerToggleGroup from './toggle-group'; // This conditional is needed to maintain the spacing before the slider in the `withSlider` case. const MaybeVStack = ( { @@ -77,6 +64,7 @@ const UnforwardedFontSizePicker = ( withSlider = false, withReset = true, } = props; + if ( ! __nextHasNoMarginBottom ) { deprecated( 'Bottom margin styles for wp.components.FontSizePicker', { since: '6.1', @@ -94,28 +82,16 @@ const UnforwardedFontSizePicker = ( availableUnits: [ 'px', 'em', 'rem' ], } ); - /** - * The main font size UI displays a toggle group when the presets are less - * than six and a select control when they are more. - */ - const fontSizesContainComplexValues = fontSizes.some( - ( { size: sizeArg } ) => ! isSimpleCssValue( sizeArg ) - ); const shouldUseSelectControl = fontSizes.length > 5; - const options = useMemo( - () => - getFontSizeOptions( - shouldUseSelectControl, - fontSizes, - disableCustomFontSizes - ), - [ shouldUseSelectControl, fontSizes, disableCustomFontSizes ] + const selectedFontSize = fontSizes.find( + ( fontSize ) => fontSize.size === value ); - const selectedOption = getSelectedOption( fontSizes, value ); - const isCustomValue = selectedOption.slug === CUSTOM_FONT_SIZE; + const isCustomValue = value !== undefined && ! selectedFontSize; + const [ showCustomValueControl, setShowCustomValueControl ] = useState( ! disableCustomFontSizes && isCustomValue ); + const headerHint = useMemo( () => { if ( showCustomValueControl ) { return `(${ __( 'Custom' ) })`; @@ -124,51 +100,49 @@ const UnforwardedFontSizePicker = ( // If we have a custom value that is not available in the font sizes, // show it as a hint as long as it's a simple CSS value. if ( isCustomValue ) { - return ( - value !== undefined && - isSimpleCssValue( value ) && - `(${ value })` - ); + return value !== undefined && isSimpleCssValue( value ) + ? `(${ value })` + : ''; + } + + if ( ! selectedFontSize ) { + return ''; } + if ( shouldUseSelectControl ) { - return ( - selectedOption?.size !== undefined && - isSimpleCssValue( selectedOption?.size ) && - `(${ selectedOption?.size })` - ); + return isSimpleCssValue( selectedFontSize.size ) + ? `(${ selectedFontSize.size })` + : ''; } // Calculate the `hint` for toggle group control. - let hint = selectedOption.name; + let hint = selectedFontSize.name ?? ''; + const fontSizesContainComplexValues = fontSizes.some( + ( fontSize ) => ! isSimpleCssValue( fontSize.size ) + ); if ( ! fontSizesContainComplexValues && - typeof selectedOption.size === 'string' + typeof selectedFontSize.size === 'string' ) { - const [ , unit ] = splitValueAndUnitFromSize( selectedOption.size ); + const [ , unit ] = splitValueAndUnitFromSize( + selectedFontSize.size + ); hint += `(${ unit })`; } return hint; }, [ showCustomValueControl, - selectedOption?.name, - selectedOption?.size, - value, isCustomValue, + selectedFontSize, + value, shouldUseSelectControl, - fontSizesContainComplexValues, + fontSizes, ] ); - if ( ! options ) { + if ( fontSizes.length === 0 && disableCustomFontSizes ) { return null; } - // This is used for select control only. We need to add support - // for ToggleGroupControl. - const currentFontSizeSR = sprintf( - // translators: %s: Currently selected font size. - __( 'Currently selected font size: %s' ), - selectedOption.name - ); return ( { __( 'Font size' ) } @@ -209,64 +183,32 @@ const UnforwardedFontSizePicker = ( { !! fontSizes.length && shouldUseSelectControl && ! showCustomValueControl && ( - - option.key === selectedOption.slug - ) } - onChange={ ( { - selectedItem, - }: { - selectedItem: FontSizeSelectOption; - } ) => { + { onChange?.( - hasUnits - ? selectedItem.size - : Number( selectedItem.size ) + hasUnits ? newValue : Number( newValue ) ); - if ( - selectedItem.key === CUSTOM_FONT_SIZE - ) { - setShowCustomValueControl( true ); - } } } - size={ size } + onSelectCustom={ () => + setShowCustomValueControl( true ) + } /> ) } { ! shouldUseSelectControl && ! showCustomValueControl && ( - { onChange?.( hasUnits ? newValue : Number( newValue ) ); } } - isBlock - size={ size } - > - { ( options as FontSizeToggleGroupOption[] ).map( - ( option ) => ( - - ) - ) } - + /> ) } { ! withSlider && ! disableCustomFontSizes && diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx new file mode 100644 index 00000000000000..83b60f5b271733 --- /dev/null +++ b/packages/components/src/font-size-picker/select.tsx @@ -0,0 +1,77 @@ +/** + * WordPress dependencies + */ +import { __, sprintf } from '@wordpress/i18n'; +/** + * Internal dependencies + */ +import CustomSelectControl from '../custom-select-control'; + +/** + * Internal dependencies + */ +import type { + FontSizePickerSelectProps, + FontSizePickerSelectOption, +} from './types'; +import { splitValueAndUnitFromSize } from './utils'; + +const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { + const { fontSizes = [], value, size, onChange, onSelectCustom } = props; + + const defaultOption: FontSizePickerSelectOption = { + key: 'default', + name: __( 'Default' ), + value: undefined, + }; + const customOption: FontSizePickerSelectOption = { + key: 'custom', + name: __( 'Custom' ), + }; + const options: FontSizePickerSelectOption[] = [ + defaultOption, + ...fontSizes.map( ( fontSize ) => { + const [ number ] = splitValueAndUnitFromSize( fontSize.size ); + return { + key: fontSize.slug, + name: fontSize.name ?? fontSize.slug, + value: fontSize.size, + __experimentalHint: number, + }; + } ), + customOption, + ]; + + const selectedOption = + options.find( ( option ) => option.value === value ) ?? customOption; + + return ( + { + if ( selectedItem === customOption ) { + onSelectCustom(); + } else { + onChange( selectedItem.value ); + } + } } + size={ size } + /> + ); +}; + +export default FontSizePickerSelect; diff --git a/packages/components/src/font-size-picker/test/utils.ts b/packages/components/src/font-size-picker/test/utils.ts index 18bc6f86e8fd03..939628f6e46094 100644 --- a/packages/components/src/font-size-picker/test/utils.ts +++ b/packages/components/src/font-size-picker/test/utils.ts @@ -1,11 +1,7 @@ /** * Internal dependencies */ -import { - isSimpleCssValue, - splitValueAndUnitFromSize, - getToggleGroupOptions, -} from '../utils'; +import { isSimpleCssValue, splitValueAndUnitFromSize } from '../utils'; const simpleCSSCases: [ number | string, boolean ][] = [ // Test integers and non-integers. @@ -80,74 +76,3 @@ describe( 'splitValueAndUnitFromSize', () => { } ); } ); - -describe( 'getToggleGroupOptions', () => { - test( 'should assign t-shirt sizes by default up until the aliases length', () => { - const optionsArray = [ - { - slug: '1', - size: '1', - name: '1', - }, - { - slug: '2', - size: '2', - name: '2', - }, - { - slug: '3', - size: '3', - name: '3', - }, - { - slug: '4', - size: '4', - name: '4', - }, - { - slug: '5', - size: '5', - }, - ]; - expect( - getToggleGroupOptions( optionsArray, [ - 'S', - 'M', - 'L', - 'XL', - 'XXL', - ] ) - ).toEqual( [ - { - key: '1', - label: 'S', - name: '1', - value: '1', - }, - { - key: '2', - label: 'M', - name: '2', - value: '2', - }, - { - key: '3', - label: 'L', - name: '3', - value: '3', - }, - { - key: '4', - label: 'XL', - name: '4', - value: '4', - }, - { - key: '5', - label: 'XXL', - name: 'XXL', - value: '5', - }, - ] ); - } ); -} ); diff --git a/packages/components/src/font-size-picker/toggle-group.tsx b/packages/components/src/font-size-picker/toggle-group.tsx new file mode 100644 index 00000000000000..de5afeb9111ccb --- /dev/null +++ b/packages/components/src/font-size-picker/toggle-group.tsx @@ -0,0 +1,58 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { + ToggleGroupControl, + ToggleGroupControlOption, +} from '../toggle-group-control'; +import type { FontSizePickerToggleGroupProps } from './types'; + +/** + * In case we have at most five font sizes, show a `T-shirt size` alias as a + * label of the font size. The label assumes that the font sizes are ordered + * accordingly - from smallest to largest. + */ +const FONT_SIZES_ALIASES = [ + /* translators: S stands for 'small' and is a size label. */ + __( 'S' ), + /* translators: M stands for 'medium' and is a size label. */ + __( 'M' ), + /* translators: L stands for 'large' and is a size label. */ + __( 'L' ), + /* translators: XL stands for 'extra large' and is a size label. */ + __( 'XL' ), + /* translators: XXL stands for 'extra extra large' and is a size label. */ + __( 'XXL' ), +]; + +const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => { + const { fontSizes, value, __nextHasNoMarginBottom, size, onChange } = props; + return ( + + { fontSizes.map( ( fontSize, index ) => ( + + ) ) } + + ); +}; + +export default FontSizePickerToggleGroup; diff --git a/packages/components/src/font-size-picker/types.ts b/packages/components/src/font-size-picker/types.ts index 2c824125bb7e1c..b5bef68a570f98 100644 --- a/packages/components/src/font-size-picker/types.ts +++ b/packages/components/src/font-size-picker/types.ts @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import type { ReactNode } from 'react'; - export type FontSizePickerProps = { /** * If `true`, it will not be possible to choose a custom fontSize. The user @@ -81,18 +76,25 @@ export type FontSize = { slug: string; }; -export type FontSizeOption = Omit< FontSize, 'size' > & - Partial< Pick< FontSize, 'size' > >; - -export type FontSizeSelectOption = Pick< FontSizeOption, 'size' > & { - key: string; - name?: string; - __experimentalHint: ReactNode; +export type FontSizePickerSelectProps = { + fontSizes: FontSize[]; + value: FontSizePickerProps[ 'value' ]; + size: FontSizePickerProps[ 'size' ]; + onChange: ( value: number | string | undefined ) => void; + onSelectCustom: () => void; }; -export type FontSizeToggleGroupOption = { +export type FontSizePickerSelectOption = { key: string; - value: number | string; - label: string; name: string; + value?: FontSize[ 'size' ]; + __experimentalHint?: string; +}; + +export type FontSizePickerToggleGroupProps = { + fontSizes: FontSize[]; + value: FontSizePickerProps[ 'value' ]; + __nextHasNoMarginBottom?: boolean; + size: FontSizePickerProps[ 'size' ]; + onChange: ( value: number | string | undefined ) => void; }; diff --git a/packages/components/src/font-size-picker/utils.ts b/packages/components/src/font-size-picker/utils.ts index db6a07d7734af4..75bf236f790a2a 100644 --- a/packages/components/src/font-size-picker/utils.ts +++ b/packages/components/src/font-size-picker/utils.ts @@ -6,42 +6,7 @@ import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import type { - FontSize, - FontSizeOption, - FontSizeSelectOption, - FontSizeToggleGroupOption, - FontSizePickerProps, -} from './types'; - -const DEFAULT_FONT_SIZE = 'default'; -const DEFAULT_FONT_SIZE_OPTION = { - slug: DEFAULT_FONT_SIZE, - name: __( 'Default' ), -}; -export const CUSTOM_FONT_SIZE = 'custom'; -const CUSTOM_FONT_SIZE_OPTION = { - slug: CUSTOM_FONT_SIZE, - name: __( 'Custom' ), -}; - -/** - * In case we have at most five font sizes, show a `T-shirt size` - * alias as a label of the font size. The label assumes that the font sizes - * are ordered accordingly - from smallest to largest. - */ -const FONT_SIZES_ALIASES = [ - /* translators: S stands for 'small' and is a size label. */ - __( 'S' ), - /* translators: M stands for 'medium' and is a size label. */ - __( 'M' ), - /* translators: L stands for 'large' and is a size label. */ - __( 'L' ), - /* translators: XL stands for 'extra large' and is a size label. */ - __( 'XL' ), - /* translators: XXL stands for 'extra extra large' and is a size label. */ - __( 'XXL' ), -]; +import type { FontSizePickerProps } from './types'; /** * Helper util to split a font size to its numeric value @@ -78,77 +43,3 @@ export function isSimpleCssValue( const sizeRegex = /^[\d\.]+(px|em|rem|vw|vh|%)?$/i; return sizeRegex.test( String( value ) ); } - -/** - * Return font size options in the proper format depending - * on the currently used control (select, toggle group). - * - * @param useSelectControl Whether to use a select control. - * @param optionsArray Array of available font sizes objects. - * @param disableCustomFontSizes Flag that indicates if custom font sizes are disabled. - * @return Array of font sizes in proper format for the used control. - */ -export function getFontSizeOptions( - useSelectControl: boolean, - optionsArray: FontSize[], - disableCustomFontSizes: boolean -): FontSizeSelectOption[] | FontSizeToggleGroupOption[] | null { - if ( disableCustomFontSizes && ! optionsArray.length ) { - return null; - } - return useSelectControl - ? getSelectOptions( optionsArray, disableCustomFontSizes ) - : getToggleGroupOptions( optionsArray ); -} - -function getSelectOptions( - optionsArray: FontSize[], - disableCustomFontSizes: boolean -): FontSizeSelectOption[] { - const options: FontSizeOption[] = [ - DEFAULT_FONT_SIZE_OPTION, - ...optionsArray, - ...( disableCustomFontSizes ? [] : [ CUSTOM_FONT_SIZE_OPTION ] ), - ]; - return options.map( ( { slug, name, size } ) => ( { - key: slug, - name: name || slug, - size, - __experimentalHint: - size && isSimpleCssValue( size ) && parseFloat( String( size ) ), - } ) ); -} - -/** - * Build options for the toggle group options. - * - * @param optionsArray An array of font size options. - * @param labelAliases An array of alternative labels. - * @return Remapped optionsArray. - */ -export function getToggleGroupOptions( - optionsArray: FontSize[], - labelAliases: string[] = FONT_SIZES_ALIASES -): FontSizeToggleGroupOption[] { - return optionsArray.map( ( { slug, size, name }, index ) => { - return { - key: slug, - value: size, - label: labelAliases[ index ], - name: name || labelAliases[ index ], - }; - } ); -} - -export function getSelectedOption( - fontSizes: FontSize[], - value: FontSizePickerProps[ 'value' ] -): FontSizeOption { - if ( ! value ) { - return DEFAULT_FONT_SIZE_OPTION; - } - return ( - fontSizes.find( ( font ) => font.size === value ) || - CUSTOM_FONT_SIZE_OPTION - ); -} From bbf17a3f5c90f488bbb9b6b34146d5b3cadca859 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 12 Oct 2022 17:07:17 +1100 Subject: [PATCH 02/26] Remove duplicate dependency header --- packages/components/src/font-size-picker/select.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx index 83b60f5b271733..4ab23140776939 100644 --- a/packages/components/src/font-size-picker/select.tsx +++ b/packages/components/src/font-size-picker/select.tsx @@ -2,14 +2,11 @@ * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -/** - * Internal dependencies - */ -import CustomSelectControl from '../custom-select-control'; /** * Internal dependencies */ +import CustomSelectControl from '../custom-select-control'; import type { FontSizePickerSelectProps, FontSizePickerSelectOption, From 450d63bda521d8abb49ebf5dd1204e90768245a6 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 15:48:11 +1100 Subject: [PATCH 03/26] Reuse types from FontSizePickerProps where possible --- .../components/src/font-size-picker/types.ts | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/components/src/font-size-picker/types.ts b/packages/components/src/font-size-picker/types.ts index b5bef68a570f98..d0a3f1681712bd 100644 --- a/packages/components/src/font-size-picker/types.ts +++ b/packages/components/src/font-size-picker/types.ts @@ -76,11 +76,12 @@ export type FontSize = { slug: string; }; -export type FontSizePickerSelectProps = { - fontSizes: FontSize[]; - value: FontSizePickerProps[ 'value' ]; - size: FontSizePickerProps[ 'size' ]; - onChange: ( value: number | string | undefined ) => void; +export type FontSizePickerSelectProps = Pick< + FontSizePickerProps, + 'value' | 'size' +> & { + fontSizes: NonNullable< FontSizePickerProps[ 'fontSizes' ] >; + onChange: NonNullable< FontSizePickerProps[ 'onChange' ] >; onSelectCustom: () => void; }; @@ -91,10 +92,10 @@ export type FontSizePickerSelectOption = { __experimentalHint?: string; }; -export type FontSizePickerToggleGroupProps = { - fontSizes: FontSize[]; - value: FontSizePickerProps[ 'value' ]; - __nextHasNoMarginBottom?: boolean; - size: FontSizePickerProps[ 'size' ]; - onChange: ( value: number | string | undefined ) => void; +export type FontSizePickerToggleGroupProps = Pick< + FontSizePickerProps, + 'value' | 'size' | '__nextHasNoMarginBottom' +> & { + fontSizes: NonNullable< FontSizePickerProps[ 'fontSizes' ] >; + onChange: NonNullable< FontSizePickerProps[ 'onChange' ] >; }; From c50fa2253e4e5d12215c1574cfee5b3320097816 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 15:49:54 +1100 Subject: [PATCH 04/26] Rename 'number' to 'parsedValue' --- packages/components/src/font-size-picker/select.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx index 4ab23140776939..3d17914959e5fb 100644 --- a/packages/components/src/font-size-picker/select.tsx +++ b/packages/components/src/font-size-picker/select.tsx @@ -28,12 +28,12 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { const options: FontSizePickerSelectOption[] = [ defaultOption, ...fontSizes.map( ( fontSize ) => { - const [ number ] = splitValueAndUnitFromSize( fontSize.size ); + const [ parsedValue ] = splitValueAndUnitFromSize( fontSize.size ); return { key: fontSize.slug, name: fontSize.name ?? fontSize.slug, value: fontSize.size, - __experimentalHint: number, + __experimentalHint: parsedValue, }; } ), customOption, From f882797f54a5a4a1e3f94179d24bf024bd75b1cd Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 16:05:03 +1100 Subject: [PATCH 05/26] Use constants for defaultOption and customOption --- .../src/font-size-picker/select.tsx | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx index 3d17914959e5fb..ce5a25992661d1 100644 --- a/packages/components/src/font-size-picker/select.tsx +++ b/packages/components/src/font-size-picker/select.tsx @@ -13,20 +13,22 @@ import type { } from './types'; import { splitValueAndUnitFromSize } from './utils'; +const DEFAULT_OPTION: FontSizePickerSelectOption = { + key: 'default', + name: __( 'Default' ), + value: undefined, +}; + +const CUSTOM_OPTION: FontSizePickerSelectOption = { + key: 'custom', + name: __( 'Custom' ), +}; + const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { const { fontSizes = [], value, size, onChange, onSelectCustom } = props; - const defaultOption: FontSizePickerSelectOption = { - key: 'default', - name: __( 'Default' ), - value: undefined, - }; - const customOption: FontSizePickerSelectOption = { - key: 'custom', - name: __( 'Custom' ), - }; const options: FontSizePickerSelectOption[] = [ - defaultOption, + DEFAULT_OPTION, ...fontSizes.map( ( fontSize ) => { const [ parsedValue ] = splitValueAndUnitFromSize( fontSize.size ); return { @@ -36,11 +38,11 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { __experimentalHint: parsedValue, }; } ), - customOption, + CUSTOM_OPTION, ]; const selectedOption = - options.find( ( option ) => option.value === value ) ?? customOption; + options.find( ( option ) => option.value === value ) ?? CUSTOM_OPTION; return ( { }: { selectedItem: FontSizePickerSelectOption; } ) => { - if ( selectedItem === customOption ) { + if ( selectedItem === CUSTOM_OPTION ) { onSelectCustom(); } else { onChange( selectedItem.value ); From b108a6ec763d4bed14400de1145a7fdbc004fde2 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 16:23:10 +1100 Subject: [PATCH 06/26] Don't show 'Custom' option in select when custom sizes are disabled --- packages/components/src/font-size-picker/index.tsx | 3 +++ packages/components/src/font-size-picker/select.tsx | 11 +++++++++-- packages/components/src/font-size-picker/types.ts | 3 +++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index b733639c9c03ba..f4f165a4ea839b 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -186,6 +186,9 @@ const UnforwardedFontSizePicker = ( { onChange?.( diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx index ce5a25992661d1..8f6afe87cf98f3 100644 --- a/packages/components/src/font-size-picker/select.tsx +++ b/packages/components/src/font-size-picker/select.tsx @@ -25,7 +25,14 @@ const CUSTOM_OPTION: FontSizePickerSelectOption = { }; const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { - const { fontSizes = [], value, size, onChange, onSelectCustom } = props; + const { + fontSizes, + value, + disableCustomFontSizes, + size, + onChange, + onSelectCustom, + } = props; const options: FontSizePickerSelectOption[] = [ DEFAULT_OPTION, @@ -38,7 +45,7 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { __experimentalHint: parsedValue, }; } ), - CUSTOM_OPTION, + ...( disableCustomFontSizes ? [] : [ CUSTOM_OPTION ] ), ]; const selectedOption = diff --git a/packages/components/src/font-size-picker/types.ts b/packages/components/src/font-size-picker/types.ts index d0a3f1681712bd..a198d65b80f723 100644 --- a/packages/components/src/font-size-picker/types.ts +++ b/packages/components/src/font-size-picker/types.ts @@ -81,6 +81,9 @@ export type FontSizePickerSelectProps = Pick< 'value' | 'size' > & { fontSizes: NonNullable< FontSizePickerProps[ 'fontSizes' ] >; + disableCustomFontSizes: NonNullable< + FontSizePickerProps[ 'disableCustomFontSizes' ] + >; onChange: NonNullable< FontSizePickerProps[ 'onChange' ] >; onSelectCustom: () => void; }; From 4cdf21cc3195ce703d05847eda15bc79c82d2446 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 16:29:00 +1100 Subject: [PATCH 07/26] Swap label and aria-label --- packages/components/src/font-size-picker/toggle-group.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/font-size-picker/toggle-group.tsx b/packages/components/src/font-size-picker/toggle-group.tsx index de5afeb9111ccb..a7aed96e76ae6f 100644 --- a/packages/components/src/font-size-picker/toggle-group.tsx +++ b/packages/components/src/font-size-picker/toggle-group.tsx @@ -47,7 +47,7 @@ const FontSizePickerToggleGroup = ( props: FontSizePickerToggleGroupProps ) => { key={ fontSize.slug } value={ fontSize.size } label={ FONT_SIZES_ALIASES[ index ] } - aria-label={ fontSize.name } + aria-label={ fontSize.name || FONT_SIZES_ALIASES[ index ] } showTooltip /> ) ) } From c76c8e6a38f9b0b8aac84d8e5671b7309956b5c6 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 16:30:14 +1100 Subject: [PATCH 08/26] Use slug in select if name is an empty string --- packages/components/src/font-size-picker/select.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/select.tsx index 8f6afe87cf98f3..8ad0521c07e7fc 100644 --- a/packages/components/src/font-size-picker/select.tsx +++ b/packages/components/src/font-size-picker/select.tsx @@ -40,7 +40,7 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { const [ parsedValue ] = splitValueAndUnitFromSize( fontSize.size ); return { key: fontSize.slug, - name: fontSize.name ?? fontSize.slug, + name: fontSize.name || fontSize.slug, value: fontSize.size, __experimentalHint: parsedValue, }; From c5e4faece94e6b6ed0ba1168dd5d382297767f3f Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Thu, 13 Oct 2022 16:51:54 +1100 Subject: [PATCH 09/26] Add test that checks all the t-shirt labels --- .../src/font-size-picker/test/index.tsx | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 808122852e2273..f2fc51bf474cc2 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -244,6 +244,27 @@ describe( 'FontSizePicker', () => { 'XL' ); } ); + it( 'should use t-shirt labels if sizes have no names', () => { + const fontSizes = []; + for ( let i = 1; i <= 5; i++ ) { + fontSizes.push( { + slug: `size-${ i }`, + size: `${ i }em`, + } ); + } + render( + + ); + for ( const label of [ 'S', 'M', 'L', 'XL', 'XXL' ] ) { + const element = screen.getByLabelText( label ); + expect( element ).toBeInTheDocument(); + expect( element.children[ 0 ].textContent ).toBe( label ); + } + } ); } ); } ); } ); From 97e572aa2cf3afb1a410a50d78673ea335048c50 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Oct 2022 15:44:47 +1100 Subject: [PATCH 10/26] Make filenames match component names --- .../{select.tsx => font-size-picker-select.tsx} | 0 .../{toggle-group.tsx => font-size-picker-toggle-group.tsx} | 0 packages/components/src/font-size-picker/index.tsx | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/components/src/font-size-picker/{select.tsx => font-size-picker-select.tsx} (100%) rename packages/components/src/font-size-picker/{toggle-group.tsx => font-size-picker-toggle-group.tsx} (100%) diff --git a/packages/components/src/font-size-picker/select.tsx b/packages/components/src/font-size-picker/font-size-picker-select.tsx similarity index 100% rename from packages/components/src/font-size-picker/select.tsx rename to packages/components/src/font-size-picker/font-size-picker-select.tsx diff --git a/packages/components/src/font-size-picker/toggle-group.tsx b/packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx similarity index 100% rename from packages/components/src/font-size-picker/toggle-group.tsx rename to packages/components/src/font-size-picker/font-size-picker-toggle-group.tsx diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index 4b5b80d84f119e..588eda6ff16230 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -34,8 +34,8 @@ import { ResetButton, } from './styles'; import { Spacer } from '../spacer'; -import FontSizePickerSelect from './select'; -import FontSizePickerToggleGroup from './toggle-group'; +import FontSizePickerSelect from './font-size-picker-select'; +import FontSizePickerToggleGroup from './font-size-picker-toggle-group'; const UnforwardedFontSizePicker = ( props: FontSizePickerProps, From 1de7d4ead6da4ec9d5523f35085dd7f8cfacd3cf Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Oct 2022 15:52:18 +1100 Subject: [PATCH 11/26] Make DEFAULT_OPTION logic more explicit --- .../src/font-size-picker/font-size-picker-select.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/components/src/font-size-picker/font-size-picker-select.tsx b/packages/components/src/font-size-picker/font-size-picker-select.tsx index 777ee5309eca19..192a0f5632f715 100644 --- a/packages/components/src/font-size-picker/font-size-picker-select.tsx +++ b/packages/components/src/font-size-picker/font-size-picker-select.tsx @@ -51,8 +51,9 @@ const FontSizePickerSelect = ( props: FontSizePickerSelectProps ) => { ...( disableCustomFontSizes ? [] : [ CUSTOM_OPTION ] ), ]; - const selectedOption = - options.find( ( option ) => option.value === value ) ?? CUSTOM_OPTION; + const selectedOption = value + ? options.find( ( option ) => option.value === value ) ?? CUSTOM_OPTION + : DEFAULT_OPTION; return ( Date: Tue, 25 Oct 2022 16:08:03 +1100 Subject: [PATCH 12/26] Restore headerHint logic to how it was --- .../components/src/font-size-picker/index.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index 588eda6ff16230..8c4f1907fa218f 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -84,19 +84,23 @@ const UnforwardedFontSizePicker = ( // If we have a custom value that is not available in the font sizes, // show it as a hint as long as it's a simple CSS value. if ( isCustomValue ) { - return value !== undefined && isSimpleCssValue( value ) - ? `(${ value })` - : ''; + return ( + value !== undefined && + isSimpleCssValue( value ) && + `(${ value })` + ); } - if ( ! selectedFontSize ) { - return ''; + if ( shouldUseSelectControl ) { + return ( + selectedFontSize?.size !== undefined && + isSimpleCssValue( selectedFontSize?.size ) && + `(${ selectedFontSize?.size })` + ); } - if ( shouldUseSelectControl ) { - return isSimpleCssValue( selectedFontSize.size ) - ? `(${ selectedFontSize.size })` - : ''; + if ( ! selectedFontSize ) { + return __( 'Default' ); } // Calculate the `hint` for toggle group control. From 8f87273f53426e925cf5ab13e6f2ff1b685790b8 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 25 Oct 2022 16:11:04 +1100 Subject: [PATCH 13/26] Use expect().toHaveContent --- packages/components/src/font-size-picker/test/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 3e19ad85949dd9..858e1b967f79e7 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -303,7 +303,7 @@ describe( 'FontSizePicker', () => { for ( const label of [ 'S', 'M', 'L', 'XL', 'XXL' ] ) { const element = screen.getByLabelText( label ); expect( element ).toBeInTheDocument(); - expect( element.children[ 0 ].textContent ).toBe( label ); + expect( element ).toHaveTextContent( label ); } } ); } ); From 299d680eb4351e7a491a5e084a4196732ea7d640 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 26 Oct 2022 16:33:35 +1100 Subject: [PATCH 14/26] Rewrite FontSizePicker unit tests to use userEvent and be more comprehensive --- .../src/font-size-picker/test/index.tsx | 582 +++++++++++------- 1 file changed, 363 insertions(+), 219 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 719dd612c45d95..46bc3fa4c54a27 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -1,290 +1,434 @@ /** * External dependencies */ -import { render, fireEvent, screen } from '@testing-library/react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ import FontSizePicker from '../'; - -const getUnitSelect = () => - document.body.querySelector( '.components-unit-control select' ); -const getUnitLabel = () => - document.body.querySelector( '.components-unit-control__unit-label' ); - -const toggleCustomInput = ( showCustomInput: boolean ) => { - const label = showCustomInput ? 'Set custom size' : 'Use size preset'; - const toggleCustom = screen.getByLabelText( label, { selector: 'button' } ); - fireEvent.click( toggleCustom ); -}; +import type { FontSize } from '../types'; describe( 'FontSizePicker', () => { - describe( 'onChange values', () => { - it( 'should not use units when the initial value is a number', () => { - let fontSize = 12; - const setFontSize = jest.fn( - ( nextSize ) => ( fontSize = nextSize ) - ); - + test.each( [ + // Use units when initial value uses units. + { value: '12px', expectedValue: '80px' }, + // Don't use units when initial value does not use units. + { value: 12, expectedValue: 80 }, + ] )( + 'should call onChange( $expectedValue ) after user types 80 when value is $value', + async ( { value, expectedValue } ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); render( ); + const input = screen.getByLabelText( 'Custom' ); + await user.clear( input ); + await user.type( input, '80' ); + expect( onChange ).toHaveBeenCalledWith( expectedValue ); + } + ); - const unitSelect = getUnitSelect(); - const unitLabel = getUnitLabel(); - const input = screen.getByLabelText( 'Custom', { - selector: 'input', + test.each( [ + // Use units when first font size uses units. + { firstFontSize: '12px', expectedValue: '80px' }, + // Don't use units when first font size does not use units. + { firstFontSize: 12, expectedValue: 80 }, + ] )( + 'should call onChange( $expectedValue ) after user types 80 when first font size is $firstFontSize', + async ( { firstFontSize, expectedValue } ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - - input.focus(); - fireEvent.change( input, { target: { value: 16 } } ); - - expect( unitSelect ).toBeFalsy(); - expect( unitLabel ).toBeTruthy(); - expect( fontSize ).toBe( 16 ); - } ); - - it( 'should use units when the initial value has a unit', () => { - let fontSize = '12px'; - const setFontSize = jest.fn( - ( nextSize ) => ( fontSize = nextSize ) - ); - + const onChange = jest.fn(); render( ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + const input = screen.getByLabelText( 'Custom' ); + await user.clear( input ); + await user.type( input, '80' ); + expect( onChange ).toHaveBeenCalledWith( expectedValue ); + } + ); - const unitSelect = getUnitSelect(); - const unitLabel = getUnitLabel(); - const input = screen.getByLabelText( 'Custom', { - selector: 'input', - } ); - - input.focus(); - fireEvent.change( input, { target: { value: 16 } } ); - - expect( unitSelect ).toBeTruthy(); - expect( unitLabel ).toBeFalsy(); - expect( fontSize ).toBe( '16px' ); + it( 'should not use units when the initial value has no unit', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); + const onChange = jest.fn(); + render( + + ); + const input = screen.getByLabelText( 'Custom' ); + await user.clear( input ); + await user.type( input, '80' ); + expect( onChange ).toHaveBeenCalledWith( 80 ); + } ); - it( 'should not use units when fontSizes size values are numbers', () => { - let fontSize; - const fontSizes = [ - { - name: 'Small', - slug: 'small', - size: 12, - }, - ]; - const setFontSize = jest.fn( - ( nextSize ) => ( fontSize = nextSize ) - ); + describe( 'with > 5 font sizes', () => { + const fontSizes = [ + { + slug: 'tiny', + name: 'Tiny', + size: '8px', + }, + { + slug: 'small', + name: 'Small', + size: '12px', + }, + { + slug: 'medium', + name: 'Medium', + size: '16px', + }, + { + slug: 'large', + name: 'Large', + size: '22px', + }, + { + slug: 'x-large', + name: 'Extra Large', + size: '30px', + }, + { + slug: 'xx-large', + // no name + size: '40px', + }, + ]; + it( 'displays a select control', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); render( ); - - toggleCustomInput( true ); - const unitSelect = getUnitSelect(); - const unitLabel = getUnitLabel(); - const input = screen.getByLabelText( 'Custom', { - selector: 'input', - } ); - - input.focus(); - fireEvent.change( input, { target: { value: 16 } } ); - - expect( unitSelect ).toBeFalsy(); - expect( unitLabel ).toBeTruthy(); - expect( fontSize ).toBe( 16 ); + await user.click( + screen.getByRole( 'button', { name: 'Font size' } ) + ); + const options = screen.getAllByRole( 'option' ); + expect( options[ 0 ] ).toHaveAccessibleName( 'Default' ); + expect( options[ 1 ] ).toHaveAccessibleName( 'Tiny 8' ); + expect( options[ 2 ] ).toHaveAccessibleName( 'Small 12' ); + expect( options[ 3 ] ).toHaveAccessibleName( 'Medium 16' ); + expect( options[ 4 ] ).toHaveAccessibleName( 'Large 22' ); + expect( options[ 5 ] ).toHaveAccessibleName( 'Extra Large 30' ); + expect( options[ 6 ] ).toHaveAccessibleName( 'xx-large 40' ); + expect( options[ 7 ] ).toHaveAccessibleName( 'Custom' ); } ); - it( 'should use units when fontSizes size values have units', () => { - let fontSize; - const fontSizes = [ - { - name: 'Small', - slug: 'small', - size: '12px', - }, - ]; - const setFontSize = jest.fn( - ( nextSize ) => ( fontSize = nextSize ) - ); + test.each( [ + { value: undefined, expectedHint: 'Size' }, + { value: '8px', expectedHint: 'Size(8px)' }, + { value: '3px', expectedHint: 'Size(Custom)' }, + ] )( + 'displays $expectedHint as header hint when value is $value', + ( { value, expectedHint } ) => { + render( + + ); + // TODO: There's no accessible way to select the label. + const label = document.querySelector( + '.components-base-control__label' + ); + expect( label ).toHaveTextContent( expectedHint ); + } + ); + + test.each( [ + { option: 'Default', value: '8px', expectedValue: undefined }, + { option: 'Small 12', value: '8px', expectedValue: '12px' }, + ] )( + 'calls onChange( $expectedValue ) when $option is selected', + async ( { option, value, expectedValue } ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Font size' } ) + ); + await user.click( + screen.getByRole( 'option', { name: option } ) + ); + expect( onChange ).toHaveBeenCalledWith( expectedValue ); + } + ); + it( 'shows custom input when Custom is selected', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); render( ); - - toggleCustomInput( true ); - const unitSelect = getUnitSelect(); - const unitLabel = getUnitLabel(); - const input = screen.getByLabelText( 'Custom', { - selector: 'input', - } ); - - input.focus(); - fireEvent.change( input, { target: { value: 16 } } ); - - expect( unitSelect ).toBeTruthy(); - expect( unitLabel ).toBeFalsy(); - expect( fontSize ).toBe( '16px' ); + await user.click( + screen.getByRole( 'button', { name: 'Font size' } ) + ); + await user.click( + screen.getByRole( 'option', { name: 'Custom' } ) + ); + expect( screen.getByLabelText( 'Custom' ) ).toBeInTheDocument(); + // TODO: onChange() shouldn't be called. + //expect( onChange ).not.toHaveBeenCalled(); } ); + + commonTests( fontSizes ); } ); - describe( 'renders different control', () => { - const options = [ + + describe( 'with <= 5 font sizes', () => { + const fontSizes = [ { - name: 'Small', slug: 'small', - size: '0.65rem', + // no name + size: '12px', }, { - name: 'Medium', slug: 'medium', - size: '1.125rem', + name: 'Medium', + size: '16px', }, { - name: 'Large', slug: 'large', - size: '1.7rem', + name: 'Large', + size: '22px', + }, + { + slug: 'huge', + name: 'Huge', + size: '30px', + }, + { + slug: 'massive', + name: 'Massive', + size: '40px', }, ]; - it( 'should render select control when we have more than five font sizes', () => { - const extraOptions = [ - { - name: 'Extra Large', - slug: 'extra-large', - size: '1.95rem', - }, - { - name: 'Extra Extra Large', - slug: 'extra-extra-large', - size: '2.5rem', - }, - { - name: 'Huge', - slug: 'huge', - size: '2.8rem', - }, - ]; - const fontSizes = [ ...options, ...extraOptions ]; + + it( 'displays a toggle group control with t-shirt sizes', () => { render( ); - // Trigger click to open the select menu and take into account - // the two extra options (default, custom); - fireEvent.click( - screen.getByLabelText( 'Font size', { selector: 'button' } ) - ); - const element = screen.getAllByRole( 'option' ); - expect( element ).toHaveLength( fontSizes.length + 2 ); + const options = screen.getAllByRole( 'radio' ); + expect( options[ 0 ] ).toHaveTextContent( 'S' ); + expect( options[ 0 ] ).toHaveAccessibleName( 'S' ); + expect( options[ 1 ] ).toHaveTextContent( 'M' ); + expect( options[ 1 ] ).toHaveAccessibleName( 'Medium' ); + expect( options[ 2 ] ).toHaveTextContent( 'L' ); + expect( options[ 2 ] ).toHaveAccessibleName( 'Large' ); + expect( options[ 3 ] ).toHaveTextContent( 'XL' ); + expect( options[ 3 ] ).toHaveAccessibleName( 'Huge' ); + expect( options[ 4 ] ).toHaveTextContent( 'XXL' ); + expect( options[ 4 ] ).toHaveAccessibleName( 'Massive' ); } ); - describe( 'segmented control', () => { - it( 'should use t-shirt labels for simple css values', () => { - const fontSizes = [ ...options ]; + + test.each( [ + { value: undefined, expectedHint: 'Size' }, + { value: '12px', expectedHint: 'small' }, + { value: '40px', expectedHint: 'Massive' }, + ] )( + 'displays $hint as header hint when value is $value', + ( { value, expectedHint } ) => { render( - ); - const element = screen.getByLabelText( 'Large' ); - expect( element ).toBeInTheDocument(); - expect( element.children[ 0 ] ).toHaveTextContent( 'L' ); - } ); - it( 'should use incremental sequence of t-shirt sizes as labels if we have complex css', () => { - const fontSizes = [ - ...options, - { - name: 'Extra Large', - slug: 'extra-large', - size: 'clamp(1.75rem, 3vw, 2.25rem)', - }, - ]; - render( - ); - const largeElement = screen.getByLabelText( 'Large' ); - expect( largeElement ).toBeInTheDocument(); - expect( largeElement ).toHaveTextContent( 'L' ); - - const extraLargeElement = - screen.getByLabelText( 'Extra Large' ); - expect( extraLargeElement ).toBeInTheDocument(); - expect( extraLargeElement.children[ 0 ] ).toHaveTextContent( - 'XL' + // TODO: There's no accessible way to select the label. + const label = document.querySelector( + '.components-base-control__label' ); + expect( label ).toHaveTextContent( expectedHint ); + } + ); + + it( 'calls onChange when a font size is selected', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - it( 'should use font size `slug` for for header hint label by default', () => { - const fontSizes = [ - { - name: 'Allosaurus Large', - slug: 'allosaurus-l', - size: '20rem', - }, - ]; - render( - - ); + const onChange = jest.fn(); + render( + + ); + await user.click( screen.getByRole( 'radio', { name: 'Medium' } ) ); + expect( onChange ).toHaveBeenCalledWith( '16px' ); + } ); - const largeFontSizeElement = screen.getByLabelText( - 'Size Allosaurus Large(rem)' - ); - expect( largeFontSizeElement ).toBeInTheDocument(); + commonTests( fontSizes ); + } ); + + function commonTests( fontSizes: FontSize[] ) { + it( 'allows custom values by default', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); - it( 'should fallback to font size `slug` for header hint label if `name` is undefined', () => { - const fontSizes = [ - { - slug: 'gigantosaurus', - size: '1000px', - }, - ]; - render( - - ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + await user.type( screen.getByLabelText( 'Custom' ), '80' ); + expect( onChange ).toHaveBeenCalledWith( '80px' ); + } ); - const giganticFontSizeElement = screen.getByLabelText( - 'Size gigantosaurus(px)' - ); - expect( giganticFontSizeElement ).toBeInTheDocument(); + it( 'does not allow custom values when disableCustomFontSizes is set', () => { + render( + + ); + expect( + screen.queryByRole( 'button', { name: 'Set custom size' } ) + ).not.toBeInTheDocument(); + } ); + + it( 'does not display a slider by default', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, } ); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + // TODO: There are two spinbuttons with similar names, so can't + // select the slider's input in an accessible way. + const sliderInput = document.querySelector( + '.components-range-control__slider' + ); + expect( sliderInput ).not.toBeInTheDocument(); } ); - } ); + + it( 'allows a slider to be used when withSlider is set', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + // TODO: There are two spinbuttons with similar names, so can't + // select the slider's input in an accessible way. + const sliderInput = document.querySelector( + '.components-range-control__slider' + ); + // Fake the slider drag using fireEvent. Testing drag-and-drop using + // userEvent is a job for the RangeControl tests. + fireEvent.change( sliderInput!, { + target: { value: 80 }, + } ); + expect( onChange ).toHaveBeenCalledWith( '80px' ); + } ); + + it( 'allows reset by default', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + await user.click( screen.getByRole( 'button', { name: 'Reset' } ) ); + expect( onChange ).toHaveBeenCalledWith( undefined ); + } ); + + it( 'does not allow reset when withReset is false', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + render( + + ); + await user.click( + screen.getByRole( 'button', { name: 'Set custom size' } ) + ); + expect( + screen.queryByRole( 'button', { name: 'Reset' } ) + ).not.toBeInTheDocument(); + } ); + } } ); From b0314d272dab6bbd2b7ca88c914daef608702293 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 28 Oct 2022 15:50:41 +1100 Subject: [PATCH 15/26] Use header label's aria-label --- .../src/font-size-picker/test/index.tsx | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 46bc3fa4c54a27..d6f8cd3f353e75 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -143,12 +143,12 @@ describe( 'FontSizePicker', () => { } ); test.each( [ - { value: undefined, expectedHint: 'Size' }, - { value: '8px', expectedHint: 'Size(8px)' }, - { value: '3px', expectedHint: 'Size(Custom)' }, + { value: undefined, expectedLabel: 'Size' }, + { value: '8px', expectedLabel: 'Size (8px)' }, + { value: '3px', expectedLabel: 'Size (Custom)' }, ] )( - 'displays $expectedHint as header hint when value is $value', - ( { value, expectedHint } ) => { + 'displays $expectedLabel as label when value is $value', + ( { value, expectedLabel } ) => { render( { value={ value } /> ); - // TODO: There's no accessible way to select the label. - const label = document.querySelector( - '.components-base-control__label' - ); - expect( label ).toHaveTextContent( expectedHint ); + expect( + screen.getByLabelText( expectedLabel ) + ).toBeInTheDocument(); } ); @@ -268,12 +266,12 @@ describe( 'FontSizePicker', () => { } ); test.each( [ - { value: undefined, expectedHint: 'Size' }, - { value: '12px', expectedHint: 'small' }, - { value: '40px', expectedHint: 'Massive' }, + { value: undefined, expectedLabel: 'Size Default' }, + { value: '12px', expectedLabel: 'Size small(px)' }, + { value: '40px', expectedLabel: 'Size Massive(px)' }, ] )( - 'displays $hint as header hint when value is $value', - ( { value, expectedHint } ) => { + 'displays $expectedLabel as label when value is $value', + ( { value, expectedLabel } ) => { render( { value={ value } /> ); - // TODO: There's no accessible way to select the label. - const label = document.querySelector( - '.components-base-control__label' - ); - expect( label ).toHaveTextContent( expectedHint ); + expect( + screen.getByLabelText( expectedLabel ) + ).toBeInTheDocument(); } ); From c2b4da6eeb6105f58984ddcc72fde62af3ca9ac3 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 28 Oct 2022 15:51:42 +1100 Subject: [PATCH 16/26] =?UTF-8?q?Put=20back=20the=20Gigantosaurus=20?= =?UTF-8?q?=F0=9F=A6=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/components/src/font-size-picker/test/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index d6f8cd3f353e75..ec1cd0198c639e 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -239,8 +239,8 @@ describe( 'FontSizePicker', () => { size: '30px', }, { - slug: 'massive', - name: 'Massive', + slug: 'gigantosaurus', + name: 'Gigantosaurus', size: '40px', }, ]; @@ -262,13 +262,13 @@ describe( 'FontSizePicker', () => { expect( options[ 3 ] ).toHaveTextContent( 'XL' ); expect( options[ 3 ] ).toHaveAccessibleName( 'Huge' ); expect( options[ 4 ] ).toHaveTextContent( 'XXL' ); - expect( options[ 4 ] ).toHaveAccessibleName( 'Massive' ); + expect( options[ 4 ] ).toHaveAccessibleName( 'Gigantosaurus' ); } ); test.each( [ { value: undefined, expectedLabel: 'Size Default' }, { value: '12px', expectedLabel: 'Size small(px)' }, - { value: '40px', expectedLabel: 'Size Massive(px)' }, + { value: '40px', expectedLabel: 'Size Gigantosaurus(px)' }, ] )( 'displays $expectedLabel as label when value is $value', ( { value, expectedLabel } ) => { From d6c4d6590854c4656c2a1d36b3d8373592c88b9e Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Fri, 28 Oct 2022 16:29:06 +1100 Subject: [PATCH 17/26] Update changelog --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 88f9563f0ec58d..7126588358389a 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -36,6 +36,7 @@ - `Button`: Refactor Storybook to controls and align docs ([#44105](https://github.com/WordPress/gutenberg/pull/44105)). - `TabPanel`: updated to satisfy `react/exhaustive-deps` eslint rule ([#44935](https://github.com/WordPress/gutenberg/pull/44935)) - `UnitControl`: Add tests ([#45260](https://github.com/WordPress/gutenberg/pull/45260)). +- `FontSizePicker`: Add more comprehensive tests ([#45298](https://github.com/WordPress/gutenberg/pull/45298)). ## 21.3.0 (2022-10-19) From a93950724835e76df452911691212429a5e858f6 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 31 Oct 2022 17:04:22 +1100 Subject: [PATCH 18/26] Remove editorialising comment --- packages/components/src/font-size-picker/test/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index ec1cd0198c639e..b70741f79f85e0 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -379,8 +379,6 @@ describe( 'FontSizePicker', () => { const sliderInput = document.querySelector( '.components-range-control__slider' ); - // Fake the slider drag using fireEvent. Testing drag-and-drop using - // userEvent is a job for the RangeControl tests. fireEvent.change( sliderInput!, { target: { value: 80 }, } ); From 22c9ab3d84f11c45177f0c9abdad04975d74812f Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 31 Oct 2022 17:12:31 +1100 Subject: [PATCH 19/26] Use getByLabelText( 'Custom Size' ) to select slider --- .../src/font-size-picker/test/index.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index b70741f79f85e0..876ecbd25b4e2e 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -350,12 +350,9 @@ describe( 'FontSizePicker', () => { await user.click( screen.getByRole( 'button', { name: 'Set custom size' } ) ); - // TODO: There are two spinbuttons with similar names, so can't - // select the slider's input in an accessible way. - const sliderInput = document.querySelector( - '.components-range-control__slider' - ); - expect( sliderInput ).not.toBeInTheDocument(); + expect( + screen.queryByLabelText( 'Custom Size' ) + ).not.toBeInTheDocument(); } ); it( 'allows a slider to be used when withSlider is set', async () => { @@ -374,11 +371,7 @@ describe( 'FontSizePicker', () => { await user.click( screen.getByRole( 'button', { name: 'Set custom size' } ) ); - // TODO: There are two spinbuttons with similar names, so can't - // select the slider's input in an accessible way. - const sliderInput = document.querySelector( - '.components-range-control__slider' - ); + const sliderInput = screen.getByLabelText( 'Custom Size' ); fireEvent.change( sliderInput!, { target: { value: 80 }, } ); From 8a0fc894ae44accac0a3f1457e1a64f7b4cd80d1 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Mon, 31 Oct 2022 17:15:49 +1100 Subject: [PATCH 20/26] Remove redundant test --- .../src/font-size-picker/test/index.tsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 876ecbd25b4e2e..69949c6f2b4424 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -66,24 +66,6 @@ describe( 'FontSizePicker', () => { } ); - it( 'should not use units when the initial value has no unit', async () => { - const user = userEvent.setup( { - advanceTimers: jest.advanceTimersByTime, - } ); - const onChange = jest.fn(); - render( - - ); - const input = screen.getByLabelText( 'Custom' ); - await user.clear( input ); - await user.type( input, '80' ); - expect( onChange ).toHaveBeenCalledWith( 80 ); - } ); - describe( 'with > 5 font sizes', () => { const fontSizes = [ { From 1673ba3254d82fec82096cf757f5dca3894905cf Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 1 Nov 2022 16:08:00 +1100 Subject: [PATCH 21/26] Add tests for non-px font sizes --- .../src/font-size-picker/test/index.tsx | 129 ++++++++++++++++-- 1 file changed, 119 insertions(+), 10 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 69949c6f2b4424..d73f0ca4edd1b6 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -76,17 +76,17 @@ describe( 'FontSizePicker', () => { { slug: 'small', name: 'Small', - size: '12px', + size: '1em', }, { slug: 'medium', name: 'Medium', - size: '16px', + size: '2rem', }, { slug: 'large', name: 'Large', - size: '22px', + size: 'clamp(1.75rem, 3vw, 2.25rem)', }, { slug: 'x-large', @@ -116,9 +116,9 @@ describe( 'FontSizePicker', () => { const options = screen.getAllByRole( 'option' ); expect( options[ 0 ] ).toHaveAccessibleName( 'Default' ); expect( options[ 1 ] ).toHaveAccessibleName( 'Tiny 8' ); - expect( options[ 2 ] ).toHaveAccessibleName( 'Small 12' ); - expect( options[ 3 ] ).toHaveAccessibleName( 'Medium 16' ); - expect( options[ 4 ] ).toHaveAccessibleName( 'Large 22' ); + expect( options[ 2 ] ).toHaveAccessibleName( 'Small 1' ); + expect( options[ 3 ] ).toHaveAccessibleName( 'Medium 2' ); + expect( options[ 4 ] ).toHaveAccessibleName( 'Large' ); expect( options[ 5 ] ).toHaveAccessibleName( 'Extra Large 30' ); expect( options[ 6 ] ).toHaveAccessibleName( 'xx-large 40' ); expect( options[ 7 ] ).toHaveAccessibleName( 'Custom' ); @@ -127,6 +127,9 @@ describe( 'FontSizePicker', () => { test.each( [ { value: undefined, expectedLabel: 'Size' }, { value: '8px', expectedLabel: 'Size (8px)' }, + { value: '1em', expectedLabel: 'Size (1em)' }, + { value: '2rem', expectedLabel: 'Size (2rem)' }, + { value: 'clamp(1.75rem, 3vw, 2.25rem)', expectedLabel: 'Size' }, { value: '3px', expectedLabel: 'Size (Custom)' }, ] )( 'displays $expectedLabel as label when value is $value', @@ -146,7 +149,14 @@ describe( 'FontSizePicker', () => { test.each( [ { option: 'Default', value: '8px', expectedValue: undefined }, - { option: 'Small 12', value: '8px', expectedValue: '12px' }, + { option: 'Tiny 8', value: undefined, expectedValue: '8px' }, + { option: 'Small 1', value: '8px', expectedValue: '1em' }, + { option: 'Medium 2', value: '8px', expectedValue: '2rem' }, + { + option: 'Large', + value: '8px', + expectedValue: 'clamp(1.75rem, 3vw, 2.25rem)', + }, ] )( 'calls onChange( $expectedValue ) when $option is selected', async ( { option, value, expectedValue } ) => { @@ -198,7 +208,7 @@ describe( 'FontSizePicker', () => { commonTests( fontSizes ); } ); - describe( 'with <= 5 font sizes', () => { + describe( 'with ≤ 5 homogeneous font sizes', () => { const fontSizes = [ { slug: 'small', @@ -286,6 +296,105 @@ describe( 'FontSizePicker', () => { commonTests( fontSizes ); } ); + describe( 'with ≤ 5 heterogeneous font sizes', () => { + const fontSizes = [ + { + slug: 'small', + name: 'Small', + size: '12px', + }, + { + slug: 'medium', + name: 'Medium', + size: '1em', + }, + { + slug: 'large', + name: 'Large', + size: '2rem', + }, + { + slug: 'x-large', + name: 'Extra Large', + size: 'clamp(1.75rem, 3vw, 2.25rem)', + }, + ]; + + it( 'displays a toggle group control with t-shirt sizes', () => { + render( + + ); + const options = screen.getAllByRole( 'radio' ); + expect( options[ 0 ] ).toHaveTextContent( 'S' ); + expect( options[ 0 ] ).toHaveAccessibleName( 'Small' ); + expect( options[ 1 ] ).toHaveTextContent( 'M' ); + expect( options[ 1 ] ).toHaveAccessibleName( 'Medium' ); + expect( options[ 2 ] ).toHaveTextContent( 'L' ); + expect( options[ 2 ] ).toHaveAccessibleName( 'Large' ); + expect( options[ 3 ] ).toHaveTextContent( 'XL' ); + expect( options[ 3 ] ).toHaveAccessibleName( 'Extra Large' ); + } ); + + test.each( [ + { value: undefined, expectedLabel: 'Size Default' }, + { value: '12px', expectedLabel: 'Size Small' }, + { value: '1em', expectedLabel: 'Size Medium' }, + { value: '2rem', expectedLabel: 'Size Large' }, + { + value: 'clamp(1.75rem, 3vw, 2.25rem)', + expectedLabel: 'Size Extra Large', + }, + ] )( + 'displays $expectedLabel as label when value is $value', + ( { value, expectedLabel } ) => { + render( + + ); + expect( + screen.getByLabelText( expectedLabel ) + ).toBeInTheDocument(); + } + ); + + test.each( [ + { radio: 'Small', expectedValue: '12px' }, + { radio: 'Medium', expectedValue: '1em' }, + { radio: 'Large', expectedValue: '2rem' }, + { + radio: 'Extra Large', + expectedValue: 'clamp(1.75rem, 3vw, 2.25rem)', + }, + ] )( + 'calls onChange( $expectedValue ) when $radio is selected', + async ( { radio, expectedValue } ) => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + const onChange = jest.fn(); + render( + + ); + await user.click( + screen.getByRole( 'radio', { name: radio } ) + ); + expect( onChange ).toHaveBeenCalledWith( expectedValue ); + } + ); + + commonTests( fontSizes ); + } ); + function commonTests( fontSizes: FontSize[] ) { it( 'allows custom values by default', async () => { const user = userEvent.setup( { @@ -369,7 +478,7 @@ describe( 'FontSizePicker', () => { ); @@ -388,7 +497,7 @@ describe( 'FontSizePicker', () => { ); From ab89dde5acb554a392182411b1cf9b328b707f32 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 1 Nov 2022 16:08:57 +1100 Subject: [PATCH 22/26] Assert length of options/radios --- packages/components/src/font-size-picker/test/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index d73f0ca4edd1b6..3ad794eb7926c5 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -114,6 +114,7 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'button', { name: 'Font size' } ) ); const options = screen.getAllByRole( 'option' ); + expect( options ).toHaveLength( 8 ); expect( options[ 0 ] ).toHaveAccessibleName( 'Default' ); expect( options[ 1 ] ).toHaveAccessibleName( 'Tiny 8' ); expect( options[ 2 ] ).toHaveAccessibleName( 'Small 1' ); @@ -245,6 +246,7 @@ describe( 'FontSizePicker', () => { /> ); const options = screen.getAllByRole( 'radio' ); + expect( options ).toHaveLength( 5 ); expect( options[ 0 ] ).toHaveTextContent( 'S' ); expect( options[ 0 ] ).toHaveAccessibleName( 'S' ); expect( options[ 1 ] ).toHaveTextContent( 'M' ); @@ -328,6 +330,7 @@ describe( 'FontSizePicker', () => { /> ); const options = screen.getAllByRole( 'radio' ); + expect( options ).toHaveLength( 4 ); expect( options[ 0 ] ).toHaveTextContent( 'S' ); expect( options[ 0 ] ).toHaveAccessibleName( 'Small' ); expect( options[ 1 ] ).toHaveTextContent( 'M' ); From 4032f0f522aef77c814b55ed2709f67b38227755 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 1 Nov 2022 16:14:26 +1100 Subject: [PATCH 23/26] Assert number of calls to onChange --- .../components/src/font-size-picker/test/index.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 3ad794eb7926c5..25a21da92527e0 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -33,6 +33,7 @@ describe( 'FontSizePicker', () => { const input = screen.getByLabelText( 'Custom' ); await user.clear( input ); await user.type( input, '80' ); + expect( onChange ).toHaveBeenCalledTimes( 3 ); // Once for the clear, then once per keystroke. expect( onChange ).toHaveBeenCalledWith( expectedValue ); } ); @@ -60,8 +61,8 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'button', { name: 'Set custom size' } ) ); const input = screen.getByLabelText( 'Custom' ); - await user.clear( input ); await user.type( input, '80' ); + expect( onChange ).toHaveBeenCalledTimes( 2 ); // Once per keystroke. expect( onChange ).toHaveBeenCalledWith( expectedValue ); } ); @@ -179,6 +180,7 @@ describe( 'FontSizePicker', () => { await user.click( screen.getByRole( 'option', { name: option } ) ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( expectedValue ); } ); @@ -292,6 +294,7 @@ describe( 'FontSizePicker', () => { /> ); await user.click( screen.getByRole( 'radio', { name: 'Medium' } ) ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( '16px' ); } ); @@ -391,6 +394,7 @@ describe( 'FontSizePicker', () => { await user.click( screen.getByRole( 'radio', { name: radio } ) ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( expectedValue ); } ); @@ -415,6 +419,7 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'button', { name: 'Set custom size' } ) ); await user.type( screen.getByLabelText( 'Custom' ), '80' ); + expect( onChange ).toHaveBeenCalledTimes( 2 ); // Once per keystroke. expect( onChange ).toHaveBeenCalledWith( '80px' ); } ); @@ -466,9 +471,10 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'button', { name: 'Set custom size' } ) ); const sliderInput = screen.getByLabelText( 'Custom Size' ); - fireEvent.change( sliderInput!, { + fireEvent.change( sliderInput, { target: { value: 80 }, } ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( '80px' ); } ); @@ -489,6 +495,7 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'button', { name: 'Set custom size' } ) ); await user.click( screen.getByRole( 'button', { name: 'Reset' } ) ); + expect( onChange ).toHaveBeenCalledTimes( 1 ); expect( onChange ).toHaveBeenCalledWith( undefined ); } ); From 591288b82286e7994a8ac06941cee85f1b9add4f Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 1 Nov 2022 16:24:00 +1100 Subject: [PATCH 24/26] Can un-comment this failing assertion now --- packages/components/src/font-size-picker/test/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 25a21da92527e0..5fdb77d526dd74 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -204,8 +204,7 @@ describe( 'FontSizePicker', () => { screen.getByRole( 'option', { name: 'Custom' } ) ); expect( screen.getByLabelText( 'Custom' ) ).toBeInTheDocument(); - // TODO: onChange() shouldn't be called. - //expect( onChange ).not.toHaveBeenCalled(); + expect( onChange ).not.toHaveBeenCalled(); } ); commonTests( fontSizes ); From 3af78ecdd43e6f956b8f548750d03bbc683ff499 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Tue, 1 Nov 2022 16:24:57 +1100 Subject: [PATCH 25/26] Update CHANGELOG.md --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 596fd112f36759..3a35c09732bba7 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -46,6 +46,7 @@ - `UnitControl`: Add tests ([#45260](https://github.com/WordPress/gutenberg/pull/45260)). - `Disabled`: Refactor the component to rely on the HTML `inert` attribute. - `FontSizePicker`: Add more comprehensive tests ([#45298](https://github.com/WordPress/gutenberg/pull/45298)). +- `FontSizePicker`: Refactor to use components instead of helper functions ([#44891](https://github.com/WordPress/gutenberg/pull/44891)). ## 21.3.0 (2022-10-19) From 639db98a1e4bacd081de03609b57524f9fbb8507 Mon Sep 17 00:00:00 2001 From: Robert Anderson Date: Wed, 2 Nov 2022 13:31:05 +1100 Subject: [PATCH 26/26] Fix regression when value="" --- .../components/src/font-size-picker/index.tsx | 2 +- .../src/font-size-picker/test/index.tsx | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/packages/components/src/font-size-picker/index.tsx b/packages/components/src/font-size-picker/index.tsx index 8c4f1907fa218f..9c4c1cac988850 100644 --- a/packages/components/src/font-size-picker/index.tsx +++ b/packages/components/src/font-size-picker/index.tsx @@ -70,7 +70,7 @@ const UnforwardedFontSizePicker = ( const selectedFontSize = fontSizes.find( ( fontSize ) => fontSize.size === value ); - const isCustomValue = value !== undefined && ! selectedFontSize; + const isCustomValue = !! value && ! selectedFontSize; const [ showCustomValueControl, setShowCustomValueControl ] = useState( ! disableCustomFontSizes && isCustomValue diff --git a/packages/components/src/font-size-picker/test/index.tsx b/packages/components/src/font-size-picker/test/index.tsx index 5fdb77d526dd74..cdcce2db1cc715 100644 --- a/packages/components/src/font-size-picker/test/index.tsx +++ b/packages/components/src/font-size-picker/test/index.tsx @@ -126,6 +126,26 @@ describe( 'FontSizePicker', () => { expect( options[ 7 ] ).toHaveAccessibleName( 'Custom' ); } ); + test.each( [ + { value: undefined, option: 'Default' }, + { value: '', option: 'Default' }, + { value: '8px', option: 'Tiny' }, + ] )( + 'defaults to $option when value is $value', + ( { value, option } ) => { + render( + + ); + expect( + screen.getByRole( 'button', { name: 'Font size' } ) + ).toHaveTextContent( option ); + } + ); + test.each( [ { value: undefined, expectedLabel: 'Size' }, { value: '8px', expectedLabel: 'Size (8px)' }, @@ -297,6 +317,7 @@ describe( 'FontSizePicker', () => { expect( onChange ).toHaveBeenCalledWith( '16px' ); } ); + commonToggleGroupTests( fontSizes ); commonTests( fontSizes ); } ); @@ -398,10 +419,54 @@ describe( 'FontSizePicker', () => { } ); + commonToggleGroupTests( fontSizes ); commonTests( fontSizes ); } ); + function commonToggleGroupTests( fontSizes: FontSize[] ) { + it( 'defaults to M when value is 16px', () => { + render( + + ); + expect( + screen.getByRole( 'radio', { checked: true } ) + ).toHaveTextContent( 'S' ); + } ); + + test.each( [ undefined, '' ] )( + 'has no selection when value is %p', + ( value ) => { + render( + + ); + expect( screen.getByRole( 'radiogroup' ) ).toBeInTheDocument(); + expect( + screen.queryByRole( 'radio', { checked: true } ) + ).not.toBeInTheDocument(); + } + ); + } + function commonTests( fontSizes: FontSize[] ) { + it( 'shows custom input when value is unknown', () => { + render( + + ); + expect( screen.getByLabelText( 'Custom' ) ).toBeInTheDocument(); + } ); + it( 'allows custom values by default', async () => { const user = userEvent.setup( { advanceTimers: jest.advanceTimersByTime,