From 2e25128e73f1c7526e01efcbe4a9cd45dab7d820 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 26 Sep 2026 09:46:11 +0200 Subject: [PATCH] fix: disable deleting moves while a game is in progress (#874) --- src/components/boards/BoardGame.tsx | 2 +- src/components/common/CompleteMoveCell.tsx | 4 ++- src/state/atoms.ts | 5 +++ src/utils/tests/atoms.test.ts | 40 ++++++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/utils/tests/atoms.test.ts diff --git a/src/components/boards/BoardGame.tsx b/src/components/boards/BoardGame.tsx index b178a70..df39c29 100644 --- a/src/components/boards/BoardGame.tsx +++ b/src/components/boards/BoardGame.tsx @@ -804,7 +804,7 @@ function BoardGame() { /> } /> - + )} diff --git a/src/components/common/CompleteMoveCell.tsx b/src/components/common/CompleteMoveCell.tsx index 395d90c..9f761b1 100644 --- a/src/components/common/CompleteMoveCell.tsx +++ b/src/components/common/CompleteMoveCell.tsx @@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next"; import { useStore } from "zustand"; import { useStoreWithEqualityFn } from "zustand/traditional"; import Comment from "@/components/common/Comment"; -import { currentTabAtom } from "@/state/atoms"; +import { currentGameInProgressAtom, currentTabAtom } from "@/state/atoms"; import type { Annotation } from "@/utils/annotation"; import { hasMorePriority, stripClock } from "@/utils/chess"; import { getTabFile } from "@/utils/tabs"; @@ -106,6 +106,7 @@ function CompleteMoveCell({ const [open, setOpen] = useState(false); const currentTab = useAtomValue(currentTabAtom); const tabFile = getTabFile(currentTab); + const gameInProgress = useAtomValue(currentGameInProgressAtom); const { t } = useTranslation(); @@ -181,6 +182,7 @@ function CompleteMoveCell({ } + disabled={gameInProgress} onClick={() => deleteMove(movePath)} > {t("Menu.DeleteMove")} diff --git a/src/state/atoms.ts b/src/state/atoms.ts index 3e63d84..129e12c 100644 --- a/src/state/atoms.ts +++ b/src/state/atoms.ts @@ -449,6 +449,11 @@ type GameState = "settingUp" | "playing" | "gameOver"; const gameStateFamily = atomFamily((_tab: string) => atom("settingUp")); export const currentGameStateAtom = tabValue(gameStateFamily); +export const currentGameInProgressAtom = atom((get) => { + const tab = get(currentTabAtom); + return tab?.type === "play" && get(gameStateFamily(tab.value)) === "playing"; +}); + const playersFamily = atomFamily((_tab: string) => atom<{ white: OpponentSettings; diff --git a/src/utils/tests/atoms.test.ts b/src/utils/tests/atoms.test.ts new file mode 100644 index 0000000..6a9aa90 --- /dev/null +++ b/src/utils/tests/atoms.test.ts @@ -0,0 +1,40 @@ +import { createStore } from "jotai"; +import { expect, test } from "vitest"; +import { + activeTabAtom, + currentGameInProgressAtom, + currentGameStateAtom, + tabsAtom, +} from "@/state/atoms"; +import type { Tab } from "@/utils/tabs"; + +function storeWithTab(type: Tab["type"]) { + const store = createStore(); + store.set(tabsAtom, [{ name: "Tab", value: "tab", type, gameOrigin: { kind: "none" } }]); + store.set(activeTabAtom, "tab"); + return store; +} + +test("a play tab is in progress only while the game is playing", () => { + const store = storeWithTab("play"); + expect(store.get(currentGameInProgressAtom)).toBe(false); + + store.set(currentGameStateAtom, "playing"); + expect(store.get(currentGameInProgressAtom)).toBe(true); + + store.set(currentGameStateAtom, "gameOver"); + expect(store.get(currentGameInProgressAtom)).toBe(false); +}); + +test("an analysis tab is never in progress", () => { + const store = storeWithTab("analysis"); + store.set(currentGameStateAtom, "playing"); + expect(store.get(currentGameInProgressAtom)).toBe(false); +}); + +test("no tab selected is not in progress", () => { + const store = createStore(); + store.set(tabsAtom, []); + store.set(activeTabAtom, null); + expect(store.get(currentGameInProgressAtom)).toBe(false); +});