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
Expand Up @@ -125,6 +125,12 @@ describe("EditProfileModal", () => {
onSave={mockOnSave}
/>,
);
expect(screen.getByTestId("edit-modal-close-button")).toHaveClass(
"bg-bg-actionbutton",
);
expect(screen.getByTestId("edit-modal-close-button")).toHaveClass(
"text-text-secondary",
);
fireEvent.click(screen.getByTestId("edit-modal-close-button"));
expect(mockOnClose).toHaveBeenCalled();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const EditProfileModal: React.FC<EditProfileModalProps> = ({
<button
data-test="edit-modal-close-button"
onClick={onClose}
className="fixed top-14 right-4 sm:top-16 sm:right-6 cursor-pointer text-bg text-lg hover:opacity-70 z-60 bg-bg-inverted rounded-full w-8 h-8 flex items-center justify-center"
className="fixed top-14 right-4 sm:top-16 sm:right-6 z-60 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-bg-actionbutton text-text-secondary hover:text-text hover:opacity-80"
>
<i className="fa-solid fa-xmark" />
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ describe("ProfileSideBar", () => {
);
expect(screen.getByText("travis-scott")).toBeInTheDocument();
expect(screen.getByText("1 FOLLOWING")).toBeInTheDocument();
expect(screen.getByText("6.0M")).toBeInTheDocument();
expect(
screen.getByTestId("follow-button-travis-scott"),
).toBeInTheDocument();
Expand Down Expand Up @@ -322,6 +323,10 @@ describe("ProfileSideBar", () => {

it("navigates to checkout on Upgrade to Artist Pro click", () => {
render(<ProfileSideBar {...defaultProps} isOwner={true} />);
expect(screen.getByTestId("upgrade-pro-button")).toHaveClass(
"bg-bg-inverted",
);
expect(screen.getByTestId("upgrade-pro-button")).toHaveClass("text-bg");
fireEvent.click(screen.getByTestId("upgrade-pro-button"));
expect(mockNavigate).toHaveBeenCalledWith("/premium");
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/Profile/ProfileSideBar/ProfileSideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ const ProfileSideBar: React.FC<ProfileSideBarProps> = ({
<button
data-test="upgrade-pro-button"
onClick={() => navigate("/premium")}
className="w-[320px] py-3 bg-bg text-bg-inverted font-semibold text-sm rounded-full hover:opacity-80 transition-colors"
className="w-[320px] py-3 bg-bg-inverted text-bg font-semibold text-sm rounded-full hover:opacity-80 transition-colors"
>
Upgrade to Premium
</button>
Expand Down
26 changes: 26 additions & 0 deletions src/components/Profile/ProfileSideBar/TrackItem.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ vi.mock("@/stores/player.store", () => ({

const mockLikesStore = {
isTrackLiked: vi.fn().mockReturnValue(false),
isTrackReposted: vi.fn().mockReturnValue(false),
repostedTrackIds: [],
itemStats: {},
toggleTrack: vi.fn(),
toggleRepost: vi.fn().mockResolvedValue(undefined),
};

vi.mock("@/stores/likes.store", () => ({
Expand Down Expand Up @@ -148,4 +152,26 @@ describe("TrackItem", () => {
fireEvent.click(screen.getByTestId("track-more-download-1"));
expect(mockNavigate).toHaveBeenCalledWith("/premium");
});

it("shows Unrepost when the track is already reposted", () => {
render(<TrackItem {...defaultProps} initialReposted />);
const container = screen.getByTestId("track-item-1");
fireEvent.mouseEnter(container);
fireEvent.click(screen.getByTestId("track-more-button-1"));
expect(screen.getByTestId("track-more-repost-1")).toHaveTextContent(
"Unrepost",
);
});

it("switches back to Repost when unreposted", async () => {
render(<TrackItem {...defaultProps} initialReposted />);
const container = screen.getByTestId("track-item-1");
fireEvent.mouseEnter(container);
fireEvent.click(screen.getByTestId("track-more-button-1"));
fireEvent.click(screen.getByTestId("track-more-repost-1"));
expect(mockLikesStore.toggleRepost).toHaveBeenCalled();
expect(screen.getByTestId("track-more-repost-1")).toHaveTextContent(
"Repost",
);
});
});
8 changes: 8 additions & 0 deletions src/components/Profile/ShareModal/ShareModal.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ describe("ShareModal", () => {

it("calls onClose when close button is clicked", () => {
render(<ShareModal url={testUrl} onClose={mockOnClose} />);
expect(screen.getByTestId("share-modal-close")).toHaveClass("top-14");
expect(screen.getByTestId("share-modal-close")).toHaveClass("right-4");
expect(screen.getByTestId("share-modal-close")).toHaveClass(
"bg-bg-actionbutton",
);
expect(screen.getByTestId("share-modal-close")).toHaveClass(
"text-text-secondary",
);
fireEvent.click(screen.getByTestId("share-modal-close"));
expect(mockOnClose).toHaveBeenCalled();
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/Profile/ShareModal/ShareModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const ShareModal: React.FC<ShareModalProps> = ({ url, onClose }) => {
<button
data-test="share-modal-close"
onClick={onClose}
className="cursor-pointer fixed top-3 right-3 text-bg text-lg hover:opacity-70 z-50 bg-bg-inverted rounded-full w-8 h-8 flex items-center justify-center mt-6 mr-6"
className="fixed top-14 right-4 sm:top-16 sm:right-6 z-50 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-bg-actionbutton text-text-secondary hover:text-text hover:opacity-80"
>
<i className="fa-solid fa-xmark" />
</button>
Expand Down
98 changes: 98 additions & 0 deletions src/components/UI/FollowButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import React from "react";
import FollowButton from "@/components/UI/FollowButton";

const mockToggleFollow = vi.fn();
const mockFollowUser = vi.fn();
const mockUnfollowUser = vi.fn();
const mockSetUser = vi.fn();

vi.mock("@/stores/auth.store", () => ({
useAuthStore: vi.fn(),
}));

vi.mock("@/services/user.service", () => ({
followUser: (...args: unknown[]) => mockFollowUser(...args),
unfollowUser: (...args: unknown[]) => mockUnfollowUser(...args),
}));

import { useAuthStore } from "@/stores/auth.store";

describe("UI FollowButton", () => {
beforeEach(() => {
vi.clearAllMocks();
mockFollowUser.mockResolvedValue(undefined);
mockUnfollowUser.mockResolvedValue(undefined);
vi.mocked(useAuthStore).mockReturnValue({
user: {
id: "user-1",
username: "me",
following_ids: [],
},
toggleFollow: mockToggleFollow,
setUser: mockSetUser,
} as any);
});

it("renders Follow when not following", () => {
render(<FollowButton username="travis-scott" userId="artist-1" />);

expect(screen.getByTestId("follow-button-travis-scott")).toHaveTextContent(
"Follow",
);
});

it("follows a user when clicked", async () => {
render(<FollowButton username="travis-scott" userId="artist-1" />);

fireEvent.click(screen.getByTestId("follow-button-travis-scott"));

await waitFor(() => {
expect(mockToggleFollow).toHaveBeenCalledWith("travis-scott", [
"artist-1",
]);
expect(mockFollowUser).toHaveBeenCalledWith("artist-1");
});
});

it("unfollows a user when already following", async () => {
vi.mocked(useAuthStore).mockReturnValue({
user: {
id: "user-1",
username: "me",
following_ids: ["artist-1"],
},
toggleFollow: mockToggleFollow,
setUser: mockSetUser,
} as any);

render(<FollowButton username="travis-scott" userId="artist-1" />);

expect(screen.getByTestId("follow-button-travis-scott")).toHaveTextContent(
"Following",
);

fireEvent.click(screen.getByTestId("follow-button-travis-scott"));

await waitFor(() => {
expect(mockToggleFollow).toHaveBeenCalledWith("travis-scott", [
"artist-1",
]);
expect(mockUnfollowUser).toHaveBeenCalledWith("artist-1");
});
});

it("shows Blocked when blocked without following", () => {
render(
<FollowButton username="travis-scott" userId="artist-1" blocked>
Blocked
</FollowButton>,
);

expect(screen.getByTestId("follow-button-travis-scott")).toHaveTextContent(
"Blocked",
);
expect(screen.getByTestId("follow-button-travis-scott")).toBeDisabled();
});
});
145 changes: 145 additions & 0 deletions src/components/UI/TrackItem.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import React from "react";
import TrackItem from "@/components/UI/TrackItem";

const mockNavigate = vi.fn();
const mockToggleTrack = vi.fn();
const mockToggleRepost = vi.fn().mockResolvedValue(undefined);
const mockToggleDownload = vi.fn();
const mockSetTrack = vi.fn();
const mockTogglePlay = vi.fn();

vi.mock("react-router-dom", () => ({
useNavigate: () => mockNavigate,
}));

vi.mock("@/stores/player.store", () => ({
usePlayerStore: Object.assign(
vi.fn(() => ({
currentTrack: null,
isPlaying: false,
setTrack: mockSetTrack,
togglePlay: mockTogglePlay,
})),
{
getState: vi.fn(() => ({
playContext: vi.fn(),
})),
},
),
}));

const mockLikesState = {
isTrackLiked: vi.fn().mockReturnValue(false),
isTrackReposted: vi.fn().mockReturnValue(false),
repostedTrackIds: [] as string[],
itemStats: {} as Record<string, { isReposted?: boolean }>,
toggleTrack: mockToggleTrack,
toggleRepost: mockToggleRepost,
};

vi.mock("@/stores/likes.store", () => ({
useLikesStore: vi.fn((selector) => {
if (typeof selector === "function") {
return selector(mockLikesState);
}
return mockLikesState;
}),
}));

vi.mock("@/stores/history.store", () => ({
useHistoryStore: vi.fn(() => ({
addTrack: vi.fn(),
})),
}));

vi.mock("@/stores/auth.store", () => ({
useAuthStore: vi.fn(() => ({
user: { id: "user-123", username: "me", isPro: true },
})),
}));

vi.mock("@/stores/useDownload", () => ({
useDownloadStore: vi.fn(() => ({
isDownloaded: vi.fn().mockReturnValue(false),
toggleDownload: mockToggleDownload,
})),
}));

vi.mock("@/services/track.service", () => ({
getRelatedTracks: vi.fn().mockResolvedValue({ tracks: [] }),
}));

vi.mock("@/pages/[username]/[trackSlug]/components/SharePopup", () => ({
default: () => <div data-test="share-popup" />,
}));

vi.mock("@/components/playlist/AddToPlaylistModal", () => ({
default: () => <div data-test="add-to-playlist-modal" />,
}));

vi.mock("@/components/UI/CoverImage", () => ({
default: (props: any) => <img {...props} />,
}));

const defaultProps = {
id: "track-1",
title: "Butterfly Effect",
artist: "Travis Scott",
artistUsername: "travisscott",
trackSlug: "butterfly-effect",
coverUrl: "https://example.com/cover.jpg",
plays: 1200,
likes: 250,
reposts: 42,
comments: 7,
};

describe("UI TrackItem", () => {
beforeEach(() => {
vi.clearAllMocks();
mockLikesState.isTrackLiked.mockReturnValue(false);
mockLikesState.isTrackReposted.mockReturnValue(false);
mockLikesState.repostedTrackIds = [];
mockLikesState.itemStats = {};
});

it("renders the track metadata", () => {
render(<TrackItem {...defaultProps} />);

expect(screen.getByText("Butterfly Effect")).toBeInTheDocument();
expect(screen.getByText("Travis Scott")).toBeInTheDocument();
});

it("shows Unrepost for already reposted tracks and flips back after click", async () => {
render(<TrackItem {...defaultProps} initialReposted />);

fireEvent.mouseEnter(screen.getByTestId("track-item-track-1"));
fireEvent.click(screen.getByTestId("track-more-button-track-1"));

expect(screen.getByTestId("track-more-repost-track-1")).toHaveTextContent(
"Unrepost",
);

fireEvent.click(screen.getByTestId("track-more-repost-track-1"));

await waitFor(() => {
expect(mockToggleRepost).toHaveBeenCalledTimes(1);
expect(screen.getByTestId("track-more-repost-track-1")).toHaveTextContent(
"Repost",
);
});
});

it("shows Download for pro users in the more menu", () => {
render(<TrackItem {...defaultProps} />);

fireEvent.mouseEnter(screen.getByTestId("track-item-track-1"));
fireEvent.click(screen.getByTestId("track-more-button-track-1"));

expect(screen.getByTestId("track-more-download-track-1")).toHaveTextContent(
"Download",
);
});
});
Loading
Loading