@shopbite-de/storefront 1.16.0 → 1.17.0

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.
Files changed (60) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.env.example +0 -4
  3. package/.github/workflows/build.yaml +14 -8
  4. package/.github/workflows/ci.yaml +125 -42
  5. package/.nuxtrc +1 -1
  6. package/CLAUDE.md +106 -0
  7. package/app/app.vue +53 -34
  8. package/app/components/AddToWishlist.vue +0 -3
  9. package/app/components/Address/Form.vue +5 -2
  10. package/app/components/Category/Listing.vue +5 -4
  11. package/app/components/Checkout/DeliveryTimeSelect.vue +4 -2
  12. package/app/components/Contact/Form.vue +3 -2
  13. package/app/components/Hero.vue +1 -1
  14. package/app/components/ImageGallery.vue +1 -1
  15. package/app/components/Navigation/DesktopLeft.vue +2 -1
  16. package/app/components/Order/Detail.vue +2 -2
  17. package/app/components/Product/Card.vue +14 -8
  18. package/app/components/SalesChannelSwitch.vue +5 -3
  19. package/app/components/User/LoginForm.vue +1 -4
  20. package/app/components/User/RegistrationForm.vue +5 -2
  21. package/app/composables/useBusinessHours.ts +11 -4
  22. package/app/composables/useCategory.ts +1 -0
  23. package/app/composables/useTopSellers.ts +2 -2
  24. package/app/layouts/account.vue +0 -1
  25. package/app/pages/anmelden.vue +8 -4
  26. package/app/pages/c/[...all].vue +2 -1
  27. package/app/pages/konto/adressen.vue +4 -1
  28. package/app/pages/konto/bestellung/[id].vue +14 -2
  29. package/app/pages/konto/profil.vue +5 -1
  30. package/app/utils/formatDate.ts +2 -1
  31. package/content.config.ts +1 -2
  32. package/eslint.config.mjs +10 -6
  33. package/node.dockerfile +7 -6
  34. package/nuxt.config.ts +17 -3
  35. package/package.json +38 -32
  36. package/renovate.json +9 -1
  37. package/server/api/address/autocomplete.get.ts +3 -2
  38. package/test/e2e/simple-checkout-as-recurring-customer.test.ts +1 -1
  39. package/test/nuxt/AddressFields.test.ts +12 -3
  40. package/test/nuxt/ContactForm.test.ts +31 -11
  41. package/test/nuxt/HeaderRight.test.ts +15 -6
  42. package/test/nuxt/LoginForm.test.ts +41 -23
  43. package/test/nuxt/PaymentAndDelivery.test.ts +2 -2
  44. package/test/nuxt/RegistrationForm.test.ts +6 -3
  45. package/test/nuxt/registrationSchema.test.ts +8 -8
  46. package/test/nuxt/useAddToCart.test.ts +5 -4
  47. package/test/nuxt/useAddressAutocomplete.test.ts +2 -0
  48. package/test/nuxt/useBusinessHours.test.ts +5 -5
  49. package/test/nuxt/useDeliveryTime.test.ts +17 -9
  50. package/test/nuxt/useProductConfigurator.test.ts +6 -4
  51. package/test/nuxt/useProductVariants.test.ts +16 -10
  52. package/test/nuxt/useProductVariantsZwei.test.ts +6 -2
  53. package/test/nuxt/useScrollAnimation.test.ts +16 -13
  54. package/test/nuxt/useTopSellers.test.ts +2 -2
  55. package/test/nuxt/useWishlistActions.test.ts +4 -3
  56. package/test/unit/useCategorySeo.spec.ts +26 -12
  57. package/tsconfig.json +21 -1
  58. package/app/middleware/trailing-slash.global.ts +0 -19
  59. package/server/utils/shopware/adminApiClient.ts +0 -24
  60. package/test/unit/sales-channels.test.ts +0 -66
@@ -2,6 +2,14 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
3
3
  import ContactForm from "~/components/Contact/Form.vue";
4
4
 
