@shopbite-de/storefront 1.17.4 → 1.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -63,9 +63,9 @@ jobs:
63
63
  uses: actions/checkout@v6
64
64
 
65
65
  - name: Install pnpm
66
- uses: pnpm/action-setup@v4
66
+ uses: pnpm/action-setup@v5
67
67
  with:
68
- version: 10.32.1
68
+ version: 10.33.0
69
69
 
70
70
  - name: Install Node.js
71
71
  uses: actions/setup-node@v6
@@ -23,9 +23,9 @@ jobs:
23
23
  steps:
24
24
  - uses: actions/checkout@v6
25
25
 
26
- - uses: pnpm/action-setup@v4
26
+ - uses: pnpm/action-setup@v5
27
27
  with:
28
- version: 10.32.1
28
+ version: 10.33.0
29
29
 
30
30
  - uses: actions/setup-node@v6
31
31
  with:
@@ -67,9 +67,9 @@ jobs:
67
67
  steps:
68
68
  - uses: actions/checkout@v6
69
69
 
70
- - uses: pnpm/action-setup@v4
70
+ - uses: pnpm/action-setup@v5
71
71
  with:
72
- version: 10.32.1
72
+ version: 10.33.0
73
73
 
74
74
  - uses: actions/setup-node@v6
75
75
  with:
@@ -100,9 +100,9 @@ jobs:
100
100
  steps:
101
101
  - uses: actions/checkout@v6
102
102
 
103
- - uses: pnpm/action-setup@v4
103
+ - uses: pnpm/action-setup@v5
104
104
  with:
105
- version: 10.32.1
105
+ version: 10.33.0
106
106
 
107
107
  - uses: actions/setup-node@v6
108
108
  with:
@@ -130,9 +130,9 @@ jobs:
130
130
  steps:
131
131
  - uses: actions/checkout@v6
132
132
 
133
- - uses: pnpm/action-setup@v4
133
+ - uses: pnpm/action-setup@v5
134
134
  with:
135
- version: 10.32.1
135
+ version: 10.33.0
136
136
 
137
137
  - uses: actions/setup-node@v6
138
138
  with:
