Skip to content

Commit 5273b61

Browse files
authored
Merge pull request #112 from moonwave99/prismock
Improve coverage of controllers and db functions
2 parents ab57bc5 + 9c63502 commit 5273b61

42 files changed

Lines changed: 3660 additions & 1753 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package-lock.json

Lines changed: 343 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"@react-router/dev": "^7.3.0",
4444
"@testing-library/jest-dom": "^6.6.3",
4545
"@testing-library/react": "^16.3.0",
46+
"@testing-library/user-event": "^14.6.1",
4647
"@types/electron-squirrel-startup": "^1.0.2",
4748
"@types/lodash": "^4.17.16",
4849
"@types/react": "^19.0.10",
@@ -60,9 +61,11 @@
6061
"eslint-plugin-import": "^2.31.0",
6162
"eslint-plugin-react-hooks": "^5.2.0",
6263
"jsdom": "^26.0.0",
64+
"prismock": "^1.35.4",
6365
"ts-node": "^10.9.2",
6466
"typescript": "^5.8.2",
6567
"vite": "^6.2.5",
68+
"vite-plugin-svgr": "^4.5.0",
6669
"vitest": "^3.1.1",
6770
"vitest-mock-extended": "^3.0.1"
6871
},
@@ -77,7 +80,6 @@
7780
"@react-router/serve": "^7.3.0",
7881
"@tanstack/react-query": "^5.68.0",
7982
"@tanstack/react-virtual": "^3.13.4",
80-
"@testing-library/user-event": "^14.6.1",
8183
"clsx": "^2.1.1",
8284
"electron-settings": "^4.0.4",
8385
"electron-squirrel-startup": "^1.0.1",
@@ -93,11 +95,9 @@
9395
"react-modal": "^3.16.3",
9496
"react-responsive": "^10.0.1",
9597
"react-router": "^7.3.0",
96-
"run-applescript": "^7.0.0",
9798
"sha1": "^1.1.1",
9899
"unzipper": "^0.12.3",
99100
"use-debounce": "^10.0.4",
100-
"vite-plugin-svgr": "^4.3.0",
101101
"zip-a-folder": "^3.1.9",
102102
"zustand": "^5.0.3"
103103
}

src/lib/utils.ts

