Split software arch desc for easier manintenance. Many fixes related to next video playing and remote playback
🏗️ Build and Test JellyTau / Run Tests (push) Failing after 12s
🏗️ Build and Test JellyTau / Build Android APK (push) Has been skipped
Traceability Validation / Check Requirement Traces (push) Failing after 1s

This commit is contained in:
2026-03-01 19:47:46 +01:00
parent 3a9c126dfe
commit 09780103a7
45 changed files with 5663 additions and 3332 deletions
+117
View File
@@ -402,6 +402,123 @@ describe("RepositoryClient", () => {
});
});
describe("Playlist Methods", () => {
beforeEach(async () => {
(invoke as any).mockResolvedValueOnce("test-handle-123");
await client.create("https://server.com", "user1", "token123", "server1");
});
it("should create a playlist", async () => {
const mockResult = { id: "playlist-001" };
(invoke as any).mockResolvedValueOnce(mockResult);
const result = await client.createPlaylist("My Playlist", ["track1", "track2"]);
expect(result).toEqual(mockResult);
expect(invoke).toHaveBeenCalledWith("playlist_create", {
handle: "test-handle-123",
name: "My Playlist",
itemIds: ["track1", "track2"],
});
});
it("should create a playlist without initial items", async () => {
const mockResult = { id: "playlist-002" };
(invoke as any).mockResolvedValueOnce(mockResult);
await client.createPlaylist("Empty Playlist");
expect(invoke).toHaveBeenCalledWith("playlist_create", {
handle: "test-handle-123",
name: "Empty Playlist",
itemIds: null,
});
});
it("should delete a playlist", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.deletePlaylist("playlist-001");
expect(invoke).toHaveBeenCalledWith("playlist_delete", {
handle: "test-handle-123",
playlistId: "playlist-001",
});
});
it("should rename a playlist", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.renamePlaylist("playlist-001", "New Name");
expect(invoke).toHaveBeenCalledWith("playlist_rename", {
handle: "test-handle-123",
playlistId: "playlist-001",
name: "New Name",
});
});
it("should get playlist items", async () => {
const mockItems = [
{ playlistItemId: "entry1", id: "track1", name: "Track 1", type: "Audio" },
{ playlistItemId: "entry2", id: "track2", name: "Track 2", type: "Audio" },
];
(invoke as any).mockResolvedValueOnce(mockItems);
const items = await client.getPlaylistItems("playlist-001");
expect(items).toEqual(mockItems);
expect(invoke).toHaveBeenCalledWith("playlist_get_items", {
handle: "test-handle-123",
playlistId: "playlist-001",
});
});
it("should add items to a playlist", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.addToPlaylist("playlist-001", ["track3", "track4"]);
expect(invoke).toHaveBeenCalledWith("playlist_add_items", {
handle: "test-handle-123",
playlistId: "playlist-001",
itemIds: ["track3", "track4"],
});
});
it("should remove items from a playlist using entry IDs", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.removeFromPlaylist("playlist-001", ["entry1", "entry2"]);
expect(invoke).toHaveBeenCalledWith("playlist_remove_items", {
handle: "test-handle-123",
playlistId: "playlist-001",
entryIds: ["entry1", "entry2"],
});
});
it("should move a playlist item", async () => {
(invoke as any).mockResolvedValueOnce(undefined);
await client.movePlaylistItem("playlist-001", "track1", 3);
expect(invoke).toHaveBeenCalledWith("playlist_move_item", {
handle: "test-handle-123",
playlistId: "playlist-001",
itemId: "track1",
newIndex: 3,
});
});
it("should throw error if not initialized before playlist operations", async () => {
const newClient = new RepositoryClient();
await expect(newClient.getPlaylistItems("pl-1")).rejects.toThrow("Repository not initialized");
await expect(newClient.createPlaylist("test")).rejects.toThrow("Repository not initialized");
await expect(newClient.deletePlaylist("pl-1")).rejects.toThrow("Repository not initialized");
});
});
describe("Error Handling", () => {
it("should throw error if invoke fails", async () => {
(invoke as any).mockRejectedValueOnce(new Error("Network error"));
+59
View File
@@ -15,6 +15,8 @@ import type {
ImageType,
ImageOptions,
Genre,
PlaylistEntry,
PlaylistCreatedResult,
} from "./types";
/**
@@ -305,6 +307,63 @@ export class RepositoryClient {
});
}
// ===== Playlist Methods (via Rust) =====
async createPlaylist(name: string, itemIds?: string[]): Promise<PlaylistCreatedResult> {
return invoke<PlaylistCreatedResult>("playlist_create", {
handle: this.ensureHandle(),
name,
itemIds: itemIds ?? null,
});
}
async deletePlaylist(playlistId: string): Promise<void> {
return invoke("playlist_delete", {
handle: this.ensureHandle(),
playlistId,
});
}
async renamePlaylist(playlistId: string, name: string): Promise<void> {
return invoke("playlist_rename", {
handle: this.ensureHandle(),
playlistId,
name,
});
}
async getPlaylistItems(playlistId: string): Promise<PlaylistEntry[]> {
return invoke<PlaylistEntry[]>("playlist_get_items", {
handle: this.ensureHandle(),
playlistId,
});
}
async addToPlaylist(playlistId: string, itemIds: string[]): Promise<void> {
return invoke("playlist_add_items", {
handle: this.ensureHandle(),
playlistId,
itemIds,
});
}
async removeFromPlaylist(playlistId: string, entryIds: string[]): Promise<void> {
return invoke("playlist_remove_items", {
handle: this.ensureHandle(),
playlistId,
entryIds,
});
}
async movePlaylistItem(playlistId: string, itemId: string, newIndex: number): Promise<void> {
return invoke("playlist_move_item", {
handle: this.ensureHandle(),
playlistId,
itemId,
newIndex,
});
}
// ===== Getters =====
get serverUrl(): string {
+9
View File
@@ -240,6 +240,15 @@ export interface Genre {
name: string;
}
// Playlist types
export interface PlaylistEntry extends MediaItem {
playlistItemId: string;
}
export interface PlaylistCreatedResult {
id: string;
}
export interface GetItemsOptions {
startIndex?: number;
limit?: number;