5
+ type ContactFormVm = {
6
+ submitted: boolean;
7
+ successMessage: string;
8
+ onSubmit: (arg: {
9
+ data: Record<string, string | undefined>;
10
+ }) => Promise<void>;
11
+ };
12
+
5
13
  const { mockInvoke } = vi.hoisted(() => {
6
14
  return {
7
15
  mockInvoke: vi.fn(),
@@ -52,7 +60,9 @@ describe("ContactForm", () => {
52
60
  comment: "Ich habe eine Frage zu Ihrem Produkt.",
53
61
  };
54
62
 
55
- await (wrapper.vm as any).onSubmit({ data: validData });
63
+ await (wrapper.vm as unknown as ContactFormVm).onSubmit({
64
+ data: validData,
65
+ });
56
66
 
57
67
  expect(mockInvoke).toHaveBeenCalled();
58
68
  expect(mockToastAdd).toHaveBeenCalledWith(
@@ -63,8 +73,10 @@ describe("ContactForm", () => {
63
73
  );
64
74
 
65
75
  // Verify submitted state
66
- expect((wrapper.vm as any).submitted).toBe(true);
67
- expect((wrapper.vm as any).successMessage).toBe(customMessage);
76
+ expect((wrapper.vm as unknown as ContactFormVm).submitted).toBe(true);
77
+ expect((wrapper.vm as unknown as ContactFormVm).successMessage).toBe(
78
+ customMessage,
79
+ );
68
80
 
69
81
  // Wait for DOM update
70
82
  await nextTick();
@@ -76,7 +88,7 @@ describe("ContactForm", () => {
76
88
 
77
89
  // Click on "Weiteres Formular senden"
78
90
  await wrapper.find("button").trigger("click");
79
- expect((wrapper.vm as any).submitted).toBe(false);
91
+ expect((wrapper.vm as unknown as ContactFormVm).submitted).toBe(false);
80
92
 
81
93
  await nextTick();
82
94
  expect(wrapper.find("form").exists()).toBe(true);
@@ -102,10 +114,14 @@ describe("ContactForm", () => {
102
114
  comment: "Ich habe eine Frage zu Ihrem Produkt.",
103
115
  };
104
116
 
105
- await (wrapper.vm as any).onSubmit({ data: validData });
117
+ await (wrapper.vm as unknown as ContactFormVm).onSubmit({
118
+ data: validData,
119
+ });
106
120
 
107
121
  const defaultMessage = "Deine Nachricht wurde erfolgreich versendet.";
108
- expect((wrapper.vm as any).successMessage).toBe(defaultMessage);
122
+ expect((wrapper.vm as unknown as ContactFormVm).successMessage).toBe(
123
+ defaultMessage,
124
+ );
109
125
 
110
126
  await nextTick();
111
127
  expect(wrapper.text()).toContain(defaultMessage);
@@ -127,7 +143,9 @@ describe("ContactForm", () => {
127
143
  comment: "Dies ist ein Test mit nur Pflichtfeldern.",
128
144
  };
129
145
 
130
- await (wrapper.vm as any).onSubmit({ data: mandatoryDataOnly });
146
+ await (wrapper.vm as unknown as ContactFormVm).onSubmit({
147
+ data: mandatoryDataOnly,
148
+ });
131
149
 
132
150
  expect(mockInvoke).toHaveBeenCalledWith(
133
151
  "sendContactMail post /contact-form",
@@ -140,7 +158,7 @@ describe("ContactForm", () => {
140
158
  },
141
159
  );
142
160
 
143
- expect((wrapper.vm as any).submitted).toBe(true);
161
+ expect((wrapper.vm as unknown as ContactFormVm).submitted).toBe(true);
144
162
  });
145
163
 
146
164
  it("shows error toast when submission fails", async () => {
@@ -158,7 +176,9 @@ describe("ContactForm", () => {
158
176
  comment: "Bitte um Unterstützung.",
159
177
  };
160
178
 
161
- await (wrapper.vm as any).onSubmit({ data: validData });
179
+ await (wrapper.vm as unknown as ContactFormVm).onSubmit({
180
+ data: validData,
181
+ });
162
182
 
163
183
  expect(mockInvoke).toHaveBeenCalled();
164
184
 
@@ -180,8 +200,8 @@ describe("ContactForm", () => {
180
200
  comment: "This is spam.",
181
201
  hp: "I am a bot",
182
202
  };
183
- await (wrapper.vm as any).onSubmit({ data: botData });
203
+ await (wrapper.vm as unknown as ContactFormVm).onSubmit({ data: botData });
184
204
  expect(mockInvoke).not.toHaveBeenCalled();
185
- expect((wrapper.vm as any).submitted).toBe(true);
205
+ expect((wrapper.vm as unknown as ContactFormVm).submitted).toBe(true);
186
206
  });
187
207
  });
@@ -7,7 +7,7 @@ const mocks = vi.hoisted(() => ({
7
7
  state: {
8
8
  isLoggedIn: false,
9
9
  isGuestSession: false,
10
- user: null as any,
10
+ user: null as { firstName: string; lastName: string } | null,
11
11
  },
12
12
  toastAddCalled: { value: false },
13
13
  }));
@@ -25,9 +25,13 @@ mockNuxtImport("useUser", () => () => ({
25
25
  }));
26
26
 
27
27
  mockNuxtImport("useToast", () => () => ({
28
- add: (payload: any) => {
29
- if (typeof global !== "undefined" && (global as any).toastAddCalled) {
30
- (global as any).toastAddCalled.value = true;
28
+ add: (_payload: unknown) => {
29
+ if (
30
+ typeof global !== "undefined" &&
31
+ (global as Record<string, { value: boolean }>).toastAddCalled
32
+ ) {
33
+ (global as Record<string, { value: boolean }>).toastAddCalled!.value =
34
+ true;
31
35
  }
32
36
  },
33
37
  }));
@@ -42,10 +46,15 @@ mockNuxtImport("useCart", () => () => ({
42
46
 
43
47
  mockNuxtImport("useRuntimeConfig", () => () => ({
44
48
  app: { baseURL: "/" },
49
+ public: { shopware: {} },
50
+ }));
51
+
52
+ mockNuxtImport("useWishlist", () => () => ({
53
+ count: ref(0),
45
54
  }));
46
55
 
47
56
  // Mock Nuxt Content queryCollection
48
- mockNuxtImport("queryCollection", () => (collection: string) => ({
57
+ mockNuxtImport("queryCollection", () => (_collection: string) => ({
49
58
  first: () =>
50
59
  Promise.resolve({
51
60
  account: {
@@ -74,7 +83,7 @@ describe("HeaderRight", () => {
74
83
  reactiveState.isGuestSession = false;
75
84
  reactiveState.user = null;
76
85
  mocks.toastAddCalled.value = false;
77
- (global as any).toastAddCalled = mocks.toastAddCalled;
86
+ (global as Record<string, unknown>).toastAddCalled = mocks.toastAddCalled;
78
87
  vi.clearAllMocks();
79
88
  });
80
89
 
@@ -1,13 +1,19 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { mountSuspended } from "@nuxt/test-utils/runtime";
2
+ import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
3
+ import { ref } from "vue";
3
4
  import LoginForm from "@/components/User/LoginForm.vue";
4
- import { useUser } from "@shopware/composables";
5
5
  import { ApiClientError } from "@shopware/api-client";
6
6
 
7
+ type LoginFormVm = {
8
+ onSubmit: (arg: {
9
+ data: { email: string; password: string };
10
+ }) => Promise<void>;
11
+ };
12
+
7
13
  vi.mock("@shopware/api-client", () => ({
8
14
  ApiClientError: class extends Error {
9
- details: any;
10
- constructor(details: any) {
15
+ details: unknown;
16
+ constructor(details: unknown) {
11
17
  super("ApiClientError");
12
18
  this.details = details;
13
19
  }
@@ -16,9 +22,7 @@ vi.mock("@shopware/api-client", () => ({
16
22
 
17
23
  vi.mock("@shopware/composables", () => ({
18
24
  useUser: vi.fn(),
19
- useWishlist: vi.fn(() => ({
20
- mergeWishlistProducts: vi.fn(),
21
- })),
25
+ useWishlist: vi.fn(),
22
26
  }));
23
27
 
24
28
  const mockToastAdd = vi.fn();
@@ -28,19 +32,30 @@ mockNuxtImport("useToast", () => {
28
32
  });
29
33
  });
30
34
 
31
- describe("LoginForm", () => {
32
- let loginMock: any;
33
- let userMock: any;
35
+ const loginMock = vi.fn();
36
+ const userMock = ref({ firstName: "John", lastName: "Doe" });
37
+ const isLoggedInMock = ref(false);
38
+
39
+ mockNuxtImport("useUser", () => {
40
+ return () => ({
41
+ isLoggedIn: isLoggedInMock,
42
+ login: loginMock,
43
+ user: userMock,
44
+ });
45
+ });
46
+
47
+ mockNuxtImport("useWishlist", () => {
48
+ return () => ({
49
+ mergeWishlistProducts: vi.fn(),
50
+ });
51
+ });
34
52
 
53
+ describe("LoginForm", () => {
35
54
  beforeEach(() => {
36
55
  vi.clearAllMocks();
37
- loginMock = vi.fn();
38
- userMock = { value: { firstName: "John", lastName: "Doe" } };
39
- (useUser as any).mockReturnValue({
40
- isLoggedIn: { value: false },
41
- login: loginMock,
42
- user: userMock,
43
- });
56
+ loginMock.mockReset();
57
+ isLoggedInMock.value = false;
58
+ userMock.value = { firstName: "John", lastName: "Doe" };
44
59
  });
45
60
 
46
61
  it("should show success toast on successful login", async () => {
@@ -48,7 +63,7 @@ describe("LoginForm", () => {
48
63
  const wrapper = await mountSuspended(LoginForm);
49
64
 
50
65
  // Call onSubmit directly if form trigger is not working in test
51
- await (wrapper.vm as any).onSubmit({
66
+ await (wrapper.vm as unknown as LoginFormVm).onSubmit({
52
67
  data: {
53
68
  email: "test@example.com",
54
69
  password: "password123",
@@ -63,6 +78,7 @@ describe("LoginForm", () => {
63
78
  expect(mockToastAdd).toHaveBeenCalledWith(
64
79
  expect.objectContaining({
65
80
  title: "Hallo John Doe!",
81
+ description: "Erfolgreich angemeldet.",
66
82
  color: "success",
67
83
  }),
68
84
  );
@@ -73,7 +89,7 @@ describe("LoginForm", () => {
73
89
  const wrapper = await mountSuspended(LoginForm);
74
90
 
75
91
  // Call onSubmit directly if form trigger is not working in test
76
- await (wrapper.vm as any).onSubmit({
92
+ await (wrapper.vm as unknown as LoginFormVm).onSubmit({
77
93
  data: {
78
94
  email: "test@example.com",
79
95
  password: "wrongpassword",
@@ -98,11 +114,11 @@ describe("LoginForm", () => {
98
114
  detail: 'The email address "test@example.com" is already in use',
99
115
  },
100
116
  ],
101
- });
117
+ } as unknown as ConstructorParameters<typeof ApiClientError>[0]);
102
118
  loginMock.mockRejectedValueOnce(apiClientError);
103
119
  const wrapper = await mountSuspended(LoginForm);
104
120
 
105
- await (wrapper.vm as any).onSubmit({
121
+ await (wrapper.vm as unknown as LoginFormVm).onSubmit({
106
122
  data: {
107
123
  email: "test@example.com",
108
124
  password: "password123",
@@ -119,11 +135,13 @@ describe("LoginForm", () => {
119
135
  });
120
136
 
121
137
  it("should handle ApiClientError with missing errors gracefully", async () => {
122
- const apiClientError = new ApiClientError({});
138
+ const apiClientError = new ApiClientError(
139
+ {} as unknown as ConstructorParameters<typeof ApiClientError>[0],
140
+ );
123
141
  loginMock.mockRejectedValueOnce(apiClientError);
124
142
  const wrapper = await mountSuspended(LoginForm);
125
143
 
126
- await (wrapper.vm as any).onSubmit({
144
+ await (wrapper.vm as unknown as LoginFormVm).onSubmit({
127
145
  data: {
128
146
  email: "test@example.com",
129
147
  password: "password123",
@@ -50,7 +50,7 @@ describe("PaymentAndDelivery", () => {
50
50
  it("updates payment method when changed", async () => {
51
51
  const wrapper = await mountSuspended(PaymentAndDelivery);
52
52
 
53
- // @ts-ignore - access internal state
53
+ // @ts-expect-error - access internal state
54
54
  wrapper.vm.selectedPaymentMethodId = "pm2";
55
55
  await new Promise((resolve) => setTimeout(resolve, 50));
56
56
 
@@ -60,7 +60,7 @@ describe("PaymentAndDelivery", () => {
60
60
  it("updates shipping method and refreshes cart when changed", async () => {
61
61
  const wrapper = await mountSuspended(PaymentAndDelivery);
62
62
 
63
- // @ts-ignore - access internal state
63
+ // @ts-expect-error - access internal state
64
64
  wrapper.vm.selectedShippingMethodId = "sm2";
65
65
  await new Promise((resolve) => setTimeout(resolve, 50));
66
66
 
@@ -38,6 +38,7 @@ mockNuxtImport("useToast", () => () => ({
38
38
 
39
39
  // Mock useRuntimeConfig
40
40
  mockNuxtImport("useRuntimeConfig", () => () => ({
41
+ app: { baseURL: "/" },
41
42
  public: {
42
43
  shopware: {
43
44
  devStorefrontUrl: "http://localhost:3000",
@@ -154,7 +155,7 @@ describe("RegistrationForm", () => {
154
155
  await new Promise((resolve) => setTimeout(resolve, 100));
155
156
 
156
157
  expect(mockRegister).toHaveBeenCalled();
157
- const calledData = mockRegister.mock.calls[0][0];
158
+ const calledData = mockRegister.mock.calls[0]![0];
158
159
  expect(calledData.email).toBe("john@example.com");
159
160
  expect(calledData.billingAddress.street).toBe("Musterstr 1");
160
161
  // Check if firstName/lastName were copied to billing address as per logic in onSubmit
@@ -169,7 +170,7 @@ describe("RegistrationForm", () => {
169
170
  detail: 'The email address "lirim@veliu.net" is already in use',
170
171
  },
171
172
  ],
172
- });
173
+ } as unknown as ConstructorParameters<typeof ApiClientError>[0]);
173
174
  mockRegister.mockRejectedValueOnce(apiClientError);
174
175
  const wrapper = await mountSuspended(RegistrationForm);
175
176
 
@@ -209,7 +210,9 @@ describe("RegistrationForm", () => {
209
210
  });
210
211
 
211
212
  it("handles ApiClientError with missing errors gracefully", async () => {
212
- const apiClientError = new ApiClientError({});
213
+ const apiClientError = new ApiClientError(
214
+ {} as unknown as ConstructorParameters<typeof ApiClientError>[0],
215
+ );
213
216
  mockRegister.mockRejectedValueOnce(apiClientError);
214
217
  const wrapper = await mountSuspended(RegistrationForm);
215
218
 
@@ -46,11 +46,11 @@ describe("registrationSchema", () => {
46
46
  const result = schema.safeParse(businessData);
47
47
  expect(result.success).toBe(false);
48
48
  if (!result.success) {
49
- expect(result.error.issues[0].message).toBe(
49
+ expect(result.error.issues[0]!.message).toBe(
50
50
  "Als Geschäftskunde ist der Firmenname ein Pflichtfeld.",
51
51
  );
52
- expect(result.error.issues[0].path).toContain("billingAddress");
53
- expect(result.error.issues[0].path).toContain("company");
52
+ expect(result.error.issues[0]!.path).toContain("billingAddress");
53
+ expect(result.error.issues[0]!.path).toContain("company");
54
54
  }
55
55
  });
56
56
 
@@ -189,10 +189,10 @@ describe("registrationSchema", () => {
189
189
  });
190
190
  expect(result.success).toBe(false);
191
191
  if (!result.success) {
192
- expect(result.error.issues[0].message).toBe(
192
+ expect(result.error.issues[0]!.message).toBe(
193
193
  "Bitte akzeptieren Sie die Datenschutzbestimmungen.",
194
194
  );
195
- expect(result.error.issues[0].path).toContain("acceptedDataProtection");
195
+ expect(result.error.issues[0]!.path).toContain("acceptedDataProtection");
196
196
  }
197
197
  });
198
198
 
@@ -207,11 +207,11 @@ describe("registrationSchema", () => {
207
207
  });
208
208
  expect(result.success).toBe(false);
209
209
  if (!result.success) {
210
- expect(result.error.issues[0].message).toBe(
210
+ expect(result.error.issues[0]!.message).toBe(
211
211
  "Bitte geben Sie Ihre Straße und Hausnummer an.",
212
212
  );
213
- expect(result.error.issues[0].path).toContain("billingAddress");
214
- expect(result.error.issues[0].path).toContain("street");
213
+ expect(result.error.issues[0]!.path).toContain("billingAddress");
214
+ expect(result.error.issues[0]!.path).toContain("street");
215
215
  }
216
216
  });
217
217
 
@@ -1,7 +1,8 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { mockNuxtImport } from "@nuxt/test-utils/runtime";
3
3
 
4
- import { useAddToCart } from "../../app/composables/useAddToCart";
4
+ import { useAddToCart } from "~/composables/useAddToCart";
5
+ import type { Schemas } from "#shopware";
5
6
  import { nextTick } from "vue";
6
7
 
7
8
  // Use vi.hoisted for variables used in mocks
@@ -66,7 +67,7 @@ describe("useAddToCart", () => {
66
67
  translated: {
67
68
  name: "Test Pizza",
68
69
  },
69
- } as any;
70
+ } as unknown as Schemas["Product"];
70
71
 
71
72
  beforeEach(() => {
72
73
  vi.clearAllMocks();
@@ -109,7 +110,7 @@ describe("useAddToCart", () => {
109
110
  cartItemLabel,
110
111
  } = useAddToCart();
111
112
  setSelectedProduct(mockProduct);
112
- setSelectedExtras([{ label: "Extra Cheese", value: "cheese" }] as any);
113
+ setSelectedExtras([{ label: "Extra Cheese", value: "cheese", price: "" }]);
113
114
  setDeselectedIngredients(["Onions"]);
114
115
  await nextTick();
115
116
  expect(cartItemLabel.value).toBe("Test Pizza +Extra Cheese -Onions");
@@ -141,7 +142,7 @@ describe("useAddToCart", () => {
141
142
  it("should add product with extras to cart as container", async () => {
142
143
  const { setSelectedProduct, setSelectedExtras, addToCart } = useAddToCart();
143
144
  setSelectedProduct(mockProduct);
144
- setSelectedExtras([{ label: "Extra Cheese", value: "cheese" }] as any);
145
+ setSelectedExtras([{ label: "Extra Cheese", value: "cheese", price: "" }]);
145
146
 
146
147
  mockAddProducts.mockResolvedValue({ id: "cart-123" });
147
148
 
@@ -11,6 +11,8 @@ mockNuxtImport("useFetch", () => mockUseFetch);
11
11
 
12
12
  // Mock useRuntimeConfig
13
13
  mockNuxtImport("useRuntimeConfig", () => () => ({
14
+ app: { baseURL: "/" },
15
+ public: { shopware: {} },
14
16
  geoapifyApiKey: "test-api-key",
15
17
  }));
16
18
 
@@ -5,7 +5,7 @@ import { useBusinessHours } from "~/composables/useBusinessHours";
5
5
 
6
6
  // Mock useAsyncData
7
7
  mockNuxtImport("useAsyncData", () => {
8
- return (key: string, handler: () => Promise<any>) => {
8
+ return (key: string, handler: () => Promise<unknown>) => {
9
9
  const data = ref(null);
10
10
  const pending = ref(false);
11
11
  const refresh = vi.fn(async () => {
@@ -97,10 +97,10 @@ describe("useBusinessHours", () => {
97
97
  const intervals = getServiceIntervals(monday);
98
98
 
99
99
  expect(intervals).toHaveLength(2);
100
- expect(intervals[0].start.getHours()).toBe(11);
101
- expect(intervals[1].start.getHours()).toBe(17);
102
- expect(intervals[0].start.getTime()).toBeLessThan(
103
- intervals[1].start.getTime(),
100
+ expect(intervals[0]!.start.getHours()).toBe(11);
101
+ expect(intervals[1]!.start.getHours()).toBe(17);
102
+ expect(intervals[0]!.start.getTime()).toBeLessThan(
103
+ intervals[1]!.start.getTime(),
104
104
  );
105
105
  });
106
106
  });
@@ -21,7 +21,7 @@ const { mockDeliveryTime, mockBusinessHours, mockHolidays } = vi.hoisted(
21
21
  { dayOfWeek: 0, openingTime: "17:30", closingTime: "23:00" },
22
22
  ],
23
23
  },
24
- mockHolidays: { value: [] },
24
+ mockHolidays: { value: [] as { start: string; end: string }[] | null },
25
25
  }),
26
26
  );
27
27
 
@@ -36,8 +36,12 @@ mockNuxtImport("useBusinessHours", () => () => ({
36
36
  return mockBusinessHours.value
37
37
  .filter((bh) => bh.dayOfWeek === dayOfWeek)
38
38
  .map((bh) => {
39
- const [startH, startM] = bh.openingTime.split(":").map(Number);
40
- const [endH, endM] = bh.closingTime.split(":").map(Number);
39
+ const startParts = bh.openingTime.split(":").map(Number);
40
+ const endParts = bh.closingTime.split(":").map(Number);
41
+ const startH = startParts[0] ?? 0;
42
+ const startM = startParts[1] ?? 0;
43
+ const endH = endParts[0] ?? 0;
44
+ const endM = endParts[1] ?? 0;
41
45
  const start = new Date(date);
42
46
  start.setHours(startH, startM, 0, 0);
43
47
  const end = new Date(date);
@@ -73,8 +77,12 @@ mockNuxtImport("useBusinessHours", () => () => ({
73
77
  ]
74
78
  .filter((bh) => bh.dayOfWeek === currentTime.getDay())
75
79
  .map((bh) => {
76
- const [startH, startM] = bh.openingTime.split(":").map(Number);
77
- const [endH, endM] = bh.closingTime.split(":").map(Number);
80
+ const startParts = bh.openingTime.split(":").map(Number);
81
+ const endParts = bh.closingTime.split(":").map(Number);
82
+ const startH = startParts[0] ?? 0;
83
+ const startM = startParts[1] ?? 0;
84
+ const endH = endParts[0] ?? 0;
85
+ const endM = endParts[1] ?? 0;
78
86
  const start = new Date(currentTime);
79
87
  start.setHours(startH, startM, 0, 0);
80
88
  const end = new Date(currentTime);
@@ -98,10 +106,10 @@ mockNuxtImport("useBusinessHours", () => () => ({
98
106
  mockNuxtImport("useHolidays", () => () => ({
99
107
  isClosedHoliday: (date: Date) => {
100
108
  if (!mockHolidays.value) return undefined;
101
- const formattedDate = date.toISOString().split("T")[0];
102
- return mockHolidays.value.some((h: any) => {
103
- const start = h.start.split("T")[0];
104
- const end = h.end.split("T")[0];
109
+ const formattedDate = date.toISOString().split("T")[0]!;
110
+ return mockHolidays.value.some((h: { start: string; end: string }) => {
111
+ const start = h.start.split("T")[0] as string;
112
+ const end = h.end.split("T")[0] as string;
105
113
  return formattedDate >= start && formattedDate <= end;
106
114
  });
107
115
  },
@@ -31,7 +31,7 @@ describe("useProductConfigurator", () => {
31
31
  id: "p1",
32
32
  optionIds: [],
33
33
  options: [],
34
- } as any;
34
+ } as unknown as typeof mockProduct.value;
35
35
  });
36
36
 
37
37
  it("should initialize with selected options from product", () => {
@@ -41,20 +41,22 @@ describe("useProductConfigurator", () => {
41
41
  name: "Size",
42
42
  options: [{ id: "o1", name: "Small" }],
43
43
  },
44
- ] as any;
44
+ ] as unknown as typeof mockConfigurator.value;
45
45
  mockProduct.value = {
46
46
  id: "p1-v1",
47
47
  parentId: "p1",
48
48
  optionIds: ["o1"],
49
49
  options: [{ id: "o1" }],
50
- } as any;
50
+ } as unknown as typeof mockProduct.value;
51
51
 
52
52
  const { isLoadingOptions } = useProductConfigurator();
53
53
  expect(isLoadingOptions.value).toBe(true);
54
54
  });
55
55
 
56
56
  it("should find variant for selected options", async () => {
57
- mockProduct.value = { parentId: "parent-1" } as any;
57
+ mockProduct.value = {
58
+ parentId: "parent-1",
59
+ } as unknown as typeof mockProduct.value;
58
60
  mockInvoke.mockResolvedValue({
59
61
  data: {
60
62
  elements: [{ id: "variant-1" }],
@@ -1,11 +1,13 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import { useProductVariants } from "../../app/composables/useProductVariants";
2
+ import { useProductVariants } from "~/composables/useProductVariants";
3
3
  import { ref } from "vue";
4
4
 
5
5
  describe("useProductVariants", () => {
6
6
  it("should return empty object when no settings provided", () => {
7
7
  const settings = ref([]);
8
- const { variants } = useProductVariants(settings as any);
8
+ const { variants } = useProductVariants(
9
+ settings as unknown as Parameters<typeof useProductVariants>[0],
10
+ );
9
11
  expect(variants.value).toEqual({});
10
12
  });
11
13
 
@@ -49,20 +51,22 @@ describe("useProductVariants", () => {
49
51
  },
50
52
  ]);
51
53
 
52
- const { variants } = useProductVariants(settings as any);
54
+ const { variants } = useProductVariants(
55
+ settings as unknown as Parameters<typeof useProductVariants>[0],
56
+ );
53
57
 
54
58
  expect(variants.value["group-size"]).toBeDefined();
55
- expect(variants.value["group-size"].name).toBe("Größe");
56
- expect(variants.value["group-size"].options).toHaveLength(2);
57
- expect(variants.value["group-size"].options[0]).toEqual({
59
+ expect(variants.value["group-size"]!.name).toBe("Größe");
60
+ expect(variants.value["group-size"]!.options).toHaveLength(2);
61
+ expect(variants.value["group-size"]!.options[0]).toEqual({
58
62
  label: "Klein",
59
63
  value: "opt-1",
60
64
  productId: "opt-1",
61
65
  });
62
66
 
63
67
  expect(variants.value["group-color"]).toBeDefined();
64
- expect(variants.value["group-color"].name).toBe("Farbe");
65
- expect(variants.value["group-color"].options).toHaveLength(1);
68
+ expect(variants.value["group-color"]!.name).toBe("Farbe");
69
+ expect(variants.value["group-color"]!.options).toHaveLength(1);
66
70
  });
67
71
 
68
72
  it("should avoid duplicate options", () => {
@@ -83,7 +87,9 @@ describe("useProductVariants", () => {
83
87
  },
84
88
  ]);
85
89
 
86
- const { variants } = useProductVariants(settings as any);
87
- expect(variants.value["group-size"].options).toHaveLength(1);
90
+ const { variants } = useProductVariants(
91
+ settings as unknown as Parameters<typeof useProductVariants>[0],
92
+ );
93
+ expect(variants.value["group-size"]!.options).toHaveLength(1);
88
94
  });
89
95
  });
@@ -22,7 +22,9 @@ describe("useProductVariantsZwei", () => {
22
22
  },
23
23
  ]);
24
24
 
25
- const { variants } = useProductVariantsZwei(settings);
25
+ const { variants } = useProductVariantsZwei(
26
+ settings as unknown as Parameters<typeof useProductVariantsZwei>[0],
27
+ );
26
28
 
27
29
  expect(variants.value["group-size"]).toBeDefined();
28
30
  expect(variants.value["group-size"]?.name).toBe("Größe");
@@ -42,7 +44,9 @@ describe("useProductVariantsZwei", () => {
42
44
  },
43
45
  ]);
44
46
 
45
- const { variants } = useProductVariantsZwei(settings);
47
+ const { variants } = useProductVariantsZwei(
48
+ settings as unknown as Parameters<typeof useProductVariantsZwei>[0],
49
+ );
46
50
  expect(variants.value["group-size"]?.options).toHaveLength(1);
47
51
  });
48
52
  });
@@ -1,13 +1,13 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { useScrollAnimation } from "../../app/composables/useScrollAnimation";
2
+ import { useScrollAnimation } from "~/composables/useScrollAnimation";
3
3
  import { mount } from "@vue/test-utils";
4
4
  import { defineComponent, nextTick } from "vue";
5
5
 
6
6
  describe("useScrollAnimation", () => {
7
- let observeMock: any;
8
- let unobserveMock: any;
9
- let disconnectMock: any;
10
- let intersectionCallback: any;
7
+ let observeMock: ReturnType<typeof vi.fn>;
8
+ let unobserveMock: ReturnType<typeof vi.fn>;
9
+ let disconnectMock: ReturnType<typeof vi.fn>;
10
+ let intersectionCallback: IntersectionObserverCallback;
11
11
 
12
12
  beforeEach(() => {
13
13
  observeMock = vi.fn();
@@ -15,14 +15,17 @@ describe("useScrollAnimation", () => {
15
15
  disconnectMock = vi.fn();
16
16
 
17
17
  // Mock IntersectionObserver
18
- global.IntersectionObserver = vi
19
- .fn()
20
- .mockImplementation(function (callback) {
21
- intersectionCallback = callback;
22
- this.observe = observeMock;
23
- this.unobserve = unobserveMock;
24
- this.disconnect = disconnectMock;
25
- }) as any;
18
+ global.IntersectionObserver = vi.fn().mockImplementation(function (
19
+ this: IntersectionObserver,
20
+ callback: IntersectionObserverCallback,
21
+ ) {
22
+ intersectionCallback = callback;
23
+ this.observe = observeMock as unknown as IntersectionObserver["observe"];
24
+ this.unobserve =
25
+ unobserveMock as unknown as IntersectionObserver["unobserve"];
26
+ this.disconnect =
27
+ disconnectMock as unknown as IntersectionObserver["disconnect"];
28
+ }) as unknown as typeof IntersectionObserver;
26
29
  });
27
30
 
28
31
  it("should initialize with isVisible false", () => {