@react-chess-tools/react-chess-clock 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +697 -0
- package/dist/index.cjs +1014 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +528 -0
- package/dist/index.d.ts +528 -0
- package/dist/index.js +969 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/components/ChessClock/ChessClock.stories.tsx +782 -0
- package/src/components/ChessClock/index.ts +44 -0
- package/src/components/ChessClock/parts/Display.tsx +69 -0
- package/src/components/ChessClock/parts/PlayPause.tsx +190 -0
- package/src/components/ChessClock/parts/Reset.tsx +90 -0
- package/src/components/ChessClock/parts/Root.tsx +37 -0
- package/src/components/ChessClock/parts/Switch.tsx +84 -0
- package/src/components/ChessClock/parts/__tests__/Display.test.tsx +149 -0
- package/src/components/ChessClock/parts/__tests__/PlayPause.test.tsx +411 -0
- package/src/components/ChessClock/parts/__tests__/Reset.test.tsx +160 -0
- package/src/components/ChessClock/parts/__tests__/Root.test.tsx +49 -0
- package/src/components/ChessClock/parts/__tests__/Switch.test.tsx +204 -0
- package/src/hooks/__tests__/clockReducer.test.ts +985 -0
- package/src/hooks/__tests__/useChessClock.test.tsx +1080 -0
- package/src/hooks/clockReducer.ts +379 -0
- package/src/hooks/useChessClock.ts +406 -0
- package/src/hooks/useChessClockContext.ts +35 -0
- package/src/index.ts +65 -0
- package/src/types.ts +217 -0
- package/src/utils/__tests__/calculateSwitchTime.test.ts +150 -0
- package/src/utils/__tests__/formatTime.test.ts +83 -0
- package/src/utils/__tests__/timeControl.test.ts +414 -0
- package/src/utils/__tests__/timingMethods.test.ts +170 -0
- package/src/utils/calculateSwitchTime.ts +37 -0
- package/src/utils/formatTime.ts +59 -0
- package/src/utils/presets.ts +47 -0
- package/src/utils/timeControl.ts +205 -0
- package/src/utils/timingMethods.ts +103 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
|
+
import { ChessClock } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("ChessClock.PlayPause", () => {
|
|
6
|
+
it("should render children by default", () => {
|
|
7
|
+
render(
|
|
8
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
9
|
+
<ChessClock.PlayPause>Toggle</ChessClock.PlayPause>
|
|
10
|
+
</ChessClock.Root>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(screen.getByRole("button")).toHaveTextContent("Toggle");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe("default content", () => {
|
|
17
|
+
it("should use default content when no props provided", () => {
|
|
18
|
+
render(
|
|
19
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
20
|
+
<ChessClock.PlayPause />
|
|
21
|
+
</ChessClock.Root>,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// Default for idle/start is "Start"
|
|
25
|
+
expect(screen.getByRole("button")).toHaveTextContent("Start");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should use default pauseContent when running", () => {
|
|
29
|
+
render(
|
|
30
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
31
|
+
<ChessClock.PlayPause />
|
|
32
|
+
</ChessClock.Root>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Default for running is "Pause"
|
|
36
|
+
expect(screen.getByRole("button")).toHaveTextContent("Pause");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("should use default resumeContent when paused", () => {
|
|
40
|
+
render(
|
|
41
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
42
|
+
<ChessClock.PlayPause />
|
|
43
|
+
</ChessClock.Root>,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const button = screen.getByRole("button");
|
|
47
|
+
|
|
48
|
+
// Initially shows "Pause" (running)
|
|
49
|
+
expect(button).toHaveTextContent("Pause");
|
|
50
|
+
|
|
51
|
+
// Click to pause
|
|
52
|
+
fireEvent.click(button);
|
|
53
|
+
|
|
54
|
+
// Now shows "Resume" (default)
|
|
55
|
+
expect(button).toHaveTextContent("Resume");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should use default content for delayed state", () => {
|
|
59
|
+
render(
|
|
60
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
61
|
+
<ChessClock.PlayPause />
|
|
62
|
+
</ChessClock.Root>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Default for delayed is "Start"
|
|
66
|
+
expect(screen.getByRole("button")).toHaveTextContent("Start");
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("partial custom content with defaults", () => {
|
|
71
|
+
it("should use custom pauseContent and defaults for other states", () => {
|
|
72
|
+
render(
|
|
73
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
74
|
+
<ChessClock.PlayPause pauseContent="⏸️ Stop" />
|
|
75
|
+
</ChessClock.Root>,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const button = screen.getByRole("button");
|
|
79
|
+
|
|
80
|
+
// Shows custom pauseContent
|
|
81
|
+
expect(button).toHaveTextContent("⏸️ Stop");
|
|
82
|
+
|
|
83
|
+
fireEvent.click(button);
|
|
84
|
+
|
|
85
|
+
// Shows default resumeContent
|
|
86
|
+
expect(button).toHaveTextContent("Resume");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should use custom startContent and defaults for other states", () => {
|
|
90
|
+
render(
|
|
91
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
92
|
+
<ChessClock.PlayPause startContent="▶️ Go!" />
|
|
93
|
+
</ChessClock.Root>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const button = screen.getByRole("button");
|
|
97
|
+
|
|
98
|
+
// Shows custom startContent
|
|
99
|
+
expect(button).toHaveTextContent("▶️ Go!");
|
|
100
|
+
|
|
101
|
+
fireEvent.click(button);
|
|
102
|
+
|
|
103
|
+
// Shows default pauseContent
|
|
104
|
+
expect(button).toHaveTextContent("Pause");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should use custom finishedContent and defaults for other states", () => {
|
|
108
|
+
render(
|
|
109
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
110
|
+
<ChessClock.PlayPause finishedContent="💀 Time's up!" />
|
|
111
|
+
</ChessClock.Root>,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const button = screen.getByRole("button");
|
|
115
|
+
|
|
116
|
+
// Shows default startContent (not finished yet)
|
|
117
|
+
expect(button).toHaveTextContent("Start");
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("custom content overrides", () => {
|
|
122
|
+
it("should render startContent when idle", () => {
|
|
123
|
+
render(
|
|
124
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
125
|
+
<ChessClock.PlayPause
|
|
126
|
+
startContent="Start"
|
|
127
|
+
pauseContent="Pause"
|
|
128
|
+
resumeContent="Resume"
|
|
129
|
+
/>
|
|
130
|
+
</ChessClock.Root>,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(screen.getByRole("button")).toHaveTextContent("Start");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("should render pauseContent when running", () => {
|
|
137
|
+
render(
|
|
138
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
139
|
+
<ChessClock.PlayPause
|
|
140
|
+
startContent="Start"
|
|
141
|
+
pauseContent="Pause"
|
|
142
|
+
resumeContent="Resume"
|
|
143
|
+
/>
|
|
144
|
+
</ChessClock.Root>,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
expect(screen.getByRole("button")).toHaveTextContent("Pause");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should render resumeContent when paused", () => {
|
|
151
|
+
render(
|
|
152
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
153
|
+
<ChessClock.PlayPause
|
|
154
|
+
startContent="Start"
|
|
155
|
+
pauseContent="Pause"
|
|
156
|
+
resumeContent="Resume"
|
|
157
|
+
/>
|
|
158
|
+
</ChessClock.Root>,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const button = screen.getByRole("button");
|
|
162
|
+
|
|
163
|
+
// Initially shows "Pause" (running)
|
|
164
|
+
expect(button).toHaveTextContent("Pause");
|
|
165
|
+
|
|
166
|
+
// Click to pause
|
|
167
|
+
fireEvent.click(button);
|
|
168
|
+
|
|
169
|
+
// Now shows "Resume"
|
|
170
|
+
expect(button).toHaveTextContent("Resume");
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should start clock on click when idle", () => {
|
|
175
|
+
render(
|
|
176
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
177
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
178
|
+
<ChessClock.PlayPause>Start</ChessClock.PlayPause>
|
|
179
|
+
</ChessClock.Root>,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const clock = screen.getByTestId("clock");
|
|
183
|
+
|
|
184
|
+
// Clock is idle
|
|
185
|
+
expect(clock).toHaveAttribute("data-clock-status", "idle");
|
|
186
|
+
|
|
187
|
+
// Click to start
|
|
188
|
+
fireEvent.click(screen.getByRole("button"));
|
|
189
|
+
|
|
190
|
+
// Clock is running
|
|
191
|
+
expect(clock).toHaveAttribute("data-clock-status", "running");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should pause clock on click when running", () => {
|
|
195
|
+
render(
|
|
196
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
197
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
198
|
+
<ChessClock.PlayPause>Pause</ChessClock.PlayPause>
|
|
199
|
+
</ChessClock.Root>,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const clock = screen.getByTestId("clock");
|
|
203
|
+
|
|
204
|
+
// Clock is running
|
|
205
|
+
expect(clock).toHaveAttribute("data-clock-status", "running");
|
|
206
|
+
|
|
207
|
+
// Click pause
|
|
208
|
+
fireEvent.click(screen.getByRole("button"));
|
|
209
|
+
|
|
210
|
+
// Clock is paused
|
|
211
|
+
expect(clock).toHaveAttribute("data-clock-status", "paused");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should resume clock on click when paused", () => {
|
|
215
|
+
render(
|
|
216
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
217
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
218
|
+
<ChessClock.PlayPause>Toggle</ChessClock.PlayPause>
|
|
219
|
+
</ChessClock.Root>,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const clock = screen.getByTestId("clock");
|
|
223
|
+
const button = screen.getByRole("button");
|
|
224
|
+
|
|
225
|
+
// Pause
|
|
226
|
+
fireEvent.click(button);
|
|
227
|
+
expect(clock).toHaveAttribute("data-clock-status", "paused");
|
|
228
|
+
|
|
229
|
+
// Resume
|
|
230
|
+
fireEvent.click(button);
|
|
231
|
+
expect(clock).toHaveAttribute("data-clock-status", "running");
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should not be disabled when clock is idle", () => {
|
|
235
|
+
render(
|
|
236
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
237
|
+
<ChessClock.PlayPause>Start</ChessClock.PlayPause>
|
|
238
|
+
</ChessClock.Root>,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
expect(screen.getByRole("button")).not.toBeDisabled();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should be disabled when clock is finished", () => {
|
|
245
|
+
render(
|
|
246
|
+
<ChessClock.Root timeControl={{ time: "5+0" }}>
|
|
247
|
+
<ChessClock.PlayPause disabled>Toggle</ChessClock.PlayPause>
|
|
248
|
+
</ChessClock.Root>,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("delayed clock start", () => {
|
|
255
|
+
it("should render startContent when delayed (no delayedContent prop)", () => {
|
|
256
|
+
render(
|
|
257
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
258
|
+
<ChessClock.PlayPause
|
|
259
|
+
startContent="Start"
|
|
260
|
+
pauseContent="Pause"
|
|
261
|
+
resumeContent="Resume"
|
|
262
|
+
/>
|
|
263
|
+
</ChessClock.Root>,
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(screen.getByRole("button")).toHaveTextContent("Start");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should render delayedContent when delayed", () => {
|
|
270
|
+
render(
|
|
271
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
272
|
+
<ChessClock.PlayPause
|
|
273
|
+
startContent="Start"
|
|
274
|
+
delayedContent="Press to start immediately"
|
|
275
|
+
pauseContent="Pause"
|
|
276
|
+
resumeContent="Resume"
|
|
277
|
+
/>
|
|
278
|
+
</ChessClock.Root>,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(screen.getByRole("button")).toHaveTextContent(
|
|
282
|
+
"Press to start immediately",
|
|
283
|
+
);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should be disabled when clock is delayed", () => {
|
|
287
|
+
render(
|
|
288
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
289
|
+
<ChessClock.PlayPause>Start</ChessClock.PlayPause>
|
|
290
|
+
</ChessClock.Root>,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// In delayed mode, the button is disabled since clock starts via player moves
|
|
294
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
describe("finished state", () => {
|
|
299
|
+
it("should be disabled when clock is finished", () => {
|
|
300
|
+
render(
|
|
301
|
+
<ChessClock.Root timeControl={{ time: "5+0" }}>
|
|
302
|
+
<ChessClock.PlayPause>Toggle</ChessClock.PlayPause>
|
|
303
|
+
</ChessClock.Root>,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// When clock is finished, PlayPause button is disabled
|
|
307
|
+
// (actual timeout simulation requires RAF which doesn't work in jsdom)
|
|
308
|
+
const button = screen.getByRole("button");
|
|
309
|
+
expect(button).toBeInTheDocument();
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should respect disabled prop", () => {
|
|
313
|
+
render(
|
|
314
|
+
<ChessClock.Root timeControl={{ time: "5+0" }}>
|
|
315
|
+
<ChessClock.PlayPause disabled>Toggle</ChessClock.PlayPause>
|
|
316
|
+
</ChessClock.Root>,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("should support custom className", () => {
|
|
324
|
+
render(
|
|
325
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
326
|
+
<ChessClock.PlayPause className="custom-playpause">
|
|
327
|
+
Toggle
|
|
328
|
+
</ChessClock.PlayPause>
|
|
329
|
+
</ChessClock.Root>,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
expect(screen.getByRole("button")).toHaveClass("custom-playpause");
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("should call onClick handler", () => {
|
|
336
|
+
const handleClick = jest.fn();
|
|
337
|
+
|
|
338
|
+
render(
|
|
339
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
340
|
+
<ChessClock.PlayPause onClick={handleClick}>
|
|
341
|
+
Toggle
|
|
342
|
+
</ChessClock.PlayPause>
|
|
343
|
+
</ChessClock.Root>,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
fireEvent.click(screen.getByRole("button"));
|
|
347
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("asChild prop", () => {
|
|
351
|
+
it("should render as custom element when asChild is true", () => {
|
|
352
|
+
render(
|
|
353
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
354
|
+
<ChessClock.PlayPause asChild>
|
|
355
|
+
<div data-testid="custom-playpause" role="button" tabIndex={0}>
|
|
356
|
+
Toggle
|
|
357
|
+
</div>
|
|
358
|
+
</ChessClock.PlayPause>
|
|
359
|
+
</ChessClock.Root>,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const customElement = screen.getByTestId("custom-playpause");
|
|
363
|
+
expect(customElement.tagName).toBe("DIV");
|
|
364
|
+
expect(customElement).toHaveTextContent("Toggle");
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it("should toggle pause when asChild element is clicked", () => {
|
|
368
|
+
const handleClick = jest.fn();
|
|
369
|
+
render(
|
|
370
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
371
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
372
|
+
<ChessClock.PlayPause asChild onClick={handleClick}>
|
|
373
|
+
<span data-testid="custom-playpause" role="button" tabIndex={0}>
|
|
374
|
+
Toggle
|
|
375
|
+
</span>
|
|
376
|
+
</ChessClock.PlayPause>
|
|
377
|
+
</ChessClock.Root>,
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
const clock = screen.getByTestId("clock");
|
|
381
|
+
const customPlayPause = screen.getByTestId("custom-playpause");
|
|
382
|
+
|
|
383
|
+
expect(clock).toHaveAttribute("data-clock-status", "running");
|
|
384
|
+
expect(customPlayPause).toHaveTextContent("Toggle");
|
|
385
|
+
|
|
386
|
+
fireEvent.click(customPlayPause);
|
|
387
|
+
|
|
388
|
+
expect(clock).toHaveAttribute("data-clock-status", "paused");
|
|
389
|
+
expect(handleClick).toHaveBeenCalled();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("should be disabled when clock is finished with asChild", () => {
|
|
393
|
+
render(
|
|
394
|
+
<ChessClock.Root timeControl={{ time: "5+0" }}>
|
|
395
|
+
<ChessClock.PlayPause asChild>
|
|
396
|
+
<div data-testid="custom-playpause" role="button" tabIndex={0}>
|
|
397
|
+
Toggle
|
|
398
|
+
</div>
|
|
399
|
+
</ChessClock.PlayPause>
|
|
400
|
+
</ChessClock.Root>,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const customElement = screen.getByTestId("custom-playpause");
|
|
404
|
+
expect(customElement).toHaveAttribute("disabled");
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should have displayName", () => {
|
|
409
|
+
expect(ChessClock.PlayPause.displayName).toBe("ChessClock.PlayPause");
|
|
410
|
+
});
|
|
411
|
+
});
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent, act } from "@testing-library/react";
|
|
3
|
+
import { ChessClock } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("ChessClock.Reset", () => {
|
|
6
|
+
it("should render children", () => {
|
|
7
|
+
render(
|
|
8
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
9
|
+
<ChessClock.Reset>Reset</ChessClock.Reset>
|
|
10
|
+
</ChessClock.Root>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(screen.getByRole("button")).toHaveTextContent("Reset");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should render button element by default", () => {
|
|
17
|
+
render(
|
|
18
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
19
|
+
<ChessClock.Reset data-testid="reset">Reset</ChessClock.Reset>
|
|
20
|
+
</ChessClock.Root>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const button = screen.getByTestId("reset");
|
|
24
|
+
expect(button.tagName).toBe("BUTTON");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should reset clock to initial time on click", () => {
|
|
28
|
+
render(
|
|
29
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
30
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
31
|
+
<ChessClock.PlayPause>Pause</ChessClock.PlayPause>
|
|
32
|
+
<ChessClock.Reset>Reset</ChessClock.Reset>
|
|
33
|
+
</ChessClock.Root>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const clock = screen.getByTestId("clock");
|
|
37
|
+
|
|
38
|
+
// Initial time
|
|
39
|
+
expect(clock).toHaveTextContent("5:00");
|
|
40
|
+
|
|
41
|
+
// Pause the clock
|
|
42
|
+
fireEvent.click(screen.getByRole("button", { name: /Pause/i }));
|
|
43
|
+
|
|
44
|
+
// Clock is paused
|
|
45
|
+
expect(clock).toHaveAttribute("data-clock-status", "paused");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should reset to new time control when specified", () => {
|
|
49
|
+
render(
|
|
50
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
51
|
+
<ChessClock.Display color="white" data-testid="clock" />
|
|
52
|
+
<ChessClock.Reset timeControl="10+5">Reset</ChessClock.Reset>
|
|
53
|
+
</ChessClock.Root>,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const clock = screen.getByTestId("clock");
|
|
57
|
+
const resetButton = screen.getByRole("button", { name: "Reset" });
|
|
58
|
+
|
|
59
|
+
// Initial time is 5 minutes
|
|
60
|
+
expect(clock).toHaveTextContent("5:00");
|
|
61
|
+
|
|
62
|
+
// Click reset with new time control
|
|
63
|
+
act(() => {
|
|
64
|
+
fireEvent.click(resetButton);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// After reset, time should be 10 minutes
|
|
68
|
+
// Note: This would work with actual clock state updates
|
|
69
|
+
expect(resetButton).toBeInTheDocument();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should be disabled when clock is idle", () => {
|
|
73
|
+
render(
|
|
74
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
75
|
+
<ChessClock.Reset>Reset</ChessClock.Reset>
|
|
76
|
+
</ChessClock.Root>,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should support custom className", () => {
|
|
83
|
+
render(
|
|
84
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
85
|
+
<ChessClock.Reset className="custom-reset">Reset</ChessClock.Reset>
|
|
86
|
+
</ChessClock.Root>,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(screen.getByRole("button")).toHaveClass("custom-reset");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should call onClick handler", () => {
|
|
93
|
+
const handleClick = jest.fn();
|
|
94
|
+
|
|
95
|
+
render(
|
|
96
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
97
|
+
<ChessClock.Reset onClick={handleClick}>Reset</ChessClock.Reset>
|
|
98
|
+
</ChessClock.Root>,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
fireEvent.click(screen.getByRole("button"));
|
|
102
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("asChild prop", () => {
|
|
106
|
+
it("should render as custom element when asChild is true", () => {
|
|
107
|
+
render(
|
|
108
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
109
|
+
<ChessClock.Reset asChild>
|
|
110
|
+
<div data-testid="custom-reset" role="button" tabIndex={0}>
|
|
111
|
+
Reset
|
|
112
|
+
</div>
|
|
113
|
+
</ChessClock.Reset>
|
|
114
|
+
</ChessClock.Root>,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const customElement = screen.getByTestId("custom-reset");
|
|
118
|
+
expect(customElement.tagName).toBe("DIV");
|
|
119
|
+
expect(customElement).toHaveTextContent("Reset");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should reset when asChild element is clicked", () => {
|
|
123
|
+
const handleClick = jest.fn();
|
|
124
|
+
|
|
125
|
+
render(
|
|
126
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
127
|
+
<ChessClock.Reset asChild onClick={handleClick}>
|
|
128
|
+
<div data-testid="custom-reset" role="button" tabIndex={0}>
|
|
129
|
+
Reset
|
|
130
|
+
</div>
|
|
131
|
+
</ChessClock.Reset>
|
|
132
|
+
</ChessClock.Root>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const customReset = screen.getByTestId("custom-reset");
|
|
136
|
+
|
|
137
|
+
fireEvent.click(customReset);
|
|
138
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should be disabled when clock is idle with asChild", () => {
|
|
142
|
+
render(
|
|
143
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
144
|
+
<ChessClock.Reset asChild>
|
|
145
|
+
<div data-testid="custom-reset" role="button" tabIndex={0}>
|
|
146
|
+
Reset
|
|
147
|
+
</div>
|
|
148
|
+
</ChessClock.Reset>
|
|
149
|
+
</ChessClock.Root>,
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const customElement = screen.getByTestId("custom-reset");
|
|
153
|
+
expect(customElement).toHaveAttribute("disabled");
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should have displayName", () => {
|
|
158
|
+
expect(ChessClock.Reset.displayName).toBe("ChessClock.Reset");
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { ChessClock } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("ChessClock.Root", () => {
|
|
6
|
+
it("should render children", () => {
|
|
7
|
+
render(
|
|
8
|
+
<ChessClock.Root timeControl={{ time: "5+3" }}>
|
|
9
|
+
<div data-testid="test-child">Test Child</div>
|
|
10
|
+
</ChessClock.Root>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(screen.getByTestId("test-child")).toBeInTheDocument();
|
|
14
|
+
expect(screen.getByText("Test Child")).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should render multiple children", () => {
|
|
18
|
+
render(
|
|
19
|
+
<ChessClock.Root timeControl={{ time: "5+3" }}>
|
|
20
|
+
<div data-testid="child-1">Child 1</div>
|
|
21
|
+
<div data-testid="child-2">Child 2</div>
|
|
22
|
+
<div data-testid="child-3">Child 3</div>
|
|
23
|
+
</ChessClock.Root>,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByTestId("child-1")).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByTestId("child-2")).toBeInTheDocument();
|
|
28
|
+
expect(screen.getByTestId("child-3")).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should provide context to children", () => {
|
|
32
|
+
const TestChild = () => {
|
|
33
|
+
// This would use useChessClockContext
|
|
34
|
+
return <div>Context Child</div>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
render(
|
|
38
|
+
<ChessClock.Root timeControl={{ time: "5+3" }}>
|
|
39
|
+
<TestChild />
|
|
40
|
+
</ChessClock.Root>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(screen.getByText("Context Child")).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should have displayName", () => {
|
|
47
|
+
expect(ChessClock.Root.displayName).toBe("ChessClock.Root");
|
|
48
|
+
});
|
|
49
|
+
});
|