mirror of
https://github.com/photoprism/photoprism.git
synced 2025-09-26 21:01:58 +08:00
Compare commits
18 Commits
7e419f7419
...
albums-mul
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e42f7c0b0f | ||
![]() |
9368820102 | ||
![]() |
31a3e22067 | ||
![]() |
b4eb58f5e6 | ||
![]() |
9130712049 | ||
![]() |
1a068e65a9 | ||
![]() |
ab565c963d | ||
![]() |
90ba179a6d | ||
![]() |
897deb9fe8 | ||
![]() |
f57d9b95a1 | ||
![]() |
43be423a9a | ||
![]() |
33c2baf2bf | ||
![]() |
df2dd80dac | ||
![]() |
5ca8253f17 | ||
![]() |
23fd7b426d | ||
![]() |
d20ca7a28e | ||
![]() |
1879558421 | ||
![]() |
3e4d9944a7 |
60
frontend/src/common/albums.js
Normal file
60
frontend/src/common/albums.js
Normal file
@@ -0,0 +1,60 @@
|
||||
// Utility functions for handling album selection logic
|
||||
|
||||
export function processAlbumSelection(selectedAlbums, availableAlbums) {
|
||||
if (!Array.isArray(selectedAlbums)) {
|
||||
return { processed: [], changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
const processed = [];
|
||||
const seenUids = new Set();
|
||||
|
||||
selectedAlbums.forEach((item) => {
|
||||
// If it's a string, try to match it with existing albums
|
||||
if (typeof item === "string" && item.trim().length > 0) {
|
||||
const matchedAlbum = availableAlbums.find(
|
||||
(album) => album.Title && album.Title.toLowerCase() === item.trim().toLowerCase()
|
||||
);
|
||||
|
||||
if (matchedAlbum && !seenUids.has(matchedAlbum.UID)) {
|
||||
// Replace string with actual album object
|
||||
processed.push(matchedAlbum);
|
||||
seenUids.add(matchedAlbum.UID);
|
||||
changed = true;
|
||||
} else if (!matchedAlbum) {
|
||||
// Keep as string for new album creation
|
||||
processed.push(item.trim());
|
||||
}
|
||||
} else if (typeof item === "object" && item?.UID && !seenUids.has(item.UID)) {
|
||||
// Keep existing album objects, but prevent duplicates
|
||||
processed.push(item);
|
||||
seenUids.add(item.UID);
|
||||
} else if (typeof item === "object" && item?.UID && seenUids.has(item.UID)) {
|
||||
// Skip duplicate album objects
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
processed,
|
||||
changed: changed || processed.length !== selectedAlbums.length
|
||||
};
|
||||
}
|
||||
|
||||
// Creates a selectedAlbums watcher for Vue components
|
||||
export function createAlbumSelectionWatcher(albumsProperty) {
|
||||
return {
|
||||
handler(newVal) {
|
||||
const availableAlbums = this[albumsProperty] || [];
|
||||
const { processed, changed } = processAlbumSelection(newVal, availableAlbums);
|
||||
|
||||
if (changed) {
|
||||
this.$nextTick(() => {
|
||||
this.selectedAlbums = processed;
|
||||
}).catch((error) => {
|
||||
console.error('Error updating selectedAlbums:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@@ -184,10 +184,27 @@ export default {
|
||||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
cloneAlbums(ppid) {
|
||||
cloneAlbums(ppidOrList) {
|
||||
if (!ppidOrList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate array input
|
||||
if (Array.isArray(ppidOrList) && ppidOrList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.album = false;
|
||||
|
||||
$api.post(`albums/${ppid}/clone`, { albums: this.selection }).then(() => this.onCloned());
|
||||
const targets = Array.isArray(ppidOrList) ? ppidOrList : [ppidOrList];
|
||||
// Deduplicate target album UIDs
|
||||
const uniqueTargets = [...new Set(targets.filter((uid) => uid))];
|
||||
|
||||
Promise.all(uniqueTargets.map((uid) => $api.post(`albums/${uid}/clone`, { albums: this.selection })))
|
||||
.then(() => this.onCloned())
|
||||
.catch((error) => {
|
||||
$notify.error(this.$gettext("Some albums could not be cloned"));
|
||||
});
|
||||
},
|
||||
onCloned() {
|
||||
this.clearClipboard();
|
||||
|
@@ -113,10 +113,28 @@ export default {
|
||||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
addToAlbum(ppid) {
|
||||
addToAlbum(ppidOrList) {
|
||||
if (!ppidOrList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate array input
|
||||
if (Array.isArray(ppidOrList) && ppidOrList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.album = false;
|
||||
|
||||
$api.post(`albums/${ppid}/photos`, { files: this.selection }).then(() => this.onAdded());
|
||||
const albumUids = Array.isArray(ppidOrList) ? ppidOrList : [ppidOrList];
|
||||
// Deduplicate album UIDs
|
||||
const uniqueAlbumUids = [...new Set(albumUids.filter((uid) => uid))];
|
||||
const body = { files: this.selection };
|
||||
|
||||
Promise.all(uniqueAlbumUids.map((uid) => $api.post(`albums/${uid}/photos`, body)))
|
||||
.then(() => this.onAdded())
|
||||
.catch((error) => {
|
||||
$notify.error(this.$gettext("Some albums could not be updated"));
|
||||
});
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
|
@@ -115,14 +115,28 @@ export default {
|
||||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
addToAlbum(ppid) {
|
||||
if (!this.canAddAlbums) {
|
||||
addToAlbum(ppidOrList) {
|
||||
if (!this.canAddAlbums || !ppidOrList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate array input
|
||||
if (Array.isArray(ppidOrList) && ppidOrList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.album = false;
|
||||
|
||||
$api.post(`albums/${ppid}/photos`, { labels: this.selection }).then(() => this.onAdded());
|
||||
const albumUids = Array.isArray(ppidOrList) ? ppidOrList : [ppidOrList];
|
||||
// Deduplicate album UIDs
|
||||
const uniqueAlbumUids = [...new Set(albumUids.filter((uid) => uid))];
|
||||
const body = { labels: this.selection };
|
||||
|
||||
Promise.all(uniqueAlbumUids.map((uid) => $api.post(`albums/${uid}/photos`, body)))
|
||||
.then(() => this.onAdded())
|
||||
.catch((error) => {
|
||||
$notify.error(this.$gettext("Some albums could not be updated"));
|
||||
});
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
|
@@ -109,10 +109,28 @@ export default {
|
||||
this.clearSelection();
|
||||
this.expanded = false;
|
||||
},
|
||||
addToAlbum(ppid) {
|
||||
addToAlbum(ppidOrList) {
|
||||
if (!ppidOrList) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate array input
|
||||
if (Array.isArray(ppidOrList) && ppidOrList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dialog.album = false;
|
||||
|
||||
$api.post(`albums/${ppid}/photos`, { subjects: this.selection }).then(() => this.onAdded());
|
||||
const albumUids = Array.isArray(ppidOrList) ? ppidOrList : [ppidOrList];
|
||||
// Deduplicate album UIDs
|
||||
const uniqueAlbumUids = [...new Set(albumUids.filter((uid) => uid))];
|
||||
const body = { subjects: this.selection };
|
||||
|
||||
Promise.all(uniqueAlbumUids.map((uid) => $api.post(`albums/${uid}/photos`, body)))
|
||||
.then(() => this.onAdded())
|
||||
.catch((error) => {
|
||||
$notify.error(this.$gettext("Some albums could not be updated"));
|
||||
});
|
||||
},
|
||||
onAdded() {
|
||||
this.clearClipboard();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<v-dialog
|
||||
:model-value="visible"
|
||||
persistent
|
||||
max-width="390"
|
||||
max-width="500"
|
||||
class="p-dialog p-photo-album-dialog"
|
||||
@keydown.esc.exact="close"
|
||||
@after-enter="afterEnter"
|
||||
@@ -17,20 +17,38 @@
|
||||
<v-card-text>
|
||||
<v-combobox
|
||||
ref="input"
|
||||
v-model="album"
|
||||
autocomplete="off"
|
||||
:placeholder="$gettext('Select or create an album')"
|
||||
:items="items"
|
||||
v-model="selectedAlbums"
|
||||
:disabled="loading"
|
||||
:loading="loading"
|
||||
hide-no-data
|
||||
hide-details
|
||||
return-object
|
||||
chips
|
||||
closable-chips
|
||||
multiple
|
||||
class="input-albums"
|
||||
:items="items"
|
||||
item-title="Title"
|
||||
item-value="UID"
|
||||
class="input-album"
|
||||
@keyup.enter.native="confirm"
|
||||
:placeholder="$gettext('Select or create albums')"
|
||||
return-object
|
||||
>
|
||||
<template #no-data>
|
||||
<v-list-item>
|
||||
<v-list-item-title>
|
||||
{{ $gettext(`Press enter to create a new album.`) }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template #chip="chip">
|
||||
<v-chip
|
||||
:model-value="chip.selected"
|
||||
:disabled="chip.disabled"
|
||||
prepend-icon="mdi-bookmark"
|
||||
class="text-truncate"
|
||||
@click:close="removeSelection(chip.index)"
|
||||
>
|
||||
{{ chip.item.title ? chip.item.title : chip.item }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
</v-card-text>
|
||||
<v-card-actions class="action-buttons">
|
||||
@@ -38,7 +56,7 @@
|
||||
{{ $gettext(`Cancel`) }}
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="!album"
|
||||
:disabled="selectedAlbums.length === 0"
|
||||
variant="flat"
|
||||
color="highlight"
|
||||
class="action-confirm text-white"
|
||||
@@ -53,6 +71,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import Album from "model/album";
|
||||
import { createAlbumSelectionWatcher } from "common/albums";
|
||||
|
||||
// TODO: Handle cases where users have more than 10000 albums.
|
||||
const MaxResults = 10000;
|
||||
@@ -65,13 +84,13 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ["close", "confirm"],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
newAlbum: null,
|
||||
album: null,
|
||||
albums: [],
|
||||
items: [],
|
||||
selectedAlbums: [],
|
||||
labels: {
|
||||
addToAlbum: this.$gettext("Add to album"),
|
||||
createAlbum: this.$gettext("Create album"),
|
||||
@@ -85,6 +104,7 @@ export default {
|
||||
this.load("");
|
||||
}
|
||||
},
|
||||
selectedAlbums: createAlbumSelectionWatcher('items'),
|
||||
},
|
||||
methods: {
|
||||
afterEnter() {
|
||||
@@ -101,23 +121,85 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.album === "object" && this.album?.UID) {
|
||||
this.loading = true;
|
||||
this.$emit("confirm", this.album?.UID);
|
||||
} else if (typeof this.album === "string" && this.album.length > 0) {
|
||||
this.loading = true;
|
||||
let newAlbum = new Album({ Title: this.album, UID: "", Favorite: false });
|
||||
const existingUids = [];
|
||||
const namesToCreate = [];
|
||||
|
||||
newAlbum
|
||||
.save()
|
||||
.then((a) => {
|
||||
this.album = a;
|
||||
this.$emit("confirm", a.UID);
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
(this.selectedAlbums || []).forEach((a) => {
|
||||
if (typeof a === "object" && a?.UID) {
|
||||
existingUids.push(a.UID);
|
||||
} else if (typeof a === "string" && a.length > 0) {
|
||||
namesToCreate.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
// Deduplicate existing UIDs
|
||||
const uniqueExistingUids = [...new Set(existingUids)];
|
||||
|
||||
this.loading = true;
|
||||
|
||||
if (namesToCreate.length === 0) {
|
||||
this.$emit("confirm", uniqueExistingUids);
|
||||
this.loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create albums in parallel and handle partial failures without closing the dialog
|
||||
const creations = namesToCreate.map((title) => ({
|
||||
title,
|
||||
promise: new Album({ Title: title, UID: "", Favorite: false }).save(),
|
||||
}));
|
||||
|
||||
Promise.allSettled(creations.map((c) => c.promise))
|
||||
.then((results) => {
|
||||
const createdAlbums = [];
|
||||
const failedTitles = [];
|
||||
|
||||
results.forEach((res, idx) => {
|
||||
const originalTitle = creations[idx].title;
|
||||
if (res.status === "fulfilled" && res.value && res.value.UID) {
|
||||
createdAlbums.push(res.value);
|
||||
} else {
|
||||
failedTitles.push(originalTitle);
|
||||
}
|
||||
});
|
||||
|
||||
if (failedTitles.length > 0) {
|
||||
// Replace successfully created string tokens with album objects so they are not retried
|
||||
const byTitle = new Map(createdAlbums.map((a) => [a.Title || a.title || "", a]));
|
||||
this.selectedAlbums = (this.selectedAlbums || []).map((it) => {
|
||||
if (typeof it === "string") {
|
||||
const t = it.trim();
|
||||
const created = byTitle.get(t);
|
||||
return created ? created : it;
|
||||
}
|
||||
return it;
|
||||
});
|
||||
|
||||
// Add created albums to the combobox items so they can be selected by object
|
||||
const known = new Set((this.items || []).map((a) => a.UID));
|
||||
createdAlbums.forEach((a) => {
|
||||
if (a && a.UID && !known.has(a.UID)) {
|
||||
this.items.push(a);
|
||||
known.add(a.UID);
|
||||
}
|
||||
});
|
||||
|
||||
// Notify user and keep dialog open for corrections
|
||||
this.$notify.error(
|
||||
this.$gettext("Some albums could not be created. Please correct the names and try again.")
|
||||
);
|
||||
return; // Do not emit confirm; keep dialog open
|
||||
}
|
||||
|
||||
// All created successfully → emit and let parent close the dialog
|
||||
const createdUids = createdAlbums
|
||||
.map((a) => a && a.UID)
|
||||
.filter((u) => typeof u === "string" && u.length > 0);
|
||||
this.$emit("confirm", [...uniqueExistingUids, ...createdUids]);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
onLoad() {
|
||||
this.loading = true;
|
||||
@@ -137,10 +219,13 @@ export default {
|
||||
},
|
||||
reset() {
|
||||
this.loading = false;
|
||||
this.newAlbum = null;
|
||||
this.selectedAlbums = [];
|
||||
this.albums = [];
|
||||
this.items = [];
|
||||
},
|
||||
removeSelection(index) {
|
||||
this.selectedAlbums.splice(index, 1);
|
||||
},
|
||||
load(q) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
|
@@ -326,8 +326,13 @@ export default {
|
||||
$notify.success(this.$gettext("Selection restored"));
|
||||
this.clearClipboard();
|
||||
},
|
||||
addToAlbum(ppid) {
|
||||
if (!ppid || !this.canManage) {
|
||||
addToAlbum(ppidOrList) {
|
||||
if (!ppidOrList || !this.canManage) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate array input
|
||||
if (Array.isArray(ppidOrList) && ppidOrList.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -338,9 +343,16 @@ export default {
|
||||
this.busy = true;
|
||||
this.dialog.album = false;
|
||||
|
||||
$api
|
||||
.post(`albums/${ppid}/photos`, { photos: this.selection })
|
||||
const albumUids = Array.isArray(ppidOrList) ? ppidOrList : [ppidOrList];
|
||||
// Deduplicate album UIDs
|
||||
const uniqueAlbumUids = [...new Set(albumUids.filter((uid) => uid))];
|
||||
const body = { photos: this.selection };
|
||||
|
||||
Promise.all(uniqueAlbumUids.map((uid) => $api.post(`albums/${uid}/photos`, body)))
|
||||
.then(() => this.onAdded())
|
||||
.catch((error) => {
|
||||
$notify.error(this.$gettext("Some albums could not be updated"));
|
||||
})
|
||||
.finally(() => {
|
||||
this.busy = false;
|
||||
});
|
||||
|
@@ -60,7 +60,7 @@
|
||||
:items="albums"
|
||||
item-title="Title"
|
||||
item-value="UID"
|
||||
:placeholder="$gettext('Select or create an album')"
|
||||
:placeholder="$gettext('Select or create albums')"
|
||||
return-object
|
||||
>
|
||||
<template #no-data>
|
||||
@@ -134,6 +134,7 @@
|
||||
import $api from "common/api";
|
||||
import $notify from "common/notify";
|
||||
import Album from "model/album";
|
||||
import { createAlbumSelectionWatcher } from "common/albums";
|
||||
import { Duration } from "luxon";
|
||||
|
||||
export default {
|
||||
@@ -206,6 +207,7 @@ export default {
|
||||
this.reset();
|
||||
}
|
||||
},
|
||||
selectedAlbums: createAlbumSelectionWatcher('albums'),
|
||||
},
|
||||
methods: {
|
||||
afterEnter() {
|
||||
@@ -393,10 +395,15 @@ export default {
|
||||
addToAlbums.push(a);
|
||||
} else if (a instanceof Album && a.UID) {
|
||||
addToAlbums.push(a.UID);
|
||||
} else if (typeof a === "object" && a?.UID) {
|
||||
addToAlbums.push(a.UID);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Deduplicate album UIDs
|
||||
addToAlbums = [...new Set(addToAlbums)];
|
||||
|
||||
async function performUpload(ctx) {
|
||||
for (let i = 0; i < ctx.selected.length; i++) {
|
||||
let file = ctx.selected[i];
|
||||
|
@@ -145,18 +145,27 @@ test.meta("testID", "albums-003").meta({ type: "short", mode: "public" })("Commo
|
||||
});
|
||||
|
||||
test.meta("testID", "albums-004").meta({ type: "short", mode: "public" })(
|
||||
"Common: Add/Remove Photos to/from album",
|
||||
"Common: Add/Remove Photos to/from multiple albums",
|
||||
async (t) => {
|
||||
// Get initial counts for both Holiday and Christmas albums
|
||||
await menu.openPage("albums");
|
||||
await toolbar.search("Holiday");
|
||||
const AlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(AlbumUid);
|
||||
const PhotoCount = await photo.getPhotoCount("all");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const HolidayPhotoCount = await photo.getPhotoCount("all");
|
||||
await menu.openPage("albums");
|
||||
await toolbar.search("Christmas");
|
||||
const ChristmasAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
const ChristmasPhotoCount = await photo.getPhotoCount("all");
|
||||
|
||||
// Select photos to add to albums
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
const SecondPhotoUid = await photo.getNthPhotoUid("image", 1);
|
||||
|
||||
// Verify photos are not in any albums initially
|
||||
await page.clickCardTitleOfUID(FirstPhotoUid);
|
||||
await t
|
||||
.click(photoedit.infoTab)
|
||||
@@ -164,19 +173,32 @@ test.meta("testID", "albums-004").meta({ type: "short", mode: "public" })(
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Holiday").visible)
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Christmas").visible)
|
||||
.notOk()
|
||||
.click(photoedit.dialogClose);
|
||||
|
||||
// Select both photos and add to multiple albums simultaneously
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await photoviewer.openPhotoViewer("uid", FirstPhotoUid);
|
||||
await photoviewer.triggerPhotoViewerAction("select-toggle");
|
||||
await photoviewer.triggerPhotoViewerAction("close-button");
|
||||
await contextmenu.triggerContextMenuAction("album", "Holiday");
|
||||
await contextmenu.triggerContextMenuAction("album", ["Holiday", "Christmas", "Food"]);
|
||||
|
||||
// Verify photos were added to Holiday album
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(AlbumUid);
|
||||
const PhotoCountAfterAdd = await photo.getPhotoCount("all");
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const HolidayPhotoCountAfterAdd = await photo.getPhotoCount("all");
|
||||
await t.expect(HolidayPhotoCountAfterAdd).eql(HolidayPhotoCount + 2);
|
||||
|
||||
await t.expect(PhotoCountAfterAdd).eql(PhotoCount + 2);
|
||||
// Verify photos were added to Christmas album
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
const ChristmasPhotoCountAfterAdd = await photo.getPhotoCount("all");
|
||||
await t.expect(ChristmasPhotoCountAfterAdd).eql(ChristmasPhotoCount + 2);
|
||||
|
||||
// Verify photo info shows all albums
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
await page.clickCardTitleOfUID(FirstPhotoUid);
|
||||
await t
|
||||
.click(photoedit.infoTab)
|
||||
@@ -184,15 +206,58 @@ test.meta("testID", "albums-004").meta({ type: "short", mode: "public" })(
|
||||
.ok()
|
||||
.expect(Selector("td").withText("Holiday").visible)
|
||||
.ok()
|
||||
.expect(Selector("td").withText("Food").visible)
|
||||
.ok()
|
||||
.expect(Selector("td").withText("Christmas").visible)
|
||||
.ok()
|
||||
.click(photoedit.dialogClose);
|
||||
|
||||
// Remove photos from Holiday album and verify count
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountAfterRemove = await photo.getPhotoCount("all");
|
||||
const HolidayPhotoCountAfterRemove = await photo.getPhotoCount("all");
|
||||
await t.expect(HolidayPhotoCountAfterRemove).eql(HolidayPhotoCountAfterAdd - 2);
|
||||
|
||||
await t.expect(PhotoCountAfterRemove).eql(PhotoCountAfterAdd - 2);
|
||||
// Verify photos are still in Christmas album
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
const ChristmasPhotoCountAfterHolidayRemove = await photo.getPhotoCount("all");
|
||||
await t.expect(ChristmasPhotoCountAfterHolidayRemove).eql(ChristmasPhotoCountAfterAdd);
|
||||
|
||||
// Verify photo info shows only Christmas album now
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
await page.clickCardTitleOfUID(FirstPhotoUid);
|
||||
await t
|
||||
.click(photoedit.infoTab)
|
||||
.expect(Selector("td").withText("Albums").visible)
|
||||
.ok()
|
||||
.expect(Selector("td").withText("Holiday").visible)
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Christmas").visible)
|
||||
.ok()
|
||||
.click(photoedit.dialogClose);
|
||||
|
||||
// Remove photos from Christmas album to clean up
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const ChristmasPhotoCountAfterRemove = await photo.getPhotoCount("all");
|
||||
await t.expect(ChristmasPhotoCountAfterRemove).eql(ChristmasPhotoCount);
|
||||
|
||||
// Delete Food album
|
||||
await menu.openPage("albums");
|
||||
await toolbar.search("Food");
|
||||
const FoodUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.selectAlbumFromUID(FoodUid);
|
||||
await contextmenu.triggerContextMenuAction("delete", "");
|
||||
|
||||
// Final verification that photos are not in any albums
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
await page.clickCardTitleOfUID(FirstPhotoUid);
|
||||
@@ -200,12 +265,46 @@ test.meta("testID", "albums-004").meta({ type: "short", mode: "public" })(
|
||||
.click(photoedit.infoTab)
|
||||
.expect(Selector("td").withText("Albums").visible)
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Food").visible)
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Holiday").visible)
|
||||
.notOk()
|
||||
.expect(Selector("td").withText("Christmas").visible)
|
||||
.notOk()
|
||||
.click(photoedit.dialogClose);
|
||||
}
|
||||
);
|
||||
|
||||
test.meta("testID", "albums-004-duplicate").meta({ type: "short", mode: "public" })(
|
||||
"Album duplication when selecting from dropdown then typing same name",
|
||||
async (t) => {
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
|
||||
await contextmenu.openContextMenu();
|
||||
await t.click(Selector("button.action-album"));
|
||||
|
||||
await t.click(Selector(".input-albums input"));
|
||||
const holidayOption = Selector("div").withText("Holiday").parent('div[role="option"]');
|
||||
|
||||
if (await holidayOption.visible) {
|
||||
await t.click(holidayOption);
|
||||
const afterDropdown = await Selector("span.v-chip").withText("Holiday").count;
|
||||
await t.expect(afterDropdown).eql(1, "Should have 1 chip after dropdown selection");
|
||||
|
||||
await t.click(Selector(".input-albums input"));
|
||||
await t.typeText(Selector(".input-albums input"), "Holiday", { replace: true }).pressKey("enter");
|
||||
|
||||
const afterTyping = await Selector("span.v-chip").withText("Holiday").count;
|
||||
await t.expect(afterTyping).eql(1, "Should still have only 1 chip after typing duplicate");
|
||||
}
|
||||
|
||||
await t.click(Selector(".action-cancel"));
|
||||
}
|
||||
);
|
||||
|
||||
test.meta("testID", "albums-005").meta({ mode: "public" })("Common: Use album search and filters", async (t) => {
|
||||
await menu.openPage("albums");
|
||||
if (t.browser.platform === "mobile") {
|
||||
@@ -231,11 +330,12 @@ test.meta("testID", "albums-005").meta({ mode: "public" })("Common: Use album se
|
||||
});
|
||||
|
||||
test.meta("testID", "albums-006").meta({ mode: "public" })("Common: Test album autocomplete", async (t) => {
|
||||
await menu.openPage("browse");
|
||||
await toolbar.search("photo:true");
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await contextmenu.openContextMenu();
|
||||
await t.click(Selector("button.action-album")).click(Selector(".input-album input"));
|
||||
await t.click(Selector("button.action-album")).click(Selector(".input-albums input"));
|
||||
|
||||
await t
|
||||
.expect(page.selectOption.withText("Holiday").visible)
|
||||
@@ -243,7 +343,7 @@ test.meta("testID", "albums-006").meta({ mode: "public" })("Common: Test album a
|
||||
.expect(page.selectOption.withText("Christmas").visible)
|
||||
.ok();
|
||||
|
||||
await t.typeText(Selector(".input-album input"), "C", { replace: true });
|
||||
await t.typeText(Selector(".input-albums input"), "C", { replace: true });
|
||||
|
||||
await t
|
||||
.expect(page.selectOption.withText("Holiday").visible)
|
||||
|
@@ -112,15 +112,23 @@ test.meta("testID", "calendar-004").meta({ type: "short", mode: "public" })(
|
||||
async (t) => {
|
||||
await menu.openPage("albums");
|
||||
const AlbumCount = await album.getAlbumCount("all");
|
||||
await toolbar.search("Holiday");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const InitialPhotoCountHoliday = await photo.getPhotoCount("all");
|
||||
await menu.openPage("calendar");
|
||||
const SecondCalendarUid = await album.getNthAlbumUid("all", 1);
|
||||
await album.openAlbumWithUid(SecondCalendarUid);
|
||||
const PhotoCountInCalendar = await photo.getPhotoCount("all");
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
const SecondPhotoUid = await photo.getNthPhotoUid("image", 1);
|
||||
const ThirdPhotoUid = await photo.getNthPhotoUid("image", 2);
|
||||
const FourthPhotoUid = await photo.getNthPhotoUid("image", 3);
|
||||
const FifthPhotoUid = await photo.getNthPhotoUid("image", 4);
|
||||
|
||||
await menu.openPage("calendar");
|
||||
await album.selectAlbumFromUID(SecondCalendarUid);
|
||||
await contextmenu.triggerContextMenuAction("clone", "NotYetExistingAlbumForCalendar");
|
||||
await contextmenu.triggerContextMenuAction("clone", ["NotYetExistingAlbumForCalendar", "Holiday"]);
|
||||
await menu.openPage("albums");
|
||||
const AlbumCountAfterCreation = await album.getAlbumCount("all");
|
||||
|
||||
@@ -145,6 +153,18 @@ test.meta("testID", "calendar-004").meta({ type: "short", mode: "public" })(
|
||||
}
|
||||
const AlbumCountAfterDelete = await album.getAlbumCount("all");
|
||||
await t.expect(AlbumCountAfterDelete).eql(AlbumCount);
|
||||
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await photo.selectPhotoFromUID(ThirdPhotoUid);
|
||||
await photo.selectPhotoFromUID(FourthPhotoUid);
|
||||
await photo.selectPhotoFromUID(FifthPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountHolidayAfterDelete = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountHolidayAfterDelete).eql(InitialPhotoCountHoliday);
|
||||
|
||||
await menu.openPage("calendar");
|
||||
await album.openAlbumWithUid(SecondCalendarUid);
|
||||
await photo.checkPhotoVisibility(FirstPhotoUid, true);
|
||||
|
@@ -127,6 +127,10 @@ test.meta("testID", "folders-004").meta({ mode: "public" })(
|
||||
async (t) => {
|
||||
await menu.openPage("albums");
|
||||
const AlbumCount = await album.getAlbumCount("all");
|
||||
await toolbar.search("Holiday");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const InitialPhotoCountHoliday = await photo.getPhotoCount("all");
|
||||
await menu.openPage("folders");
|
||||
const ThirdFolderUid = await album.getNthAlbumUid("all", 2);
|
||||
await album.openAlbumWithUid(ThirdFolderUid);
|
||||
@@ -134,7 +138,7 @@ test.meta("testID", "folders-004").meta({ mode: "public" })(
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
await menu.openPage("folders");
|
||||
await album.selectAlbumFromUID(ThirdFolderUid);
|
||||
await contextmenu.triggerContextMenuAction("clone", "NotYetExistingAlbumForFolder");
|
||||
await contextmenu.triggerContextMenuAction("clone", ["Holiday", "NotYetExistingAlbumForFolder"]);
|
||||
await menu.openPage("albums");
|
||||
const AlbumCountAfterCreation = await album.getAlbumCount("all");
|
||||
|
||||
@@ -155,6 +159,13 @@ test.meta("testID", "folders-004").meta({ mode: "public" })(
|
||||
|
||||
await t.expect(AlbumCountAfterDelete).eql(AlbumCount);
|
||||
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountHolidayAfterDelete = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountHolidayAfterDelete).eql(InitialPhotoCountHoliday);
|
||||
|
||||
await menu.openPage("folders");
|
||||
await album.openAlbumWithUid(ThirdFolderUid);
|
||||
await photo.checkPhotoVisibility(FirstPhotoUid, true);
|
||||
|
@@ -124,12 +124,17 @@ test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Rename Label
|
||||
await t.expect(Selector("div.no-results").visible).ok();
|
||||
});
|
||||
|
||||
test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Add label to album", async (t) => {
|
||||
test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Add label to albums", async (t) => {
|
||||
await menu.openPage("albums");
|
||||
await toolbar.search("Christmas");
|
||||
const AlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(AlbumUid);
|
||||
const PhotoCount = await photo.getPhotoCount("all");
|
||||
const ChristmasAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
const InitialPhotoCountChristmas = await photo.getPhotoCount("all");
|
||||
await menu.openPage("albums");
|
||||
await toolbar.search("Holiday");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const InitialPhotoCountHoliday = await photo.getPhotoCount("all");
|
||||
await menu.openPage("labels");
|
||||
await toolbar.search("sunglasses");
|
||||
const LabelSunglasses = await label.getNthLabeltUid(0);
|
||||
@@ -143,12 +148,12 @@ test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Add label to
|
||||
await menu.openPage("labels");
|
||||
await label.triggerHoverAction("uid", LabelSunglasses, "select");
|
||||
await contextmenu.checkContextMenuCount("1");
|
||||
await contextmenu.triggerContextMenuAction("album", "Christmas");
|
||||
await contextmenu.triggerContextMenuAction("album", ["Christmas", "Holiday"]);
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(AlbumUid);
|
||||
const PhotoCountAfterAdd = await photo.getPhotoCount("all");
|
||||
await album.openAlbumWithUid(ChristmasAlbumUid);
|
||||
const PhotoCountAfterAddChristmas = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountAfterAdd).eql(PhotoCount + 5);
|
||||
await t.expect(PhotoCountAfterAddChristmas).eql(InitialPhotoCountChristmas + 5);
|
||||
|
||||
await photo.triggerHoverAction("uid", FirstPhotoSunglasses, "select");
|
||||
await photo.triggerHoverAction("uid", SecondPhotoSunglasses, "select");
|
||||
@@ -157,9 +162,21 @@ test.meta("testID", "labels-003").meta({ mode: "public" })("Common: Add label to
|
||||
await photo.triggerHoverAction("uid", FifthPhotoSunglasses, "select");
|
||||
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountAfterDelete = await photo.getPhotoCount("all");
|
||||
const PhotoCountAfterDeleteChristmas = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountAfterDelete).eql(PhotoCountAfterAdd - 5);
|
||||
await t.expect(PhotoCountAfterDeleteChristmas).eql(PhotoCountAfterAddChristmas - 5);
|
||||
await menu.openPage("albums");
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.triggerHoverAction("uid", FirstPhotoSunglasses, "select");
|
||||
await photo.triggerHoverAction("uid", SecondPhotoSunglasses, "select");
|
||||
await photo.triggerHoverAction("uid", ThirdPhotoSunglasses, "select");
|
||||
await photo.triggerHoverAction("uid", FourthPhotoSunglasses, "select");
|
||||
await photo.triggerHoverAction("uid", FifthPhotoSunglasses, "select");
|
||||
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountHolidayAfterDelete = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountHolidayAfterDelete).eql(InitialPhotoCountHoliday);
|
||||
});
|
||||
|
||||
test.meta("testID", "labels-004").meta({ mode: "public" })("Common: Delete label", async (t) => {
|
||||
|
@@ -116,15 +116,24 @@ test.meta("testID", "moments-003").meta({ mode: "public" })(
|
||||
async (t) => {
|
||||
await menu.openPage("albums");
|
||||
const AlbumCount = await album.getAlbumCount("all");
|
||||
await toolbar.search("Holiday");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const InitialPhotoCountHoliday = await photo.getPhotoCount("all");
|
||||
await menu.openPage("moments");
|
||||
const FirstMomentUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(FirstMomentUid);
|
||||
const SecondMomentUid = await album.getNthAlbumUid("all", 1);
|
||||
await album.openAlbumWithUid(SecondMomentUid);
|
||||
const PhotoCountInMoment = await photo.getPhotoCount("all");
|
||||
const FirstPhotoUid = await photo.getNthPhotoUid("image", 0);
|
||||
const SecondPhotoUid = await photo.getNthPhotoUid("image", 1);
|
||||
const ThirdPhotoUid = await photo.getNthPhotoUid("image", 2);
|
||||
const FourthPhotoUid = await photo.getNthPhotoUid("image", 3);
|
||||
const FifthPhotoUid = await photo.getNthPhotoUid("image", 4);
|
||||
const SixthPhotoUid = await photo.getNthPhotoUid("image", 5);
|
||||
const SeventhPhotoUid = await photo.getNthPhotoUid("image", 6);
|
||||
await menu.openPage("moments");
|
||||
await album.selectAlbumFromUID(FirstMomentUid);
|
||||
await contextmenu.triggerContextMenuAction("clone", "NotYetExistingAlbumForMoment");
|
||||
await album.selectAlbumFromUID(SecondMomentUid);
|
||||
await contextmenu.triggerContextMenuAction("clone", ["NotYetExistingAlbumForMoment", "Holiday"]);
|
||||
await menu.openPage("albums");
|
||||
const AlbumCountAfterCreation = await album.getAlbumCount("all");
|
||||
|
||||
@@ -144,10 +153,24 @@ test.meta("testID", "moments-003").meta({ mode: "public" })(
|
||||
await contextmenu.triggerContextMenuAction("delete", "");
|
||||
const AlbumCountAfterDelete = await album.getAlbumCount("all");
|
||||
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await photo.selectPhotoFromUID(ThirdPhotoUid);
|
||||
await photo.selectPhotoFromUID(FourthPhotoUid);
|
||||
await photo.selectPhotoFromUID(FifthPhotoUid);
|
||||
await photo.selectPhotoFromUID(SixthPhotoUid);
|
||||
await photo.selectPhotoFromUID(SeventhPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
|
||||
const PhotoCountHolidayAfterDelete = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountHolidayAfterDelete).eql(InitialPhotoCountHoliday);
|
||||
|
||||
await t.expect(AlbumCountAfterDelete).eql(AlbumCount);
|
||||
|
||||
await menu.openPage("moments");
|
||||
await album.openAlbumWithUid(FirstMomentUid);
|
||||
await album.openAlbumWithUid(SecondMomentUid);
|
||||
await photo.checkPhotoVisibility(FirstPhotoUid, true);
|
||||
await photo.checkPhotoVisibility(SecondPhotoUid, true);
|
||||
}
|
||||
|
@@ -114,6 +114,10 @@ test.meta("testID", "states-003").meta({ mode: "public" })(
|
||||
async (t) => {
|
||||
await menu.openPage("albums");
|
||||
const AlbumCount = await album.getAlbumCount("all");
|
||||
await toolbar.search("Holiday");
|
||||
const HolidayAlbumUid = await album.getNthAlbumUid("all", 0);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
const InitialPhotoCountHoliday = await photo.getPhotoCount("all");
|
||||
await menu.openPage("states");
|
||||
await toolbar.search("Canada");
|
||||
const FirstStateUid = await album.getNthAlbumUid("all", 0);
|
||||
@@ -123,7 +127,7 @@ test.meta("testID", "states-003").meta({ mode: "public" })(
|
||||
const SecondPhotoUid = await photo.getNthPhotoUid("image", 1);
|
||||
await menu.openPage("states");
|
||||
await album.selectAlbumFromUID(FirstStateUid);
|
||||
await contextmenu.triggerContextMenuAction("clone", "NotYetExistingAlbumForState");
|
||||
await contextmenu.triggerContextMenuAction("clone", ["NotYetExistingAlbumForState", "Holiday"]);
|
||||
await menu.openPage("albums");
|
||||
const AlbumCountAfterCreation = await album.getAlbumCount("all");
|
||||
|
||||
@@ -144,6 +148,13 @@ test.meta("testID", "states-003").meta({ mode: "public" })(
|
||||
const AlbumCountAfterDelete = await album.getAlbumCount("all");
|
||||
|
||||
await t.expect(AlbumCountAfterDelete).eql(AlbumCount);
|
||||
await album.openAlbumWithUid(HolidayAlbumUid);
|
||||
await photo.selectPhotoFromUID(FirstPhotoUid);
|
||||
await photo.selectPhotoFromUID(SecondPhotoUid);
|
||||
await contextmenu.triggerContextMenuAction("remove", "");
|
||||
const PhotoCountHolidayAfterDelete = await photo.getPhotoCount("all");
|
||||
|
||||
await t.expect(PhotoCountHolidayAfterDelete).eql(InitialPhotoCountHoliday);
|
||||
|
||||
await menu.openPage("states");
|
||||
await album.openAlbumWithUid(FirstStateUid);
|
||||
|
@@ -39,10 +39,22 @@ export default class Page {
|
||||
if (action === "delete") {
|
||||
await t.click(Selector("button.action-confirm"));
|
||||
}
|
||||
if ((action === "album") | (action === "clone")) {
|
||||
await t.typeText(Selector(".input-album input"), albumName, { replace: true });
|
||||
if (await Selector("div").withText(albumName).parent('div[role="option"]').visible) {
|
||||
await t.click(Selector("div").withText(albumName).parent('div[role="option"]'));
|
||||
if ((action === "album") || (action === "clone")) {
|
||||
await t.click(Selector(".input-albums"));
|
||||
|
||||
// Handle single album name or array of album names
|
||||
const albumNames = Array.isArray(albumName) ? albumName : [albumName];
|
||||
|
||||
for (const name of albumNames) {
|
||||
if (await Selector("div").withText(name).parent('div[role="option"]').visible) {
|
||||
// Click on the album option to select it
|
||||
await t
|
||||
.click(Selector("div").withText(name).parent('div[role="option"]'))
|
||||
.click(Selector("div i.mdi-bookmark"));
|
||||
} else {
|
||||
await t.typeText(Selector(".input-albums input"), name).click(Selector("div i.mdi-bookmark"));
|
||||
}
|
||||
await t.expect(Selector("span.v-chip").withText(name).visible).ok();
|
||||
}
|
||||
await t.click(Selector("button.action-confirm"));
|
||||
}
|
||||
|
71
frontend/tests/vitest/common/albums.test.js
Normal file
71
frontend/tests/vitest/common/albums.test.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { processAlbumSelection } from '../../../src/common/albums.js';
|
||||
|
||||
function album(title, uid) {
|
||||
return { Title: title, UID: uid };
|
||||
}
|
||||
|
||||
describe('processAlbumSelection', () => {
|
||||
it('trims whitespace and matches existing albums (case-insensitive)', () => {
|
||||
const available = [album('Summer', '1')];
|
||||
const selected = [' summer '];
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, available);
|
||||
|
||||
expect(processed).toHaveLength(1);
|
||||
expect(processed[0]).toEqual(available[0]);
|
||||
expect(changed).toBe(true);
|
||||
});
|
||||
|
||||
it('deduplicates identical UIDs and strings resolving to same album', () => {
|
||||
const a1 = album('Trips', 't1');
|
||||
const selected = [a1, a1, 'trips', 'TRIPS'];
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, [a1]);
|
||||
|
||||
expect(processed).toHaveLength(1);
|
||||
expect(processed[0]).toEqual(a1);
|
||||
expect(changed).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps unmatched names as trimmed strings (for creation later)', () => {
|
||||
const selected = [' New Album '];
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, []);
|
||||
|
||||
expect(processed).toEqual(['New Album']);
|
||||
// No structural change: only trimming does not count as change if lengths are equal and no replacements/drops
|
||||
expect(changed).toBe(false);
|
||||
});
|
||||
|
||||
it('drops empty / whitespace-only entries and reports change', () => {
|
||||
const selected = [' ', '\n', '\t', ' Name '];
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, []);
|
||||
|
||||
expect(processed).toEqual(['Name']);
|
||||
expect(changed).toBe(true);
|
||||
});
|
||||
|
||||
it('reconciles selection with new available items (race condition)', () => {
|
||||
const selected = [' Road Trip '];
|
||||
const availableThen = [album('Road Trip', 'rt01')];
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, availableThen);
|
||||
|
||||
expect(processed).toHaveLength(1);
|
||||
expect(processed[0]).toEqual(availableThen[0]);
|
||||
expect(changed).toBe(true);
|
||||
});
|
||||
|
||||
it('preserves existing album objects and prevents duplicates', () => {
|
||||
const a = album('Family', 'fam1');
|
||||
const selected = [a, { ...a }]; // two distinct objects with same UID
|
||||
|
||||
const { processed, changed } = processAlbumSelection(selected, [a]);
|
||||
|
||||
expect(processed).toHaveLength(1);
|
||||
expect(processed[0]).toEqual(a);
|
||||
expect(changed).toBe(true);
|
||||
});
|
||||
});
|
Reference in New Issue
Block a user