@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,204 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
3
|
+
import { ChessClock } from "../../index";
|
|
4
|
+
|
|
5
|
+
describe("ChessClock.Switch", () => {
|
|
6
|
+
it("should render children", () => {
|
|
7
|
+
render(
|
|
8
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
9
|
+
<ChessClock.Switch>Switch Clock</ChessClock.Switch>
|
|
10
|
+
</ChessClock.Root>,
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
expect(screen.getByRole("button")).toHaveTextContent("Switch Clock");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should render button element by default", () => {
|
|
17
|
+
render(
|
|
18
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
19
|
+
<ChessClock.Switch data-testid="switch">Switch</ChessClock.Switch>
|
|
20
|
+
</ChessClock.Root>,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const button = screen.getByTestId("switch");
|
|
24
|
+
expect(button.tagName).toBe("BUTTON");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should switch active player on click", () => {
|
|
28
|
+
render(
|
|
29
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
30
|
+
<ChessClock.Display color="white" data-testid="white" />
|
|
31
|
+
<ChessClock.Display color="black" data-testid="black" />
|
|
32
|
+
<ChessClock.Switch>Switch</ChessClock.Switch>
|
|
33
|
+
</ChessClock.Root>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const whiteClock = screen.getByTestId("white");
|
|
37
|
+
const blackClock = screen.getByTestId("black");
|
|
38
|
+
const switchButton = screen.getByRole("button");
|
|
39
|
+
|
|
40
|
+
// White starts active
|
|
41
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "true");
|
|
42
|
+
expect(blackClock).toHaveAttribute("data-clock-active", "false");
|
|
43
|
+
|
|
44
|
+
// Click switch
|
|
45
|
+
fireEvent.click(switchButton);
|
|
46
|
+
|
|
47
|
+
// Black should now be active
|
|
48
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "false");
|
|
49
|
+
expect(blackClock).toHaveAttribute("data-clock-active", "true");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should support custom className", () => {
|
|
53
|
+
render(
|
|
54
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
55
|
+
<ChessClock.Switch className="custom-switch">Switch</ChessClock.Switch>
|
|
56
|
+
</ChessClock.Root>,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
expect(screen.getByRole("button")).toHaveClass("custom-switch");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should be disabled when clock is idle", () => {
|
|
63
|
+
render(
|
|
64
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
65
|
+
<ChessClock.Switch>Switch</ChessClock.Switch>
|
|
66
|
+
</ChessClock.Root>,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
expect(screen.getByRole("button")).toBeDisabled();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("delayed clock start", () => {
|
|
73
|
+
it("should be enabled when clock start is delayed", () => {
|
|
74
|
+
render(
|
|
75
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
76
|
+
<ChessClock.Switch>Switch</ChessClock.Switch>
|
|
77
|
+
</ChessClock.Root>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(screen.getByRole("button")).not.toBeDisabled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should start timer after two switches", async () => {
|
|
84
|
+
render(
|
|
85
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "delayed" }}>
|
|
86
|
+
<ChessClock.Display color="white" data-testid="white" />
|
|
87
|
+
<ChessClock.Display color="black" data-testid="black" />
|
|
88
|
+
<ChessClock.Switch>Switch</ChessClock.Switch>
|
|
89
|
+
</ChessClock.Root>,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const whiteClock = screen.getByTestId("white");
|
|
93
|
+
const blackClock = screen.getByTestId("black");
|
|
94
|
+
const switchButton = screen.getByRole("button");
|
|
95
|
+
|
|
96
|
+
// Initially no active player in delayed mode, status is "delayed"
|
|
97
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "false");
|
|
98
|
+
expect(blackClock).toHaveAttribute("data-clock-active", "false");
|
|
99
|
+
expect(whiteClock).toHaveAttribute("data-clock-status", "delayed");
|
|
100
|
+
|
|
101
|
+
// First switch - black becomes active but status remains "delayed"
|
|
102
|
+
fireEvent.click(switchButton);
|
|
103
|
+
|
|
104
|
+
// Wait for state update
|
|
105
|
+
await waitFor(
|
|
106
|
+
() => {
|
|
107
|
+
expect(blackClock).toHaveAttribute("data-clock-active", "true");
|
|
108
|
+
},
|
|
109
|
+
{ timeout: 300 },
|
|
110
|
+
);
|
|
111
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "false");
|
|
112
|
+
// Status is still "delayed" after first switch
|
|
113
|
+
expect(whiteClock).toHaveAttribute("data-clock-status", "delayed");
|
|
114
|
+
|
|
115
|
+
// Add a small delay to ensure React has processed all state updates
|
|
116
|
+
// This is needed because the callback captures the stale state values
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
118
|
+
|
|
119
|
+
// Second switch - white becomes active and status changes to "running"
|
|
120
|
+
fireEvent.click(switchButton);
|
|
121
|
+
|
|
122
|
+
// Wait for state update - white should become active and status should be "running"
|
|
123
|
+
await waitFor(
|
|
124
|
+
() => {
|
|
125
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "true");
|
|
126
|
+
expect(whiteClock).toHaveAttribute("data-clock-status", "running");
|
|
127
|
+
},
|
|
128
|
+
{ timeout: 300 },
|
|
129
|
+
);
|
|
130
|
+
expect(blackClock).toHaveAttribute("data-clock-active", "false");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should call onClick handler", () => {
|
|
135
|
+
const handleClick = jest.fn();
|
|
136
|
+
|
|
137
|
+
render(
|
|
138
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
139
|
+
<ChessClock.Switch onClick={handleClick}>Switch</ChessClock.Switch>
|
|
140
|
+
</ChessClock.Root>,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
fireEvent.click(screen.getByRole("button"));
|
|
144
|
+
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("asChild prop", () => {
|
|
148
|
+
it("should render as custom element when asChild is true", () => {
|
|
149
|
+
render(
|
|
150
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
151
|
+
<ChessClock.Switch asChild>
|
|
152
|
+
<div data-testid="custom-switch">Switch</div>
|
|
153
|
+
</ChessClock.Switch>
|
|
154
|
+
</ChessClock.Root>,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const customElement = screen.getByTestId("custom-switch");
|
|
158
|
+
expect(customElement.tagName).toBe("DIV");
|
|
159
|
+
expect(customElement).toHaveTextContent("Switch");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should switch when asChild element is clicked", () => {
|
|
163
|
+
render(
|
|
164
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "immediate" }}>
|
|
165
|
+
<ChessClock.Display color="white" data-testid="white" />
|
|
166
|
+
<ChessClock.Display color="black" data-testid="black" />
|
|
167
|
+
<ChessClock.Switch asChild>
|
|
168
|
+
<div data-testid="custom-switch" role="button" tabIndex={0}>
|
|
169
|
+
Switch
|
|
170
|
+
</div>
|
|
171
|
+
</ChessClock.Switch>
|
|
172
|
+
</ChessClock.Root>,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const whiteClock = screen.getByTestId("white");
|
|
176
|
+
const customSwitch = screen.getByTestId("custom-switch");
|
|
177
|
+
|
|
178
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "true");
|
|
179
|
+
|
|
180
|
+
fireEvent.click(customSwitch);
|
|
181
|
+
|
|
182
|
+
expect(whiteClock).toHaveAttribute("data-clock-active", "false");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should be disabled when clock is idle with asChild", () => {
|
|
186
|
+
render(
|
|
187
|
+
<ChessClock.Root timeControl={{ time: "5+0", clockStart: "manual" }}>
|
|
188
|
+
<ChessClock.Switch asChild>
|
|
189
|
+
<div data-testid="custom-switch" role="button" tabIndex={0}>
|
|
190
|
+
Switch
|
|
191
|
+
</div>
|
|
192
|
+
</ChessClock.Switch>
|
|
193
|
+
</ChessClock.Root>,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const customElement = screen.getByTestId("custom-switch");
|
|
197
|
+
expect(customElement).toHaveAttribute("disabled");
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should have displayName", () => {
|
|
202
|
+
expect(ChessClock.Switch.displayName).toBe("ChessClock.Switch");
|
|
203
|
+
});
|
|
204
|
+
});
|