Add inspector panel with preset grid and intensity slider
This commit is contained in:
+2
-1
@@ -2,4 +2,5 @@ module.exports = {
|
||||
...require( '@wordpress/scripts/config/jest-unit.config' ),
|
||||
testMatch: [ '**/tests/jest/**/*.test.js' ],
|
||||
setupFiles: [ '<rootDir>/tests/jest/setup.js' ],
|
||||
};
|
||||
setupFilesAfterEnv: [ '<rootDir>/tests/jest/setup-matchers.js' ],
|
||||
};
|
||||
|
||||
Generated
+2254
-79
File diff suppressed because it is too large
Load Diff
+6
-2
@@ -13,6 +13,10 @@
|
||||
"packages-update": "wp-scripts packages-update"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wordpress/scripts": "^27.0.0"
|
||||
"@testing-library/jest-dom": "^7.0.0",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@wordpress/components": "^29.12.0",
|
||||
"@wordpress/scripts": "^27.0.0",
|
||||
"jsdom": "^29.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Inspector panel for the Filtered Image block.
|
||||
*
|
||||
* Renders a swatch grid for the preset list and a RangeControl for intensity.
|
||||
* Both controls are Gutenberg-standard so keyboard navigation and ARIA
|
||||
* behaviour are inherited from the framework.
|
||||
*
|
||||
* @package ImageFilters
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { PanelBody, RangeControl } from '@wordpress/components';
|
||||
import { PRESETS } from './presets';
|
||||
|
||||
const labelFor = ( preset ) => preset.label;
|
||||
|
||||
/**
|
||||
* The filter panel: presets + intensity.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Object} props.attributes Block attributes.
|
||||
* @param {string} props.attributes.filter Current preset slug.
|
||||
* @param {number} props.attributes.intensity Current intensity 0–100.
|
||||
* @param {Function} props.setAttributes Gutenberg setter.
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export function FilterPanel( { attributes, setAttributes } ) {
|
||||
const { filter, intensity } = attributes;
|
||||
|
||||
return (
|
||||
<PanelBody
|
||||
title={ __( 'Filter', 'image-filters' ) }
|
||||
initialOpen={ true }
|
||||
className="ksolo-image-filter-panel"
|
||||
>
|
||||
<div
|
||||
className="ksolo-image-filter-presets"
|
||||
role="group"
|
||||
aria-label={ __( 'Filter presets', 'image-filters' ) }
|
||||
>
|
||||
{ PRESETS.map( ( preset ) => {
|
||||
const isActive = preset.slug === filter;
|
||||
return (
|
||||
<button
|
||||
key={ preset.slug }
|
||||
type="button"
|
||||
className="ksolo-image-filter-preset"
|
||||
aria-pressed={ isActive }
|
||||
aria-label={ labelFor( preset ) }
|
||||
onClick={ () => setAttributes( { filter: preset.slug } ) }
|
||||
>
|
||||
<span
|
||||
className="ksolo-image-filter-preset__swatch"
|
||||
aria-hidden="true"
|
||||
style={ { background: preset.color } }
|
||||
/>
|
||||
<span className="ksolo-image-filter-preset__label">
|
||||
{ preset.label }
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
<RangeControl
|
||||
label={ __( 'Filter intensity', 'image-filters' ) }
|
||||
value={ intensity }
|
||||
min={ 0 }
|
||||
max={ 100 }
|
||||
step={ 1 }
|
||||
__next40pxDefaultSize
|
||||
__nextHasNoMarginBottom
|
||||
onChange={ ( value ) => setAttributes( { intensity: value } ) }
|
||||
help={ __(
|
||||
'Set to 0 to disable the filter, 100 for full strength.',
|
||||
'image-filters'
|
||||
) }
|
||||
/>
|
||||
</PanelBody>
|
||||
);
|
||||
}
|
||||
|
||||
export default FilterPanel;
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import { FilterPanel } from '../../src/inspector';
|
||||
|
||||
jest.mock( '@wordpress/i18n', () => ( {
|
||||
__: ( str ) => str,
|
||||
} ) );
|
||||
|
||||
const baseProps = {
|
||||
attributes: { filter: 'normal', intensity: 100 },
|
||||
setAttributes: jest.fn(),
|
||||
};
|
||||
|
||||
// React tracks input values on controlled components via an internal
|
||||
// getter/setter pair so it can detect genuine value changes. Assigning
|
||||
// `slider.value = '40'` directly bypasses that tracker and React rejects
|
||||
// the subsequent input event as a no-op. Using the native value setter
|
||||
// keeps React's tracker in sync so the onChange handler actually fires.
|
||||
const setNativeValue = ( element, value ) => {
|
||||
const proto = Object.getPrototypeOf( element );
|
||||
const descriptor = Object.getOwnPropertyDescriptor( proto, 'value' );
|
||||
descriptor.set.call( element, value );
|
||||
element.dispatchEvent( new Event( 'input', { bubbles: true } ) );
|
||||
};
|
||||
|
||||
describe( 'FilterPanel', () => {
|
||||
test( 'renders a button for each preset', () => {
|
||||
render( <FilterPanel { ...baseProps } /> );
|
||||
const group = screen.getByRole( 'group', { name: /Filter presets/ } );
|
||||
const buttons = within( group ).getAllByRole( 'button' );
|
||||
// 8 preset buttons.
|
||||
expect( buttons.length ).toBe( 8 );
|
||||
} );
|
||||
|
||||
test( 'marks the active preset with aria-pressed', () => {
|
||||
render( <FilterPanel { ...{ ...baseProps, attributes: { filter: 'dramatic', intensity: 100 } } } /> );
|
||||
const dramatic = screen.getByRole( 'button', { name: /Dramatic/ } );
|
||||
expect( dramatic ).toHaveAttribute( 'aria-pressed', 'true' );
|
||||
} );
|
||||
|
||||
test( 'toggles a preset via setAttributes when clicked', () => {
|
||||
const setAttributes = jest.fn();
|
||||
render( <FilterPanel { ...{ ...baseProps, setAttributes } } /> );
|
||||
const vivid = screen.getByRole( 'button', { name: /Vivid/ } );
|
||||
vivid.click();
|
||||
expect( setAttributes ).toHaveBeenCalledWith( { filter: 'vivid' } );
|
||||
} );
|
||||
|
||||
test( 'renders the intensity slider with the current value', () => {
|
||||
render( <FilterPanel { ...{ ...baseProps, attributes: { filter: 'normal', intensity: 60 } } } /> );
|
||||
const slider = screen.getByRole( 'slider' );
|
||||
expect( slider.value ).toBe( '60' );
|
||||
} );
|
||||
|
||||
test( 'updates intensity via setAttributes', () => {
|
||||
const setAttributes = jest.fn();
|
||||
render( <FilterPanel { ...{ ...baseProps, setAttributes } } /> );
|
||||
const slider = screen.getByRole( 'slider' );
|
||||
setNativeValue( slider, '40' );
|
||||
expect( setAttributes ).toHaveBeenCalledWith( { intensity: 40 } );
|
||||
} );
|
||||
} );
|
||||
@@ -0,0 +1,2 @@
|
||||
// Register @testing-library/jest-dom custom matchers (toHaveAttribute, etc.).
|
||||
import '@testing-library/jest-dom';
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
// Minimal setup. Test files import from '@wordpress/blocks' etc. and
|
||||
// get the standard Jest config from wp-scripts.
|
||||
// get the standard Jest config from wp-scripts.
|
||||
|
||||
Reference in New Issue
Block a user