Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- #### Carousel
- Pause automatic rotation on pointer-initiated focus [#1731](https://github.com/IgniteUI/igniteui-webcomponents/issues/1731)
- Ensure `igcSlideChanged` event is emitted when a slide is changed [#1772](https://github.com/IgniteUI/igniteui-webcomponents/issues/1772)

## [6.1.1] - 2025-06-25
### Fixed
Expand Down
131 changes: 124 additions & 7 deletions src/components/carousel/carousel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
waitUntil,
} from '@open-wc/testing';

import { type SinonFakeTimers, spy, useFakeTimers } from 'sinon';
import { type SinonFakeTimers, spy, stub, useFakeTimers } from 'sinon';
import IgcButtonComponent from '../button/button.js';
import {
arrowLeft,
Expand Down Expand Up @@ -88,12 +88,6 @@ describe('Carousel', () => {
IgcCarouselIndicatorComponent.tagName
)
);

clock = useFakeTimers({ toFake: ['setInterval'] });
});

afterEach(() => {
clock.restore();
});

describe('Initialization', () => {
Expand Down Expand Up @@ -556,6 +550,26 @@ describe('Carousel', () => {
detail: 0,
});
});

it('should properly call `igcSlideChanged` event', async () => {
const eventSpy = spy(carousel, 'emitEvent');

stub(carousel, 'select')
.onFirstCall()
.resolves(true)
.onSecondCall()
.resolves(false);

// select second indicator
simulateClick(defaultIndicators[1]);
await slideChangeComplete(slides[0], slides[1]);

// select second indicator again
simulateClick(defaultIndicators[1]);
await slideChangeComplete(slides[0], slides[1]);

expect(eventSpy.callCount).to.equal(1);
});
});

describe('Keyboard', () => {
Expand Down Expand Up @@ -643,6 +657,12 @@ describe('Carousel', () => {
});

describe('Automatic rotation', () => {
beforeEach(async () => {
clock = useFakeTimers({ toFake: ['setInterval'] });
});

afterEach(() => clock.restore());

it('should automatically change slides', async () => {
expect(carousel.current).to.equal(0);

Expand All @@ -655,6 +675,21 @@ describe('Carousel', () => {
expect(carousel.current).to.equal(1);
});

it('should properly call `igcSlideChanged` event', async () => {
const eventSpy = spy(carousel, 'emitEvent');

carousel.disableLoop = true;
carousel.interval = 100;
await elementUpdated(carousel);

expect(carousel.current).to.equal(0);

await clock.tickAsync(300);

expect(carousel.current).to.equal(2);
expect(eventSpy.callCount).to.equal(2);
});

it('should pause/play on pointerenter/pointerleave', async () => {
const eventSpy = spy(carousel, 'emitEvent');
const divContainer = carousel.shadowRoot?.querySelector(
Expand Down Expand Up @@ -1021,6 +1056,88 @@ describe('Carousel', () => {

expect(carousel.current).to.equal(0);
});

it('should properly call `igcSlideChanged` event', async () => {
carousel = await fixture<IgcCarouselComponent>(
html`<igc-carousel>
<igc-carousel-slide>
<span>1</span>
</igc-carousel-slide>
<igc-carousel-slide>
<span>2</span>
</igc-carousel-slide>
</igc-carousel>`
);

carouselSlidesContainer = carousel.shadowRoot?.querySelector(
'div[aria-live="polite"]'
) as Element;

const eventSpy = spy(carousel, 'emitEvent');

const prevStub = stub(carousel, 'prev');
const nextStub = stub(carousel, 'next');

prevStub.resolves(false);
nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false);

carousel.disableLoop = true;
await elementUpdated(carousel);

expect(carousel.current).to.equal(0);

// swipe right - disabled
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { x: 100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[0], slides[2]);

// swipe left
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { x: -100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[0], slides[1]);

// swipe left - disabled
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { x: -100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[0], slides[1]);

expect(eventSpy.callCount).to.equal(1);

eventSpy.resetHistory();
prevStub.resetHistory();
nextStub.resetHistory();

prevStub.resolves(false);
nextStub.onFirstCall().resolves(true).onSecondCall().resolves(false);

carousel.vertical = true;
await elementUpdated(carousel);

expect(eventSpy.callCount).to.equal(0);

// swipe down - disabled
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { y: 100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[2], slides[0]);

// swipe up
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { y: -100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[2], slides[1]);

// swipe up - disabled
simulatePointerDown(carouselSlidesContainer);
simulatePointerMove(carouselSlidesContainer, {}, { y: -100 }, 10);
simulateLostPointerCapture(carouselSlidesContainer);
await slideChangeComplete(slides[1], slides[0]);

expect(eventSpy.callCount).to.equal(1);
});
});
});
});
26 changes: 16 additions & 10 deletions src/components/carousel/carousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,13 +451,14 @@ export default class IgcCarouselComponent extends EventEmitterMixin<

private handleHorizontalSwipe({ data: { direction } }: SwipeEvent) {
if (!this.vertical) {
this.handleInteraction(async () => {
const callback = () => {
if (isLTR(this)) {
direction === 'left' ? await this.next() : await this.prev();
} else {
direction === 'left' ? await this.prev() : await this.next();
return direction === 'left' ? this.next : this.prev;
}
});
return direction === 'left' ? this.prev : this.next;
};

this.handleInteraction(callback());
}
}

Expand Down Expand Up @@ -485,14 +486,15 @@ export default class IgcCarouselComponent extends EventEmitterMixin<
}

private async handleInteraction(
callback: () => Promise<unknown>
callback: () => Promise<boolean>
): Promise<void> {
if (this.interval) {
this.resetInterval();
}

await callback.call(this);
this.emitEvent('igcSlideChanged', { detail: this.current });
if (await callback.call(this)) {
this.emitEvent('igcSlideChanged', { detail: this.current });
}

if (this.interval) {
this.restartInterval();
Expand Down Expand Up @@ -538,8 +540,12 @@ export default class IgcCarouselComponent extends EventEmitterMixin<

if (asNumber(this.interval) > 0) {
this._lastInterval = setInterval(() => {
if (this.isPlaying && this.total) {
this.next();
if (
this.isPlaying &&
this.total &&
!(this.disableLoop && this.nextIndex === 0)
) {
this.select(this.slides[this.nextIndex], 'next');
this.emitEvent('igcSlideChanged', { detail: this.current });
} else {
this.pause();
Expand Down