@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.
Files changed (37) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +697 -0
  3. package/dist/index.cjs +1014 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +528 -0
  6. package/dist/index.d.ts +528 -0
  7. package/dist/index.js +969 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +63 -0
  10. package/src/components/ChessClock/ChessClock.stories.tsx +782 -0
  11. package/src/components/ChessClock/index.ts +44 -0
  12. package/src/components/ChessClock/parts/Display.tsx +69 -0
  13. package/src/components/ChessClock/parts/PlayPause.tsx +190 -0
  14. package/src/components/ChessClock/parts/Reset.tsx +90 -0
  15. package/src/components/ChessClock/parts/Root.tsx +37 -0
  16. package/src/components/ChessClock/parts/Switch.tsx +84 -0
  17. package/src/components/ChessClock/parts/__tests__/Display.test.tsx +149 -0
  18. package/src/components/ChessClock/parts/__tests__/PlayPause.test.tsx +411 -0
  19. package/src/components/ChessClock/parts/__tests__/Reset.test.tsx +160 -0
  20. package/src/components/ChessClock/parts/__tests__/Root.test.tsx +49 -0
  21. package/src/components/ChessClock/parts/__tests__/Switch.test.tsx +204 -0
  22. package/src/hooks/__tests__/clockReducer.test.ts +985 -0
  23. package/src/hooks/__tests__/useChessClock.test.tsx +1080 -0
  24. package/src/hooks/clockReducer.ts +379 -0
  25. package/src/hooks/useChessClock.ts +406 -0
  26. package/src/hooks/useChessClockContext.ts +35 -0
  27. package/src/index.ts +65 -0
  28. package/src/types.ts +217 -0
  29. package/src/utils/__tests__/calculateSwitchTime.test.ts +150 -0
  30. package/src/utils/__tests__/formatTime.test.ts +83 -0
  31. package/src/utils/__tests__/timeControl.test.ts +414 -0
  32. package/src/utils/__tests__/timingMethods.test.ts +170 -0
  33. package/src/utils/calculateSwitchTime.ts +37 -0
  34. package/src/utils/formatTime.ts +59 -0
  35. package/src/utils/presets.ts +47 -0
  36. package/src/utils/timeControl.ts +205 -0
  37. 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
+ });