@shopbite-de/storefront 1.16.1 → 1.17.4

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 (54) hide show
  1. package/.env.example +0 -4
  2. package/.github/workflows/build.yaml +20 -9
  3. package/.github/workflows/ci.yaml +125 -42
  4. package/.nuxtrc +1 -1
  5. package/CLAUDE.md +106 -0
  6. package/app/components/Address/Form.vue +5 -2
  7. package/app/components/Category/Listing.vue +5 -4
  8. package/app/components/Checkout/DeliveryTimeSelect.vue +4 -2
  9. package/app/components/Contact/Form.vue +3 -2
  10. package/app/components/Hero.vue +1 -1
  11. package/app/components/ImageGallery.vue +1 -1
  12. package/app/components/Navigation/DesktopLeft.vue +2 -1
  13. package/app/components/Order/Detail.vue +2 -2
  14. package/app/components/Product/Card.vue +14 -8
  15. package/app/components/SalesChannelSwitch.vue +5 -3
  16. package/app/components/User/RegistrationForm.vue +5 -2
  17. package/app/composables/useBusinessHours.ts +11 -4
  18. package/app/composables/useCategory.ts +1 -0
  19. package/app/composables/useTopSellers.ts +2 -2
  20. package/app/pages/c/[...all].vue +2 -1
  21. package/app/pages/konto/adressen.vue +4 -1
  22. package/app/pages/konto/bestellung/[id].vue +14 -2
  23. package/app/pages/konto/profil.vue +5 -1
  24. package/app/utils/formatDate.ts +2 -1
  25. package/content.config.ts +1 -2
  26. package/eslint.config.mjs +10 -6
  27. package/node.dockerfile +7 -6
  28. package/nuxt.config.ts +16 -3
  29. package/package.json +42 -32
  30. package/renovate.json +10 -1
  31. package/server/api/address/autocomplete.get.ts +3 -2
  32. package/test/e2e/simple-checkout-as-recurring-customer.test.ts +1 -1
  33. package/test/nuxt/AddressFields.test.ts +12 -3
  34. package/test/nuxt/ContactForm.test.ts +31 -11
  35. package/test/nuxt/HeaderRight.test.ts +15 -6
  36. package/test/nuxt/LoginForm.test.ts +16 -8
  37. package/test/nuxt/PaymentAndDelivery.test.ts +2 -2
  38. package/test/nuxt/RegistrationForm.test.ts +6 -3
  39. package/test/nuxt/registrationSchema.test.ts +8 -8
  40. package/test/nuxt/useAddToCart.test.ts +5 -4
  41. package/test/nuxt/useAddressAutocomplete.test.ts +2 -0
  42. package/test/nuxt/useBusinessHours.test.ts +5 -5
  43. package/test/nuxt/useDeliveryTime.test.ts +17 -9
  44. package/test/nuxt/useProductConfigurator.test.ts +6 -4
  45. package/test/nuxt/useProductVariants.test.ts +16 -10
  46. package/test/nuxt/useProductVariantsZwei.test.ts +6 -2
  47. package/test/nuxt/useScrollAnimation.test.ts +16 -13
  48. package/test/nuxt/useTopSellers.test.ts +2 -2
  49. package/test/nuxt/useWishlistActions.test.ts +4 -3
  50. package/test/unit/useCategorySeo.spec.ts +26 -12
  51. package/tsconfig.json +21 -1
  52. package/.claude/settings.local.json +0 -8
  53. package/server/utils/shopware/adminApiClient.ts +0 -24
  54. package/test/unit/sales-channels.test.ts +0 -66
@@ -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", () => {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { mockNuxtImport } from "@nuxt/test-utils/runtime";
3
- import { useTopSellers } from "../../app/composables/useTopSellers";
3
+ import { useTopSellers } from "~/composables/useTopSellers";
4
4
 
