Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import FavoriteIcon from "@mui/icons-material/Favorite";
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorder";
import IconButton from "@shared/uis/Icon-Button.tsx";

export function FavoriteHeartButton({
isFavorited,
isLoading = false,
onClick,
className,
}: {
isFavorited: boolean;
isLoading?: boolean;
onClick: () => void;
className?: string;
}) {
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (event) => {
event.preventDefault();
event.stopPropagation();
onClick();
};

return (
<IconButton
onClick={handleClick}
disabled={isLoading}
aria-label={isFavorited ? "Favorited" : "Add to favorites"}
aria-pressed={isFavorited}
className={className}
>
{isFavorited ? <FavoriteIcon className="!text-rose-500" /> : <FavoriteBorderIcon />}
</IconButton>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/** @jest-environment jsdom */

import "@testing-library/jest-dom/jest-globals";
import { describe, test, expect, jest } from "@jest/globals";
import { render, screen, fireEvent } from "@testing-library/react";
import { FavoriteHeartButton } from "../FavoriteHeartButton";

describe("FavoriteHeartButton", () => {
test("未登録状態: 'Add to favorites'ラベルで表示され、クリックでonClickが呼ばれる", () => {
const onClick = jest.fn();
render(<FavoriteHeartButton isFavorited={false} onClick={onClick} />);

const button = screen.getByRole("button", { name: "Add to favorites" });
expect(button).not.toBeDisabled();

fireEvent.click(button);
expect(onClick).toHaveBeenCalledTimes(1);
});

test("登録済み状態: 'Favorited'ラベルで表示される", () => {
render(<FavoriteHeartButton isFavorited={true} onClick={jest.fn()} />);

expect(screen.getByRole("button", { name: "Favorited" })).toBeInTheDocument();
});

test("isLoading=trueの場合はボタンが無効化される", () => {
render(<FavoriteHeartButton isFavorited={false} isLoading={true} onClick={jest.fn()} />);

expect(screen.getByRole("button", { name: "Add to favorites" })).toBeDisabled();
});

test("クリックイベントの伝播を止める(カードのクリックに巻き込まれない)", () => {
const onClick = jest.fn();
const onCardClick = jest.fn();

render(
<div onClick={onCardClick}>
<FavoriteHeartButton isFavorited={false} onClick={onClick} />
</div>,
);

fireEvent.click(screen.getByRole("button", { name: "Add to favorites" }));

expect(onClick).toHaveBeenCalledTimes(1);
expect(onCardClick).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import { useAddFavorite } from "../hooks/use-add-favorite";
import { useAuth } from "@shared/auth/AuthHooks.ts";
import { FavoriteHeartButton } from "../components/FavoriteHeartButton";

export function FavoriteButtonContainer({ heritageId }: { heritageId: number }) {
export function FavoriteButtonContainer({
heritageId,
className,
}: {
heritageId: number;
className?: string;
}) {
const { user } = useAuth();
const { submit, isAdded, isLoading } = useAddFavorite();

if (!user) return null;

const handleClick = (event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
void submit(heritageId);
};

return (
<button
type="button"
aria-label={isAdded ? "Favorited" : "Add to favorites"}
aria-pressed={isAdded}
disabled={isLoading || isAdded}
onClick={handleClick}
>
{isAdded ? "♥" : "♡"}
</button>
<FavoriteHeartButton
isFavorited={isAdded}
isLoading={isLoading || isAdded}
onClick={() => void submit(heritageId)}
className={className}
/>
);
}
2 changes: 1 addition & 1 deletion client/src/app/features/top/cards/HeritageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function HeritageCard({
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-black/80 via-black/30 to-transparent" />

<div className="absolute left-3 top-3">
<FavoriteButtonContainer heritageId={item.id} />
<FavoriteButtonContainer heritageId={item.id} className="!text-white" />
</div>

{item.isEndangered && (
Expand Down
Loading