Skip to content

Commit 1c36959

Browse files
authored
Fix autoPlayLoop (#1239)
* chore: basic fix for autoPlayLoop * chore: implement looping * chore: add navigationDirection to deck state * chore: mention the compromise made * test: add tests for autoPlayLoop * chore: add changeset for the fix
1 parent f3fd6f7 commit 1c36959

File tree

8 files changed

+239
-92
lines changed

8 files changed

+239
-92
lines changed

.changeset/gentle-berries-judge.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'spectacle': patch
3+
---
4+
5+
autoPlayLoop now works as intended

packages/spectacle/src/components/appear.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ type SteppedComponentProps = {
6060
children: ReactNode | ((step: number, isActive: boolean) => ReactNode);
6161
className?: string;
6262
tagName?: keyof JSX.IntrinsicElements;
63+
// TODO v10: change this type to React.CSSProperties
6364
activeStyle?: Partial<CSSStyleDeclaration>;
65+
// TODO v10: change this type to React.CSSProperties
6466
inactiveStyle?: Partial<CSSStyleDeclaration>;
6567
numSteps?: number;
6668
alwaysAppearActive?: boolean;
@@ -117,6 +119,8 @@ type StepperProps<T extends unknown[] = unknown[]> = {
117119
tagName?: keyof JSX.IntrinsicElements;
118120
values: T;
119121
alwaysVisible?: boolean;
122+
// TODO v10: change this type to React.CSSProperties
120123
activeStyle?: Partial<CSSStyleDeclaration>;
124+
// TODO v10: change this type to React.CSSProperties
121125
inactiveStyle?: Partial<CSSStyleDeclaration>;
122126
};

packages/spectacle/src/components/deck/deck.test.tsx

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import Deck from './index';
22
import Slide from '../slide/slide';
3-
import { render } from '@testing-library/react';
3+
import { act, render } from '@testing-library/react';
44
import { CSSObject } from 'styled-components';
5+
import { Stepper } from '../appear';
56

67
describe('<Deck />', () => {
8+
beforeEach(() => {
9+
jest.useFakeTimers();
10+
});
11+
12+
afterEach(() => {
13+
jest.useRealTimers();
14+
});
15+
716
it('should allow for backdrop color overrides from theme prop', () => {
817
const deckWithStyle = (
918
backdropStyle: CSSObject,
@@ -34,4 +43,106 @@ describe('<Deck />', () => {
3443
backgroundColor: 'black'
3544
});
3645
});
46+
47+
it('should automatically advance with autoPlay=true', () => {
48+
const slideNumberTracker = jest.fn();
49+
const stepperActiveTracker = jest.fn();
50+
const autoPlayInterval = 5000;
51+
52+
const deckWithExtraProps = (
53+
props: Partial<React.ComponentProps<typeof Deck>>
54+
) => (
55+
<Deck
56+
autoPlay
57+
autoPlayInterval={autoPlayInterval}
58+
{...props}
59+
template={({ slideNumber }) => {
60+
slideNumberTracker(slideNumber);
61+
return null;
62+
}}
63+
>
64+
<Slide>Slide 1</Slide>
65+
<Slide>Slide 2</Slide>
66+
<Slide>
67+
Slide 3
68+
<Stepper values={[1]}>
69+
{(_value, _step, isActive) => {
70+
stepperActiveTracker(isActive);
71+
return null;
72+
}}
73+
</Stepper>
74+
</Slide>
75+
</Deck>
76+
);
77+
78+
const { rerender } = render(deckWithExtraProps({ autoPlayLoop: false }));
79+
80+
expect(slideNumberTracker).toHaveBeenLastCalledWith(1);
81+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(false);
82+
83+
act(() => {
84+
jest.advanceTimersByTime(autoPlayInterval);
85+
});
86+
87+
// autoPlay advanced to the next slide
88+
expect(slideNumberTracker).toHaveBeenLastCalledWith(2);
89+
90+
act(() => {
91+
jest.advanceTimersByTime(autoPlayInterval);
92+
});
93+
94+
// autoPlay advanced to the final slide
95+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(false);
96+
expect(slideNumberTracker).toHaveBeenLastCalledWith(3);
97+
98+
act(() => {
99+
jest.advanceTimersByTime(autoPlayInterval);
100+
});
101+
102+
// autoPlay activated the stepper on the third slide
103+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(true);
104+
expect(slideNumberTracker).toHaveBeenLastCalledWith(3);
105+
106+
act(() => {
107+
jest.advanceTimersByTime(autoPlayInterval);
108+
});
109+
110+
// autoPlay stalled at the end as there are no more steps
111+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(true);
112+
expect(slideNumberTracker).toHaveBeenLastCalledWith(3);
113+
114+
// Rerender with looping activated
115+
rerender(deckWithExtraProps({ autoPlayLoop: true }));
116+
117+
act(() => {
118+
jest.advanceTimersByTime(autoPlayInterval);
119+
});
120+
121+
// autoPlay looped around to the first slide
122+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(false);
123+
expect(slideNumberTracker).toHaveBeenLastCalledWith(1);
124+
125+
// Skipping ahead to the last step on the last slide
126+
act(() => {
127+
jest.advanceTimersByTime(autoPlayInterval);
128+
});
129+
act(() => {
130+
jest.advanceTimersByTime(autoPlayInterval);
131+
});
132+
act(() => {
133+
jest.advanceTimersByTime(autoPlayInterval);
134+
});
135+
136+
// autoPlay brought us to the third slide with the stepper activated
137+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(true);
138+
expect(slideNumberTracker).toHaveBeenLastCalledWith(3);
139+
140+
act(() => {
141+
jest.advanceTimersByTime(autoPlayInterval);
142+
});
143+
144+
// autoPlay looped around to the first slide
145+
expect(stepperActiveTracker).toHaveBeenLastCalledWith(false);
146+
expect(slideNumberTracker).toHaveBeenLastCalledWith(1);
147+
});
37148
});

packages/spectacle/src/components/deck/deck.tsx

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ import { KEYBOARD_SHORTCUTS_IDS } from '../../utils/constants';
4444
export type DeckContextType = {
4545
deckId: string | number;
4646
slideCount: number;
47+
slideIds: SlideId[];
4748
useAnimations: boolean;
49+
autoPlayLoop: boolean;
50+
navigationDirection: number;
4851
slidePortalNode: HTMLDivElement;
4952
onSlideClick(e: MouseEvent, slideId: SlideId): void;
5053
onMobileSlide(eventData: SwipeEventData): void;
@@ -54,8 +57,6 @@ export type DeckContextType = {
5457
backdropNode: HTMLDivElement;
5558
notePortalNode: HTMLDivElement;
5659
initialized: boolean;
57-
passedSlideIds: Set<SlideId>;
58-
upcomingSlideIds: Set<SlideId>;
5960
activeView: {
6061
slideId: SlideId;
6162
slideIndex: number;
@@ -173,6 +174,7 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
173174
initialized,
174175
pendingView,
175176
activeView,
177+
navigationDirection,
176178

177179
initializeTo,
178180
skipTo,
@@ -290,11 +292,7 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
290292
enabled: autoPlay,
291293
loop: autoPlayLoop,
292294
interval: autoPlayInterval,
293-
navigation: {
294-
skipTo,
295-
stepForward,
296-
isFinalSlide: activeView.slideIndex === slideIds.length - 1
297-
}
295+
stepForward
298296
});
299297

300298
const handleSlideClick = useCallback<
@@ -310,22 +308,6 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
310308
const activeSlideId = slideIds[activeView.slideIndex];
311309
const pendingSlideId = slideIds[pendingView.slideIndex];
312310

313-
const [passed, upcoming] = useMemo(() => {
314-
const p = new Set<SlideId>();
315-
const u = new Set<SlideId>();
316-
let foundActive = false;
317-
for (const slideId of slideIds) {
318-
if (foundActive) {
319-
u.add(slideId);
320-
} else if (slideId === activeSlideId) {
321-
foundActive = true;
322-
} else {
323-
p.add(slideId);
324-
}
325-
}
326-
return [p, u] as const;
327-
}, [slideIds, activeSlideId]);
328-
329311
const fullyInitialized = initialized && slideIdsInitialized;
330312

331313
// Slides don't actually render their content to their position in the DOM-
@@ -446,20 +428,21 @@ export const DeckInternal = forwardRef<DeckRef, DeckInternalProps>(
446428
value={{
447429
deckId,
448430
slideCount: slideIds.length,
431+
slideIds,
449432
useAnimations,
450433
slidePortalNode: slidePortalNode!,
451434
onSlideClick: handleSlideClick,
452435
onMobileSlide: onMobileSlide,
453436
theme: restTheme,
437+
autoPlayLoop,
438+
navigationDirection,
454439

455440
frameOverrideStyle: frameStyle,
456441
wrapperOverrideStyle: wrapperStyle,
457442

458443
backdropNode: backdropRef.current!,
459444
notePortalNode: notePortalNode!,
460445
initialized: fullyInitialized,
461-
passedSlideIds: passed,
462-
upcomingSlideIds: upcoming,
463446
activeView: {
464447
...activeView,
465448
slideId: activeSlideId
@@ -540,7 +523,10 @@ type BackdropOverrides = {
540523

541524
export type DeckRef = Omit<
542525
DeckStateAndActions,
543-
'pendingView' | 'commitTransition' | 'cancelTransition'
526+
| 'cancelTransition'
527+
| 'commitTransition'
528+
| 'navigationDirection'
529+
| 'pendingView'
544530
> & {
545531
numberOfSlides: number;
546532
};

0 commit comments

Comments
 (0)