5
5
  const { mockInvoke } = vi.hoisted(() => ({
6
6
  mockInvoke: vi.fn(),
@@ -31,7 +31,7 @@ describe("useTopSellers", () => {
31
31
  const result = await loadTopSellers();
32
32
 
33
33
  expect(mockInvoke).toHaveBeenCalledWith(
34
- "getTopSellers post /product",
34
+ "readProduct post /product",
35
35
  expect.any(Object),
36
36
  );
37
37
  expect(result).toEqual(mockElements);
@@ -1,6 +1,7 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { mockNuxtImport } from "@nuxt/test-utils/runtime";
3
3
  import { useWishlistActions } from "../../app/composables/useWishlistActions";
4
+ import type { Schemas } from "#shopware";
4
5
 
5
6
  const {
6
7
  mockAddProducts,
@@ -44,7 +45,7 @@ describe("useWishlistActions", () => {
44
45
  id: "prod-1",
45
46
  translated: { name: "Test Product" },
46
47
  productNumber: "SW123",
47
- } as any;
48
+ } as unknown as Schemas["Product"];
48
49
 
49
50
  beforeEach(() => {
50
51
  vi.clearAllMocks();
@@ -93,7 +94,7 @@ describe("useWishlistActions", () => {
93
94
  const products = [
94
95
  { id: "p1", translated: { name: "P1" } },
95
96
  { id: "p2", translated: { name: "P2" } },
96
- ] as any[];
97
+ ] as unknown as Schemas["Product"][];
97
98
  const { addAllItemsToCart, isAddingToCart } = useWishlistActions();
98
99
  mockAddProducts.mockResolvedValue({ id: "cart-1" });
99
100
 
@@ -119,7 +120,7 @@ describe("useWishlistActions", () => {
119
120
  const products = [
120
121
  { id: "p1", translated: { name: "P1" } },
121
122
  { id: "p2", childCount: 1, translated: { name: "P2" } },
122
- ] as any[];
123
+ ] as unknown as Schemas["Product"][];
123
124
  const { addAllItemsToCart } = useWishlistActions();
124
125
  mockAddProducts.mockResolvedValue({ id: "cart-1" });
125
126
 
@@ -1,6 +1,9 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { ref } from "vue";
3
3
 
4
+ // Re-import the mocks for assertions
5
+ import { useHead, useSeoMeta } from "#imports";
6
+
4
7
  // Create shared mocks to be exported by both '#imports' and '#app' in a hoisted-safe way
5
8
  const shared = vi.hoisted(() => ({
6
9
  useHead: vi.fn(),
@@ -41,24 +44,25 @@ vi.mock("#app", async () => {
41
44
  };
42
45
  });
43
46
 
44
- // Re-import the mocks for assertions
45
- import { useHead, useSeoMeta } from "#imports";
46
-
47
47
  // Target under test will be dynamically imported after setting up globals
48
- let useCategorySeo: (arg: any) => any;
48
+ let useCategorySeo: (
49
+ arg: unknown,
50
+ ) => ReturnType<
51
+ (typeof import("../../app/composables/useCategorySeo"))["useCategorySeo"]
52
+ >;
49
53
 
50
54
  describe("useCategorySeo", () => {
51
55
  beforeEach(async () => {
52
56
  vi.clearAllMocks();
53
57
  // Provide globals for auto-imported functions (when not transformed in unit env)
54
58
  const vue = await import("vue");
55
- (globalThis as any).computed = vue.computed;
56
- (globalThis as any).ref = vue.ref;
57
- (globalThis as any).useRuntimeConfig = () => ({
59
+ (globalThis as Record<string, unknown>).computed = vue.computed;
60
+ (globalThis as Record<string, unknown>).ref = vue.ref;
61
+ (globalThis as Record<string, unknown>).useRuntimeConfig = () => ({
58
62
  public: { site: { name: "My Store" }, storeUrl: "https://example.com" },
59
63
  });
60
- (globalThis as any).useHead = useHead;
61
- (globalThis as any).useSeoMeta = useSeoMeta;
64
+ (globalThis as Record<string, unknown>).useHead = useHead;
65
+ (globalThis as Record<string, unknown>).useSeoMeta = useSeoMeta;
62
66
 
63
67
  // Dynamic import after globals are ready
64
68
  useCategorySeo = (await import("../../app/composables/useCategorySeo"))
@@ -66,7 +70,17 @@ describe("useCategorySeo", () => {
66
70
  });
67
71
 
68
72
  it("computes core SEO refs and injects head tags", () => {
69
- const category = ref<any>({
73
+ const category = ref<{
74
+ translated?: {
75
+ metaTitle?: string;
76
+ metaDescription?: string;
77
+ breadcrumb?: string[];
78
+ name?: string;
79
+ };
80
+ seoUrl?: string;
81
+ active?: boolean;
82
+ media?: { url?: string };
83
+ }>({
70
84
  translated: {
71
85
  metaTitle: "Pizza & Pasta",
72
86
  metaDescription: "Leckere Pizza und Pasta bestellen",
@@ -96,7 +110,7 @@ describe("useCategorySeo", () => {
96
110
  useHead as unknown as ReturnType<typeof vi.fn>,
97
111
  ).toHaveBeenCalledTimes(1);
98
112
  const headArg = (useHead as unknown as ReturnType<typeof vi.fn>).mock
99
- .calls[0][0];
113
+ .calls[0]![0]!;
100
114
 
101
115
  // Canonical link
102
116
  const link = headArg.link?.[0];
@@ -116,7 +130,7 @@ describe("useCategorySeo", () => {
116
130
  });
117
131
 
118
132
  it("sets robots to noindex when category is inactive", () => {
119
- const category = ref<any>({
133
+ const category = ref({
120
134
  translated: { name: "Salate" },
121
135
  active: false,
122
136
  seoUrl: "/c/salate",
package/tsconfig.json CHANGED
@@ -1,4 +1,24 @@
1
1
  {
2
- // https://v3.nuxtjs.org/concepts/typescript
3
2
  "extends": "./.nuxt/tsconfig.json",
3
+ "include": [
4
+ ".nuxt/nuxt.d.ts",
5
+ ".nuxt/nuxt.node.d.ts",
6
+ "app/**/*",
7
+ "server/**/*",
8
+ "shared/**/*",
9
+ "modules/**/*",
10
+ "layers/**/*",
11
+ "test/**/*",
12
+ "tests/**/*",
13
+ "*.d.ts",
14
+ "nuxt.config.*",
15
+ ".config/nuxt.*",
16
+ "content.config.*"
17
+ ],
18
+ "exclude": [
19
+ "node_modules",
20
+ ],
21
+ "compilerOptions": {
22
+ "typeRoots": ["./api-types", "./node_modules/@types", "./node_modules"]
23
+ }
4
24
  }
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(find:*)",
5
- "Bash(grep:*)"
6
- ]
7
- }
8
- }
@@ -1,24 +0,0 @@
1
- import { createAdminAPIClient } from "@shopware/api-client";
2
- import type { operations } from "@shopware/api-client/admin-api-types";
3
-
4
- /**
5
- * Creates a Shopware Admin API client
6
- */
7
- export function createAdminApiClient(baseURL: string, accessToken: string) {
8
- return createAdminAPIClient<operations>({
9
- baseURL,
10
- accessToken,
11
- });
12
- }
13
-
14
- /**
15
- * Fetches sales channels using the Admin API client
16
- */
17
- export async function getSalesChannels(client: any) {
18
- const response = await client.invoke({
19
- method: "GET",
20
- path: "/api/v3/sales-channel",
21
- });
22
-
23
- return response.data;
24
- }
@@ -1,66 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import {
3
- createAdminApiClient,
4
- getSalesChannels,
5
- } from "../../server/utils/shopware/adminApiClient";
6
-
7
- describe("Shopware Admin API Client", () => {
8
- describe("createAdminApiClient", () => {
9
- it("should create a client with correct configuration", () => {
10
- const endpoint = "https://shopware.example.com/api";
11
- const accessToken = "test-token";
12
-
13
- const client = createAdminApiClient(endpoint, accessToken);
14
-
15
- expect(client).toBeDefined();
16
- // The client should have the invoke method
17
- expect(client).toHaveProperty("invoke");
18
- expect(typeof client.invoke).toBe("function");
19
- });
20
- });
21
-
22
- describe("getSalesChannels", () => {
23
- it("should fetch sales channels successfully", async () => {
24
- // Mock the client
25
- const mockClient = {
26
- invoke: vi.fn().mockResolvedValue({
27
- data: [
28
- { id: "1", name: "Storefront", typeId: "storefront" },
29
- { id: "2", name: "Headless", typeId: "headless" },
30
- ],
31
- }),
32
- };
33
-
34
- const result = await getSalesChannels(mockClient);
35
-
36
- expect(result).toEqual([
37
- { id: "1", name: "Storefront", typeId: "storefront" },
38
- { id: "2", name: "Headless", typeId: "headless" },
39
- ]);
40
-
41
- expect(mockClient.invoke).toHaveBeenCalledWith({
42
- method: "GET",
43
- path: "/api/v3/sales-channel",
44
- });
45
- });
46
-
47
- it("should handle errors when fetching sales channels", async () => {
48
- const mockClient = {
49
- invoke: vi.fn().mockRejectedValue(new Error("API error")),
50
- };
51
-
52
- await expect(getSalesChannels(mockClient)).rejects.toThrow("API error");
53
- });
54
-
55
- it("should handle empty response gracefully", async () => {
56
- const mockClient = {
57
- invoke: vi.fn().mockResolvedValue({
58
- data: [],
59
- }),
60
- };
61
-
62
- const result = await getSalesChannels(mockClient);
63
- expect(result).toEqual([]);
64
- });
65
- });
66
- });