Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The following properties are exposed in the Interactivity API context:

| Property | Type | Description |
| :----------------- | :--------- | :--------------------------------------------------------------------------------------------------- |
| `transition` | `'slide'` or `'fade'` | The active transition style; determines whether the Fade plugin is loaded. |
| `isPlaying` | `boolean` | `true` if Autoplay is currently running. |
| `timerIterationId` | `number` | Increments every time the Autoplay timer resets (slide change). Bind to `key` to restart animations. |
| `autoplay` | `boolean` or `{ delay, ... }` | Autoplay config or `false` if disabled. |
Expand Down
1 change: 1 addition & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The parent block acts as the controller and wrapper. It handles configuration, s

| Attribute | Type | Default | Description |
| :-------------------------- | :------ | :------------ | :------------------------------------------ |
| `transition` | string | `'slide'` | Transition style: `'slide'` or `'fade'`. When `'fade'`, `carouselAlign`, `containScroll`, `dragFree`, and `slidesToScroll` are overridden at runtime (center/false/false/1). |
| `loop` | boolean | `false` | Infinite scrolling. |
| `dragFree` | boolean | `false` | Momentum scrolling. |
| `carouselAlign` | string | `'start'` | Slide alignment (`start`, `center`, `end`). |
Expand Down
12 changes: 11 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@wordpress/interactivity": "6.37.0",
"embla-carousel": "8.6.0",
"embla-carousel-autoplay": "8.6.0",
"embla-carousel-fade": "8.6.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
Expand Down
69 changes: 69 additions & 0 deletions src/blocks/carousel/__tests__/edit.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import Edit from '../edit';
import type { CarouselAttributes } from '../types';
import type { ReactNode as MockReactNode } from 'react';
import { SelectControl, ToggleControl, RangeControl } from '@wordpress/components';

let mockBlockCount = 0;

Expand Down Expand Up @@ -142,6 +143,7 @@ jest.mock( '../components/TemplatePicker', () => ( {
} ) );

