Add SharePanel component with social links and copy-to-clipboard

This commit is contained in:
2026-04-17 01:29:31 +03:00
parent a70dbd2546
commit 8651e8df0f
2 changed files with 120 additions and 0 deletions
@@ -0,0 +1,55 @@
// @vitest-environment jsdom
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { SharePanel } from "./SharePanel.js";
vi.mock("@/i18n/provider.js", () => ({
useTranslation: () => ({ t: (k: string) => k }),
}));
describe("SharePanel", () => {
let writeTextMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
writeTextMock = vi.fn().mockResolvedValue(undefined);
Object.defineProperty(navigator, "clipboard", {
value: { writeText: writeTextMock },
writable: true,
configurable: true,
});
});
it("renders 3 social links for en locale (no Weibo)", () => {
render(<SharePanel url="https://example.com/flight" locale="en" onClose={() => {}} />);
expect(screen.getByTestId("share-facebook")).toBeTruthy();
expect(screen.getByTestId("share-vk")).toBeTruthy();
expect(screen.getByTestId("share-twitter")).toBeTruthy();
expect(screen.queryByTestId("share-weibo")).toBeNull();
});
it("renders 4 social links for zh locale (includes Weibo)", () => {
render(<SharePanel url="https://example.com/flight" locale="zh" onClose={() => {}} />);
expect(screen.getByTestId("share-facebook")).toBeTruthy();
expect(screen.getByTestId("share-vk")).toBeTruthy();
expect(screen.getByTestId("share-twitter")).toBeTruthy();
expect(screen.getByTestId("share-weibo")).toBeTruthy();
});
it("Facebook href contains encoded URL", () => {
render(<SharePanel url="https://example.com/flight?a=1" locale="en" onClose={() => {}} />);
const link = screen.getByTestId("share-facebook") as HTMLAnchorElement;
expect(link.href).toContain(encodeURIComponent("https://example.com/flight?a=1"));
});
it("copy button calls navigator.clipboard.writeText and onClose", async () => {
const onClose = vi.fn();
render(<SharePanel url="https://example.com/flight" locale="en" onClose={onClose} />);
fireEvent.click(screen.getByTestId("share-copy"));
await waitFor(() => {
expect(writeTextMock).toHaveBeenCalledWith("https://example.com/flight");
});
await waitFor(() => {
expect(onClose).toHaveBeenCalled();
});
});
});
@@ -0,0 +1,65 @@
import type { FC } from "react";
import { useTranslation } from "@/i18n/provider.js";
import "./actions.scss";
export interface SharePanelProps {
url: string;
locale: string;
onClose: () => void;
}
export const SharePanel: FC<SharePanelProps> = ({ url, locale, onClose }) => {
const { t } = useTranslation();
const encoded = encodeURIComponent(url);
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(url);
onClose();
} catch {
// silent
}
};
return (
<div className="share-panel" data-testid="share-panel">
<a
data-testid="share-facebook"
href={`https://www.facebook.com/sharer/sharer.php?u=${encoded}`}
target="_blank"
rel="noopener noreferrer"
>
Facebook
</a>
<a
data-testid="share-vk"
href={`https://vk.com/share.php?url=${encoded}`}
target="_blank"
rel="noopener noreferrer"
>
VK
</a>
<a
data-testid="share-twitter"
href={`https://twitter.com/share?text=${encodeURIComponent("My Flight")}&url=${encoded}`}
target="_blank"
rel="noopener noreferrer"
>
Twitter
</a>
{locale === "zh" && (
<a
data-testid="share-weibo"
href={`https://service.weibo.com/share/share.php?url=${encoded}`}
target="_blank"
rel="noopener noreferrer"
>
Weibo
</a>
)}
<button type="button" data-testid="share-copy" onClick={handleCopy}>
{t("SHARED.COPY") || "Copy"}
</button>
</div>
);
};