diff --git a/commafeed-client/src/components/ActionButton.test.tsx b/commafeed-client/src/components/ActionButton.test.tsx
new file mode 100644
index 00000000..174e3dd6
--- /dev/null
+++ b/commafeed-client/src/components/ActionButton.test.tsx
@@ -0,0 +1,50 @@
+import type { I18nContext } from "@lingui/react"
+import { MantineProvider } from "@mantine/core"
+import { fireEvent, render, screen, waitFor } from "@testing-library/react"
+import { useActionButton } from "hooks/useActionButton"
+import { describe, expect, it, vi } from "vitest"
+import { ActionButton } from "./ActionButton"
+
+vi.mock("@lingui/react", () => ({
+ useLingui: vi.fn().mockReturnValue({
+ _: msg => msg,
+ } as I18nContext),
+}))
+vi.mock("hooks/useActionButton")
+
+// reduce delay for faster tests
+vi.mock("app/constants", () => ({ Constants: { tooltip: { delay: 10 } } }))
+
+const label = "Test Label"
+const icon = "Test Icon"
+describe("ActionButton", () => {
+ it("renders Button with label on desktop", () => {
+ vi.mocked(useActionButton).mockReturnValue({ mobile: false, spacing: 0 })
+
+ render(, { wrapper: MantineProvider })
+ expect(screen.getByText(label)).toBeInTheDocument()
+ expect(screen.getByText(icon)).toBeInTheDocument()
+ })
+
+ it("renders ActionIcon with tooltip on mobile", async () => {
+ vi.mocked(useActionButton).mockReturnValue({ mobile: true, spacing: 0 })
+
+ render(, { wrapper: MantineProvider })
+ expect(screen.queryByText(label)).not.toBeInTheDocument()
+ expect(screen.getByText(icon)).toBeInTheDocument()
+
+ fireEvent.mouseEnter(screen.getByRole("button"))
+ const tooltip = await waitFor(() => screen.getByRole("tooltip"))
+ expect(tooltip).toContainHTML(label)
+ })
+
+ it("calls onClick handler when clicked", () => {
+ vi.mocked(useActionButton).mockReturnValue({ mobile: false, spacing: 0 })
+ const clickListener = vi.fn()
+
+ render(, { wrapper: MantineProvider })
+ fireEvent.click(screen.getByRole("button"))
+
+ expect(clickListener).toHaveBeenCalled()
+ })
+})
diff --git a/commafeed-client/src/components/ActionButton.tsx b/commafeed-client/src/components/ActionButton.tsx
index 9f3feae9..01bead87 100644
--- a/commafeed-client/src/components/ActionButton.tsx
+++ b/commafeed-client/src/components/ActionButton.tsx
@@ -7,8 +7,8 @@ import { useActionButton } from "hooks/useActionButton"
import { type MouseEventHandler, type ReactNode, forwardRef } from "react"
interface ActionButtonProps {
+ icon: ReactNode
className?: string
- icon?: ReactNode
label?: string | MessageDescriptor
onClick?: MouseEventHandler
variant?: ActionIconVariant & ButtonVariant