diff --git a/frontend/composables/partials/use-actions-factory.test.ts b/frontend/composables/partials/use-actions-factory.test.ts new file mode 100644 index 000000000..e5ff546d5 --- /dev/null +++ b/frontend/composables/partials/use-actions-factory.test.ts @@ -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; + + 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); + }); +}); diff --git a/frontend/composables/partials/use-actions-factory.ts b/frontend/composables/partials/use-actions-factory.ts index 4e655915c..bf6a6fcfa 100644 --- a/frontend/composables/partials/use-actions-factory.ts +++ b/frontend/composables/partials/use-actions-factory.ts @@ -12,6 +12,7 @@ interface StoreActions extends ReadOnlyStoreActions { createOne(createData: T): Promise; updateOne(updateData: T): Promise; deleteOne(id: string | number): Promise; + deleteMany(ids: (string | number)[]): Promise; } /** @@ -165,11 +166,23 @@ export function useStoreActions( 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 { getAll, refresh, createOne, updateOne, deleteOne, + deleteMany, }; } diff --git a/frontend/pages/group/data/categories.vue b/frontend/pages/group/data/categories.vue index c8164cd7c..6cd18d5f6 100644 --- a/frontend/pages/group/data/categories.vue +++ b/frontend/pages/group/data/categories.vue @@ -209,12 +209,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - if (!item.id) { - continue; - } - await categoryStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id); + await categoryStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/foods.vue b/frontend/pages/group/data/foods.vue index 538223f8c..0cdd842db 100644 --- a/frontend/pages/group/data/foods.vue +++ b/frontend/pages/group/data/foods.vue @@ -528,9 +528,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - await foodStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id); + await foodStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/labels.vue b/frontend/pages/group/data/labels.vue index e4224727b..5c7730e96 100644 --- a/frontend/pages/group/data/labels.vue +++ b/frontend/pages/group/data/labels.vue @@ -261,9 +261,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - await labelStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id); + await labelStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/recipe-actions.vue b/frontend/pages/group/data/recipe-actions.vue index bf518972f..aa5ae7045 100644 --- a/frontend/pages/group/data/recipe-actions.vue +++ b/frontend/pages/group/data/recipe-actions.vue @@ -249,9 +249,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - await actionStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id); + await actionStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/tags.vue b/frontend/pages/group/data/tags.vue index d30ae01f1..82e8a577a 100644 --- a/frontend/pages/group/data/tags.vue +++ b/frontend/pages/group/data/tags.vue @@ -211,12 +211,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - if (!item.id) { - continue; - } - await tagStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id).filter(id => !!id); + await tagStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/tools.vue b/frontend/pages/group/data/tools.vue index f854afe1a..400487862 100644 --- a/frontend/pages/group/data/tools.vue +++ b/frontend/pages/group/data/tools.vue @@ -263,9 +263,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - await toolStore.actions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id); + await toolStore.actions.deleteMany(ids); bulkDeleteTarget.value = []; } diff --git a/frontend/pages/group/data/units.vue b/frontend/pages/group/data/units.vue index d296afcf1..18f87292c 100644 --- a/frontend/pages/group/data/units.vue +++ b/frontend/pages/group/data/units.vue @@ -465,9 +465,8 @@ export default defineNuxtComponent({ } async function deleteSelected() { - for (const item of bulkDeleteTarget.value) { - await unitActions.deleteOne(item.id); - } + const ids = bulkDeleteTarget.value.map(item => item.id); + await unitActions.deleteMany(ids); bulkDeleteTarget.value = []; }