mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-14 06:15:26 -05:00
fix: Improved bulk deletion by reducing refreshs (#6634)
Co-authored-by: David Schinkel <david@zollsoft.de> Co-authored-by: Hayden <64056131+hay-kot@users.noreply.github.com>
This commit is contained in:
58
frontend/composables/partials/use-actions-factory.test.ts
Normal file
58
frontend/composables/partials/use-actions-factory.test.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { describe, expect, test, vi } from "vitest";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { useStoreActions } from "./use-actions-factory";
|
||||||
|
import type { BaseCRUDAPI } from "~/lib/api/base/base-clients";
|
||||||
|
|
||||||
|
describe("useStoreActions", () => {
|
||||||
|
const mockApi = {
|
||||||
|
getAll: vi.fn(),
|
||||||
|
createOne: vi.fn(),
|
||||||
|
updateOne: vi.fn(),
|
||||||
|
deleteOne: vi.fn(),
|
||||||
|
} as unknown as BaseCRUDAPI<unknown, unknown, unknown>;
|
||||||
|
|
||||||
|
const mockStore = ref([]);
|
||||||
|
const mockLoading = ref(false);
|
||||||
|
|
||||||
|
test("deleteMany calls deleteOne for each ID and refreshes once", async () => {
|
||||||
|
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
|
||||||
|
|
||||||
|
mockApi.deleteOne = vi.fn().mockResolvedValue({ response: { data: {} } });
|
||||||
|
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
|
||||||
|
|
||||||
|
const ids = ["1", "2", "3"];
|
||||||
|
await actions.deleteMany(ids);
|
||||||
|
|
||||||
|
expect(mockApi.deleteOne).toHaveBeenCalledTimes(3);
|
||||||
|
expect(mockApi.deleteOne).toHaveBeenCalledWith("1");
|
||||||
|
expect(mockApi.deleteOne).toHaveBeenCalledWith("2");
|
||||||
|
expect(mockApi.deleteOne).toHaveBeenCalledWith("3");
|
||||||
|
|
||||||
|
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleteMany handles empty array", async () => {
|
||||||
|
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
|
||||||
|
|
||||||
|
mockApi.deleteOne = vi.fn();
|
||||||
|
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
|
||||||
|
|
||||||
|
await actions.deleteMany([]);
|
||||||
|
|
||||||
|
expect(mockApi.deleteOne).not.toHaveBeenCalled();
|
||||||
|
expect(mockApi.getAll).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleteMany sets loading state", async () => {
|
||||||
|
const actions = useStoreActions("test-store", mockApi, mockStore, mockLoading);
|
||||||
|
|
||||||
|
mockApi.deleteOne = vi.fn().mockResolvedValue({});
|
||||||
|
mockApi.getAll = vi.fn().mockResolvedValue({ data: { items: [] } });
|
||||||
|
|
||||||
|
const promise = actions.deleteMany(["1"]);
|
||||||
|
expect(mockLoading.value).toBe(true);
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
expect(mockLoading.value).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,7 @@ interface StoreActions<T extends BoundT> extends ReadOnlyStoreActions<T> {
|
|||||||
createOne(createData: T): Promise<T | null>;
|
createOne(createData: T): Promise<T | null>;
|
||||||
updateOne(updateData: T): Promise<T | null>;
|
updateOne(updateData: T): Promise<T | null>;
|
||||||
deleteOne(id: string | number): Promise<T | null>;
|
deleteOne(id: string | number): Promise<T | null>;
|
||||||
|
deleteMany(ids: (string | number)[]): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,11 +166,23 @@ export function useStoreActions<T extends BoundT>(
|
|||||||
return response?.data || null;
|
return response?.data || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteMany(ids: (string | number)[]) {
|
||||||
|
loading.value = true;
|
||||||
|
for (const id of ids) {
|
||||||
|
await api.deleteOne(id);
|
||||||
|
}
|
||||||
|
if (allRef?.value) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getAll,
|
getAll,
|
||||||
refresh,
|
refresh,
|
||||||
createOne,
|
createOne,
|
||||||
updateOne,
|
updateOne,
|
||||||
deleteOne,
|
deleteOne,
|
||||||
|
deleteMany,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,12 +209,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id);
|
||||||
if (!item.id) {
|
await categoryStore.actions.deleteMany(ids);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await categoryStore.actions.deleteOne(item.id);
|
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,9 +528,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id);
|
||||||
await foodStore.actions.deleteOne(item.id);
|
await foodStore.actions.deleteMany(ids);
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -261,9 +261,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id);
|
||||||
await labelStore.actions.deleteOne(item.id);
|
await labelStore.actions.deleteMany(ids);
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,9 +249,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id);
|
||||||
await actionStore.actions.deleteOne(item.id);
|
await actionStore.actions.deleteMany(ids);
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,12 +211,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id);
|
||||||
if (!item.id) {
|
await tagStore.actions.deleteMany(ids);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
await tagStore.actions.deleteOne(item.id);
|
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,9 +263,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id);
|
||||||
await toolStore.actions.deleteOne(item.id);
|
await toolStore.actions.deleteMany(ids);
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -465,9 +465,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSelected() {
|
async function deleteSelected() {
|
||||||
for (const item of bulkDeleteTarget.value) {
|
const ids = bulkDeleteTarget.value.map(item => item.id);
|
||||||
await unitActions.deleteOne(item.id);
|
await unitActions.deleteMany(ids);
|
||||||
}
|
|
||||||
bulkDeleteTarget.value = [];
|
bulkDeleteTarget.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user