Repository: shopware/frontends (ts)

Issue:
[BUG] AddToCart and Clearance sale activated
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

If I have a product with that **stock configuration**:
![image](https://github.com/shopware/frontends/assets/3763023/b3615c74-4bd0-43ea-bd4e-220bf6539404)

I can still click the **AddToCart** Button in Frontend and get a success message.

However, the product is not added to the cart ... everything looks correct as a customer.

### Expected Behavior

As a customer, I would expect a message that the product is not in stock and the AddToCart Button should be disabled.

I would also expect another error message when I am allowed (not deactivated) to click the AddToCart Button.

Suggestion: Compare the template with the default storefront and how the flow is there.

### Steps To Reproduce

**Test-Product:**
http://localhost:3000/LAVENDA-Product-Variants/SW20004.1

### Environment

_No response_

### Anything else?

_No response_

Pull request description:
fix(vue-demo): disable add to cart button when product is OOS
### Description

<!-- Describe the changes you did and which issue you're closing -->

 closes #887 

### Type of change

<!-- Comment out the type of change your PR is making -->

<!-- Bug fix (non-breaking change that fixes an issue) -->
<!-- New feature (non-breaking change which adds functionality) -->
<!-- Breaking change (fix or feature that would cause existing functionality to not work as expected) -->

### ToDo's

<!-- Add the todo's that need to be done before merge -->
<!-- Remember to run `pnpm run changeset` and describe your change (plus potential migration guide/important notes) to your pull request. -->

<!-- - [ ] Changeset file provided [read more](https://github.com/shopware/frontends/blob/main/CONTRIBUTION.md#changelog-preparation) -->
<!-- - [ ] Documentation added/updated -->
<!-- - [ ] Unit-Tests added/updated -->
<!-- - [ ] E2E-Tests added/updated -->
<!-- - [ ] Related Issue updated -->

### Screenshots (if applicable)

<!-- Please attach any relevant screenshots or images to help explain your changes. -->

### Additional context

<!-- Add any other context about the pull request here. -->


Patch:
diff --git a/.changeset/six-pigs-laugh.md b/.changeset/six-pigs-laugh.md
new file mode 100644
index 00000000..d976e9cb
--- /dev/null
+++ b/.changeset/six-pigs-laugh.md
@@ -0,0 +1,5 @@
+---
+"vue-demo-store": patch
+---
+
+Disable add to cart button when product is out of stock
diff --git a/examples/product-detail-page/src/components/ProductView.vue b/examples/product-detail-page/src/components/ProductView.vue
index b8ee360b..8d94ea2a 100644
--- a/examples/product-detail-page/src/components/ProductView.vue
+++ b/examples/product-detail-page/src/components/ProductView.vue
@@ -122,8 +122,12 @@ const unitName = computed(() => product.value?.unit?.name);
           {{ regulationPrice ? "*" : "" }}
         </div>
         <button
+          :disabled="!product.available"
           @click="addToCartProxy()"
           class="mt-4 w-2/3 relative inline-flex items-center justify-center p-0.5 overflow-hidden text-sm font-medium text-gray-900 rounded-lg group bg-gradient-to-br from-cyan-500 to-blue-500 group-hover:from-cyan-500 group-hover:to-blue-500 hover:text-white dark:text-white focus:ring-4 focus:outline-none focus:ring-cyan-200 dark:focus:ring-cyan-800"
+          :class="{
+            'opacity-50 cursor-not-allowed': !product.available,
+          }"
         >
           <span
             class="w-full relative px-5 py-2.5 transition-all ease-in duration-75 bg-white dark:bg-gray-900 rounded-md group-hover:bg-opacity-0"
diff --git a/packages/cms-base/components/SwProductAddToCart.vue b/packages/cms-base/components/SwProductAddToCart.vue
index 761ad241..eac0783c 100644
--- a/packages/cms-base/components/SwProductAddToCart.vue
+++ b/packages/cms-base/components/SwProductAddToCart.vue
@@ -56,9 +56,13 @@ const addToCartProxy = async () => {
     </div>
     <div class="basis-3/4 ml-4">
       <button
-        @click="addToCartProxy"
+        :disabled="!product.available"
         class="py-2 px-6 w-full mt-4 bg-gradient-to-r from-cyan-500 to-blue-500 transition ease-in-out hover:bg-gradient-to-l duration-300 cursor-pointer border border-transparent rounded-md flex items-center justify-center text-base font-medium text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
+        :class="{
+          'opacity-50 cursor-not-allowed': !product.available,
+        }"
         data-testid="add-to-cart-button"
+        @click="addToCartProxy"
       >
         🛍 {{ translations.product.addToCart }}
       </button>
diff --git a/packages/cms-base/components/SwProductCard.vue b/packages/cms-base/components/SwProductCard.vue
index 8824550c..215ab721 100644
--- a/packages/cms-base/components/SwProductCard.vue
+++ b/packages/cms-base/components/SwProductCard.vue
@@ -237,13 +237,15 @@ const srcPath = computed(() => {
         <button
           v-if="!fromPrice"
           type="button"
-          @click="addToCartProxy"
           class="w-full justify-center my-8 md-m-0 py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 transform transition duration-400 flex"
           :class="{
             'text-white bg-blue-600 hover:bg-blue-700': !isInCart,
             'text-gray-600 bg-gray-100': isInCart,
+            'opacity-50 cursor-not-allowed': !product.available,
           }"
           data-testid="add-to-cart-button"
+          :disabled="!product.available"
+          @click="addToCartProxy"
         >
           {{ translations.product.addToCart }}
           <div v-if="isInCart" class="flex ml-2">
diff --git a/packages/composables/src/useAddToCart.ts b/packages/composables/src/useAddToCart.ts
index fdb6c188..8f240fe7 100644
--- a/packages/composables/src/useAddToCart.ts
+++ b/packages/composables/src/useAddToCart.ts
@@ -46,7 +46,6 @@ export function useAddToCart(
   async function addToCart(): Promise<Schemas["Cart"]> {
     if (!_product.value?.id) throw new Error("Product id is required");
 
-    if (!quantity.value) quantity.value = 1;
     const addToCartResponse = await addProduct({
       id: _product.value?.id,
       quantity: quantity.value,
diff --git a/packages/composables/src/useCheckout.ts b/packages/composables/src/useCheckout.ts
index 68640b5b..4c6cb219 100644
--- a/packages/composables/src/useCheckout.ts
+++ b/packages/composables/src/useCheckout.ts
@@ -112,7 +112,7 @@ export function useCheckout(): UseCheckoutReturn {
       },
     );
     storeShippingMethods.value =
-      response.data.elements.sort(
+      response.data.elements?.sort(
         (a: Schemas["ShippingMethod"], b: Schemas["ShippingMethod"]) =>
           (a.position ?? 0) - (b.position ?? 0),
       ) || [];
diff --git a/packages/composables/src/useCmsTranslations.ts b/packages/composables/src/useCmsTranslations.ts
index be3ba332..0970f809 100644
--- a/packages/composables/src/useCmsTranslations.ts
+++ b/packages/composables/src/useCmsTranslations.ts
@@ -1,9 +1,5 @@
 import { inject } from "vue";
 
 export function useCmsTranslations() {
-  try {
-    return inject("cmsTranslations", {});
-  } catch ($error) {
-    return {};
-  }
+  return inject("cmsTranslations", {});
 }
diff --git a/packages/composables/src/useListing.ts b/packages/composables/src/useListing.ts
index b7885eb5..4814f676 100644
--- a/packages/composables/src/useListing.ts
+++ b/packages/composables/src/useListing.ts
@@ -210,11 +210,6 @@ export function useListing(params?: {
     searchMethod = async (
       searchCriteria: operations["readProductListing post /product-listing/{categoryId}"]["body"],
     ) => {
-      if (!categoryId) {
-        throw new Error(
-          "[useListing][search] Search category id does not exist.",
-        );
-      }
       const { data } = await apiClient.invoke(
         "readProductListing post /product-listing/{categoryId}",
         {
@@ -222,7 +217,7 @@ export function useListing(params?: {
             "sw-include-seo-urls": true,
           },
           pathParams: {
-            categoryId,
+            categoryId: categoryId as string, // null exception in useCategory,
           },
           body: searchCriteria,
         },
diff --git a/packages/composables/src/useUrlResolver.ts b/packages/composables/src/useUrlResolver.ts
index 836495ec..7582a579 100644
--- a/packages/composables/src/useUrlResolver.ts
+++ b/packages/composables/src/useUrlResolver.ts
@@ -2,13 +2,7 @@ import { inject } from "vue";
 import { buildUrlPrefix } from "@shopware-pwa/helpers-next";
 
 export function useUrlResolver() {
-  const getUrlPrefix = () => {
-    try {
-      return inject("urlPrefix", "");
-    } catch ($error) {
-      return "";
-    }
-  };
+  const getUrlPrefix = () => inject("urlPrefix", "");
 
   const resolveUrl = (url: string) => {
     // @see: https://codeql.github.com/codeql-query-help/javascript/js-polynomial-redos/
@@ -19,15 +13,15 @@ export function useUrlResolver() {
     const navigationPattern = /[a-zA-Z0-9]+\/navigation\/[a-zA-Z0-9]+/;
     const urlPrefix = getUrlPrefix();
 
-    switch (true) {
-      case navigationPattern.test(url): {
-        // Remove first lang segment of the URL
-        const newUrl = url.split("/").slice(1);
-        return newUrl ? `${buildUrlPrefix(newUrl.join("/"), urlPrefix)}` : url;
+    if (navigationPattern.test(url)) {
+      const newUrl = url.split("/").slice(1);
+
+      if (newUrl.length > 0) {
+        return `${buildUrlPrefix(newUrl.join("/"), urlPrefix)}`;
       }
-      default:
-        return url;
     }
+
+    return url;
   };
 
   return {
diff --git a/templates/vue-demo-store/app.vue b/templates/vue-demo-store/app.vue
index 6a2c4766..d22f15ec 100644
--- a/templates/vue-demo-store/app.vue
+++ b/templates/vue-demo-store/app.vue
@@ -81,7 +81,7 @@ if (languages.value?.elements.length && router.currentRoute.value.name) {
     defaultLocale,
   );
 
-  provide("cmsTranslations", messages.value[prefix ?? defaultLocale] ?? {});
+  provide("cmsTranslations", messages.value[prefix || defaultLocale] ?? {});
 
   // Language set on the backend side
   if (localeProperties.value.localeId) {
diff --git a/templates/vue-demo-store/components/account/AccountOrderDetails.vue b/templates/vue-demo-store/components/account/AccountOrderDetails.vue
index df14a6b4..40c970e9 100644
--- a/templates/vue-demo-store/components/account/AccountOrderDetails.vue
+++ b/templates/vue-demo-store/components/account/AccountOrderDetails.vue
@@ -9,6 +9,7 @@ const props = defineProps<{
   orderId: string;
 }>();
 const isLoading = ref(false);
+const { getErrorsCodes } = useCartNotification();
 const { pushSuccess, pushError } = useNotifications();
 const { t } = useI18n();
 const {
@@ -22,7 +23,7 @@ const {
   changePaymentMethod,
   statusTechnicalName,
 } = await useOrderDetails(props.orderId);
-const { addProducts } = useCart();
+const { addProducts, count } = useCart();
 const addingProducts = ref(false);
 onMounted(() => {
   loadOrderDetails();
@@ -74,8 +75,16 @@ const handleReorder = async () => {
 
   try {
     addingProducts.value = true;
+    const itemsBefore = count.value;
     await addProducts(items);
-    pushSuccess(t("account.messages.productsAdded"));
+
+    getErrorsCodes()?.forEach((element) => {
+      pushError(t(`errors.${element.messageKey}`, { ...element }));
+    });
+
+    if (itemsBefore < count.value) {
+      pushSuccess(t("account.messages.productsAdded"));
+    }
   } catch (error) {
     console.error(error);
     pushError(t("messages.error"));
diff --git a/templates/vue-demo-store/components/cms/element/CmsElementProductDescriptionReviews.vue b/templates/vue-demo-store/components/cms/element/CmsElementProductDescriptionReviews.vue
index 85ee1465..9bf19d83 100644
--- a/templates/vue-demo-store/components/cms/element/CmsElementProductDescriptionReviews.vue
+++ b/templates/vue-demo-store/components/cms/element/CmsElementProductDescriptionReviews.vue
@@ -29,7 +29,7 @@ let translations: Translations = {
     description: "Description",
     reviews: "Reviews",
     messages: {
-      reviewAdded: "Review added successfully",
+      reviewAdded: "Thank you for submitting your review",
     },
   },
 };
diff --git a/templates/vue-demo-store/components/product/ProductCard.vue b/templates/vue-demo-store/components/product/ProductCard.vue
index 777cddc0..980e7ade 100644
--- a/templates/vue-demo-store/components/product/ProductCard.vue
+++ b/templates/vue-demo-store/components/product/ProductCard.vue
@@ -188,8 +188,10 @@ const srcPath = computed(() => {
           :class="{
             'text-white bg-primary-600 hover:bg-primary-700': !isInCart,
             'text-secondary-600 bg-secondary-100': isInCart,
+            'opacity-50 cursor-not-allowed': !product.available,
           }"
           data-testid="add-to-cart-button"
+          :disabled="!product.available"
           @click="addToCartProxy"
         >
           {{ $t("product.addToCart") }}


Test patch:
diff --git a/apps/e2e-tests/tests/addProductReview.spec.ts b/apps/e2e-tests/tests/addProductReview.spec.ts
index cc50fec9..29829860 100644
--- a/apps/e2e-tests/tests/addProductReview.spec.ts
+++ b/apps/e2e-tests/tests/addProductReview.spec.ts
@@ -30,6 +30,8 @@ test.describe.only("Add review", () => {
     await homePage.openCartPage();
     await page.waitForLoadState("networkidle");
     await productPage.fillReviewForm();
-    await expect(page.getByText("Review added successfully")).toHaveCount(1);
+    await expect(
+      page.getByText("Thank you for submitting your review"),
+    ).toHaveCount(1);
   });
 });
diff --git a/packages/composables/src/index.test.ts b/packages/composables/src/index.test.ts
index cd1f33bd..6f2336d5 100644
--- a/packages/composables/src/index.test.ts
+++ b/packages/composables/src/index.test.ts
@@ -17,4 +17,19 @@ describe("resolveCmsComponent", () => {
     const result = getDefaultApiParams();
     expect(result).toEqual({});
   });
+
+  it("cms section component", () => {
+    const result = resolveCmsComponent({
+      apiAlias: "cms_section",
+    } as unknown as Schemas["CmsBlock"]);
+    expect(result.isResolved).toBe(true);
+  });
+
+  it("cms custom component", () => {
+    const result = resolveCmsComponent({
+      apiAlias: "cms_custom",
+    } as unknown as Schemas["CmsBlock"]);
+
+    expect(result.isResolved).toBe(true);
+  });
 });
diff --git a/packages/composables/src/useAddToCart.test.ts b/packages/composables/src/useAddToCart.test.ts
index 8f052b5b..6c943438 100644
--- a/packages/composables/src/useAddToCart.test.ts
+++ b/packages/composables/src/useAddToCart.test.ts
@@ -33,6 +33,7 @@ describe("useB2bQuoteManagement", () => {
 
   it("add product to cart - empty cart", async () => {
     const { vm } = await useSetup(() => useAddToCart(ref()));
+    expect(vm.count).toBe(0);
     await expect(vm.addToCart()).rejects.toThrow();
   });
 });
diff --git a/packages/composables/src/useAddress.test.ts b/packages/composables/src/useAddress.test.ts
index 45f23375..794a9f3b 100644
--- a/packages/composables/src/useAddress.test.ts
+++ b/packages/composables/src/useAddress.test.ts
@@ -1,7 +1,9 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 import { useAddress } from "./useAddress";
 import { useSetup } from "./_test";
 import type { Schemas } from "#shopware";
+import { ApiClientError } from "@shopware/api-client";
+import type { FetchResponse } from "ofetch";
 
 const MOCKED_ADDRESS = {
   countryId: "6777d83705454d078fc9a7419296c7dc",
@@ -26,6 +28,29 @@ describe("useAddress", () => {
     );
   });
 
+  it("load customer address - error", async () => {
+    const { vm } = await useSetup(useAddress, {
+      apiClient: {
+        invoke: vi.fn().mockImplementation(() => {
+          throw new ApiClientError({
+            status: 403,
+            _data: {
+              errors: [
+                {
+                  code: "TEST",
+                  status: 403,
+                  detail: "Forbidden",
+                },
+              ],
+            },
+          } as unknown as FetchResponse<{ errors: Array<{ title: string }> }>);
+        }),
+      },
+    });
+
+    await expect(vm.loadCustomerAddresses()).rejects.toThrowError();
+  });
+
   it("create address", async () => {
     const { vm, injections } = await useSetup(useAddress);
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
diff --git a/packages/composables/src/useB2bQuoteManagement.test.ts b/packages/composables/src/useB2bQuoteManagement.test.ts
index 36761934..c2c02e2f 100644
--- a/packages/composables/src/useB2bQuoteManagement.test.ts
+++ b/packages/composables/src/useB2bQuoteManagement.test.ts
@@ -7,7 +7,9 @@ import { useSetup } from "./_test";
 describe("useB2bQuoteManagement", () => {
   it("getQuoteList", async () => {
     const { vm, injections } = useSetup(useB2bQuoteManagement);
-    injections.apiClient.invoke.mockResolvedValue({ data: { elements: [] } });
+    injections.apiClient.invoke.mockResolvedValue({
+      data: { elements: [{ id: "test-1" }] },
+    });
 
     await vm.getQuoteList();
 
@@ -16,6 +18,16 @@ describe("useB2bQuoteManagement", () => {
     );
   });
 
+  it("getQuoteList - no elements", async () => {
+    const { vm, injections } = useSetup(useB2bQuoteManagement);
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+
+    expect(await vm.getQuoteList()).toStrictEqual([]);
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("readQuotes"),
+    );
+  });
+
   it("getQuote", async () => {
     const { vm, injections } = useSetup(() => useB2bQuoteManagement());
     injections.apiClient.invoke.mockResolvedValue({ data: null });
diff --git a/packages/composables/src/useCart.test.ts b/packages/composables/src/useCart.test.ts
index 62a124df..4e4793bd 100644
--- a/packages/composables/src/useCart.test.ts
+++ b/packages/composables/src/useCart.test.ts
@@ -1,7 +1,8 @@
 import { describe, expect, it, beforeEach, vi } from "vitest";
 import { useCart } from "./useCart";
 import { useSetup } from "./_test";
-import type { operations } from "#shopware";
+import type { operations, Schemas } from "#shopware";
+import Cart from "./mocks/Cart";
 
 describe("useCart", () => {
   beforeEach(() => {
@@ -66,6 +67,12 @@ describe("useCart", () => {
     );
   });
 
+  it("refresh the cart - new cart", async () => {
+    expect(await vm.refreshCart(Cart as unknown as Schemas["Cart"])).toEqual(
+      Cart,
+    );
+  });
+
   it("add set of products", async () => {
     await vm.addProducts(itemsMock.items);
     expect(injections.apiClient.invoke).toHaveBeenCalledWith(
@@ -95,4 +102,18 @@ describe("useCart", () => {
       }),
     );
   });
+
+  it("remove item", async () => {
+    await vm.removeItem({
+      id: "01893ed931d571718e8138e7df7d68d1",
+    } as Schemas["LineItem"]);
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("removeLineItem"),
+      expect.objectContaining({
+        body: {
+          ids: ["01893ed931d571718e8138e7df7d68d1"],
+        },
+      }),
+    );
+  });
 });
diff --git a/packages/composables/src/useCategory.test.ts b/packages/composables/src/useCategory.test.ts
new file mode 100644
index 00000000..927fd14d
--- /dev/null
+++ b/packages/composables/src/useCategory.test.ts
@@ -0,0 +1,14 @@
+import { describe, expect, it } from "vitest";
+import { useCategory } from "./useCategory";
+import { useSetup } from "./_test";
+import { ref } from "vue";
+import type { Schemas } from "#shopware";
+
+describe("useCategory", () => {
+  it("empty category init", () => {
+    const { vm } = useSetup(() =>
+      useCategory(ref({ id: "1" } as Schemas["Category"])),
+    );
+    expect(vm.category).toStrictEqual({ id: "1" });
+  });
+});
diff --git a/packages/composables/src/useCategorySearch.test.ts b/packages/composables/src/useCategorySearch.test.ts
index 02480a96..f605c0bb 100644
--- a/packages/composables/src/useCategorySearch.test.ts
+++ b/packages/composables/src/useCategorySearch.test.ts
@@ -24,6 +24,31 @@ describe("useCategorySearch", () => {
     );
   });
 
+  it("search method - with cms associations ", () => {
+    const { vm, injections } = useSetup(useCategorySearch);
+    injections.apiClient.invoke.mockResolvedValue({
+      data: {},
+    });
+    vm.search("categoryId", {
+      withCmsAssociations: true,
+    });
+
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("readCategory"),
+      expect.objectContaining({
+        pathParams: {
+          navigationId: "categoryId",
+        },
+        headers: {
+          "sw-include-seo-urls": true,
+        },
+        body: {
+          associations: cmsAssociations,
+        },
+      }),
+    );
+  });
+
   it("advanced search method", () => {
     const { vm, injections } = useSetup(useCategorySearch);
     injections.apiClient.invoke.mockResolvedValue({
diff --git a/packages/composables/src/useCheckout.test.ts b/packages/composables/src/useCheckout.test.ts
index ddb4d7a9..d2552e25 100644
--- a/packages/composables/src/useCheckout.test.ts
+++ b/packages/composables/src/useCheckout.test.ts
@@ -6,7 +6,7 @@ describe("useCheckout", () => {
   it("getShippingMethods - empty", async () => {
     const { vm, injections } = useSetup(useCheckout);
     injections.apiClient.invoke.mockResolvedValue({
-      data: { elements: [] },
+      data: { elements: undefined },
     });
 
     await vm.getShippingMethods();
@@ -58,7 +58,25 @@ describe("useCheckout", () => {
   it("getPaymentMethods", async () => {
     const { vm, injections } = useSetup(useCheckout);
     injections.apiClient.invoke.mockResolvedValue({
-      data: { elements: [] },
+      data: { elements: [{ id: "1" }] },
+    });
+
+    await vm.getPaymentMethods();
+
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("readPaymentMethod"),
+      expect.objectContaining({}),
+    );
+    await vm.getPaymentMethods({
+      forceReload: false,
+    });
+    expect(vm.paymentMethods).toEqual([{ id: "1" }]);
+  });
+
+  it("getPaymentMethods - empty", async () => {
+    const { vm, injections } = useSetup(useCheckout);
+    injections.apiClient.invoke.mockResolvedValue({
+      data: { elements: undefined },
     });
 
     await vm.getPaymentMethods();
@@ -67,6 +85,7 @@ describe("useCheckout", () => {
       expect.stringContaining("readPaymentMethod"),
       expect.objectContaining({}),
     );
+    expect(vm.paymentMethods).toEqual([]);
   });
 
   it("createOrder", async () => {
diff --git a/packages/composables/src/useNavigation.test.ts b/packages/composables/src/useNavigation.test.ts
index 2fb0d83a..cac70013 100644
--- a/packages/composables/src/useNavigation.test.ts
+++ b/packages/composables/src/useNavigation.test.ts
@@ -29,4 +29,18 @@ describe("useNavigation", () => {
     });
     expect(vm.navigationElements?.length).toBe(0);
   });
+
+  it("should set the menu - empty return", async () => {
+    const { vm, injections } = useSetup(useNavigation);
+    injections.apiClient.invoke.mockResolvedValue({ data: undefined });
+
+    await vm.loadNavigationElements({
+      depth: 3,
+    });
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("readNavigation"),
+      expect.anything(),
+    );
+    expect(vm.navigationElements).toStrictEqual([]);
+  });
 });
diff --git a/packages/composables/src/ useOrderPayment.test.ts b/packages/composables/src/useOrderPayment.test.ts
similarity index 100%
rename from packages/composables/src/ useOrderPayment.test.ts
rename to packages/composables/src/useOrderPayment.test.ts
diff --git a/packages/composables/src/useProductPrice.test.ts b/packages/composables/src/useProductPrice.test.ts
index 8cb67b05..10be3128 100644
--- a/packages/composables/src/useProductPrice.test.ts
+++ b/packages/composables/src/useProductPrice.test.ts
@@ -39,4 +39,28 @@ describe("useProductPrice", () => {
     expect(vm.displayFrom).toBe(true);
     expect(vm.displayFromVariants).toBe(20);
   });
+
+  it("displayFrom", () => {
+    const { vm } = useSetup(() =>
+      useProductPrice(
+        ref(
+          Object.assign(
+            { ...productTierPrices.product },
+            {
+              parentId: null,
+              variantListingConfig: {
+                displayParent: true,
+              },
+              calculatedPrices: undefined,
+              listPrice: {
+                percentage: 100,
+              },
+            },
+          ) as unknown as Schemas["Product"],
+        ),
+      ),
+    );
+    expect(vm.displayFrom).toBe(false);
+    expect(vm.isListPrice).toBe(false);
+  });
 });
diff --git a/packages/composables/src/useProductWishlist.test.ts b/packages/composables/src/useProductWishlist.test.ts
index 2b3916a6..b431e60b 100644
--- a/packages/composables/src/useProductWishlist.test.ts
+++ b/packages/composables/src/useProductWishlist.test.ts
@@ -1,6 +1,10 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 import { useProductWishlist } from "./useProductWishlist";
 import { useSetup } from "./_test";
+import { useUser } from "./useUser";
+import { ref } from "vue";
+
+vi.mock("./useUser");
 
 describe("useProductWishlist", () => {
   describe("methods not logged in", () => {
@@ -19,9 +23,24 @@ describe("useProductWishlist", () => {
         expect(vm.addToWishlist()).resolves.toEqual(undefined);
       });
     });
+
+    it("removeFromWishlist", () => {
+      vi.mocked(useUser).mockReturnValue({
+        isLoggedIn: ref(false),
+        isGuestSession: ref(true),
+      } as ReturnType<typeof useUser>);
+      const { vm } = useSetup(() => useProductWishlist("test3"));
+
+      expect(vm.removeFromWishlist()).resolves.toEqual(undefined);
+    });
   });
 
   describe("methods logged in", () => {
+    vi.mocked(useUser).mockReturnValue({
+      isLoggedIn: ref(true),
+      isGuestSession: ref(false),
+    } as ReturnType<typeof useUser>);
+
     describe("addToWishlist - id only", () => {
       it("wishlist add product", () => {
         const { vm } = useSetup(() => useProductWishlist("test3"));
@@ -29,5 +48,11 @@ describe("useProductWishlist", () => {
         expect(vm.addToWishlist()).resolves.toEqual(undefined);
       });
     });
+
+    it("removeFromWishlist", () => {
+      const { vm } = useSetup(() => useProductWishlist("test3"));
+
+      expect(vm.removeFromWishlist()).resolves.toEqual(undefined);
+    });
   });
 });
diff --git a/packages/composables/src/useSessionContext.test.ts b/packages/composables/src/useSessionContext.test.ts
index 4d6abbd6..1b887220 100644
--- a/packages/composables/src/useSessionContext.test.ts
+++ b/packages/composables/src/useSessionContext.test.ts
@@ -1,8 +1,13 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 import { useSessionContext } from "./useSessionContext";
 import { useSetup } from "./_test";
+import type { Schemas } from "#shopware";
+
+const consoleErrorSpy = vi.spyOn(console, "error");
+consoleErrorSpy.mockImplementation(() => {});
 
 describe("useSessionContext", () => {
+  const consoleErrorSpy = vi.spyOn(console, "error");
   it("setLanguage", () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -17,6 +22,15 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("setLanguage - no id", () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+    vm.setLanguage({ id: "" });
+    expect(injections.apiClient.invoke).not.toHaveBeenCalledWith(
+      expect.stringContaining("updateContext"),
+    );
+  });
+
   it("setCountry", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -40,6 +54,19 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("refreshSessionContext - error", async () => {
+    const { vm } = useSetup(() => useSessionContext(), {
+      apiClient: {
+        invoke: vi.fn().mockImplementation(() => {
+          throw new Error("error test");
+        }),
+      },
+    });
+    consoleErrorSpy.mockImplementation(() => {});
+    await vm.refreshSessionContext();
+    expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
+  });
+
   it("setShippingMethod", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -54,6 +81,15 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("setShippingMethod - error", async () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+
+    expect(vm.setShippingMethod({ id: undefined })).rejects.toThrowError(
+      "You need to provide shipping method id in order to set shipping method.",
+    );
+  });
+
   it("setPaymentMethod", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -68,6 +104,15 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("setPaymentMethod - error", async () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+
+    expect(vm.setPaymentMethod({ id: "" })).rejects.toThrowError(
+      "You need to provide payment method id in order to set payment method.",
+    );
+  });
+
   it("setCurrency", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -82,6 +127,13 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("setCurrency - error", async () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+    vm.setCurrency({ id: "" });
+    expect(consoleErrorSpy).toHaveBeenCalled();
+  });
+
   it("setActiveShippingAddress", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -96,6 +148,14 @@ describe("useSessionContext", () => {
     );
   });
 
+  it("setActiveShippingAddress - error", async () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+    expect(vm.setActiveShippingAddress({ id: "" })).rejects.toThrowError(
+      "You need to provide address id in order to set the address.",
+    );
+  });
+
   it("setActiveBillingAddress", async () => {
     const { vm, injections } = useSetup(() => useSessionContext());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
@@ -109,4 +169,53 @@ describe("useSessionContext", () => {
       }),
     );
   });
+
+  it("setActiveBillingAddress - error", async () => {
+    const { vm, injections } = useSetup(() => useSessionContext());
+    injections.apiClient.invoke.mockResolvedValue({ data: {} });
+    expect(vm.setActiveBillingAddress({ id: "" })).rejects.toThrowError(
+      "You need to provide address id in order to set the address.",
+    );
+  });
+
+  it("setContext", () => {
+    const { vm } = useSetup(() => useSessionContext());
+    vm.setContext({
+      countryId: "test",
+    } as unknown as Schemas["SalesChannelContext"]);
+    expect(vm.sessionContext).toStrictEqual({
+      countryId: "test",
+    });
+    expect(vm.activeShippingAddress).toBe(null);
+  });
+
+  it("activeShippingAddress - active address", () => {
+    const { vm } = useSetup(() => useSessionContext());
+    vm.setContext({
+      customer: {
+        activeShippingAddress: {
+          city: "test",
+        },
+      },
+    } as unknown as Schemas["SalesChannelContext"]);
+
+    expect(vm.activeShippingAddress).toStrictEqual({
+      city: "test",
+    });
+  });
+
+  it("activeShippingAddress - shipping location", () => {
+    const { vm } = useSetup(() => useSessionContext());
+    vm.setContext({
+      shippingLocation: {
+        address: {
+          city: "test",
+        },
+      },
+    } as unknown as Schemas["SalesChannelContext"]);
+
+    expect(vm.activeShippingAddress).toStrictEqual({
+      city: "test",
+    });
+  });
 });
diff --git a/packages/composables/src/useSyncWishlist.test.ts b/packages/composables/src/useSyncWishlist.test.ts
index fc926e28..fa261ccc 100644
--- a/packages/composables/src/useSyncWishlist.test.ts
+++ b/packages/composables/src/useSyncWishlist.test.ts
@@ -1,8 +1,12 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 import { useSyncWishlist } from "./useSyncWishlist";
 import { useSetup } from "./_test";
+import { ApiClientError } from "@shopware/api-client";
+import type { FetchResponse } from "ofetch";
 
 describe("useSyncWishlist", () => {
+  const consoleErrorSpy = vi.spyOn(console, "error");
+  consoleErrorSpy.mockImplementation(() => {});
   describe("methods", () => {
     describe("addToWishlist", () => {
       it("wishlist add product", () => {
@@ -42,5 +46,32 @@ describe("useSyncWishlist", () => {
         expect(vm.items.length).toBe(0);
       });
     });
+
+    describe("getWishlistProducts - error", () => {
+      it("getWishlistProducts", async () => {
+        const { vm } = useSetup(() => useSyncWishlist(), {
+          apiClient: {
+            invoke: vi.fn().mockImplementation(() => {
+              throw new ApiClientError({
+                status: 500,
+                _data: {
+                  errors: [
+                    {
+                      title: "Test error",
+                    },
+                  ],
+                },
+              } as unknown as FetchResponse<{
+                errors: Array<{ title: string }>;
+              }>);
+            }),
+          },
+        });
+        vm.getWishlistProducts();
+        expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
+        expect(vm.count).toBe(0);
+        expect(vm.items.length).toBe(0);
+      });
+    });
   });
 });
diff --git a/packages/composables/src/useUrlResolver.test.ts b/packages/composables/src/useUrlResolver.test.ts
index 1763543d..ebf18d9f 100644
--- a/packages/composables/src/useUrlResolver.test.ts
+++ b/packages/composables/src/useUrlResolver.test.ts
@@ -1,6 +1,13 @@
-import { describe, expect, it } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 import { useUrlResolver } from "./useUrlResolver";
 import { useSetup } from "./_test";
+import { buildUrlPrefix } from "@shopware-pwa/helpers-next";
+
+vi.mock("@shopware-pwa/helpers-next", async () => {
+  return {
+    buildUrlPrefix: vi.fn(),
+  };
+});
 
 describe("useUrlResolver", () => {
   it("resolve url", async () => {
@@ -8,4 +15,11 @@ describe("useUrlResolver", () => {
     expect(vm.resolveUrl("/test-url/abc")).toBe("/test-url/abc");
     expect(() => vm.resolveUrl("x".repeat(2084))).toThrow();
   });
+
+  it("resolve url with navigation pattern", async () => {
+    const { vm } = await useSetup(useUrlResolver);
+    vm.resolveUrl("/de/navigation/123");
+
+    expect(buildUrlPrefix).toHaveBeenCalled();
+  });
 });
diff --git a/packages/composables/src/useUser.test.ts b/packages/composables/src/useUser.test.ts
index f3b2247e..107050d8 100644
--- a/packages/composables/src/useUser.test.ts
+++ b/packages/composables/src/useUser.test.ts
@@ -74,6 +74,30 @@ describe("useUser", () => {
     );
   });
 
+  it("register function with refresh", async () => {
+    const { vm, injections } = useSetup(() => useUser());
+    injections.apiClient.invoke.mockResolvedValue({
+      data: {
+        active: true,
+        id: "test123",
+        guest: false,
+      },
+    });
+
+    await vm.register(REGISTRATION_DATA);
+
+    expect(injections.apiClient.invoke).toHaveBeenCalledWith(
+      expect.stringContaining("register"),
+      expect.objectContaining({
+        body: {
+          ...REGISTRATION_DATA,
+          storefrontUrl: "http://localhost:3000", // This is the default value from the useInternationalization
+        },
+      }),
+    );
+    expect(vm.isLoggedIn).toBe(true);
+  });
+
   it("logout", async () => {
     const { vm, injections } = useSetup(() => useUser());
     injections.apiClient.invoke.mockResolvedValue({ data: {} });
diff --git a/packages/composables/vitest.config.ts b/packages/composables/vitest.config.ts
index fd97d914..3fd83702 100644
--- a/packages/composables/vitest.config.ts
+++ b/packages/composables/vitest.config.ts
@@ -9,8 +9,8 @@ export default defineConfig({
       // "100": true, // TODO: our goal ;)
       thresholds: {
         statements: 97,
-        branches: 81,
-        functions: 89,
+        branches: 90,
+        functions: 91,
         lines: 97,
       },
       exclude: [