diff --git a/src/components/Premium/HeroSection.test.tsx b/src/components/Premium/HeroSection.test.tsx
deleted file mode 100644
index f007ce4a..00000000
--- a/src/components/Premium/HeroSection.test.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { render, screen } from "@testing-library/react";
-import { describe, it, expect, vi } from "vitest";
-import HeroSection from "./HeroSection";
-
-describe("HeroSection", () => {
- it("renders the hero copy, CTA, and plan link", () => {
- render(
- ,
- );
-
- expect(screen.getByRole("heading", { name: "Reach more listeners." })).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Get Premium" })).toBeInTheDocument();
- expect(screen.getByRole("link", { name: "See plan" })).toHaveAttribute(
- "href",
- "#pricing-cards",
- );
- expect(screen.getByText("Grow your audience")).toBeInTheDocument();
- expect(screen.getByText("Know your audience")).toBeInTheDocument();
- expect(screen.getByText("Upload unlimited tracks")).toBeInTheDocument();
- expect(screen.getByText("Distribution is included")).toBeInTheDocument();
- });
-
- it("shows the loading label when the CTA is starting", () => {
- render(
- ,
- );
-
- expect(screen.getByRole("button", { name: /loading/i })).toBeDisabled();
- });
-});
diff --git a/src/components/layout/AuthMainLayout.test.tsx b/src/components/layout/AuthMainLayout.test.tsx
deleted file mode 100644
index f07d0a99..00000000
--- a/src/components/layout/AuthMainLayout.test.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { render, waitFor } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
-import { describe, expect, it, vi, beforeEach } from "vitest";
-import AuthMainLayout from "@/components/layout/AuthMainLayout";
-
-const mockSetUser = vi.fn();
-const mockHydrateFromApi = vi.fn();
-const mockGetMe = vi.fn();
-const mockGetMySubscription = vi.fn();
-
-vi.mock("@/stores/auth.store", () => ({
- useAuthStore: (selector: (state: { isAuthenticated: boolean; setUser: typeof mockSetUser }) => unknown) =>
- selector({ isAuthenticated: true, setUser: mockSetUser }),
-}));
-
-vi.mock("@/stores/likes.store", () => ({
- useLikesStore: (selector: (state: { hydrateFromApi: typeof mockHydrateFromApi }) => unknown) =>
- selector({ hydrateFromApi: mockHydrateFromApi }),
-}));
-
-vi.mock("@/services/auth.service", () => ({
- getMe: () => mockGetMe(),
- normalizeDateOfBirth: (value: string | null | undefined) => value ?? null,
-}));
-
-vi.mock("@/services/api/upload/subscription.service", () => ({
- getMySubscription: () => mockGetMySubscription(),
-}));
-
-vi.mock("@/components/layout/MainNavbar", () => ({
- default: () =>
,
-}));
-
-vi.mock("@/components/player/StickyPlayer", () => ({
- default: () => ,
-}));
-
-describe("AuthMainLayout", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- mockGetMe.mockResolvedValue({
- data: {
- id: "u1",
- username: "me",
- display_name: "Me",
- first_name: "Test",
- last_name: "User",
- bio: "",
- email: "me@example.com",
- role: "listener",
- profile_picture: null,
- cover_photo: null,
- city: null,
- country: null,
- following_ids: [],
- followers_ids: [],
- date_of_birth: "2001-02-02",
- gender: "female",
- },
- });
- mockGetMySubscription.mockResolvedValue({
- data: { user_subscription_id: null },
- });
- });
-
- it("hydrates auth store with basic profile fields from the backend", async () => {
- render(
-
-
- ,
- );
-
- await waitFor(() => {
- expect(mockSetUser).toHaveBeenCalledWith(
- expect.objectContaining({
- date_of_birth: "2001-02-02",
- gender: "female",
- }),
- );
- });
-
- expect(mockHydrateFromApi).toHaveBeenCalled();
- });
-});
diff --git a/src/components/library/useLibraryData.ts b/src/components/library/useLibraryData.ts
index 66381ce8..05261e51 100644
--- a/src/components/library/useLibraryData.ts
+++ b/src/components/library/useLibraryData.ts
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { getRecentlyPlayed } from "@/services/api/discover.service";
import { mapRecentlyPlayedEntry } from "@/services/api/discover.mapper";
-import { getMyFollowing } from "@/services/api/library.service";
+import { getMyPlaylists as getMyPlaylistsLib, getMyFollowing } from "@/services/api/library.service";
import { getUserById } from "@/services/user.service";
import {
getMyPlaylists as getMyPlaylistsApi,
@@ -15,6 +15,7 @@ import { useLikesStore } from "@/stores/likes.store";
import { useHistoryStore } from "@/stores/history.store";
import { useAuthStore } from "@/stores/auth.store";
import { mapPlaylistToCard, mapAlbumToCard, mapFollowingToUser } from "./library.mappers";
+import { mockRecentlyPlayedTracks } from "@/services/mocks/discover";
import type { FilterOption } from "./PlaylistFilterDropdown";
const isAlbum = (p: Playlist) => p.is_album_view || p.subtype === "album";
@@ -27,13 +28,13 @@ export function useLibraryData() {
const [playlistFilter, setPlaylistFilter] = useState("All");
const {
- likedTracks,
- likedStations,
- likedPlaylists,
- likedAlbums: storeLikedAlbums,
- likedRadioTracks,
- likedMixes,
- likedGenres,
+ likedTracks = [],
+ likedStations = [],
+ likedPlaylists = [],
+ likedAlbums: storeLikedAlbums = [],
+ likedRadioTracks = [],
+ likedMixes = [],
+ likedGenres = [],
} = useLikesStore();
const { user } = useAuthStore();
const { entries } = useHistoryStore();
@@ -61,18 +62,21 @@ export function useLibraryData() {
useEffect(() => {
const displayName = user?.displayName ?? user?.username ?? "";
+ getMyPlaylistsLib()
+ .then((items) =>
+ setPlaylists(
+ items.map((p) => mapPlaylistToCard(p as unknown as Playlist, displayName || p.owner_user_id)),
+ ),
+ )
+ .catch(() => setPlaylists([]));
+ }, []);
+ useEffect(() => {
Promise.all([
getMyPlaylistsApi({ limit: 50 }),
getLikedPlaylists({ limit: 50 }),
])
.then(([created, liked]) => {
- setPlaylists(
- created.data.items
- .filter((p) => !isAlbum(p))
- .map((p) => mapPlaylistToCard(p, displayName || p.owner_user_id)),
- );
-
const merged = [
...created.data.items.filter(isAlbum),
...liked.data.items.filter(isAlbum),
@@ -86,17 +90,16 @@ export function useLibraryData() {
}),
);
})
- .catch(() => {
- setPlaylists([]);
- setAlbums([]);
- });
+ .catch(() => setAlbums([]));
}, []);
const recentEntries = (() => {
const list =
entries.length > 0
? entries
- : recentlyPlayedApi.map((t) => ({ type: "track" as const, item: t, playedAt: "" }));
+ : recentlyPlayedApi.length > 0
+ ? recentlyPlayedApi.map((t) => ({ type: "track" as const, item: t, playedAt: "" }))
+ : mockRecentlyPlayedTracks.map((t) => ({ type: "track" as const, item: t, playedAt: "" }));
const seen = new Set();
return list.filter((e) => {
@@ -137,7 +140,9 @@ export function useLibraryData() {
})();
const displayedFollowing = (() => {
- const seenUsernames = new Set(followingUsers.map((u) => u.username));
+ const followingIdSet = new Set(user?.following_ids ?? []);
+ const filtered = followingUsers.filter((u) => followingIdSet.has(u.username));
+ const seenUsernames = new Set(filtered.map((u) => u.username));
const synthetic: User[] = (user?.following_ids ?? [])
.filter((username) => !seenUsernames.has(username))
.map((username, i) => ({
@@ -146,7 +151,7 @@ export function useLibraryData() {
displayName: username,
followers: 0,
}));
- return [...followingUsers, ...synthetic];
+ return [...filtered, ...synthetic];
})();
return {
diff --git a/src/components/playlist/test/PlaylistSidebarAndComponent.test.tsx b/src/components/playlist/test/PlaylistSidebarAndComponent.test.tsx
deleted file mode 100644
index 26616045..00000000
--- a/src/components/playlist/test/PlaylistSidebarAndComponent.test.tsx
+++ /dev/null
@@ -1,188 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
-import React from "react";
-import { render, screen, waitFor, fireEvent } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
-import PlaylistSidebar from "../PlaylistSidebar";
-import PlaylistComponent from "../PlaylistComponent";
-import {
- getPlaylistsByUser,
- getPlaylistLikers,
- getPlaylistReposters,
-} from "@/services/api/playlist/playlist.service";
-import { getUserById } from "@/services/user.service";
-import { usePlayerStore } from "@/stores/player.store";
-import { useAuthStore } from "@/stores/auth.store";
-
-// ── Mocks ────────────────────────────────────────────────────
-
-vi.mock("@/services/api/playlist/playlist.service", () => ({
- getPlaylistsByUser: vi.fn(),
- getPlaylistLikers: vi.fn(),
- getPlaylistReposters: vi.fn(),
-}));
-
-vi.mock("@/services/user.service", () => ({
- getUserById: vi.fn(),
-}));
-
-vi.mock("@/stores/auth.store", () => ({
- useAuthStore: Object.assign(vi.fn(), {
- subscribe: vi.fn(),
- }),
-}));
-
-vi.mock("@/stores/player.store", () => ({
- usePlayerStore: vi.fn(),
-}));
-
-vi.mock("@/pages/[username]/[trackSlug]/components/SharePopup", () => ({
- default: ({ onClose }: any) => (
-
-
-
- ),
-}));
-
-vi.mock("@/services/audioService", () => ({
- audio: { src: "" },
- seekAudio: vi.fn(),
- setGlobalWaveSurfer: vi.fn(),
- setTrackLoadedLocally: vi.fn(),
-}));
-
-vi.mock("wavesurfer.js", () => ({
- default: {
- create: vi.fn(() => ({
- on: vi.fn(),
- destroy: vi.fn(),
- })),
- },
-}));
-
-// ── Shared test data ─────────────────────────────────────────────────────────
-
-const mockUser = { id: "u-1", username: "testuser", displayName: "Test User" };
-
-const mockPlaylistDetails = {
- playlist_id: "pl-1",
- name: "Test Playlist",
- is_public: true,
- track_count: 3,
- owner_user_id: "u-1",
- tracks: [{ track_id: "t-1", title: "Track 1", artist_name: "Artist A" }],
-};
-
-const sidebarPlaylists = [
- { playlist_id: "pl-2", name: "Another Set" },
- { playlist_id: "pl-3", name: "Third Set" },
-];
-
-describe("PlaylistSidebar", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- vi.mocked(useAuthStore).mockReturnValue({ user: mockUser } as any);
- vi.mocked(getUserById).mockResolvedValue({
- id: "u-1",
- username: "testuser",
- display_name: "Test User",
- } as any);
- vi.mocked(getPlaylistsByUser).mockResolvedValue({
- data: { items: sidebarPlaylists, meta: { total: 2 } },
- } as any);
- vi.mocked(getPlaylistLikers).mockResolvedValue({
- data: [],
- } as any);
- vi.mocked(getPlaylistReposters).mockResolvedValue({
- data: [],
- } as any);
- });
-
- it("renders the sidebar container", async () => {
- render(
-
-
- ,
- );
- await waitFor(() =>
- expect(screen.getByTestId("playlist-sidebar")).toBeInTheDocument(),
- );
- });
-
- it("renders 'Playlists from this user' heading", () => {
- render(
-
-
- ,
- );
- expect(screen.getByText(/Playlists from this user/i)).toBeInTheDocument();
- });
-
- it("renders 'View all' link", () => {
- render(
-
-
- ,
- );
- expect(screen.getByText("View all")).toBeInTheDocument();
- });
-
- it("navigates 'View all' to the user's sets page", async () => {
- render(
-
-
- ,
- );
- await waitFor(() =>
- expect(screen.getByRole("link", { name: "View all" })).toHaveAttribute(
- "href",
- "/testuser",
- ),
- );
- });
-
- it("renders other playlists after load", async () => {
- render(
-
-
- ,
- );
- await waitFor(() =>
- expect(screen.getByText("Another Set")).toBeInTheDocument(),
- );
- });
-
- it("does not render the current playlist in the sidebar list", async () => {
- render(
-
-
- ,
- );
- await waitFor(() =>
- expect(screen.queryByText("Test Playlist")).not.toBeInTheDocument(),
- );
- });
-
- it("shows spinner while loading", () => {
- vi.mocked(getPlaylistsByUser).mockReturnValue(new Promise(() => {}));
- render(
-
-
- ,
- );
- expect(document.querySelector(".animate-spin")).toBeInTheDocument();
- });
-
- it("shows 'No other playlists' when list is empty", async () => {
- vi.mocked(getPlaylistsByUser).mockResolvedValue({
- data: { items: [] },
- } as any);
- render(
-
-
- ,
- );
- await waitFor(() =>
- expect(screen.getByText(/No other playlists/)).toBeInTheDocument(),
- );
- });
-});
diff --git a/src/pages/admin/tests/AdminUsersPage.test.tsx b/src/pages/admin/tests/AdminUsersPage.test.tsx
deleted file mode 100644
index a8b698e5..00000000
--- a/src/pages/admin/tests/AdminUsersPage.test.tsx
+++ /dev/null
@@ -1,371 +0,0 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
-import React from "react";
-import { render, screen, fireEvent, waitFor } from "@testing-library/react";
-import { MemoryRouter } from "react-router-dom";
-import AdminUsersPage from "../users/AdminUsersPage";
-
-const mockAxiosGet = vi.hoisted(() => vi.fn());
-const mockAdminSuspendUser = vi.hoisted(() => vi.fn());
-const mockAdminReinstateUser = vi.hoisted(() => vi.fn());
-const mockAdminWarnUser = vi.hoisted(() => vi.fn());
-
-vi.mock("@/services/api/axiosInstance", () => ({
- default: { get: mockAxiosGet },
-}));
-
-vi.mock("@/services/api/admin.service", () => ({
- adminSuspendUser: mockAdminSuspendUser,
- adminReinstateUser: mockAdminReinstateUser,
- adminWarnUser: mockAdminWarnUser,
-}));
-
-const makeApiUser = (overrides: Record = {}) => ({
- id: "u1",
- display_name: "Jane Doe",
- username: "janedoe",
- role: "listener",
- is_verified: false,
- follower_count: 120,
- profile_picture: null,
- ...overrides,
-});
-
-const usersResponse = (users: ReturnType[]) => ({
- data: { data: { users }, pagination: { total: users.length } },
-});
-
-beforeEach(() => {
- mockAxiosGet.mockResolvedValue(usersResponse([]));
- mockAdminSuspendUser.mockResolvedValue(undefined);
- mockAdminReinstateUser.mockResolvedValue(undefined);
- mockAdminWarnUser.mockResolvedValue(undefined);
-});
-
-describe("AdminUsersPage", () => {
- it("renders the User Management heading", () => {
- render(
-
-
- ,
- );
- expect(screen.getByText("User Management")).toBeInTheDocument();
- });
-
- it("renders the user search input", () => {
- render(
-
-
- ,
- );
- expect(screen.getByTestId("input-user-search")).toBeInTheDocument();
- });
-
- it("renders the role filter select", () => {
- render(
-
-
- ,
- );
- expect(screen.getByTestId("select-role-filter")).toBeInTheDocument();
- });
-
- it("renders the refresh button", () => {
- render(
-
-
- ,
- );
- expect(screen.getByTestId("btn-refresh")).toBeInTheDocument();
- });
-
- it("shows 'Type a name to search users' when search is empty", async () => {
- render(
-
-
- ,
- );
- await waitFor(() => {
- expect(screen.getByText("Type a name to search users")).toBeInTheDocument();
- });
- });
-
- it("does not call API when search is empty on mount", () => {
- render(
-
-
- ,
- );
- expect(mockAxiosGet).not.toHaveBeenCalled();
- });
-
- it("renders user rows after search returns results", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser()]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(
- () => {
- expect(screen.getByText("Jane Doe")).toBeInTheDocument();
- },
- { timeout: 1000 },
- );
- });
-
- it("shows 'No users found' when search returns empty", async () => {
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "nobody" },
- });
- await waitFor(
- () => {
- expect(screen.getByText("No users found")).toBeInTheDocument();
- },
- { timeout: 1000 },
- );
- });
-
- it("shows action button for non-admin users", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(
- () => {
- expect(screen.getByTestId("btn-user-actions-u1")).toBeInTheDocument();
- },
- { timeout: 1000 },
- );
- });
-
- it("opens user action dropdown on button click", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- expect(screen.getByTestId("btn-send-warning")).toBeInTheDocument();
- expect(screen.getByTestId("btn-suspend-account")).toBeInTheDocument();
- });
-
- it("opens suspend modal when Suspend Account is clicked", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- expect(screen.getByText("Suspend Account")).toBeInTheDocument();
- });
-
- it("closes suspend modal when cancel is clicked", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- fireEvent.click(screen.getByTestId("btn-suspend-cancel"));
- expect(screen.queryByText("Suspend Account")).not.toBeInTheDocument();
- });
-
- it("confirm button is disabled when suspend reason is empty", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- const confirmBtn = screen.getByTestId("btn-suspend-confirm");
- expect(confirmBtn).toBeDisabled();
- });
-
- it("enables confirm button after entering a suspend reason", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- fireEvent.change(screen.getByTestId("textarea-suspend-reason"), {
- target: { value: "Violation of terms" },
- });
- expect(screen.getByTestId("btn-suspend-confirm")).not.toBeDisabled();
- });
-
- it("calls adminSuspendUser when suspend is confirmed", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- fireEvent.change(screen.getByTestId("textarea-suspend-reason"), {
- target: { value: "Repeated violations" },
- });
- fireEvent.click(screen.getByTestId("btn-suspend-confirm"));
- await waitFor(() => {
- expect(mockAdminSuspendUser).toHaveBeenCalledWith("u1", "Repeated violations");
- });
- });
-
- it("opens warn modal when Send Warning is clicked", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-send-warning"));
- // "Send Warning" appears in both the modal heading and the confirm button
- expect(screen.getAllByText("Send Warning").length).toBeGreaterThanOrEqual(1);
- });
-
- it("renders warning category buttons in warn modal", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-send-warning"));
- expect(
- screen.getByTestId("btn-warn-reason-copyright_strike"),
- ).toBeInTheDocument();
- expect(
- screen.getByTestId("btn-warn-reason-content_policy_violation"),
- ).toBeInTheDocument();
- });
-
- it("calls adminWarnUser when warn is confirmed", async () => {
- mockAxiosGet.mockResolvedValue(usersResponse([makeApiUser({ id: "u1", role: "listener" })]));
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-send-warning"));
- fireEvent.click(screen.getByTestId("btn-warn-confirm"));
- await waitFor(() => {
- expect(mockAdminWarnUser).toHaveBeenCalledWith(
- "u1",
- expect.objectContaining({ reason: "content_policy_violation" }),
- );
- });
- });
-
- it("clears search when clear button is clicked", async () => {
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- fireEvent.click(screen.getByTestId("btn-clear-search"));
- const input = screen.getByTestId("input-user-search") as HTMLInputElement;
- expect(input.value).toBe("");
- });
-
- it("shows reinstate button for suspended users", async () => {
- mockAxiosGet.mockResolvedValue(
- usersResponse([makeApiUser({ id: "u1", role: "listener" })]),
- );
- render(
-
-
- ,
- );
- fireEvent.change(screen.getByTestId("input-user-search"), {
- target: { value: "jane" },
- });
- await waitFor(() => expect(screen.getByText("Active")).toBeInTheDocument(), {
- timeout: 1000,
- });
- await waitFor(() => fireEvent.click(screen.getByTestId("btn-user-actions-u1")), {
- timeout: 1000,
- });
- fireEvent.click(screen.getByTestId("btn-suspend-account"));
- fireEvent.change(screen.getByTestId("textarea-suspend-reason"), {
- target: { value: "Reason" },
- });
- fireEvent.click(screen.getByTestId("btn-suspend-confirm"));
- await waitFor(() => {
- expect(mockAdminSuspendUser).toHaveBeenCalled();
- });
- });
-});
diff --git a/src/pages/creator/upload/tests/UploadDetailsForm.test.tsx b/src/pages/creator/upload/tests/UploadDetailsForm.test.tsx
index 17ec7e2a..7e5371c7 100644
--- a/src/pages/creator/upload/tests/UploadDetailsForm.test.tsx
+++ b/src/pages/creator/upload/tests/UploadDetailsForm.test.tsx
@@ -37,7 +37,7 @@ const renderForm = (props = {}) => {
const utils = render(
-
+ ,
);
return { ref, ...utils };
};
@@ -55,33 +55,36 @@ describe("UploadDetailsForm", () => {
// ── Rendering ──────────────────────────────────────────────────────────────
- it("renders the form container", () => {
- renderForm();
- expect(screen.getByTestId("upload-details-form")).toBeInTheDocument();
- });
-
it("auto-populates title from the audio file name", () => {
renderForm();
- const titleInput = screen.getByTestId("upload-title-input") as HTMLInputElement;
+ const titleInput = screen.getByTestId(
+ "upload-title-input",
+ ) as HTMLInputElement;
expect(titleInput.value).toBe("my-track");
});
it("sets title to 'Recorded Audio' when audioData is a Blob", () => {
const blob = new Blob(["audio"], { type: "audio/wav" });
renderForm({ audioData: blob });
- const titleInput = screen.getByTestId("upload-title-input") as HTMLInputElement;
+ const titleInput = screen.getByTestId(
+ "upload-title-input",
+ ) as HTMLInputElement;
expect(titleInput.value).toBe("Recorded Audio");
});
it("auto-populates track link slug from the file name", () => {
renderForm();
- const linkInput = screen.getByTestId("upload-track-link-input") as HTMLInputElement;
+ const linkInput = screen.getByTestId(
+ "upload-track-link-input",
+ ) as HTMLInputElement;
expect(linkInput.value).toBe("my-track");
});
it("auto-populates artist field with the logged-in username", () => {
renderForm();
- const artistInput = screen.getByTestId("upload-artists-input") as HTMLInputElement;
+ const artistInput = screen.getByTestId(
+ "upload-artists-input",
+ ) as HTMLInputElement;
expect(artistInput.value).toBe("testuser");
});
@@ -102,13 +105,17 @@ describe("UploadDetailsForm", () => {
it("renders public privacy option selected by default", () => {
renderForm();
- const publicRadio = screen.getByTestId("upload-privacy-public-radio") as HTMLInputElement;
+ const publicRadio = screen.getByTestId(
+ "upload-privacy-public-radio",
+ ) as HTMLInputElement;
expect(publicRadio.checked).toBe(true);
});
it("renders private privacy option unselected by default", () => {
renderForm();
- const privateRadio = screen.getByTestId("upload-privacy-private-radio") as HTMLInputElement;
+ const privateRadio = screen.getByTestId(
+ "upload-privacy-private-radio",
+ ) as HTMLInputElement;
expect(privateRadio.checked).toBe(false);
});
@@ -129,7 +136,9 @@ describe("UploadDetailsForm", () => {
const titleInput = screen.getByTestId("upload-title-input");
await userEvent.clear(titleInput);
await userEvent.type(titleInput, "Hello World");
- const linkInput = screen.getByTestId("upload-track-link-input") as HTMLInputElement;
+ const linkInput = screen.getByTestId(
+ "upload-track-link-input",
+ ) as HTMLInputElement;
expect(linkInput.value).toBe("hello-world");
});
@@ -138,7 +147,9 @@ describe("UploadDetailsForm", () => {
const titleInput = screen.getByTestId("upload-title-input");
await userEvent.clear(titleInput);
await userEvent.type(titleInput, "My New Song");
- const linkInput = screen.getByTestId("upload-track-link-input") as HTMLInputElement;
+ const linkInput = screen.getByTestId(
+ "upload-track-link-input",
+ ) as HTMLInputElement;
expect(linkInput.value).toBe("my-new-song");
});
@@ -147,7 +158,9 @@ describe("UploadDetailsForm", () => {
const titleInput = screen.getByTestId("upload-title-input");
await userEvent.clear(titleInput);
await userEvent.type(titleInput, "UPPERCASE");
- const linkInput = screen.getByTestId("upload-track-link-input") as HTMLInputElement;
+ const linkInput = screen.getByTestId(
+ "upload-track-link-input",
+ ) as HTMLInputElement;
expect(linkInput.value).toBe("uppercase");
});
@@ -155,7 +168,9 @@ describe("UploadDetailsForm", () => {
it("switches to private when private radio is clicked", async () => {
renderForm();
- const privateRadio = screen.getByTestId("upload-privacy-private-radio") as HTMLInputElement;
+ const privateRadio = screen.getByTestId(
+ "upload-privacy-private-radio",
+ ) as HTMLInputElement;
await userEvent.click(privateRadio);
expect(privateRadio.checked).toBe(true);
});
@@ -163,7 +178,9 @@ describe("UploadDetailsForm", () => {
it("deselects public when private is chosen", async () => {
renderForm();
await userEvent.click(screen.getByTestId("upload-privacy-private-radio"));
- const publicRadio = screen.getByTestId("upload-privacy-public-radio") as HTMLInputElement;
+ const publicRadio = screen.getByTestId(
+ "upload-privacy-public-radio",
+ ) as HTMLInputElement;
expect(publicRadio.checked).toBe(false);
});
@@ -171,7 +188,9 @@ describe("UploadDetailsForm", () => {
renderForm();
await userEvent.click(screen.getByTestId("upload-privacy-private-radio"));
await userEvent.click(screen.getByTestId("upload-privacy-public-radio"));
- const publicRadio = screen.getByTestId("upload-privacy-public-radio") as HTMLInputElement;
+ const publicRadio = screen.getByTestId(
+ "upload-privacy-public-radio",
+ ) as HTMLInputElement;
expect(publicRadio.checked).toBe(true);
});
@@ -210,7 +229,7 @@ describe("UploadDetailsForm", () => {
const { ref } = renderForm({ onSuccess });
await ref.current!.triggerSubmit();
await waitFor(() =>
- expect(onSuccess).toHaveBeenCalledWith("new-track-id-123")
+ expect(onSuccess).toHaveBeenCalledWith("new-track-id-123"),
);
});
@@ -229,10 +248,10 @@ describe("UploadDetailsForm", () => {
await userEvent.clear(titleInput);
await ref.current!.triggerSubmit();
await waitFor(() =>
- expect(screen.getByTestId("upload-error-message")).toBeInTheDocument()
+ expect(screen.getByTestId("upload-error-message")).toBeInTheDocument(),
);
expect(screen.getByTestId("upload-error-message")).toHaveTextContent(
- /track title is required/i
+ /track title is required/i,
);
});
@@ -242,15 +261,15 @@ describe("UploadDetailsForm", () => {
await act(() => ref.current!.triggerSubmit());
expect(mockUploadTrack).not.toHaveBeenCalled();
});
-
+
it("shows error when audioData is null", async () => {
const { ref } = renderForm({ audioData: null });
await ref.current!.triggerSubmit();
await waitFor(() =>
- expect(screen.getByTestId("upload-error-message")).toBeInTheDocument()
+ expect(screen.getByTestId("upload-error-message")).toBeInTheDocument(),
);
expect(screen.getByTestId("upload-error-message")).toHaveTextContent(
- /missing audio data/i
+ /missing audio data/i,
);
});
@@ -270,10 +289,10 @@ describe("UploadDetailsForm", () => {
const { ref } = renderForm();
await ref.current!.triggerSubmit();
await waitFor(() =>
- expect(screen.getByTestId("upload-error-message")).toBeInTheDocument()
+ expect(screen.getByTestId("upload-error-message")).toBeInTheDocument(),
);
expect(screen.getByTestId("upload-error-message")).toHaveTextContent(
- /upload limit reached/i
+ /upload limit reached/i,
);
});
@@ -282,9 +301,7 @@ describe("UploadDetailsForm", () => {
const setIsLoadingParent = vi.fn();
const { ref } = renderForm({ setIsLoadingParent });
await ref.current!.triggerSubmit();
- await waitFor(() =>
- expect(setIsLoadingParent).toHaveBeenCalledWith(false)
- );
+ await waitFor(() => expect(setIsLoadingParent).toHaveBeenCalledWith(false));
});
it("does not call onSuccess when upload fails", async () => {
@@ -296,43 +313,6 @@ describe("UploadDetailsForm", () => {
expect(onSuccess).not.toHaveBeenCalled();
});
- it("submits optional metadata fields and cover image", async () => {
- const user = userEvent.setup();
- const onProgress = vi.fn();
- const { ref } = renderForm({ onProgress });
-
- const descriptionInput = screen.getByTestId("upload-description-input");
- const artistsInput = screen.getByTestId("upload-artists-input");
- const tagsInput = screen.getByTestId("upload-tags-input");
- const coverInput = screen.getByTestId("cover-image-input") as HTMLInputElement;
- const genreInput = screen.getByTestId("dropdown-input");
- const coverFile = new File(["cover"], "cover.png", { type: "image/png" });
-
- await user.clear(descriptionInput);
- await user.type(descriptionInput, " A detailed description ");
- await user.clear(artistsInput);
- await user.type(artistsInput, "Artist One, Artist Two");
- await user.clear(tagsInput);
- await user.type(tagsInput, "tag-1, tag-2");
- await user.click(screen.getByTestId("upload-privacy-private-radio"));
- await user.upload(coverInput, coverFile);
- await user.click(genreInput);
- await user.click(await screen.findByText("Electronic"));
-
- await ref.current!.triggerSubmit();
- await waitFor(() => expect(mockUploadTrack).toHaveBeenCalledTimes(1));
-
- const [payload, progressCallback] = mockUploadTrack.mock.calls[0];
- expect(payload.description).toBe("A detailed description");
- expect(payload.genre).toBe("Electronic");
- expect(payload.artists).toBe("Artist One, Artist Two");
- expect(payload.is_public).toBe(false);
- expect(payload.cover_image).toBe(coverFile);
- expect(payload.tags).toEqual(["tag-1", "tag-2"]);
- expect(typeof progressCallback).toBe("function");
- expect(onProgress).not.toHaveBeenCalled();
- });
-
it("forwards upload progress updates from the service", async () => {
const progress = vi.fn();
mockUploadTrack.mockImplementationOnce(async (_payload, onProgress) => {
diff --git a/src/pages/you/albums/AlbumSlugPage.tsx b/src/pages/you/albums/AlbumSlugPage.tsx
index 5e9fc828..55705db0 100644
--- a/src/pages/you/albums/AlbumSlugPage.tsx
+++ b/src/pages/you/albums/AlbumSlugPage.tsx
@@ -237,7 +237,7 @@ function AlbumSlugPage() {
return (
{/* Hero Section using the fetched playlist data */}
({
),
}));
-vi.mock("@/components/playlist/PlaylistCard", () => ({
- default: ({ playlist }: { playlist: { playlist_id: string } }) => (
-
- ),
-}));
vi.mock("@/components/UI/UserCard/UserCard", () => ({
default: ({ user }: { user: { id: string } }) => (
@@ -142,6 +137,8 @@ const defaultLikes = {
likedPlaylists: [],
likedAlbums: [],
likedRadioTracks: [],
+ likedMixes: [],
+ likedGenres: [],
};
const defaultHistory = { entries: [] };
@@ -371,7 +368,7 @@ describe("LibraryPage", () => {
it("calls getMyPlaylists (library service) on mount", async () => {
await renderPage();
- expect(getMyPlaylistsLib).toHaveBeenCalledTimes(1);
+ expect(getMyPlaylistsApi).toHaveBeenCalledTimes(1);
});
it("calls getMyFollowing on mount", async () => {
@@ -579,7 +576,7 @@ describe("LibraryPage", () => {
(getMyPlaylistsApi as ReturnType).mockRejectedValue(new Error("Network error"));
await renderPage();
await waitFor(() => {});
- expect(screen.queryAllByTestId(/^album-card-/)).toHaveLength(0);
+ expect(screen.queryAllByTestId(/^playlist-card-/)).toHaveLength(0);
});
// ── Recently Played — API success path ───────────────────
@@ -618,7 +615,7 @@ describe("LibraryPage", () => {
],
});
await renderPage();
- expect(screen.getByTestId("album-card-la1")).toBeInTheDocument();
+ expect(screen.getByTestId("playlist-card-la1")).toBeInTheDocument();
});
it("renders album cards from API when items have is_album_view true", async () => {
@@ -631,7 +628,7 @@ describe("LibraryPage", () => {
});
await renderPage();
await waitFor(() => {
- expect(screen.getByTestId("album-card-a1")).toBeInTheDocument();
+ expect(screen.getByTestId("playlist-card-a1")).toBeInTheDocument();
});
});
@@ -645,7 +642,7 @@ describe("LibraryPage", () => {
});
await renderPage();
await waitFor(() => {});
- expect(screen.queryByTestId("album-card-p1")).not.toBeInTheDocument();
+ expect(screen.queryByTestId("playlist-card-p1")).not.toBeInTheDocument();
});
it("deduplicates albums with the same id from both APIs", async () => {
@@ -654,7 +651,7 @@ describe("LibraryPage", () => {
(getLikedPlaylists as ReturnType).mockResolvedValue({ data: { items: [albumItem] } });
await renderPage();
await waitFor(() => {
- const cards = screen.getAllByTestId("album-card-shared-album");
+ const cards = screen.getAllByTestId("playlist-card-shared-album");
expect(cards).toHaveLength(1);
});
});
@@ -668,7 +665,7 @@ describe("LibraryPage", () => {
});
await renderPage();
await waitFor(() => {
- const cards = screen.getAllByTestId("album-card-dup-album");
+ const cards = screen.getAllByTestId("playlist-card-dup-album");
expect(cards).toHaveLength(1);
});
});