mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-22 01:43:29 +01:00
Rendering playlist icon collage of 3x3 (#35)
This commit is contained in:
@@ -259,7 +259,9 @@ describe("scenarios", () => {
|
||||
bonob,
|
||||
bonobUrl,
|
||||
musicService,
|
||||
linkCodes
|
||||
{
|
||||
linkCodes: () => linkCodes
|
||||
}
|
||||
);
|
||||
|
||||
const sonosDriver = new SonosDriver(server, bonobUrl, bonob);
|
||||
@@ -275,7 +277,9 @@ describe("scenarios", () => {
|
||||
bonob,
|
||||
bonobUrl,
|
||||
musicService,
|
||||
linkCodes
|
||||
{
|
||||
linkCodes: () => linkCodes
|
||||
}
|
||||
);
|
||||
|
||||
const sonosDriver = new SonosDriver(server, bonobUrl, bonob);
|
||||
@@ -291,7 +295,9 @@ describe("scenarios", () => {
|
||||
bonob,
|
||||
bonobUrl,
|
||||
musicService,
|
||||
linkCodes
|
||||
{
|
||||
linkCodes: () => linkCodes
|
||||
}
|
||||
);
|
||||
|
||||
const sonosDriver = new SonosDriver(server, bonobUrl, bonob);
|
||||
|
||||
@@ -2,6 +2,8 @@ import { v4 as uuid } from "uuid";
|
||||
import dayjs from "dayjs";
|
||||
import request from "supertest";
|
||||
import Image from "image-js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import { MusicService } from "../src/music_service";
|
||||
import makeServer, {
|
||||
@@ -502,9 +504,11 @@ describe("server", () => {
|
||||
theService,
|
||||
bonobUrl,
|
||||
musicService as unknown as MusicService,
|
||||
linkCodes as unknown as LinkCodes,
|
||||
accessTokens as unknown as AccessTokens,
|
||||
clock
|
||||
{
|
||||
linkCodes: () => linkCodes as unknown as LinkCodes,
|
||||
accessTokens: () => accessTokens as unknown as AccessTokens,
|
||||
clock,
|
||||
}
|
||||
);
|
||||
|
||||
it("should return the login page", async () => {
|
||||
@@ -626,8 +630,10 @@ describe("server", () => {
|
||||
aService(),
|
||||
bonobUrl,
|
||||
musicService as unknown as MusicService,
|
||||
new InMemoryLinkCodes(),
|
||||
accessTokens
|
||||
{
|
||||
linkCodes: () => new InMemoryLinkCodes(),
|
||||
accessTokens: () => accessTokens,
|
||||
}
|
||||
);
|
||||
|
||||
const authToken = uuid();
|
||||
@@ -1055,14 +1061,25 @@ describe("server", () => {
|
||||
aService(),
|
||||
url("http://localhost:1234"),
|
||||
musicService as unknown as MusicService,
|
||||
new InMemoryLinkCodes(),
|
||||
accessTokens
|
||||
{
|
||||
linkCodes: () => new InMemoryLinkCodes(),
|
||||
accessTokens: () => accessTokens,
|
||||
}
|
||||
);
|
||||
|
||||
const authToken = uuid();
|
||||
const albumId = uuid();
|
||||
let accessToken: string;
|
||||
|
||||
const coverArtResponse = (
|
||||
opt: Partial<{ status: number; contentType: string; data: Buffer }>
|
||||
) => ({
|
||||
status: 200,
|
||||
contentType: "image/jpeg",
|
||||
data: Buffer.from(uuid(), "ascii"),
|
||||
...opt,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
accessToken = accessTokens.mint(authToken);
|
||||
});
|
||||
@@ -1102,7 +1119,7 @@ describe("server", () => {
|
||||
|
||||
describe("artist art", () => {
|
||||
["0", "-1", "foo"].forEach((size) => {
|
||||
describe(`when the size is ${size}`, () => {
|
||||
describe(`invalid size of ${size}`, () => {
|
||||
it(`should return a 400`, async () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
const res = await request(server)
|
||||
@@ -1116,51 +1133,290 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is some", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const coverArt = {
|
||||
status: 200,
|
||||
contentType: "image/jpeg",
|
||||
data: Buffer.from("some image", "ascii"),
|
||||
};
|
||||
describe("fetching a single image", () => {
|
||||
describe("when the images is available", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const coverArt = coverArtResponse({});
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValue(coverArt);
|
||||
musicLibrary.coverArt.mockResolvedValue(coverArt);
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(coverArt.status);
|
||||
expect(res.header["content-type"]).toEqual(
|
||||
coverArt.contentType
|
||||
);
|
||||
expect(res.status).toEqual(coverArt.status);
|
||||
expect(res.header["content-type"]).toEqual(
|
||||
coverArt.contentType
|
||||
);
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
albumId,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
albumId,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the image is not available", () => {
|
||||
it("should return a 404", async () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValue(undefined);
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there isn't one", () => {
|
||||
it("should return a 404", async () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
describe("fetching multiple images as a collage", () => {
|
||||
const png = fs.readFileSync(path.join(__dirname, '..', 'docs', 'images', 'chartreuseFuchsia.png'));
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValue(undefined);
|
||||
describe("fetching a collage of 4 when all are available", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
expect(res.status).toEqual(404);
|
||||
ids.forEach((_) => {
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.header["content-type"]).toEqual("image/png");
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
200
|
||||
);
|
||||
});
|
||||
|
||||
const image = await Image.load(res.body);
|
||||
expect(image.width).toEqual(200);
|
||||
expect(image.height).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching a collage of 4, however only 1 is available", () => {
|
||||
it("should return the single image", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
contentType: "image/some-mime-type"
|
||||
})
|
||||
);
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.header["content-type"]).toEqual("image/some-mime-type");
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching a collage of 4 and all are missing", () => {
|
||||
it("should return a 404", async () => {
|
||||
const ids = ["1", "2", "3", "4"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
ids.forEach((_) => {
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
});
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/200?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching a collage of 9 when all are available", () => {
|
||||
it("should return the image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
ids.forEach((_) => {
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.header["content-type"]).toEqual("image/png");
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
|
||||
const image = await Image.load(res.body);
|
||||
expect(image.width).toEqual(180);
|
||||
expect(image.height).toEqual(180);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching a collage of 9 when only 2 are available", () => {
|
||||
it("should still return an image and a 200", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
})
|
||||
);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
})
|
||||
);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(undefined);
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.header["content-type"]).toEqual("image/png");
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
|
||||
const image = await Image.load(res.body);
|
||||
expect(image.width).toEqual(180);
|
||||
expect(image.height).toEqual(180);
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching a collage of 11", () => {
|
||||
it("should still return an image and a 200, though will only display 9", async () => {
|
||||
const ids = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"];
|
||||
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
ids.forEach((_) => {
|
||||
musicLibrary.coverArt.mockResolvedValueOnce(
|
||||
coverArtResponse({
|
||||
data: png,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${ids.join(
|
||||
"&"
|
||||
)}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(200);
|
||||
expect(res.header["content-type"]).toEqual("image/png");
|
||||
|
||||
expect(musicService.login).toHaveBeenCalledWith(authToken);
|
||||
ids.forEach((id) => {
|
||||
expect(musicLibrary.coverArt).toHaveBeenCalledWith(
|
||||
id,
|
||||
"artist",
|
||||
180
|
||||
);
|
||||
});
|
||||
|
||||
const image = await Image.load(res.body);
|
||||
expect(image.width).toEqual(180);
|
||||
expect(image.height).toEqual(180);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the image is not available", () => {
|
||||
it("should return a 404", async () => {
|
||||
musicService.login.mockResolvedValue(musicLibrary);
|
||||
|
||||
musicLibrary.coverArt.mockResolvedValue(undefined);
|
||||
|
||||
const res = await request(server)
|
||||
.get(
|
||||
`/art/artist/${albumId}/size/180?${BONOB_ACCESS_TOKEN_HEADER}=${accessToken}`
|
||||
)
|
||||
.set(BONOB_ACCESS_TOKEN_HEADER, accessToken);
|
||||
|
||||
expect(res.status).toEqual(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1263,7 +1519,7 @@ describe("server", () => {
|
||||
|
||||
describe("/icon", () => {
|
||||
const server = (
|
||||
clock: Clock = SystemClock,
|
||||
clock: Clock = SystemClock,
|
||||
iconColors: {
|
||||
foregroundColor: string | undefined;
|
||||
backgroundColor: string | undefined;
|
||||
@@ -1274,10 +1530,12 @@ describe("server", () => {
|
||||
aService(),
|
||||
url("http://localhost:1234"),
|
||||
jest.fn() as unknown as MusicService,
|
||||
new InMemoryLinkCodes(),
|
||||
jest.fn() as unknown as AccessTokens,
|
||||
clock,
|
||||
iconColors
|
||||
{
|
||||
linkCodes: () => new InMemoryLinkCodes(),
|
||||
accessTokens: () => jest.fn() as unknown as AccessTokens,
|
||||
clock,
|
||||
iconColors,
|
||||
}
|
||||
);
|
||||
|
||||
describe("invalid icon names", () => {
|
||||
@@ -1329,6 +1587,7 @@ describe("server", () => {
|
||||
"recentlyPlayed",
|
||||
"mostPlayed",
|
||||
"discover",
|
||||
"error",
|
||||
].forEach((type) => {
|
||||
describe(`type=${type}`, () => {
|
||||
describe(`legacy icon`, () => {
|
||||
@@ -1364,9 +1623,12 @@ describe("server", () => {
|
||||
});
|
||||
|
||||
it("should return icon colors as per config if overriden", async () => {
|
||||
const response = await request(server(SystemClock, { foregroundColor: 'brightblue', backgroundColor: 'brightpink' })).get(
|
||||
`/icon/${type}/size/180`
|
||||
);
|
||||
const response = await request(
|
||||
server(SystemClock, {
|
||||
foregroundColor: "brightblue",
|
||||
backgroundColor: "brightpink",
|
||||
})
|
||||
).get(`/icon/${type}/size/180`);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
const svg = Buffer.from(response.body).toString();
|
||||
@@ -1382,12 +1644,12 @@ describe("server", () => {
|
||||
expect(response.status).toEqual(200);
|
||||
const svg = Buffer.from(response.body).toString();
|
||||
expect(svg).toContain(`foobar1000`);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return a christmas icon on christmas day", async () => {
|
||||
const response = await request(server({ now: () => dayjs("2022/12/25") })).get(
|
||||
`/icon/${type}/size/180`
|
||||
);
|
||||
const response = await request(
|
||||
server({ now: () => dayjs("2022/12/25") })
|
||||
).get(`/icon/${type}/size/180`);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
const svg = Buffer.from(response.body).toString();
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
defaultArtistArtURI,
|
||||
searchResult,
|
||||
iconArtURI,
|
||||
playlistAlbumArtURL,
|
||||
} from "../src/smapi";
|
||||
|
||||
import {
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
albumToAlbumSummary,
|
||||
artistToArtistSummary,
|
||||
MusicService,
|
||||
playlistToPlaylistSummary,
|
||||
} from "../src/music_service";
|
||||
import { AccessTokens } from "../src/access_tokens";
|
||||
import dayjs from "dayjs";
|
||||
@@ -158,7 +160,6 @@ describe("service config", () => {
|
||||
expect(imageSizeMap(size)).toEqual(`/size/${size}`);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -303,6 +304,81 @@ describe("album", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("playlistAlbumArtURL", () => {
|
||||
describe("when the playlist has no albumIds", () => {
|
||||
it("should return question mark icon", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [aTrack({ album: undefined }), aTrack({ album: undefined })],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/icon/error/size/legacy?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the playlist has 2 distinct albumIds", () => {
|
||||
it("should return them on the url to the image", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the playlist has 4 distinct albumIds", () => {
|
||||
it("should return them on the url to the image", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "3" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "4" })) }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2&3&4/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the playlist has 9 distinct albumIds", () => {
|
||||
it("should return 9 of the ids on the url", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
const playlist = aPlaylist({
|
||||
entries: [
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "1" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "2" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "3" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "4" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "5" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "6" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "7" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "8" })) }),
|
||||
aTrack({ album: albumToAlbumSummary(anAlbum({ id: "9" })) }),
|
||||
],
|
||||
});
|
||||
|
||||
expect(playlistAlbumArtURL(bonobUrl, playlist).href()).toEqual(
|
||||
`http://localhost:1234/context-path/art/album/1&2&3&4&5&6&7&8&9/size/180?search=yes`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("defaultAlbumArtURI", () => {
|
||||
it("should create the correct URI", () => {
|
||||
const bonobUrl = url("http://localhost:1234/context-path?search=yes");
|
||||
@@ -381,9 +457,11 @@ describe("api", () => {
|
||||
service,
|
||||
bonobUrl,
|
||||
musicService as unknown as MusicService,
|
||||
linkCodes as unknown as LinkCodes,
|
||||
accessTokens as unknown as AccessTokens,
|
||||
clock
|
||||
{
|
||||
linkCodes: () => linkCodes as unknown as LinkCodes,
|
||||
accessTokens: () => accessTokens as unknown as AccessTokens,
|
||||
clock,
|
||||
}
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -940,7 +1018,11 @@ describe("api", () => {
|
||||
itemType: "container",
|
||||
id: `genre:${genre.id}`,
|
||||
title: genre.name,
|
||||
albumArtURI: iconArtURI(bonobUrl, iconForGenre(genre.name), genre.name).href(),
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
iconForGenre(genre.name),
|
||||
genre.name
|
||||
).href(),
|
||||
})),
|
||||
index: 0,
|
||||
total: expectedGenres.length,
|
||||
@@ -962,7 +1044,11 @@ describe("api", () => {
|
||||
itemType: "container",
|
||||
id: `genre:${genre.id}`,
|
||||
title: genre.name,
|
||||
albumArtURI: iconArtURI(bonobUrl, iconForGenre(genre.name), genre.name).href(),
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
iconForGenre(genre.name),
|
||||
genre.name
|
||||
).href(),
|
||||
})),
|
||||
index: 1,
|
||||
total: expectedGenres.length,
|
||||
@@ -973,30 +1059,40 @@ describe("api", () => {
|
||||
});
|
||||
|
||||
describe("asking for playlists", () => {
|
||||
const expectedPlayLists = [
|
||||
{ id: "1", name: "pl1" },
|
||||
{ id: "2", name: "pl2" },
|
||||
{ id: "3", name: "pl3" },
|
||||
{ id: "4", name: "pl4" },
|
||||
];
|
||||
const playlist1 = aPlaylist({ id: "1", name: "pl1" });
|
||||
const playlist2 = aPlaylist({ id: "2", name: "pl2" });
|
||||
const playlist3 = aPlaylist({ id: "3", name: "pl3" });
|
||||
const playlist4 = aPlaylist({ id: "4", name: "pl4" });
|
||||
|
||||
const playlists = [playlist1, playlist2, playlist3, playlist4];
|
||||
|
||||
beforeEach(() => {
|
||||
musicLibrary.playlists.mockResolvedValue(expectedPlayLists);
|
||||
musicLibrary.playlists.mockResolvedValue(
|
||||
playlists.map(playlistToPlaylistSummary)
|
||||
);
|
||||
musicLibrary.playlist.mockResolvedValueOnce(playlist1);
|
||||
musicLibrary.playlist.mockResolvedValueOnce(playlist2);
|
||||
musicLibrary.playlist.mockResolvedValueOnce(playlist3);
|
||||
musicLibrary.playlist.mockResolvedValueOnce(playlist4);
|
||||
});
|
||||
|
||||
describe("asking for all playlists", () => {
|
||||
it("should return a collection of playlists", async () => {
|
||||
const result = await ws.getMetadataAsync({
|
||||
id: `playlists`,
|
||||
id: "playlists",
|
||||
index: 0,
|
||||
count: 100,
|
||||
});
|
||||
expect(result[0]).toEqual(
|
||||
getMetadataResult({
|
||||
mediaCollection: expectedPlayLists.map((playlist) => ({
|
||||
mediaCollection: playlists.map((playlist) => ({
|
||||
itemType: "playlist",
|
||||
id: `playlist:${playlist.id}`,
|
||||
title: playlist.name,
|
||||
albumArtURI: playlistAlbumArtURL(
|
||||
bonobUrlWithAccessToken,
|
||||
playlist
|
||||
).href(),
|
||||
canPlay: true,
|
||||
attributes: {
|
||||
readOnly: "false",
|
||||
@@ -1005,7 +1101,7 @@ describe("api", () => {
|
||||
},
|
||||
})),
|
||||
index: 0,
|
||||
total: expectedPlayLists.length,
|
||||
total: playlists.length,
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1020,22 +1116,25 @@ describe("api", () => {
|
||||
});
|
||||
expect(result[0]).toEqual(
|
||||
getMetadataResult({
|
||||
mediaCollection: [
|
||||
expectedPlayLists[1]!,
|
||||
expectedPlayLists[2]!,
|
||||
].map((playlist) => ({
|
||||
itemType: "playlist",
|
||||
id: `playlist:${playlist.id}`,
|
||||
title: playlist.name,
|
||||
canPlay: true,
|
||||
attributes: {
|
||||
readOnly: "false",
|
||||
userContent: "false",
|
||||
renameable: "false",
|
||||
},
|
||||
})),
|
||||
mediaCollection: [playlists[1]!, playlists[2]!].map(
|
||||
(playlist) => ({
|
||||
itemType: "playlist",
|
||||
id: `playlist:${playlist.id}`,
|
||||
title: playlist.name,
|
||||
albumArtURI: playlistAlbumArtURL(
|
||||
bonobUrlWithAccessToken,
|
||||
playlist
|
||||
).href(),
|
||||
canPlay: true,
|
||||
attributes: {
|
||||
readOnly: "false",
|
||||
userContent: "false",
|
||||
renameable: "false",
|
||||
},
|
||||
})
|
||||
),
|
||||
index: 1,
|
||||
total: expectedPlayLists.length,
|
||||
total: playlists.length,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -138,15 +138,19 @@ describe("URLBuilder", () => {
|
||||
describe("with URLSearchParams", () => {
|
||||
it("should return a new URLBuilder with the new search params appended", () => {
|
||||
const original = url("https://example.com/some-path?a=b&c=d");
|
||||
const searchParams = new URLSearchParams({ x: "y" });
|
||||
searchParams.append("z", "1");
|
||||
searchParams.append("z", "2");
|
||||
|
||||
const updated = original.append({
|
||||
searchParams: new URLSearchParams({ x: "y", z: "1" }),
|
||||
searchParams,
|
||||
});
|
||||
|
||||
expect(original.href()).toEqual("https://example.com/some-path?a=b&c=d");
|
||||
expect(`${original.searchParams()}`).toEqual("a=b&c=d")
|
||||
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?a=b&c=d&x=y&z=1");
|
||||
expect(`${updated.searchParams()}`).toEqual("a=b&c=d&x=y&z=1")
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?a=b&c=d&x=y&z=1&z=2");
|
||||
expect(`${updated.searchParams()}`).toEqual("a=b&c=d&x=y&z=1&z=2")
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -168,15 +172,19 @@ describe("URLBuilder", () => {
|
||||
|
||||
it("should return a new URLBuilder with the new search params", () => {
|
||||
const original = url("https://example.com/some-path?a=b&c=d");
|
||||
const searchParams = new URLSearchParams({ x: "y" });
|
||||
searchParams.append("z", "1");
|
||||
searchParams.append("z", "2");
|
||||
|
||||
const updated = original.with({
|
||||
searchParams: { x: "y", z: "1" },
|
||||
searchParams,
|
||||
});
|
||||
|
||||
expect(original.href()).toEqual("https://example.com/some-path?a=b&c=d");
|
||||
expect(`${original.searchParams()}`).toEqual("a=b&c=d")
|
||||
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?x=y&z=1");
|
||||
expect(`${updated.searchParams()}`).toEqual("x=y&z=1")
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?x=y&z=1&z=2");
|
||||
expect(`${updated.searchParams()}`).toEqual("x=y&z=1&z=2")
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,15 +204,19 @@ describe("URLBuilder", () => {
|
||||
|
||||
it("should return a new URLBuilder with the new search params", () => {
|
||||
const original = url("https://example.com/some-path?a=b&c=d");
|
||||
const searchParams = new URLSearchParams({ x: "y" });
|
||||
searchParams.append("z", "1");
|
||||
searchParams.append("z", "2");
|
||||
|
||||
const updated = original.with({
|
||||
searchParams: new URLSearchParams({ x: "y", z: "1" }),
|
||||
searchParams,
|
||||
});
|
||||
|
||||
expect(original.href()).toEqual("https://example.com/some-path?a=b&c=d");
|
||||
expect(`${original.searchParams()}`).toEqual("a=b&c=d")
|
||||
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?x=y&z=1");
|
||||
expect(`${updated.searchParams()}`).toEqual("x=y&z=1")
|
||||
expect(updated.href()).toEqual("https://example.com/some-path?x=y&z=1&z=2");
|
||||
expect(`${updated.searchParams()}`).toEqual("x=y&z=1&z=2")
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
35
tests/utils.test.ts
Normal file
35
tests/utils.test.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { takeWithRepeats } from "../src/utils";
|
||||
|
||||
describe("takeWithRepeat", () => {
|
||||
describe("when there is nothing in the input", () => {
|
||||
it("should return an array of undefineds", () => {
|
||||
expect(takeWithRepeats([], 3)).toEqual([undefined, undefined, undefined]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are exactly the amount required", () => {
|
||||
it("should return them all", () => {
|
||||
expect(takeWithRepeats(["a", undefined, "c"], 3)).toEqual([
|
||||
"a",
|
||||
undefined,
|
||||
"c",
|
||||
]);
|
||||
expect(takeWithRepeats(["a"], 1)).toEqual(["a"]);
|
||||
expect(takeWithRepeats([undefined], 1)).toEqual([undefined]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are less than the amount required", () => {
|
||||
it("should cycle through the ones available", () => {
|
||||
expect(takeWithRepeats(["a", "b"], 3)).toEqual(["a", "b", "a"]);
|
||||
expect(takeWithRepeats(["a", "b"], 5)).toEqual(["a", "b", "a", "b", "a"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there more than the amount required", () => {
|
||||
it("should return the first n items", () => {
|
||||
expect(takeWithRepeats(["a", "b", "c"], 2)).toEqual(["a", "b"]);
|
||||
expect(takeWithRepeats(["a", undefined, "c"], 2)).toEqual(["a", undefined]);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user