返回 reveal.js
slide.test.tsx
根目录 / react / src / components / slide.test.tsx
1 import { render } from '@testing-library/react';
2 import { describe, it, expect, vi } from 'vitest';
3 import { Slide } from './slide';
4 import { RevealContext } from '../reveal-context';
5
6 describe('Slide', () => {
7 it('renders as a <section> element', () => {
8 const { container } = render(<Slide>Hello</Slide>);
9 const section = container.querySelector('section');
10 expect(section).toBeInTheDocument();
11 expect(section).toHaveTextContent('Hello');
12 });
13
14 it('passes data-* attributes through', () => {
15 const { container } = render(
16 <Slide data-background="#ff0000" data-transition="zoom" data-auto-animate="">
17 Content
18 </Slide>
19 );
20
21 const section = container.querySelector('section');
22 expect(section).toHaveAttribute('data-background', '#ff0000');
23 expect(section).toHaveAttribute('data-transition', 'zoom');
24 expect(section).toHaveAttribute('data-auto-animate', '');
25 });
26
27 it('maps background shorthand props to slide data attributes', () => {
28 const { container } = render(
29 <Slide
30 background="#111827"
31 backgroundImage="https://example.com/hero.png"
32 backgroundColor="#000000"
33 backgroundOpacity={0.4}
34 backgroundTransition="zoom"
35 backgroundVideoLoop
36 >
37 Content
38 </Slide>
39 );
40
41 const section = container.querySelector('section');
42 expect(section).toHaveAttribute('data-background', '#111827');
43 expect(section).toHaveAttribute('data-background-image', 'https://example.com/hero.png');
44 expect(section).toHaveAttribute('data-background-color', '#000000');
45 expect(section).toHaveAttribute('data-background-opacity', '0.4');
46 expect(section).toHaveAttribute('data-background-transition', 'zoom');
47 expect(section).toHaveAttribute('data-background-video-loop', '');
48 });
49
50 it('maps visibility and auto-animate shorthand props to slide data attributes', () => {
51 const { container } = render(
52 <Slide
53 visibility="hidden"
54 autoAnimate
55 autoAnimateId="hero"
56 autoAnimateRestart
57 autoAnimateUnmatched={false}
58 autoAnimateEasing="ease-out"
59 autoAnimateDuration={0.6}
60 autoAnimateDelay={0.1}
61 >
62 Content
63 </Slide>
64 );
65
66 const section = container.querySelector('section');
67 expect(section).toHaveAttribute('data-visibility', 'hidden');
68 expect(section).toHaveAttribute('data-auto-animate', '');
69 expect(section).toHaveAttribute('data-auto-animate-id', 'hero');
70 expect(section).toHaveAttribute('data-auto-animate-restart', '');
71 expect(section).toHaveAttribute('data-auto-animate-unmatched', 'false');
72 expect(section).toHaveAttribute('data-auto-animate-easing', 'ease-out');
73 expect(section).toHaveAttribute('data-auto-animate-duration', '0.6');
74 expect(section).toHaveAttribute('data-auto-animate-delay', '0.1');
75 });
76
77 it('maps common Reveal slide shorthand props to data attributes', () => {
78 const { container } = render(
79 <Slide
80 transition="zoom-in fade-out"
81 transitionSpeed="fast"
82 autoSlide={1500}
83 notes="Speaker notes"
84 backgroundInteractive
85 preload
86 >
87 Content
88 </Slide>
89 );
90
91 const section = container.querySelector('section');
92 expect(section).toHaveAttribute('data-transition', 'zoom-in fade-out');
93 expect(section).toHaveAttribute('data-transition-speed', 'fast');
94 expect(section).toHaveAttribute('data-autoslide', '1500');
95 expect(section).toHaveAttribute('data-notes', 'Speaker notes');
96 expect(section).toHaveAttribute('data-background-interactive', '');
97 expect(section).toHaveAttribute('data-preload', '');
98 });
99
100 it('prefers explicit data-* attributes over shorthand props', () => {
101 const { container } = render(
102 <Slide
103 background="#111827"
104 backgroundColor="#000000"
105 autoAnimate
106 visibility="hidden"
107 transition="zoom"
108 notes="Shorthand notes"
109 preload
110 data-background="#222222"
111 data-background-color="#333333"
112 data-auto-animate=""
113 data-visibility="uncounted"
114 data-transition="fade"
115 data-notes="Raw notes"
116 data-preload=""
117 >
118 Content
119 </Slide>
120 );
121
122 const section = container.querySelector('section');
123 expect(section).toHaveAttribute('data-background', '#222222');
124 expect(section).toHaveAttribute('data-background-color', '#333333');
125 expect(section).toHaveAttribute('data-auto-animate', '');
126 expect(section).toHaveAttribute('data-visibility', 'uncounted');
127 expect(section).toHaveAttribute('data-transition', 'fade');
128 expect(section).toHaveAttribute('data-notes', 'Raw notes');
129 expect(section).toHaveAttribute('data-preload', '');
130 });
131
132 it('applies className and style', () => {
133 const { container } = render(
134 <Slide className="intro" style={{ fontSize: '20px' }}>
135 Styled
136 </Slide>
137 );
138
139 const section = container.querySelector('section');
140 expect(section).toHaveClass('intro');
141 expect(section).toHaveStyle({ fontSize: '20px' });
142 });
143
144 it('passes id and aria attributes', () => {
145 const { container } = render(
146 <Slide id="slide-1" aria-label="Introduction">
147 Accessible
148 </Slide>
149 );
150
151 const section = container.querySelector('section');
152 expect(section).toHaveAttribute('id', 'slide-1');
153 expect(section).toHaveAttribute('aria-label', 'Introduction');
154 });
155
156 it('renders children of any type', () => {
157 const { container } = render(
158 <Slide>
159 <h1>Title</h1>
160 <p>Paragraph</p>
161 </Slide>
162 );
163
164 const section = container.querySelector('section');
165 expect(section?.querySelector('h1')).toHaveTextContent('Title');
166 expect(section?.querySelector('p')).toHaveTextContent('Paragraph');
167 });
168
169 it('calls syncSlide when data-* attributes change after mount', () => {
170 const deck = {
171 syncSlide: vi.fn(),
172 } as any;
173
174 const { container, rerender } = render(
175 <RevealContext.Provider value={deck}>
176 <Slide data-background-color="#111">Content</Slide>
177 </RevealContext.Provider>
178 );
179
180 const section = container.querySelector('section');
181 expect(section).toBeInTheDocument();
182 expect(deck.syncSlide).not.toHaveBeenCalled();
183 deck.syncSlide.mockClear();
184
185 rerender(
186 <RevealContext.Provider value={deck}>
187 <Slide data-background-color="#222">Content</Slide>
188 </RevealContext.Provider>
189 );
190
191 expect(deck.syncSlide).toHaveBeenCalledTimes(1);
192 expect(deck.syncSlide).toHaveBeenCalledWith(container.querySelector('section'));
193 });
194
195 it('calls syncSlide when shorthand background props change after mount', () => {
196 const deck = {
197 syncSlide: vi.fn(),
198 } as any;
199
200 const { container, rerender } = render(
201 <RevealContext.Provider value={deck}>
202 <Slide backgroundColor="#111">Content</Slide>
203 </RevealContext.Provider>
204 );
205
206 expect(deck.syncSlide).not.toHaveBeenCalled();
207 deck.syncSlide.mockClear();
208
209 rerender(
210 <RevealContext.Provider value={deck}>
211 <Slide backgroundColor="#222">Content</Slide>
212 </RevealContext.Provider>
213 );
214
215 expect(deck.syncSlide).toHaveBeenCalledTimes(1);
216 expect(deck.syncSlide).toHaveBeenCalledWith(container.querySelector('section'));
217 });
218
219 it('calls syncSlide when shorthand Reveal slide props change after mount', () => {
220 const deck = {
221 syncSlide: vi.fn(),
222 } as any;
223
224 const { container, rerender } = render(
225 <RevealContext.Provider value={deck}>
226 <Slide autoSlide={1000}>Content</Slide>
227 </RevealContext.Provider>
228 );
229
230 expect(deck.syncSlide).not.toHaveBeenCalled();
231 deck.syncSlide.mockClear();
232
233 rerender(
234 <RevealContext.Provider value={deck}>
235 <Slide autoSlide={2000}>Content</Slide>
236 </RevealContext.Provider>
237 );
238
239 expect(deck.syncSlide).toHaveBeenCalledTimes(1);
240 expect(deck.syncSlide).toHaveBeenCalledWith(container.querySelector('section'));
241 });
242
243 it('does not call syncSlide on first render even when data-* attributes are present', () => {
244 const deck = {
245 syncSlide: vi.fn(),
246 } as any;
247
248 render(
249 <RevealContext.Provider value={deck}>
250 <Slide data-background="#111">Content</Slide>
251 </RevealContext.Provider>
252 );
253
254 expect(deck.syncSlide).not.toHaveBeenCalled();
255 });
256
257 it('does not call syncSlide for non-data prop updates', () => {
258 const deck = {
259 syncSlide: vi.fn(),
260 } as any;
261
262 const { rerender } = render(
263 <RevealContext.Provider value={deck}>
264 <Slide data-background-color="#111" className="a">
265 Content
266 </Slide>
267 </RevealContext.Provider>
268 );
269
270 deck.syncSlide.mockClear();
271
272 rerender(
273 <RevealContext.Provider value={deck}>
274 <Slide data-background-color="#111" className="b">
275 Content
276 </Slide>
277 </RevealContext.Provider>
278 );
279
280 expect(deck.syncSlide).not.toHaveBeenCalled();
281 });
282
283 it('does not call syncSlide on mount when no data-* attributes are present', () => {
284 const deck = {
285 syncSlide: vi.fn(),
286 } as any;
287
288 render(
289 <RevealContext.Provider value={deck}>
290 <Slide className="plain">Content</Slide>
291 </RevealContext.Provider>
292 );
293
294 expect(deck.syncSlide).not.toHaveBeenCalled();
295 });
296 });
297
297 lines Plain Text