mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Icons for years (#220)
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
Playlist,
|
||||
SimilarArtist,
|
||||
AlbumSummary,
|
||||
RadioStation,
|
||||
RadioStation
|
||||
} from "../src/music_service";
|
||||
|
||||
import { b64Encode } from "../src/b64";
|
||||
@@ -166,12 +166,6 @@ export const SAMPLE_GENRES = [
|
||||
];
|
||||
export const randomGenre = () => SAMPLE_GENRES[randomInt(SAMPLE_GENRES.length)];
|
||||
|
||||
export const aYear = (year: string) => ({ id: year, year });
|
||||
|
||||
export const Y2024 = aYear("2024");
|
||||
export const Y2023 = aYear("2023");
|
||||
export const Y1969 = aYear("1969");
|
||||
|
||||
export function aTrack(fields: Partial<Track> = {}): Track {
|
||||
const id = uuid();
|
||||
const artist = anArtist();
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
allOf,
|
||||
features,
|
||||
STAR_WARS,
|
||||
NO_FEATURES,
|
||||
} from "../src/icon";
|
||||
|
||||
describe("SvgIcon", () => {
|
||||
@@ -27,7 +28,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
@@ -58,7 +61,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -107,7 +112,9 @@ describe("SvgIcon", () => {
|
||||
<rect x="0" y="0" width="24" height="24" fill="red"/>
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -131,7 +138,9 @@ describe("SvgIcon", () => {
|
||||
<rect x="-4" y="-4" width="36" height="36" fill="pink"/>
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -149,7 +158,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -169,7 +180,9 @@ describe("SvgIcon", () => {
|
||||
<rect x="0" y="0" width="24" height="24" fill="red"/>
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -179,7 +192,7 @@ describe("SvgIcon", () => {
|
||||
|
||||
describe("foreground color", () => {
|
||||
describe("with no viewPort increase", () => {
|
||||
it("should add a rectangle the same size as the original viewPort", () => {
|
||||
it("should change the fill values", () => {
|
||||
expect(
|
||||
new SvgIcon(svgIcon24)
|
||||
.with({ features: { foregroundColor: "red" } })
|
||||
@@ -189,7 +202,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1" fill="red"/>
|
||||
<path d="path2" fill="none" stroke="red"/>
|
||||
<text font-size="25" fill="none" stroke="red">80's</text>
|
||||
<path d="path3" fill="red"/>
|
||||
<text font-size="25" fill="red">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -197,7 +212,7 @@ describe("SvgIcon", () => {
|
||||
});
|
||||
|
||||
describe("with a viewPort increase", () => {
|
||||
it("should add a rectangle the same size as the original viewPort", () => {
|
||||
it("should change the fill values", () => {
|
||||
expect(
|
||||
new SvgIcon(svgIcon24)
|
||||
.with({
|
||||
@@ -212,7 +227,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32">
|
||||
<path d="path1" fill="pink"/>
|
||||
<path d="path2" fill="none" stroke="pink"/>
|
||||
<text font-size="25" fill="none" stroke="pink">80's</text>
|
||||
<path d="path3" fill="pink"/>
|
||||
<text font-size="25" fill="pink">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -230,7 +247,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -249,7 +268,9 @@ describe("SvgIcon", () => {
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1" fill="red"/>
|
||||
<path d="path2" fill="none" stroke="red"/>
|
||||
<text font-size="25" fill="none" stroke="red">80's</text>
|
||||
<path d="path3" fill="red"/>
|
||||
<text font-size="25" fill="red">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
@@ -257,6 +278,48 @@ describe("SvgIcon", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("text", () => {
|
||||
describe("when text value specified", () => {
|
||||
it("should change the text values", () => {
|
||||
expect(
|
||||
new SvgIcon(svgIcon24)
|
||||
.with({ features: { text: "yipppeeee" } })
|
||||
.toString()
|
||||
).toEqual(
|
||||
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">yipppeeee</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">yipppeeee</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("of undefined", () => {
|
||||
it("should not do anything", () => {
|
||||
expect(
|
||||
new SvgIcon(svgIcon24)
|
||||
.with({ features: { text: undefined } })
|
||||
.toString()
|
||||
).toEqual(
|
||||
xmlTidy(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="path1"/>
|
||||
<path d="path2" fill="none" stroke="#000"/>
|
||||
<text font-size="25" fill="none">80's</text>
|
||||
<path d="path3"/>
|
||||
<text font-size="25">80's</text>
|
||||
</svg>
|
||||
`)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("swapping the svg", () => {
|
||||
describe("with no other changes", () => {
|
||||
it("should swap out the svg, but maintain the IconFeatures", () => {
|
||||
@@ -315,10 +378,14 @@ describe("SvgIcon", () => {
|
||||
|
||||
class DummyIcon implements Icon {
|
||||
svg: string;
|
||||
features: Partial<IconFeatures>;
|
||||
features: IconFeatures;
|
||||
|
||||
constructor(svg: string, features: Partial<IconFeatures>) {
|
||||
this.svg = svg;
|
||||
this.features = features;
|
||||
this.features = {
|
||||
...NO_FEATURES,
|
||||
...features
|
||||
};
|
||||
}
|
||||
|
||||
public apply = (transformer: Transformer): Icon => transformer(this);
|
||||
@@ -347,6 +414,7 @@ describe("transform", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "blue",
|
||||
backgroundColor: "blue",
|
||||
text: "a",
|
||||
},
|
||||
})
|
||||
.apply(
|
||||
@@ -354,6 +422,7 @@ describe("transform", () => {
|
||||
features: {
|
||||
foregroundColor: "override1",
|
||||
backgroundColor: "override2",
|
||||
text: "b",
|
||||
},
|
||||
})
|
||||
) as DummyIcon;
|
||||
@@ -363,6 +432,7 @@ describe("transform", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "override1",
|
||||
backgroundColor: "override2",
|
||||
text: "b",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -379,6 +449,7 @@ describe("transform", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "blue",
|
||||
backgroundColor: "blue",
|
||||
text: "bob",
|
||||
},
|
||||
})
|
||||
.apply(
|
||||
@@ -392,6 +463,7 @@ describe("transform", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "blue",
|
||||
backgroundColor: "blue",
|
||||
text: "bob"
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -408,6 +480,7 @@ describe("features", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "blue",
|
||||
backgroundColor: "blue",
|
||||
text: "foobar"
|
||||
})
|
||||
) as DummyIcon;
|
||||
|
||||
@@ -415,6 +488,7 @@ describe("features", () => {
|
||||
viewPortIncreasePercent: 100,
|
||||
foregroundColor: "blue",
|
||||
backgroundColor: "blue",
|
||||
text: "foobar"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1366,11 +1366,25 @@ describe("server", () => {
|
||||
"..%2F..%2Ffoo",
|
||||
"%2Fetc%2Fpasswd",
|
||||
".%2Fbob.js",
|
||||
".",
|
||||
"..",
|
||||
"1",
|
||||
"%23%24",
|
||||
].forEach((type) => {
|
||||
describe(`trying to retrieve an icon with name ${type}`, () => {
|
||||
it(`should fail`, async () => {
|
||||
const response = await request(server()).get(
|
||||
`/icon/${type}/size/legacy`
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("missing icons", () => {
|
||||
[
|
||||
"1",
|
||||
"notAValidIcon",
|
||||
"notAValidIcon:withSomeText"
|
||||
].forEach((type) => {
|
||||
describe(`trying to retrieve an icon with name ${type}`, () => {
|
||||
it(`should fail`, async () => {
|
||||
@@ -1398,6 +1412,20 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("invalid text", () => {
|
||||
["..", "foobar.123", "_dog_", "{ whoop }"].forEach((text) => {
|
||||
describe(`trying to retrieve an icon with text ${text}`, () => {
|
||||
it(`should fail`, async () => {
|
||||
const response = await request(server()).get(
|
||||
`/icon/yyyy:${text}/size/60`
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(400);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("fetching", () => {
|
||||
[
|
||||
"artists",
|
||||
@@ -1527,6 +1555,41 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("specifing some text", () => {
|
||||
const text = "somethingWicked"
|
||||
|
||||
describe(`legacy icon`, () => {
|
||||
it("should return the png image", async () => {
|
||||
const response = await request(server()).get(
|
||||
`/icon/yyyy:${text}/size/legacy`
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.header["content-type"]).toEqual("image/png");
|
||||
const image = await Image.load(response.body);
|
||||
expect(image.width).toEqual(80);
|
||||
expect(image.height).toEqual(80);
|
||||
});
|
||||
});
|
||||
|
||||
describe("svg icon", () => {
|
||||
it(`should return an svg image with the text replaced`, async () => {
|
||||
const response = await request(server()).get(
|
||||
`/icon/yyyy:${text}/size/60`
|
||||
);
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.header["content-type"]).toEqual(
|
||||
"image/svg+xml; charset=utf-8"
|
||||
);
|
||||
const svg = Buffer.from(response.body).toString();
|
||||
expect(svg).toContain(
|
||||
`>${text}</text>`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,9 +39,6 @@ import {
|
||||
ROCK,
|
||||
TRIP_HOP,
|
||||
PUNK,
|
||||
Y2024,
|
||||
Y2023,
|
||||
Y1969,
|
||||
aPlaylist,
|
||||
aRadioStation,
|
||||
} from "./builders";
|
||||
@@ -562,6 +559,24 @@ describe("coverArtURI", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("iconArtURI", () => {
|
||||
const bonobUrl = new URLBuilder(
|
||||
"http://bonob.example.com:8080/context?search=yes"
|
||||
);
|
||||
|
||||
describe("with no text", () => {
|
||||
it("should return just the icon uri", () => {
|
||||
expect(iconArtURI(bonobUrl, "mushroom").href()).toEqual("http://bonob.example.com:8080/context/icon/mushroom/size/legacy?search=yes")
|
||||
});
|
||||
});
|
||||
|
||||
describe("with text", () => {
|
||||
it("should return just the icon uri", () => {
|
||||
expect(iconArtURI(bonobUrl, "yyyy", "foobar10000").href()).toEqual("http://bonob.example.com:8080/context/icon/yyyy:foobar10000/size/legacy?search=yes")
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("wsdl api", () => {
|
||||
const musicService = {
|
||||
generateToken: jest.fn(),
|
||||
@@ -1383,7 +1398,7 @@ describe("wsdl api", () => {
|
||||
});
|
||||
|
||||
describe("asking for a year", () => {
|
||||
const expectedYears = [Y1969, Y2023, Y2024];
|
||||
const expectedYears = [{ year: "?" }, { year: "1969" }, { year: "1980" }, { year: "2001" }, { year: "2010" }];
|
||||
|
||||
beforeEach(() => {
|
||||
musicLibrary.years.mockResolvedValue(expectedYears);
|
||||
@@ -1396,17 +1411,22 @@ describe("wsdl api", () => {
|
||||
index: 0,
|
||||
count: 100,
|
||||
});
|
||||
const albumListForYear = (year: string, icon: URLBuilder) => ({
|
||||
itemType: "albumList",
|
||||
id: `year:${year}`,
|
||||
title: year,
|
||||
albumArtURI: icon.href(),
|
||||
});
|
||||
|
||||
expect(result[0]).toEqual(
|
||||
getMetadataResult({
|
||||
mediaCollection: expectedYears.map((year) => ({
|
||||
itemType: "albumList",
|
||||
id: `year:${year.id}`,
|
||||
title: year.year,
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
"music",
|
||||
).href(),
|
||||
})),
|
||||
mediaCollection: [
|
||||
albumListForYear("?", iconArtURI(bonobUrl, "music")),
|
||||
albumListForYear("1969", iconArtURI(bonobUrl, "yyyy", "1969")),
|
||||
albumListForYear("1980", iconArtURI(bonobUrl, "yyyy", "1980")),
|
||||
albumListForYear("2001", iconArtURI(bonobUrl, "yyyy", "2001")),
|
||||
albumListForYear("2010", iconArtURI(bonobUrl, "yyyy", "2010")),
|
||||
],
|
||||
index: 0,
|
||||
total: expectedYears.length,
|
||||
})
|
||||
@@ -1418,21 +1438,22 @@ describe("wsdl api", () => {
|
||||
it("should return just that page", async () => {
|
||||
const result = await ws.getMetadataAsync({
|
||||
id: `years`,
|
||||
index: 1,
|
||||
index: 2,
|
||||
count: 2,
|
||||
});
|
||||
expect(result[0]).toEqual(
|
||||
getMetadataResult({
|
||||
mediaCollection: [Y2023, Y2024].map((year) => ({
|
||||
mediaCollection: [{ year: "1980" }, { year: "2001" }].map((year) => ({
|
||||
itemType: "albumList",
|
||||
id: `year:${year.id}`,
|
||||
id: `year:${year.year}`,
|
||||
title: year.year,
|
||||
albumArtURI: iconArtURI(
|
||||
bonobUrl,
|
||||
"music"
|
||||
"yyyy",
|
||||
year.year
|
||||
).href(),
|
||||
})),
|
||||
index: 1,
|
||||
index: 2,
|
||||
total: expectedYears.length,
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user