Skip to content

Commit 54afe02

Browse files
committed
[lib] fix: selected playlist filter in library conversion
1 parent cc56df5 commit 54afe02

5 files changed

Lines changed: 141 additions & 7 deletions

File tree

.claude/settings.local.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(yarn build)",
5+
"Bash(yarn test)",
6+
"Bash(yarn check-types)",
7+
"Bash(yarn check-lint)",
8+
"Bash(yarn lint-fix)"
9+
]
10+
},
11+
"enableAllProjectMcpServers": false
12+
}

packages/raga-lib/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"clean": "rm -rf lib/*",
1212
"dev": "yarn build --watch",
1313
"lint-fix": "eslint --fix .",
14-
"publish-npm": "npm publish --access public"
14+
"publish-npm": "npm publish --access public",
15+
"test": "node --test lib/**/*.test.js"
1516
},
1617
"dependencies": {
1718
"ansis": "^3.17.0",
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import assert from "node:assert";
2+
import { describe,test } from "node:test";
3+
4+
import type { SwinsianLibraryPlist, SwinsianTrackDefinition } from "../index.js";
5+
import convertSwinsianToItunesXmlLibrary from "./index.js";
6+
7+
void describe("convertSwinsianToItunesXmlLibrary", () => {
8+
const mockLibrary: SwinsianLibraryPlist = {
9+
"Application Version": "1.0",
10+
Date: new Date(),
11+
Features: 5,
12+
"Library Persistent ID": "test-lib-id",
13+
"Major Version": 1,
14+
"Minor Version": 0,
15+
"Music Folder": "/path/to/music",
16+
"Show Content Ratings": true,
17+
Tracks: {
18+
1: {
19+
"Track ID": 1,
20+
"Persistent ID": "test-persistent-id",
21+
Name: "Test Track",
22+
Artist: "Test Artist",
23+
"Total Time": 180000,
24+
"Play Count": 5,
25+
"Date Added": new Date("2024-01-01"),
26+
Location: "file:///path/to/track.mp3",
27+
} as SwinsianTrackDefinition,
28+
},
29+
Playlists: [
30+
{
31+
"Playlist ID": "1",
32+
"Playlist Persistent ID": "playlist-id-1",
33+
Name: "Playlist 1",
34+
"Playlist Items": [{ "Track ID": 1 }],
35+
},
36+
{
37+
"Playlist ID": "2",
38+
"Playlist Persistent ID": "playlist-id-2",
39+
Name: "Playlist 2",
40+
"Playlist Items": [{ "Track ID": 1 }],
41+
},
42+
],
43+
};
44+
45+
void test("should convert library without filtering when no selectedPlaylistIds provided", () => {
46+
const result = convertSwinsianToItunesXmlLibrary(mockLibrary);
47+
48+
assert.strictEqual(result.Playlists.length, 2);
49+
assert.strictEqual(result.Playlists[0].Name, "Playlist 1");
50+
assert.strictEqual(result.Playlists[1].Name, "Playlist 2");
51+
});
52+
53+
void test("should filter playlists when selectedPlaylistIds provided", () => {
54+
const result = convertSwinsianToItunesXmlLibrary(mockLibrary, ["playlist-id-1"]);
55+
56+
assert.strictEqual(result.Playlists.length, 1);
57+
assert.strictEqual(result.Playlists[0].Name, "Playlist 1");
58+
assert.strictEqual(result.Playlists[0]["Playlist Persistent ID"], "playlist-id-1");
59+
});
60+
61+
void test("should handle empty selectedPlaylistIds array", () => {
62+
const result = convertSwinsianToItunesXmlLibrary(mockLibrary, []);
63+
64+
// Should not filter when empty array is provided
65+
assert.strictEqual(result.Playlists.length, 2);
66+
});
67+
68+
void test("should handle non-matching selectedPlaylistIds", () => {
69+
const result = convertSwinsianToItunesXmlLibrary(mockLibrary, ["non-existent-id"]);
70+
71+
assert.strictEqual(result.Playlists.length, 0);
72+
});
73+
74+
void test("should handle library with no Playlists property", () => {
75+
const libraryWithoutPlaylists = { ...mockLibrary };
76+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
77+
delete (libraryWithoutPlaylists as any).Playlists;
78+
79+
const result = convertSwinsianToItunesXmlLibrary(libraryWithoutPlaylists, ["playlist-id-1"]);
80+
81+
assert.strictEqual(Array.isArray(result.Playlists), true);
82+
assert.strictEqual(result.Playlists.length, 0);
83+
});
84+
85+
void test("should handle library with null Playlists property", () => {
86+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
87+
const libraryWithNullPlaylists = { ...mockLibrary, Playlists: null as any };
88+
89+
const result = convertSwinsianToItunesXmlLibrary(libraryWithNullPlaylists, ["playlist-id-1"]);
90+
91+
assert.strictEqual(Array.isArray(result.Playlists), true);
92+
assert.strictEqual(result.Playlists.length, 0);
93+
});
94+
95+
void test("should handle library with non-array Playlists property", () => {
96+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
97+
const libraryWithInvalidPlaylists = { ...mockLibrary, Playlists: "invalid" as any };
98+
99+
const result = convertSwinsianToItunesXmlLibrary(libraryWithInvalidPlaylists, ["playlist-id-1"]);
100+
101+
assert.strictEqual(Array.isArray(result.Playlists), true);
102+
assert.strictEqual(result.Playlists.length, 0);
103+
});
104+
105+
void test("should convert tracks properly", () => {
106+
const result = convertSwinsianToItunesXmlLibrary(mockLibrary);
107+
108+
assert.strictEqual(typeof result.Tracks[1], "object");
109+
assert.strictEqual(result.Tracks[1]["Track ID"], 1);
110+
// Persistent ID gets converted from string to hex format
111+
assert.strictEqual(typeof result.Tracks[1]["Persistent ID"], "string");
112+
assert.strictEqual(result.Tracks[1]["Persistent ID"].length, 16);
113+
});
114+
});

packages/raga-lib/src/convert-swinsian-to-itunes-xml-library/index.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,20 @@ export default function (
2626
musicAppLibrary.Tracks[track["Track ID"]] = newTrackDefinition;
2727
});
2828

