Skip to content
Closed
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
34 changes: 34 additions & 0 deletions src/components/Premium/HeroSection.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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(
<HeroSection
onGetStarted={vi.fn()}
isStarting={false}
disabled={false}
/>,
);

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(
<HeroSection onGetStarted={vi.fn()} isStarting disabled={false} />,
);

expect(screen.getByRole("button", { name: /loading/i })).toBeDisabled();
});
});
84 changes: 84 additions & 0 deletions src/components/layout/AuthMainLayout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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: () => <div data-test="mock-main-navbar" />,
}));

vi.mock("@/components/player/StickyPlayer", () => ({
default: () => <div data-test="mock-sticky-player" />,
}));

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(
<MemoryRouter>
<AuthMainLayout />
</MemoryRouter>,
);

await waitFor(() => {
expect(mockSetUser).toHaveBeenCalledWith(
expect.objectContaining({
date_of_birth: "2001-02-02",
gender: "female",
}),
);
});

expect(mockHydrateFromApi).toHaveBeenCalled();
});
});
47 changes: 21 additions & 26 deletions src/components/library/useLibraryData.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { getRecentlyPlayed } from "@/services/api/discover.service";
import { mapRecentlyPlayedEntry } from "@/services/api/discover.mapper";
import { getMyPlaylists as getMyPlaylistsLib, getMyFollowing } from "@/services/api/library.service";
import { getMyFollowing } from "@/services/api/library.service";
import { getUserById } from "@/services/user.service";
import {
getMyPlaylists as getMyPlaylistsApi,
Expand All @@ -15,7 +15,6 @@ 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";
Expand All @@ -28,13 +27,13 @@ export function useLibraryData() {
const [playlistFilter, setPlaylistFilter] = useState<FilterOption>("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();
Expand Down Expand Up @@ -62,21 +61,18 @@ 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),
Expand All @@ -90,16 +86,17 @@ export function useLibraryData() {
}),
);
})
.catch(() => setAlbums([]));
.catch(() => {
setPlaylists([]);
setAlbums([]);
});
}, []);

const recentEntries = (() => {
const list =
entries.length > 0
? entries
: recentlyPlayedApi.length > 0
? recentlyPlayedApi.map((t) => ({ type: "track" as const, item: t, playedAt: "" }))
: mockRecentlyPlayedTracks.map((t) => ({ type: "track" as const, item: t, playedAt: "" }));
: recentlyPlayedApi.map((t) => ({ type: "track" as const, item: t, playedAt: "" }));

const seen = new Set<string>();
return list.filter((e) => {
Expand Down Expand Up @@ -140,9 +137,7 @@ export function useLibraryData() {
})();

const displayedFollowing = (() => {
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 seenUsernames = new Set(followingUsers.map((u) => u.username));
const synthetic: User[] = (user?.following_ids ?? [])
.filter((username) => !seenUsernames.has(username))
.map((username, i) => ({
Expand All @@ -151,7 +146,7 @@ export function useLibraryData() {
displayName: username,
followers: 0,
}));
return [...filtered, ...synthetic];
return [...followingUsers, ...synthetic];
})();

return {
Expand Down
Loading
Loading