diff --git a/src/components/ui/setting-switch.test.tsx b/src/components/ui/setting-switch.test.tsx new file mode 100644 index 00000000..d9645393 --- /dev/null +++ b/src/components/ui/setting-switch.test.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { SettingSwitch } from "./setting-switch"; + +describe("SettingSwitch", () => { + it("renders an element with role=switch", () => { + render(); + expect(screen.getByRole("switch")).not.toBeNull(); + }); + + it("reflects the checked state via aria-checked and data-state", () => { + const { rerender } = render(); + const on = screen.getByRole("switch"); + expect(on.getAttribute("aria-checked")).toBe("true"); + expect(on.getAttribute("data-state")).toBe("checked"); + + rerender(); + const off = screen.getByRole("switch"); + expect(off.getAttribute("aria-checked")).toBe("false"); + expect(off.getAttribute("data-state")).toBe("unchecked"); + }); + + it("calls onCheckedChange with the negation of checked when clicked", () => { + const onCheckedChange = jest.fn(); + + const { rerender } = render( + + ); + fireEvent.click(screen.getByRole("switch")); + expect(onCheckedChange).toHaveBeenLastCalledWith(true); + + rerender(); + fireEvent.click(screen.getByRole("switch")); + expect(onCheckedChange).toHaveBeenLastCalledWith(false); + }); + + it("toggles on Enter and on Space", () => { + const onCheckedChange = jest.fn(); + render(); + const sw = screen.getByRole("switch"); + + fireEvent.keyDown(sw, { key: "Enter" }); + expect(onCheckedChange).toHaveBeenCalledTimes(1); + + fireEvent.keyDown(sw, { key: " " }); + expect(onCheckedChange).toHaveBeenCalledTimes(2); + expect(onCheckedChange).toHaveBeenNthCalledWith(1, true); + expect(onCheckedChange).toHaveBeenNthCalledWith(2, true); + + fireEvent.keyDown(sw, { key: "a" }); + expect(onCheckedChange).toHaveBeenCalledTimes(2); + }); + + it("does not toggle and is not focusable when disabled", () => { + const onCheckedChange = jest.fn(); + render(); + const sw = screen.getByRole("switch"); + + fireEvent.click(sw); + fireEvent.keyDown(sw, { key: "Enter" }); + fireEvent.keyDown(sw, { key: " " }); + expect(onCheckedChange).not.toHaveBeenCalled(); + + expect(sw.getAttribute("aria-disabled")).toBe("true"); + expect(sw.tabIndex).toBe(-1); + }); +}); diff --git a/src/components/ui/setting-switch.tsx b/src/components/ui/setting-switch.tsx index 02499d58..adae7eee 100644 --- a/src/components/ui/setting-switch.tsx +++ b/src/components/ui/setting-switch.tsx @@ -33,7 +33,7 @@ const SettingSwitch = React.forwardRef( ref={ref} tabIndex={disabled ? -1 : 0} className={cn( - "tw-relative tw-inline-flex tw-h-5.5 tw-w-10 tw-shrink-0 tw-cursor-pointer tw-items-center tw-rounded-full tw-transition-colors", + "tw-relative tw-inline-flex tw-h-[calc(var(--toggle-s-thumb-height)_+_var(--toggle-s-border-width)*2)] tw-w-[var(--toggle-s-width)] tw-shrink-0 tw-cursor-pointer tw-items-center tw-rounded-[var(--toggle-radius)] tw-transition-colors", "focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2", checked ? "tw-bg-interactive-accent" : "tw-bg-[--background-modifier-border-hover]", disabled && "tw-cursor-not-allowed tw-opacity-50", @@ -45,8 +45,10 @@ const SettingSwitch = React.forwardRef( >