29-
if (selectedPlaylistIds) {
29+
if (selectedPlaylistIds && selectedPlaylistIds.length > 0) {
3030
log.info(
3131
`Filtering playlists to only include ${selectedPlaylistIds.length.toString()} playlists`,
3232
);
33-
// Optionally filter playlists to only include those that have been selected for export
34-
musicAppLibrary.Playlists = musicAppLibrary.Playlists.filter((playlist) =>
35-
selectedPlaylistIds.includes(playlist["Playlist Persistent ID"]),
36-
);
33+
34+
if (!Array.isArray(musicAppLibrary.Playlists)) {
35+
log.warn("No playlists found in library or Playlists is not an array");
36+
musicAppLibrary.Playlists = [];
37+
} else {
38+
// Optionally filter playlists to only include those that have been selected for export
39+
musicAppLibrary.Playlists = musicAppLibrary.Playlists.filter((playlist) =>
40+
selectedPlaylistIds.includes(playlist["Playlist Persistent ID"]),
41+
);
42+
}
3743
}
3844

3945
return musicAppLibrary;

packages/raga-lib/src/utils/plistUtils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { readFileSync } from "node:fs";
22

3-
import { build, parse, type PlistObject } from "plist";
3+
import plist, { type PlistObject } from "plist";
4+
const { build, parse } = plist;
45

56
import { log } from "./log.js";
67
import { collapsePropertiesIntoSingleLine } from "./xmlUtils.js";

0 commit comments

Comments
 (0)