@@ -18,6 +18,7 @@ const {
18
18
  correction,
19
19
  isInvalidCity,
20
20
  checkAddress,
21
+ flushPendingCheck,
21
22
  applyCorrection,
22
23
  } = useAddressValidation(model, {
23
24
  isShipping: props.isShipping,
@@ -27,6 +28,7 @@ const {
27
28
 
28
29
  defineExpose({
29
30
  checkAddress,
31
+ flushPendingCheck,
30
32
  showCorrection,
31
33
  });
32
34
  </script>
@@ -109,6 +109,7 @@ const checkoutButtonLabel = computed<string>(() => {
109
109
  :with-delete-button="false"
110
110
  class="p-6 bg-elevated"
111
111
  />
112
+ <CheckoutVoucherInput />
112
113
  <UButton
113
114
  :icon="
114
115
  isValidToProceed ? 'i-lucide-shopping-cart' : 'i-lucide-lock'
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ voucherCode,
4
+ voucherLoading,
5
+ applyVoucher,
6
+ appliedPromotionCodes,
7
+ removeItem,
8
+ } = useVoucherCode();
9
+ </script>
10
+
11
+ <template>
12
+ <div class="flex flex-col gap-2">
13
+ <div class="flex flex-row gap-2">
14
+ <UInput
15
+ v-model="voucherCode"
16
+ placeholder="Gutscheincode"
17
+ class="flex-1"
18
+ :disabled="voucherLoading"
19
+ @keyup.enter="applyVoucher"
20
+ />
21
+ <UButton
22
+ label="Einlösen"
23
+ variant="outline"
24
+ :loading="voucherLoading"
25
+ :disabled="!voucherCode.trim() || voucherLoading"
26
+ @click="applyVoucher"
27
+ />
28
+ </div>
29
+ <div
30
+ v-for="promo in appliedPromotionCodes"
31
+ :key="promo.id"
32
+ class="flex flex-row justify-between items-center text-sm text-success"
33
+ >
34
+ <span class="flex items-center gap-1">
35
+ <UIcon name="i-lucide-tag" />
36
+ {{ promo.label }}
37
+ </span>
38
+ <UButton
39
+ icon="i-lucide-x"
40
+ size="xs"
41
+ variant="ghost"
42
+ color="neutral"
43
+ aria-label="Gutschein entfernen"
44
+ @click="removeItem(promo)"
45
+ />
46
+ </div>
47
+ </div>
48
+ </template>
@@ -1,14 +1,21 @@
1
1
  <script setup lang="ts">
2
- import { useNavigation } from "~/composables/useNavigation";
2
+ import { useUser } from "@shopware/composables";
3
3
 
4
- const config = useRuntimeConfig();
4
+ const { mainMenu } = useNavigation(false);
5
+ const multiChannelEnabled =
6
+ useRuntimeConfig().public.shopBite.feature.multiChannel;
5
7
 
6
- const multiChannelEnabled = computed(
7
- () => config.public.shopBite.feature.multiChannel === "true",
8
- );
8
+ const { isLoggedIn, isGuestSession, logout } = useUser();
9
+ const toast = useToast();
9
10
 
10
- console.log(multiChannelEnabled.value);
11
- const { mainMenu } = useNavigation(false);
11
+ const logoutHandler = () => {
12
+ logout();
13
+ toast.add({
14
+ title: "Tschüss!",
15
+ description: "Erfolgreich abgemeldet.",
16
+ color: "success",
17
+ });
18
+ };
12
19
  </script>
13
20
 
14
21
  <template>
@@ -18,7 +25,58 @@ const { mainMenu } = useNavigation(false);
18
25
  orientation="vertical"
19
26
  class="-mx-2.5"
20
27
  />
21
- <div v-if="multiChannelEnabled" class="my-4">
28
+
29
+ <USeparator class="my-4" />
30
+
31
+ <div class="flex flex-col gap-1">
32
+ <template v-if="isLoggedIn || isGuestSession">
33
+ <UButton
34
+ color="neutral"
35
+ variant="ghost"
36
+ to="/konto"
37
+ icon="i-lucide-user"
38
+ label="Mein Konto"
39
+ class="justify-start"
40
+ />
41
+ <UButton
42
+ color="neutral"
43
+ variant="ghost"
44
+ to="/konto/bestellungen"
45
+ icon="i-lucide-pizza"
46
+ label="Bestellungen"
47
+ class="justify-start"
48
+ />
49
+ <UButton
50
+ color="neutral"
51
+ variant="ghost"
52
+ icon="i-lucide-log-out"
53
+ label="Abmelden"
54
+ class="justify-start"
55
+ @click="logoutHandler"
56
+ />
57
+ </template>
58
+ <template v-else>
59
+ <UButton
60
+ color="neutral"
61
+ variant="ghost"
62
+ to="/anmelden"
63
+ icon="i-lucide-user"
64
+ label="Anmelden"
65
+ class="justify-start"
66
+ />
67
+ <UButton
68
+ color="neutral"
69
+ variant="ghost"
70
+ to="/registrierung"
71
+ icon="i-lucide-user-plus"
72
+ label="Registrieren"
73
+ class="justify-start"
74
+ />
75
+ </template>
76
+ </div>
77
+
78
+ <div v-if="multiChannelEnabled" class="mt-4">
79
+ <USeparator class="mb-4" />
22
80
  <SalesChannelSwitch />
23
81
  </div>
24
82
  </template>
@@ -1,8 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { useNavigation } from "~/composables/useNavigation";
3
-
4
2
  const { mainMenu } = useNavigation(false);
5
-
6
3
  const loginSlide = ref(false);
7
4
  </script>
8
5
 
@@ -13,6 +10,7 @@ const loginSlide = ref(false);
13
10
  </template>
14
11
 
15
12
  <UNavigationMenu color="primary" variant="pill" :items="mainMenu" />
13
+ <SalesChannelSwitch />
16
14
 
17
15
  <template #right>
18
16
  <HeaderRight />
@@ -8,9 +8,7 @@ const { apiClient } = useShopwareContext();
8
8
 
9
9
  const config = useRuntimeConfig();
10
10
 
11
- const isMultiChannel = computed(
12
- () => config.public.shopBite.feature.multiChannel === "true",
13
- );
11
+ const isMultiChannel = useRuntimeConfig().public.shopBite.feature.multiChannel;
14
12
 
15
13
  const storeUrl = computed(() => config.public.storeUrl);
16
14
 
@@ -65,30 +65,12 @@ const billingAddressFields = ref();
65
65
  const shippingAddressFields = ref();
66
66
 
67
67
  async function onSubmit(event: FormSubmitEvent<RegistrationSchema>) {
68
- const registrationData = { ...event.data };
69
-
70
- // Check for address corrections
71
- // If a correction is already shown, treat it as found to prevent submission
72
- const billingCorrectionFound =
73
- Boolean(billingAddressFields.value?.showCorrection || false) ||
74
- (await billingAddressFields.value?.checkAddress());
75
-
76
- let shippingCorrectionFound = false;
68
+ billingAddressFields.value?.flushPendingCheck();
77
69
  if (state.isShippingAddressDifferent) {
78
- shippingCorrectionFound =
79
- Boolean(shippingAddressFields.value?.showCorrection || false) ||
80
- (await shippingAddressFields.value?.checkAddress());
70
+ shippingAddressFields.value?.flushPendingCheck();
81
71
  }
82
72
 
83
- if (billingCorrectionFound || shippingCorrectionFound) {
84
- toast.add({
85
- title: "Adresskorrektur vorgeschlagen",
86
- description:
87
- "Bitte überprüfen Sie die vorgeschlagene Adresskorrektur, bevor Sie fortfahren.",
88
- color: "info",
89
- });
90
- return;
91
- }
73
+ const registrationData = { ...event.data };
92
74
 
93
75
  if (
94
76
  !registrationData.billingAddress.firstName &&
@@ -64,6 +64,7 @@ export function useAddressValidation(
64
64
 
65
65
  if (debounceTimer) clearTimeout(debounceTimer);
66
66
  debounceTimer = setTimeout(() => {
67
+ debounceTimer = null;
67
68
  if (model.value.street) {
68
69
  checkAddress();
69
70
  }
@@ -72,6 +73,16 @@ export function useAddressValidation(
72
73
  { deep: true },
73
74
  );
74
75
 
76
+ function flushPendingCheck() {
77
+ if (debounceTimer) {
78
+ clearTimeout(debounceTimer);
79
+ debounceTimer = null;
80
+ if (model.value.street) {
81
+ checkAddress();
82
+ }
83
+ }
84
+ }
85
+
75
86
  onUnmounted(() => {
76
87
  if (debounceTimer) clearTimeout(debounceTimer);
77
88
  });
@@ -90,6 +101,7 @@ export function useAddressValidation(
90
101
  correction,
91
102
  isInvalidCity,
92
103
  checkAddress,
104
+ flushPendingCheck,
93
105
  applyCorrection,
94
106
  };
95
107
  }
@@ -0,0 +1,51 @@
1
+ export function useVoucherCode() {
2
+ const { cart, addPromotionCode, appliedPromotionCodes, removeItem } =
3
+ useCart();
4
+ const toast = useToast();
5
+
6
+ const voucherCode = ref("");
7
+ const voucherLoading = ref(false);
8
+
9
+ async function applyVoucher() {
10
+ const code = voucherCode.value.trim();
11
+ if (!code || voucherLoading.value) return;
12
+ voucherLoading.value = true;
13
+ try {
14
+ await addPromotionCode(code);
15
+ const errors = cart.value?.errors ?? {};
16
+ const promotionError = Object.values(errors).find(
17
+ (e) => (e as { promotionCode?: string }).promotionCode === code,
18
+ ) as { translatedMessage?: string } | undefined;
19
+ if (promotionError) {
20
+ toast.add({
21
+ title: "Gutschein ungültig",
22
+ description:
23
+ promotionError.translatedMessage ??
24
+ "Der eingegebene Gutscheincode ist ungültig oder abgelaufen.",
25
+ color: "error",
26
+ icon: "i-lucide-x-circle",
27
+ });
28
+ } else {
29
+ voucherCode.value = "";
30
+ }
31
+ } catch {
32
+ toast.add({
33
+ title: "Gutschein ungültig",
34
+ description:
35
+ "Der eingegebene Gutscheincode ist ungültig oder abgelaufen.",
36
+ color: "error",
37
+ icon: "i-lucide-x-circle",
38
+ });
39
+ } finally {
40
+ voucherLoading.value = false;
41
+ }
42
+ }
43
+
44
+ return {
45
+ voucherCode,
46
+ voucherLoading,
47
+ applyVoucher,
48
+ appliedPromotionCodes,
49
+ removeItem,
50
+ };
51
+ }
package/nuxt.config.ts CHANGED
@@ -57,7 +57,7 @@ export default defineNuxtConfig({
57
57
  shopBite: {
58
58
  menuCategoryId: "",
59
59
  feature: {
60
- multiChannel: "",
60
+ multiChannel: false,
61
61
  secureKey: "",
62
62
  contactForm: false,
63
63
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbite-de/storefront",
3
- "version": "1.17.4",
3
+ "version": "1.18.1",
4
4
  "main": "nuxt.config.ts",
5
5
  "description": "Shopware storefront for food delivery shops",
6
6
  "keywords": [
@@ -26,7 +26,7 @@
26
26
  "@nuxt/image": "^2.0.0",
27
27
  "@nuxt/scripts": "0.13.2",
28
28
  "@nuxt/ui": "^4.5.1",
29
- "@nuxtjs/robots": "^5.7.1",
29
+ "@nuxtjs/robots": "^6.0.0",
30
30
  "@sentry/nuxt": "^10.43.0",
31
31
  "@shopware/api-client": "^1.4.0",
32
32
  "@shopware/api-gen": "^1.4.0",
@@ -53,7 +53,7 @@
53
53
  "@typescript-eslint/eslint-plugin": "^8.57.0",
54
54
  "@typescript-eslint/parser": "^8.57.0",
55
55
  "@vitejs/plugin-vue": "^6.0.5",
56
- "@vitest/ui": "4.1.0",
56
+ "@vitest/ui": "4.1.2",
57
57
  "@vue/compiler-dom": "^3.5.30",
58
58
  "@vue/server-renderer": "^3.5.30",
59
59
  "@vue/test-utils": "^2.4.6",
@@ -65,7 +65,7 @@
65
65
  "playwright-core": "^1.58.2",
66
66
  "prettier": "^3.8.1",
67
67
  "tailwindcss": "^4.2.1",
68
- "typescript": "^5.9.3",
68
+ "typescript": "^6.0.0",
69
69
  "vitest": "^4.1.0"
70
70
  },
71
71
  "scripts": {
package/renovate.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "automerge": true,
4
+ "platformAutomerge": true,
5
+ "labels": ["dependencies"],
3
6
  "extends": ["config:recommended"],
7
+ "separateMultipleMajor": true,
8
+ "rebaseWhen": "auto",
4
9
  "packageRules": [
5
10
  {
6
- "matchUpdateTypes": ["minor", "patch", "lockfileUpdate"],
7
- "automerge": true,
8
- "automergeType": "pr",
9
- "automergeStrategy": "rebase",
10
- "platformAutomerge": true
11
+ "matchUpdateTypes": ["major", "minor"],
12
+ "automerge": false
11
13
  }
12
14
  ]
13
15
  }
@@ -0,0 +1,196 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { ref, nextTick } from "vue";
3
+ import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
4
+ import CheckoutVoucherInput from "~/components/Checkout/VoucherInput.vue";
5
+
6
+ const { mockAddPromotionCode, mockRemoveItem } = vi.hoisted(() => ({
7
+ mockAddPromotionCode: vi.fn().mockResolvedValue(undefined),
8
+ mockRemoveItem: vi.fn(),
9
+ }));
10
+
11
+ const mockCart = ref<{ errors?: Record<string, unknown> } | null>(null);
12
+ const mockAppliedPromotionCodes = ref<{ id: string; label: string }[]>([]);
13
+
14
+ mockNuxtImport("useCart", () => () => ({
15
+ cart: mockCart,
16
+ addPromotionCode: mockAddPromotionCode,
17
+ appliedPromotionCodes: mockAppliedPromotionCodes,
18
+ removeItem: mockRemoveItem,
19
+ }));
20
+
21
+ const mockToastAdd = vi.fn();
22
+ mockNuxtImport("useToast", () => () => ({
23
+ add: mockToastAdd,
24
+ }));
25
+
26
+ describe("CheckoutVoucherInput", () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ mockAddPromotionCode.mockResolvedValue(undefined);
30
+ mockAppliedPromotionCodes.value = [];
31
+ mockCart.value = null;
32
+ });
33
+
34
+ it("renders the input and button", async () => {
35
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
36
+ expect(wrapper.find("input").exists()).toBe(true);
37
+ expect(wrapper.text()).toContain("Einlösen");
38
+ });
39
+
40
+ it("apply button is disabled when input is empty", async () => {
41
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
42
+ const button = wrapper.find("button");
43
+ expect(button.attributes("disabled")).toBeDefined();
44
+ });
45
+
46
+ it("apply button is enabled when input has a value", async () => {
47
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
48
+ await wrapper.find("input").setValue("SAVE10");
49
+ await nextTick();
50
+ const button = wrapper.find("button");
51
+ expect(button.attributes("disabled")).toBeUndefined();
52
+ });
53
+
54
+ it("does not call addPromotionCode a second time if already loading", async () => {
55
+ let resolvePromotion!: () => void;
56
+ mockAddPromotionCode.mockReturnValueOnce(
57
+ new Promise<void>((resolve) => {
58
+ resolvePromotion = resolve;
59
+ }),
60
+ );
61
+
62
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
63
+ await wrapper.find("input").setValue("SAVE10");
64
+ await nextTick();
65
+
66
+ // First invocation — in-flight
67
+ wrapper.find("button").trigger("click");
68
+ await nextTick();
69
+
70
+ // Second invocation while still loading
71
+ wrapper.find("button").trigger("click");
72
+ await nextTick();
73
+ await wrapper.find("input").trigger("keyup.enter");
74
+ await nextTick();
75
+
76
+ resolvePromotion();
77
+ mockCart.value = { errors: {} };
78
+ await new Promise((resolve) => setTimeout(resolve, 50));
79
+
80
+ expect(mockAddPromotionCode).toHaveBeenCalledTimes(1);
81
+ });
82
+
83
+ it("disables the button and input while loading to prevent concurrent submits", async () => {
84
+ let resolvePromotion!: () => void;
85
+ mockAddPromotionCode.mockReturnValueOnce(
86
+ new Promise<void>((resolve) => {
87
+ resolvePromotion = resolve;
88
+ }),
89
+ );
90
+
91
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
92
+ await wrapper.find("input").setValue("SAVE10");
93
+ await nextTick();
94
+
95
+ wrapper.find("button").trigger("click");
96
+ await nextTick();
97
+
98
+ // While the request is in-flight both button and input must be disabled
99
+ expect(wrapper.find("button").attributes("disabled")).toBeDefined();
100
+ expect(wrapper.find("input").attributes("disabled")).toBeDefined();
101
+
102
+ // Resolve and confirm the loading guard is lifted
103
+ resolvePromotion();
104
+ mockCart.value = { errors: {} };
105
+ await new Promise((resolve) => setTimeout(resolve, 50));
106
+
107
+ // Input is no longer disabled by loading (code was cleared on success, button
108
+ // disabled due to empty input is expected and correct)
109
+ expect(wrapper.find("input").attributes("disabled")).toBeUndefined();
110
+ });
111
+
112
+ it("clears the input and does not show a toast on successful voucher apply", async () => {
113
+ mockCart.value = { errors: {} };
114
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
115
+ await wrapper.find("input").setValue("SAVE10");
116
+ await wrapper.find("button").trigger("click");
117
+ await new Promise((resolve) => setTimeout(resolve, 50));
118
+
119
+ expect(mockAddPromotionCode).toHaveBeenCalledWith("SAVE10");
120
+ expect(mockToastAdd).not.toHaveBeenCalled();
121
+ expect(wrapper.find("input").element.value).toBe("");
122
+ });
123
+
124
+ it("shows the translated error from cart.errors when the code is invalid", async () => {
125
+ mockCart.value = {
126
+ errors: {
127
+ "promotion-not-found": {
128
+ promotionCode: "INVALID",
129
+ translatedMessage: 'Gutscheincode "INVALID" existiert nicht.',
130
+ },
131
+ },
132
+ };
133
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
134
+ await wrapper.find("input").setValue("INVALID");
135
+ await wrapper.find("button").trigger("click");
136
+ await new Promise((resolve) => setTimeout(resolve, 50));
137
+
138
+ expect(mockToastAdd).toHaveBeenCalledWith(
139
+ expect.objectContaining({
140
+ title: "Gutschein ungültig",
141
+ description: 'Gutscheincode "INVALID" existiert nicht.',
142
+ color: "error",
143
+ }),
144
+ );
145
+ expect(wrapper.find("input").element.value).toBe("INVALID");
146
+ });
147
+
148
+ it("shows a generic error toast when addPromotionCode throws", async () => {
149
+ mockAddPromotionCode.mockRejectedValueOnce(new Error("network error"));
150
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
151
+ await wrapper.find("input").setValue("SAVE10");
152
+ await wrapper.find("button").trigger("click");
153
+ await new Promise((resolve) => setTimeout(resolve, 50));
154
+
155
+ expect(mockToastAdd).toHaveBeenCalledWith(
156
+ expect.objectContaining({
157
+ title: "Gutschein ungültig",
158
+ color: "error",
159
+ }),
160
+ );
161
+ });
162
+
163
+ it("submits on Enter key", async () => {
164
+ mockCart.value = { errors: {} };
165
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
166
+ await wrapper.find("input").setValue("ENTER10");
167
+ await wrapper.find("input").trigger("keyup.enter");
168
+ await new Promise((resolve) => setTimeout(resolve, 50));
169
+
170
+ expect(mockAddPromotionCode).toHaveBeenCalledWith("ENTER10");
171
+ });
172
+
173
+ it("renders applied promotion codes", async () => {
174
+ mockAppliedPromotionCodes.value = [
175
+ { id: "promo-1", label: "10% Rabatt" },
176
+ { id: "promo-2", label: "Gratis Versand" },
177
+ ];
178
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
179
+ expect(wrapper.text()).toContain("10% Rabatt");
180
+ expect(wrapper.text()).toContain("Gratis Versand");
181
+ });
182
+
183
+ it("calls removeItem when remove button is clicked", async () => {
184
+ const promo = { id: "promo-1", label: "10% Rabatt" };
185
+ mockAppliedPromotionCodes.value = [promo];
186
+ const wrapper = await mountSuspended(CheckoutVoucherInput);
187
+
188
+ const removeButtons = wrapper.findAll(
189
+ 'button[aria-label="Gutschein entfernen"]',
190
+ );
191
+ expect(removeButtons).toHaveLength(1);
192
+ await removeButtons[0]!.trigger("click");
193
+
194
+ expect(mockRemoveItem).toHaveBeenCalledWith(promo);
195
+ });
196
+ });
@@ -53,6 +53,7 @@ describe("RegistrationForm", () => {
53
53
  beforeEach(() => {
54
54
  vi.clearAllMocks();
55
55
  mockIsLoggedIn.value = false;
56
+ mockGetSuggestions.mockResolvedValue([]);
56
57
  });
57
58
 
58
59
  it("renders correctly", async () => {
@@ -251,8 +252,8 @@ describe("RegistrationForm", () => {
251
252
  );
252
253
  });
253
254
 
254
- it("shows correction suggestion and stops submission when address needs correction", async () => {
255
- mockGetSuggestions.mockResolvedValueOnce([
255
+ it("proceeds with registration even when getSuggestions returns a correction", async () => {
256
+ mockGetSuggestions.mockResolvedValue([
256
257
  {
257
258
  street: "Corrected Street 123",
258
259
  city: "Corrected City",
@@ -263,11 +264,9 @@ describe("RegistrationForm", () => {
263
264
 
264
265
  const wrapper = await mountSuspended(RegistrationForm);
265
266
 
266
- // Register as guest to avoid password requirements
267
267
  (wrapper.vm as unknown as { state: { guest: boolean } }).state.guest = true;
268
268
  await nextTick();
269
269
 
270
- // Fill required fields
271
270
  await wrapper.find('input[name="firstName"]').setValue("John");
272
271
  await wrapper.find('input[name="lastName"]').setValue("Doe");
273
272
  await wrapper.find('input[name="email"]').setValue("john@example.com");
@@ -300,17 +299,10 @@ describe("RegistrationForm", () => {
300
299
  await wrapper.find("form").trigger("submit");
301
300
  await new Promise((resolve) => setTimeout(resolve, 100));
302
301
 
303
- // Should NOT have called register
304
- expect(mockRegister).not.toHaveBeenCalled();
305
-
306
- // Should have shown a toast
307
- expect(mockToastAdd).toHaveBeenCalledWith(
308
- expect.objectContaining({
309
- title: "Adresskorrektur vorgeschlagen",
310
- }),
311
- );
302
+ // Registration must not be blocked by the correction suggestion
303
+ expect(mockRegister).toHaveBeenCalled();
312
304
 
313
- // Should show the correction alert in the form
305
+ // Correction UI surfaces non-blocking (flushPendingCheck triggers the async check)
314
306
  expect(wrapper.text()).toContain(
315
307
  "Meinten Sie: Corrected Street 123, 54321 Corrected City?",
316
308
  );