const createAttributes = (): CarouselAttributes => ( {
transition: 'slide',
loop: false,
dragFree: false,
carouselAlign: 'start',
Expand Down Expand Up @@ -217,4 +219,71 @@ describe( 'Carousel Edit setup flow', () => {
Object.defineProperty( globalThis, 'document', originalDocumentDescriptor );
}
} );

it( 'renders a Transition select and hides slide-only controls when fade is active', () => {
render(
<Edit
attributes={ { ...createAttributes(), transition: 'fade' } }
setAttributes={ jest.fn() }
clientId="client-fade"
/>,
);

const selectLabels = ( SelectControl as unknown as jest.Mock ).mock.calls.map(
( [ props ] ) => props.label,
);
const toggleLabels = ( ToggleControl as unknown as jest.Mock ).mock.calls.map(
( [ props ] ) => props.label,
);

expect( selectLabels ).toContain( 'Transition' );
expect( selectLabels ).not.toContain( 'Alignment' );
expect( selectLabels ).not.toContain( 'Contain Scroll' );
expect( toggleLabels ).not.toContain( 'Free Drag' );
expect( toggleLabels ).not.toContain( 'Scroll Auto' );
expect( ( RangeControl as unknown as jest.Mock ).mock.calls.some(
( [ props ] ) => props.label === 'Slides to Scroll',
) ).toBe( false );
} );

it( 'keeps slide-only controls visible when slide transition is active', () => {
render(
<Edit
attributes={ createAttributes() }
setAttributes={ jest.fn() }
clientId="client-slide"
/>,
);

const selectLabels = ( SelectControl as unknown as jest.Mock ).mock.calls.map(
( [ props ] ) => props.label,
);
const toggleLabels = ( ToggleControl as unknown as jest.Mock ).mock.calls.map(
( [ props ] ) => props.label,
);

expect( selectLabels ).toContain( 'Alignment' );
expect( selectLabels ).toContain( 'Contain Scroll' );
expect( toggleLabels ).toContain( 'Free Drag' );
expect( toggleLabels ).toContain( 'Scroll Auto' );
} );

it( 'calls setAttributes with the selected transition', () => {
const setAttributes = jest.fn();
render(
<Edit
attributes={ createAttributes() }
setAttributes={ setAttributes }
clientId="client-transition-change"
/>,
);

const transitionCall = ( SelectControl as unknown as jest.Mock ).mock.calls.find(
( [ props ] ) => props.label === 'Transition',
);

transitionCall[ 0 ].onChange( 'fade' );

expect( setAttributes ).toHaveBeenCalledWith( { transition: 'fade' } );
} );
} );
29 changes: 29 additions & 0 deletions src/blocks/carousel/__tests__/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe( 'CarouselAttributes Type', () => {
describe( 'Structure Validation', () => {
it( 'should accept valid complete attributes', () => {
const attributes: CarouselAttributes = {
transition: 'fade',
loop: true,
dragFree: false,
carouselAlign: 'center',
Expand All @@ -43,6 +44,7 @@ describe( 'CarouselAttributes Type', () => {

it( 'should have all required properties', () => {
const attributes: CarouselAttributes = {
transition: 'slide',
loop: false,
dragFree: true,
carouselAlign: 'start',
Expand All @@ -62,6 +64,7 @@ describe( 'CarouselAttributes Type', () => {

// Verify all keys exist
const requiredKeys = [
'transition',
'loop',
'dragFree',
'carouselAlign',
Expand All @@ -85,6 +88,16 @@ describe( 'CarouselAttributes Type', () => {
} );
} );

describe( 'Transition Options', () => {
it.each( [ 'slide', 'fade' ] as const )(
'should accept transition value: %s',
( transition ) => {
const attributes: Partial< CarouselAttributes > = { transition };
expect( attributes.transition ).toBe( transition );
},
);
} );

describe( 'Alignment Options', () => {
it.each( [ 'start', 'center', 'end' ] as const )(
'should accept carouselAlign value: %s',
Expand Down Expand Up @@ -240,6 +253,7 @@ describe( 'CarouselContext Type', () => {
describe( 'Autoplay State', () => {
it( 'should accept context with autoplay disabled', () => {
const context: CarouselContext = {
transition: 'slide',
options: {
loop: false,
align: 'start',
Expand All @@ -262,6 +276,7 @@ describe( 'CarouselContext Type', () => {

it( 'should accept context with autoplay configuration object', () => {
const context: CarouselContext = {
transition: 'slide',
options: {
loop: true,
align: 'center',
Expand Down Expand Up @@ -296,6 +311,7 @@ describe( 'CarouselContext Type', () => {
describe( 'Scroll State Management', () => {
it( 'should track first slide state correctly', () => {
const context: CarouselContext = {
transition: 'slide',
options: { loop: false },
autoplay: false,
isPlaying: false,
Expand All @@ -316,6 +332,7 @@ describe( 'CarouselContext Type', () => {

it( 'should track middle slide state correctly', () => {
const context: CarouselContext = {
transition: 'slide',
options: { loop: false },
autoplay: false,
isPlaying: false,
Expand All @@ -336,6 +353,7 @@ describe( 'CarouselContext Type', () => {

it( 'should track last slide state correctly', () => {
const context: CarouselContext = {
transition: 'slide',
options: { loop: false },
autoplay: false,
isPlaying: false,
Expand All @@ -356,6 +374,7 @@ describe( 'CarouselContext Type', () => {

it( 'should handle single slide carousel', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -378,6 +397,7 @@ describe( 'CarouselContext Type', () => {
describe( 'Timer and Animation State', () => {
it( 'should track timerIterationId for animation resets', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: { delay: 3000, stopOnInteraction: true, stopOnMouseEnter: false },
isPlaying: true,
Expand All @@ -400,6 +420,7 @@ describe( 'CarouselContext Type', () => {

it( 'should have initial timerIterationId of 0', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -422,6 +443,7 @@ describe( 'CarouselContext Type', () => {
const element = document.createElement( 'div' );

const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -442,6 +464,7 @@ describe( 'CarouselContext Type', () => {

it( 'should allow null ref', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -461,6 +484,7 @@ describe( 'CarouselContext Type', () => {

it( 'should work without ref property', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -481,6 +505,7 @@ describe( 'CarouselContext Type', () => {
describe( 'Embla Options Integration', () => {
it( 'should accept slidesToScroll as number in options', () => {
const context: CarouselContext = {
transition: 'slide',
options: {
loop: true,
slidesToScroll: 2,
Expand All @@ -502,6 +527,7 @@ describe( 'CarouselContext Type', () => {

it( 'should accept slidesToScroll as "auto" in options', () => {
const context: CarouselContext = {
transition: 'slide',
options: {
loop: true,
slidesToScroll: 'auto',
Expand Down Expand Up @@ -533,6 +559,7 @@ describe( 'CarouselContext Type', () => {

patterns.forEach( ( pattern ) => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -554,6 +581,7 @@ describe( 'CarouselContext Type', () => {
describe( 'Scroll Snaps Array', () => {
it( 'should accept empty scrollSnaps array', () => {
const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand All @@ -580,6 +608,7 @@ describe( 'CarouselContext Type', () => {
];

const context: CarouselContext = {
transition: 'slide',
options: {},
autoplay: false,
isPlaying: false,
Expand Down
Loading