many changes
This commit is contained in:
@@ -0,0 +1,359 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/svelte";
|
||||
import MediaCard from "./MediaCard.svelte";
|
||||
|
||||
vi.mock("$lib/stores/auth", () => ({
|
||||
auth: {
|
||||
getRepository: vi.fn(() => ({
|
||||
getImageUrl: vi.fn(),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("MediaCard - Async Image Loading", () => {
|
||||
let mockRepository: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockRepository = {
|
||||
getImageUrl: vi.fn(),
|
||||
};
|
||||
|
||||
vi.mocked((global as any).__stores_auth?.auth?.getRepository).mockReturnValue(mockRepository);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllTimers();
|
||||
});
|
||||
|
||||
describe("Image Loading", () => {
|
||||
it("should load image URL asynchronously", async () => {
|
||||
const mockImageUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
|
||||
mockRepository.getImageUrl.mockResolvedValue(mockImageUrl);
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
const { container } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
// Component should render immediately with placeholder
|
||||
expect(container).toBeTruthy();
|
||||
|
||||
// Wait for image URL to load
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledWith(
|
||||
"item123",
|
||||
"Primary",
|
||||
expect.objectContaining({
|
||||
maxWidth: 300,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should show placeholder while image is loading", async () => {
|
||||
const mockImageUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
|
||||
mockRepository.getImageUrl.mockImplementation(
|
||||
() => new Promise((resolve) => setTimeout(() => resolve(mockImageUrl), 100))
|
||||
);
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
const { container } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
// Placeholder should be visible initially
|
||||
const placeholder = container.querySelector(".placeholder");
|
||||
if (placeholder) {
|
||||
expect(placeholder).toBeTruthy();
|
||||
}
|
||||
|
||||
// Wait for image to load
|
||||
vi.useFakeTimers();
|
||||
vi.advanceTimersByTime(100);
|
||||
vi.useRealTimers();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should update image URL when item changes", async () => {
|
||||
const mockImageUrl1 = "https://server.com/Items/item1/Images/Primary?api_key=token";
|
||||
const mockImageUrl2 = "https://server.com/Items/item2/Images/Primary?api_key=token";
|
||||
|
||||
mockRepository.getImageUrl.mockResolvedValueOnce(mockImageUrl1);
|
||||
|
||||
const mediaItem1 = {
|
||||
id: "item1",
|
||||
name: "Album 1",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "tag1",
|
||||
};
|
||||
|
||||
const { rerender } = render(MediaCard, {
|
||||
props: { item: mediaItem1 },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledWith("item1", "Primary", expect.any(Object));
|
||||
});
|
||||
|
||||
// Change item
|
||||
mockRepository.getImageUrl.mockResolvedValueOnce(mockImageUrl2);
|
||||
|
||||
const mediaItem2 = {
|
||||
id: "item2",
|
||||
name: "Album 2",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "tag2",
|
||||
};
|
||||
|
||||
await rerender({ item: mediaItem2 });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledWith("item2", "Primary", expect.any(Object));
|
||||
});
|
||||
});
|
||||
|
||||
it("should not reload image if item ID hasn't changed", async () => {
|
||||
const mockImageUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
|
||||
mockRepository.getImageUrl.mockResolvedValue(mockImageUrl);
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
const { rerender } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Rerender with same item
|
||||
await rerender({ item: mediaItem });
|
||||
|
||||
// Should not call getImageUrl again
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle missing primary image tag gracefully", async () => {
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
// primaryImageTag is undefined
|
||||
};
|
||||
|
||||
const { container } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
// Should render without calling getImageUrl
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should show placeholder
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should handle image load errors gracefully", async () => {
|
||||
mockRepository.getImageUrl.mockRejectedValue(new Error("Failed to load image"));
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
const { container } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Should still render without crashing
|
||||
expect(container).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Image Options", () => {
|
||||
it("should pass correct options to getImageUrl", async () => {
|
||||
mockRepository.getImageUrl.mockResolvedValue("https://server.com/image");
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledWith(
|
||||
"item123",
|
||||
"Primary",
|
||||
{
|
||||
maxWidth: 300,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should include tag in image options when available", async () => {
|
||||
mockRepository.getImageUrl.mockResolvedValue("https://server.com/image");
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "tag123",
|
||||
};
|
||||
|
||||
render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledWith(
|
||||
"item123",
|
||||
"Primary",
|
||||
{
|
||||
maxWidth: 300,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Caching", () => {
|
||||
it("should cache image URLs to avoid duplicate requests", async () => {
|
||||
const mockImageUrl = "https://server.com/Items/item123/Images/Primary?api_key=token";
|
||||
mockRepository.getImageUrl.mockResolvedValue(mockImageUrl);
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
// Render same item multiple times
|
||||
const { rerender } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// Rerender with same item
|
||||
await rerender({ item: mediaItem });
|
||||
|
||||
// Should still only have called once (cached)
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should have separate cache entries for different items", async () => {
|
||||
const mockImageUrl1 = "https://server.com/Items/item1/Images/Primary?api_key=token";
|
||||
const mockImageUrl2 = "https://server.com/Items/item2/Images/Primary?api_key=token";
|
||||
|
||||
let callCount = 0;
|
||||
mockRepository.getImageUrl.mockImplementation(() => {
|
||||
callCount++;
|
||||
return Promise.resolve(callCount === 1 ? mockImageUrl1 : mockImageUrl2);
|
||||
});
|
||||
|
||||
const item1 = {
|
||||
id: "item1",
|
||||
name: "Album 1",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "tag1",
|
||||
};
|
||||
|
||||
const item2 = {
|
||||
id: "item2",
|
||||
name: "Album 2",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "tag2",
|
||||
};
|
||||
|
||||
const { rerender } = render(MediaCard, {
|
||||
props: { item: item1 },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
await rerender({ item: item2 });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// Change back to item 1 - should use cached value
|
||||
await rerender({ item: item1 });
|
||||
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Reactive Updates", () => {
|
||||
it("should respond to property changes via $effect", async () => {
|
||||
mockRepository.getImageUrl.mockResolvedValue("https://server.com/image");
|
||||
|
||||
const mediaItem = {
|
||||
id: "item123",
|
||||
name: "Test Album",
|
||||
type: "MusicAlbum",
|
||||
primaryImageTag: "abc123",
|
||||
};
|
||||
|
||||
const { rerender } = render(MediaCard, {
|
||||
props: { item: mediaItem },
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRepository.getImageUrl).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const previousCallCount = mockRepository.getImageUrl.mock.calls.length;
|
||||
|
||||
// Update a property that shouldn't trigger reload
|
||||
await rerender({
|
||||
item: {
|
||||
...mediaItem,
|
||||
name: "Updated Album Name",
|
||||
},
|
||||
});
|
||||
|
||||
// Should not call getImageUrl again (same primaryImageTag)
|
||||
expect(mockRepository.getImageUrl.mock.calls.length).toBe(previousCallCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user