- Implemented copy buttons for phone number and Telegram username in the ContactLinks component, enhancing user interaction. - Integrated tooltip feedback to indicate successful copy actions. - Updated tests to cover new copy functionality and ensure proper rendering of copy buttons based on props. - Added localization support for new copy-related strings in the i18n messages.
228 lines
7.4 KiB
TypeScript
228 lines
7.4 KiB
TypeScript
/**
|
|
* Unit tests for ContactLinks: phone/Telegram display, labels, layout.
|
|
* Ported from webapp/js/contactHtml.test.js buildContactLinksHtml.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
import { render, screen, fireEvent, act } from "@testing-library/react";
|
|
import { ContactLinks } from "./ContactLinks";
|
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
import { resetAppStore } from "@/test/test-utils";
|
|
|
|
function renderWithTooltip(ui: React.ReactElement) {
|
|
return render(<TooltipProvider>{ui}</TooltipProvider>);
|
|
}
|
|
|
|
const openPhoneLinkMock = vi.fn();
|
|
const openTelegramProfileMock = vi.fn();
|
|
const triggerHapticLightMock = vi.fn();
|
|
const copyToClipboardMock = vi.fn();
|
|
|
|
vi.mock("@/lib/open-phone-link", () => ({
|
|
openPhoneLink: (...args: unknown[]) => openPhoneLinkMock(...args),
|
|
}));
|
|
vi.mock("@/lib/telegram-link", () => ({
|
|
openTelegramProfile: (...args: unknown[]) => openTelegramProfileMock(...args),
|
|
}));
|
|
vi.mock("@/lib/telegram-haptic", () => ({
|
|
triggerHapticLight: () => triggerHapticLightMock(),
|
|
}));
|
|
vi.mock("@/lib/copy-to-clipboard", () => ({
|
|
copyToClipboard: (...args: unknown[]) => copyToClipboardMock(...args),
|
|
}));
|
|
|
|
describe("ContactLinks", () => {
|
|
beforeEach(() => {
|
|
resetAppStore();
|
|
openPhoneLinkMock.mockClear();
|
|
openTelegramProfileMock.mockClear();
|
|
triggerHapticLightMock.mockClear();
|
|
copyToClipboardMock.mockClear();
|
|
});
|
|
|
|
it("returns null when phone and username are missing", () => {
|
|
const { container } = render(
|
|
<ContactLinks phone={null} username={null} />
|
|
);
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it("renders phone only with label and tel: link", () => {
|
|
render(<ContactLinks phone="+79991234567" username={null} showLabels />);
|
|
expect(document.querySelector('a[href^="tel:"]')).toBeInTheDocument();
|
|
expect(screen.getByText(/Phone/i)).toBeInTheDocument();
|
|
});
|
|
|
|
it("displays phone formatted for Russian numbers", () => {
|
|
render(<ContactLinks phone="79146522209" username={null} />);
|
|
expect(screen.getByText(/\+7 914 652-22-09/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders username only with label and t.me link", () => {
|
|
render(<ContactLinks phone={null} username="alice_dev" showLabels />);
|
|
expect(document.querySelector('a[href*="t.me"]')).toBeInTheDocument();
|
|
expect(screen.getByText(/alice_dev/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders both phone and username with labels", () => {
|
|
render(
|
|
<ContactLinks
|
|
phone="+79001112233"
|
|
username="bob"
|
|
showLabels
|
|
/>
|
|
);
|
|
expect(document.querySelector('a[href^="tel:"]')).toBeInTheDocument();
|
|
expect(document.querySelector('a[href*="t.me"]')).toBeInTheDocument();
|
|
expect(screen.getByText(/\+7 900 111-22-33/)).toBeInTheDocument();
|
|
expect(screen.getByText(/@bob/)).toBeInTheDocument();
|
|
});
|
|
|
|
it("strips leading @ from username and displays with @", () => {
|
|
render(<ContactLinks phone={null} username="@alice" />);
|
|
const link = document.querySelector('a[href*="t.me/alice"]');
|
|
expect(link).toBeInTheDocument();
|
|
expect(link?.textContent).toContain("@alice");
|
|
});
|
|
|
|
it("calls openPhoneLink and triggerHapticLight when phone link is clicked", () => {
|
|
render(
|
|
<ContactLinks phone="+79991234567" username={null} showLabels={false} />
|
|
);
|
|
const telLink = document.querySelector<HTMLAnchorElement>('a[href^="tel:"]');
|
|
expect(telLink).toBeInTheDocument();
|
|
|
|
fireEvent.click(telLink!);
|
|
|
|
expect(openPhoneLinkMock).toHaveBeenCalledWith("+79991234567");
|
|
expect(triggerHapticLightMock).toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls openTelegramProfile and triggerHapticLight when Telegram link is clicked", () => {
|
|
render(
|
|
<ContactLinks phone={null} username="alice_dev" showLabels={false} />
|
|
);
|
|
const tgLink = document.querySelector<HTMLAnchorElement>('a[href*="t.me"]');
|
|
expect(tgLink).toBeInTheDocument();
|
|
|
|
fireEvent.click(tgLink!);
|
|
|
|
expect(openTelegramProfileMock).toHaveBeenCalledWith("alice_dev");
|
|
expect(triggerHapticLightMock).toHaveBeenCalled();
|
|
});
|
|
|
|
describe("showCopyButtons with layout block", () => {
|
|
it("renders copy phone button with aria-label when phone is present", () => {
|
|
renderWithTooltip(
|
|
<ContactLinks
|
|
phone="+79991234567"
|
|
username={null}
|
|
layout="block"
|
|
showCopyButtons
|
|
/>
|
|
);
|
|
expect(
|
|
screen.getByRole("button", { name: /Copy phone number|Скопировать номер/i })
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("renders copy Telegram button with aria-label when username is present", () => {
|
|
renderWithTooltip(
|
|
<ContactLinks
|
|
phone={null}
|
|
username="alice_dev"
|
|
layout="block"
|
|
showCopyButtons
|
|
/>
|
|
);
|
|
expect(
|
|
screen.getByRole("button", {
|
|
name: /Copy Telegram username|Скопировать логин Telegram/i,
|
|
})
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("calls copyToClipboard with raw phone and triggerHapticLight when copy phone is clicked", async () => {
|
|
copyToClipboardMock.mockResolvedValue(true);
|
|
renderWithTooltip(
|
|
<ContactLinks
|
|
phone="+79991234567"
|
|
username={null}
|
|
layout="block"
|
|
showCopyButtons
|
|
/>
|
|
);
|
|
const copyBtn = screen.getByRole("button", {
|
|
name: /Copy phone number|Скопировать номер/i,
|
|
});
|
|
await act(async () => {
|
|
fireEvent.click(copyBtn);
|
|
});
|
|
|
|
expect(copyToClipboardMock).toHaveBeenCalledWith("+79991234567");
|
|
expect(triggerHapticLightMock).toHaveBeenCalled();
|
|
});
|
|
|
|
it("calls copyToClipboard with @username when copy Telegram is clicked", async () => {
|
|
copyToClipboardMock.mockResolvedValue(true);
|
|
renderWithTooltip(
|
|
<ContactLinks
|
|
phone={null}
|
|
username="alice_dev"
|
|
layout="block"
|
|
showCopyButtons
|
|
/>
|
|
);
|
|
const copyBtn = screen.getByRole("button", {
|
|
name: /Copy Telegram username|Скопировать логин Telegram/i,
|
|
});
|
|
await act(async () => {
|
|
fireEvent.click(copyBtn);
|
|
});
|
|
|
|
expect(copyToClipboardMock).toHaveBeenCalledWith("@alice_dev");
|
|
expect(triggerHapticLightMock).toHaveBeenCalled();
|
|
});
|
|
|
|
it("shows Copied text in tooltip after successful copy", async () => {
|
|
copyToClipboardMock.mockResolvedValue(true);
|
|
renderWithTooltip(
|
|
<ContactLinks
|
|
phone="+79991234567"
|
|
username={null}
|
|
layout="block"
|
|
showCopyButtons
|
|
/>
|
|
);
|
|
const copyBtn = screen.getByRole("button", {
|
|
name: /Copy phone number|Скопировать номер/i,
|
|
});
|
|
await act(async () => {
|
|
fireEvent.click(copyBtn);
|
|
});
|
|
|
|
const tooltip = await screen.findByRole("tooltip", {
|
|
name: /Copied|Скопировано/i,
|
|
});
|
|
expect(tooltip).toBeInTheDocument();
|
|
});
|
|
|
|
it("does not show copy buttons when showCopyButtons is false", () => {
|
|
render(
|
|
<ContactLinks
|
|
phone="+79991234567"
|
|
username="bob"
|
|
layout="block"
|
|
showCopyButtons={false}
|
|
/>
|
|
);
|
|
expect(
|
|
screen.queryByRole("button", { name: /Copy phone number|Скопировать номер/i })
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("button", { name: /Copy Telegram username|Скопировать логин Telegram/i })
|
|
).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|