Lines changed: 130 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { capitalize, deburr, uniqBy } from 'lodash';
2-
import type { MouseEvent } from 'react';
1+
import { capitalize, deburr, uniqBy } from "lodash";
2+
import type { MouseEvent } from "react";
33
import type {
44
ReleaseType,
55
Release,
@@ -14,33 +14,51 @@ import type {
1414
ArtistWithReleases,
1515
GroupWithArtists,
1616
WithAdditionalArtists,
17-
1817
} from "@/types/types";
19-
import { getCover } from './links';
20-
21-
const releaseTypes: ReleaseType[] =
22-
['Album', 'Compilation', 'EP', 'Single', 'Bootleg', 'Various', 'Tribute', 'Soundtrack'];
23-
24-
export const VARIOUS_ARTISTS_FOLDER = '[V:A]';
25-
export const VARIOUS_ARTISTS_NAME = '_VV_AA_';
26-
27-
export function getReleaseTitle({ title, subReleases = [] }:
28-
Pick<ReleaseWithArtistAndSubreleases, 'title' | 'subReleases'>): string {
18+
import { getCover } from "./links";
19+
20+
const releaseTypes: ReleaseType[] = [
21+
"Album",
22+
"Compilation",
23+
"EP",
24+
"Single",
25+
"Bootleg",
26+
"Various",
27+
"Tribute",
28+
"Soundtrack",
29+
];
30+
31+
export const VARIOUS_ARTISTS_FOLDER = "[V:A]";
32+
export const VARIOUS_ARTISTS_NAME = "_VV_AA_";
33+
34+
export function getReleaseTitle({
35+
title,
36+
subReleases = [],
37+
}: Pick<ReleaseWithArtistAndSubreleases, "title" | "subReleases">): string {
2938
if (!subReleases.length) {
3039
return title;
3140
}
3241
const match = title.match(/(.*) CD(\d+)/);
3342
return match ? match[1] : title;
3443
}
3544

36-
export function getReleaseArtist(
37-
{ artist, additionalArtists }: Pick<ReleaseWithArtist & WithAdditionalArtists, 'artist' | 'additionalArtists'>
38-
) {
39-
return [artist, ...additionalArtists].map(x => normalizeArtistName(x.name)).join(', ');
45+
export function getReleaseArtist({
46+
artist,
47+
additionalArtists,
48+
}: Pick<
49+
ReleaseWithArtist & WithAdditionalArtists,
50+
"artist" | "additionalArtists"
51+
>) {
52+
return [artist, ...additionalArtists]
53+
.map((x) => normalizeArtistName(x.name))
54+
.join(", ");
4055
}
4156

4257
export function getUniqueArtists(releases: ReleaseWithArtist[]): Artist[] {
43-
return uniqBy(releases.map(x => x.artist), (x => x.id));
58+
return uniqBy(
59+
releases.map((x) => x.artist),
60+
(x) => x.id
61+
);
4462
}
4563

4664
export function getMainReleaseTitle(release: ReleaseWithArtist) {
@@ -52,12 +70,15 @@ export function getMainReleaseTitle(release: ReleaseWithArtist) {
5270
}
5371

5472
export function countReleasesByType(releases: Release[]): ReleaseCountByType {
55-
return releases.reduce((memo, { type }) => ({ ...memo, [type]: memo[type] ? memo[type] + 1 : 1 }), {} as ReleaseCountByType);
73+
return releases.reduce(
74+
(memo, { type }) => ({ ...memo, [type]: memo[type] ? memo[type] + 1 : 1 }),
75+
{} as ReleaseCountByType
76+
);
5677
}
5778

5879
export function estimateListCardSize() {
5980
return {
60-
width: '100%',
81+
width: "100%",
6182
height: 6 * 16,
6283
};
6384
}
@@ -66,7 +87,7 @@ export function normalizeTitle(title: string) {
6687
return title
6788
.replace(/ CD(\d+)/, "")
6889
.replaceAll(/\(\w: (.*)\)/g, "")
69-
.replaceAll(' : ', ' / ')
90+
.replaceAll(" : ", " / ")
7091
.trim();
7192
}
7293

@@ -78,25 +99,29 @@ export function normalizeArtistName(name: string) {
7899
}
79100

80101
export function normalizeArtistDisplayName(name: string) {
81-
return name === VARIOUS_ARTISTS_NAME ? 'Various Artists' : name;
102+
return name === VARIOUS_ARTISTS_NAME ? "Various Artists" : name;
82103
}
83104

84105
export function getDiscInfo({ subReleases }: ReleaseWithArtistAndSubreleases) {
85-
return subReleases.length
86-
? `(${subReleases.length + 1} discs)`
87-
: null;
106+
return subReleases.length ? `(${subReleases.length + 1} discs)` : null;
88107
}
89108

90-
export function getReleaseDuration(release: ReleaseWithArtistAndTracksAndSubreleases) {
109+
export function getReleaseDuration(
110+
release: ReleaseWithArtistAndTracksAndSubreleases
111+
) {
91112
const allTracks = [
92113
...release.tracks,
93-
...(release.subReleases.length ? release.subReleases.flatMap(x => x.tracks) : [])
114+
...(release.subReleases.length
115+
? release.subReleases.flatMap((x) => x.tracks)
116+
: []),
94117
];
95118

96119
return {
97120
trackCount: allTracks.length,
98-
duration: formatDuration(allTracks.reduce((memo, { duration }) => memo += duration, 0))
99-
}
121+
duration: formatDuration(
122+
allTracks.reduce((memo, { duration }) => (memo += duration), 0)
123+
),
124+
};
100125
}
101126

102127
export function formatDuration(duration: number) {
@@ -112,37 +137,52 @@ export function formatDuration(duration: number) {
112137
return formatted;
113138
}
114139

115-
116140
export function isEmpty(obj: object) {
117141
return Object.keys(obj).length === 0;
118142
}
119143

120-
export function sortReleasesByTypeAndYear(releases: ReleaseWithArtist[]) {
121-
return releaseTypes
122-
.flatMap(type => releases
123-
.filter(x => x.type === type)
144+
type Sortable = number | string | boolean | Date;
145+
146+
export function sortBy(key: string, order: "asc" | "desc" = "asc") {
147+
return (a: Record<string, Sortable>, b: Record<string, Sortable>) =>
148+
(a[key] > b[key] ? 1 : -1) * (order === "asc" ? 1 : -1);
149+
}
150+
151+
export function sortReleasesByTypeAndYear(
152+
releases: Pick<Release, "type" | "year" | "title">[]
153+
) {
154+
return releaseTypes.flatMap((type) =>
155+
releases
156+
.filter((x) => x.type === type)
124157
.sort((a, b) => {
125158
if (!a.year || !b.year) {
126159
return 0;
127160
}
128161
if (a.year === b.year) {
129162
return a.title.toLowerCase() > b.title.toLowerCase() ? 1 : -1;
130163
}
131-
return Math.sign(a.year - b.year)
164+
return Math.sign(a.year - b.year);
132165
})
133-
)
166+
);
134167
}
135168

136-
export function getReleaseWithTracklistHeight(release: ReleaseWithArtistAndTracksAndSubreleases): number {
137-
const maxTracks = Math.max(...[
138-
release,
139-
...release.subReleases
140-
].map(x => x.tracks?.length));
169+
export function getReleaseWithTracklistHeight(
170+
release: ReleaseWithArtistAndTracksAndSubreleases
171+
): number {
172+
const maxTracks = Math.max(
173+
...[release, ...release.subReleases].map((x) => x.tracks?.length)
174+
);
141175
// cover height + margin + gap + tracks
142-
return 128 + 16 + 4 + (release.subReleases.length ? 32 : 0) + (maxTracks * (40 + 4));
176+
return (
177+
128 + 16 + 4 + (release.subReleases.length ? 32 : 0) + maxTracks * (40 + 4)
178+
);
143179
}
144180

145-
export async function mapSeries<T, U>(array: T[], callback: (item: T, index: number) => Promise<U>, interval = 0): Promise<U[]> {
181+
export async function mapSeries<T, U>(
182+
array: T[],
183+
callback: (item: T, index: number) => Promise<U>,
184+
interval = 0
185+
): Promise<U[]> {
146186
if (!array.length) {
147187
return [];
148188
}
@@ -162,16 +202,19 @@ export async function mapSeries<T, U>(array: T[], callback: (item: T, index: num
162202
});
163203
}
164204

165-
export function sortByQueryPosition<K extends string, T extends {
166-
[Property in K]: string;
167-
}>(query: string, key: keyof T, a: T, b: T) {
205+
export function sortByQueryPosition<
206+
K extends string,
207+
T extends {
208+
[Property in K]: string;
209+
},
210+
>(query: string, key: keyof T, a: T, b: T) {
168211
const posA = a[key].toLowerCase().indexOf(query.toLowerCase());
169212
const posB = b[key].toLowerCase().indexOf(query.toLowerCase());
170213
return Math.sign(posA - posB);
171214
}
172215

173216
export function wait(ms = 100) {
174-
return new Promise(resolve => setTimeout(resolve, ms));
217+
return new Promise((resolve) => setTimeout(resolve, ms));
175218
}
176219

177220
export function getReleaseContextMenuParams({
@@ -202,7 +245,7 @@ export function refreshCovers(releases: Release[]) {
202245
const seed = `${Math.random() * 100000}`.slice(0, 5);
203246
element.src = `${getCover(hash)}?_=${seed}`;
204247
});
205-
})
248+
});
206249
}
207250

208251
export function withStopPropagation(handler: (event: MouseEvent) => void) {
@@ -212,13 +255,19 @@ export function withStopPropagation(handler: (event: MouseEvent) => void) {
212255
};
213256
}
214257

215-
type Item = CollectionWithReleases | ArtistWithReleases | ReleaseWithArtistAndSubreleases | GroupWithArtists;
258+
type Item =
259+
| CollectionWithReleases
260+
| ArtistWithReleases
261+
| ReleaseWithArtistAndSubreleases
262+
| GroupWithArtists;
216263

217-
export function getCoverRelease(item: Item): ReleaseWithArtistAndSubreleases | null {
264+
export function getCoverRelease(
265+
item: Item
266+
): ReleaseWithArtistAndSubreleases | null {
218267
if (item._type === "release") {
219268
return item;
220269
}
221-
if (item._type === 'group') {
270+
if (item._type === "group") {
222271
const coverArtist = item.coverArtist || item.artists[0];
223272
return coverArtist ? getCoverRelease(coverArtist) : null;
224273
}
@@ -231,24 +280,41 @@ type NewReleaseInfo = {
231280
newTitle: string;
232281
newType: ReleaseType;
233282
newYear: number;
234-
}
235-
236-
type EditReleaseParam = (
237-
Pick<Release, 'id' | 'path' | 'hash' | 'title' | 'artist_id' | 'year' | 'type' | 'discTitle' | 'discNumber'>
238-
& NewReleaseInfo
239-
);
283+
};
240284

241-
export function didReleaseInfoChange(infos: EditReleaseParam[], excludeDiscTitle?: boolean) {
242-
return infos.some(
243-
(x: EditReleaseParam) => ['path', 'title', 'type', 'year', 'discTitle'].slice(0, excludeDiscTitle ? -1 : undefined)
244-
.some(key => x[key as keyof EditReleaseParam] !== x[`new${capitalize(key)}` as keyof NewReleaseInfo])
285+
type EditReleaseParam = Pick<
286+
Release,
287+
| "id"
288+
| "path"
289+
| "hash"
290+
| "title"
291+
| "artist_id"
292+
| "year"
293+
| "type"
294+
| "discTitle"
295+
| "discNumber"
296+
> &
297+
NewReleaseInfo;
298+
299+
export function didReleaseInfoChange(
300+
infos: EditReleaseParam[],
301+
excludeDiscTitle?: boolean
302+
) {
303+
return infos.some((x: EditReleaseParam) =>
304+
["path", "title", "type", "year", "discTitle"]
305+
.slice(0, excludeDiscTitle ? -1 : undefined)
306+
.some(
307+
(key) =>
308+
x[key as keyof EditReleaseParam] !==
309+
x[`new${capitalize(key)}` as keyof NewReleaseInfo]
310+
)
245311
);
246312
}
247313

248314
export function withCoverRelease(artist: ArtistWithReleases) {
249315
return {
250316
...artist,
251-
coverRelease: artist.coverRelease || artist.releases[0]
317+
coverRelease: artist.coverRelease || artist.releases[0],
252318
};
253319
}
254320

@@ -261,7 +327,7 @@ export function normalizeDiacritics(input: string) {
261327
input
262328
.normalize("NFD")
263329
.replace(/[\u0300-\u036f]/g, "")
264-
.replace(/\((\d+)\)$/, '')
330+
.replace(/\((\d+)\)$/, "")
265331
.trim()
266332
);
267-
}
333+
}

src/main/__mocks__/settings.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { beforeEach } from "vitest";
2+
import { mockReset } from "vitest-mock-extended";
3+
4+
beforeEach(() => {
5+
mockReset(initSettings);
6+
});
7+
8+
export const initSettings = vi.fn();

0 commit comments

Comments
 (0)