@shopbite-de/storefront 1.5.3 → 1.6.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.
- package/.env.example +3 -1
- package/app/components/Address/Fields.vue +128 -0
- package/app/components/Checkout/PaymentAndDelivery.vue +49 -27
- package/app/components/Header.vue +26 -13
- package/app/components/User/LoginForm.vue +32 -11
- package/app/components/User/RegistrationForm.vue +105 -180
- package/app/composables/useAddressAutocomplete.ts +84 -0
- package/app/composables/useAddressValidation.ts +95 -0
- package/app/composables/useValidCitiesForDelivery.ts +12 -0
- package/app/validation/registrationSchema.ts +105 -124
- package/content/unternehmen/zahlung-und-versand.md +29 -0
- package/nuxt.config.ts +1 -0
- package/package.json +1 -1
- package/server/api/address/autocomplete.get.ts +33 -0
- package/test/nuxt/AddressFields.test.ts +284 -0
- package/test/nuxt/Header.test.ts +124 -0
- package/test/nuxt/LoginForm.test.ts +141 -0
- package/test/nuxt/PaymentAndDelivery.test.ts +78 -0
- package/test/nuxt/RegistrationForm.test.ts +255 -0
- package/test/nuxt/RegistrationValidation.test.ts +39 -0
- package/test/nuxt/registrationSchema.test.ts +242 -0
- package/test/nuxt/useAddressAutocomplete.test.ts +161 -0
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
// schemas/registrationSchema.ts
|
|
2
2
|
import * as z from "zod";
|
|
3
3
|
|
|
4
|
+
const baseAddressSchema = z.object({
|
|
5
|
+
firstName: z.string().optional(),
|
|
6
|
+
lastName: z.string().optional(),
|
|
7
|
+
company: z.string().optional(),
|
|
8
|
+
department: z.string().optional(),
|
|
9
|
+
salutationId: z.string().optional(),
|
|
10
|
+
street: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1, { message: "Bitte geben Sie Ihre Straße und Hausnummer an." })
|
|
13
|
+
.refine((val) => /[0-9]/.test(val), {
|
|
14
|
+
message: "Bitte geben Sie Ihre Straße und Hausnummer an.",
|
|
15
|
+
}),
|
|
16
|
+
additionalAddressLine1: z.string().optional(),
|
|
17
|
+
zipcode: z.string().optional(),
|
|
18
|
+
city: z.string().min(1, {
|
|
19
|
+
message: "Bitte geben Sie Ihren Ort an.",
|
|
20
|
+
}),
|
|
21
|
+
countryId: z.string().min(1, { message: "Bitte wählen Sie ein Land aus." }),
|
|
22
|
+
phoneNumber: z.string().min(7, {
|
|
23
|
+
message: "Bitte geben Sie eine gültige Telefonnummer an (mind. 7 Zeichen).",
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
4
27
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
28
|
export const createRegistrationSchema = (state: any) =>
|
|
6
29
|
z.object({
|
|
7
|
-
accountType: z.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
30
|
+
accountType: z.enum(["private", "business"], {
|
|
31
|
+
errorMap: () => ({ message: "Bitte wählen Sie einen Kontotyp aus." }),
|
|
32
|
+
}),
|
|
33
|
+
email: z.string().email("Bitte geben Sie eine gültige E-Mail-Adresse ein."),
|
|
34
|
+
firstName: z
|
|
35
|
+
.string()
|
|
36
|
+
.min(1, { message: "Bitte geben Sie Ihren Vornamen an." }),
|
|
37
|
+
lastName: z
|
|
38
|
+
.string()
|
|
39
|
+
.min(1, { message: "Bitte geben Sie Ihren Nachnamen an." }),
|
|
11
40
|
guest: z.boolean(),
|
|
41
|
+
acceptedDataProtection: z.boolean().refine((val) => val, {
|
|
42
|
+
message: "Bitte akzeptieren Sie die Datenschutzbestimmungen.",
|
|
43
|
+
}),
|
|
44
|
+
storefrontUrl: z.string().optional(),
|
|
12
45
|
password: z
|
|
13
46
|
.string()
|
|
14
47
|
.optional()
|
|
@@ -17,7 +50,7 @@ export const createRegistrationSchema = (state: any) =>
|
|
|
17
50
|
return state.guest || (val && val.length >= 8);
|
|
18
51
|
},
|
|
19
52
|
{
|
|
20
|
-
message: "
|
|
53
|
+
message: "Das Passwort muss mindestens 8 Zeichen lang sein.",
|
|
21
54
|
},
|
|
22
55
|
),
|
|
23
56
|
passwordConfirm: z
|
|
@@ -28,129 +61,77 @@ export const createRegistrationSchema = (state: any) =>
|
|
|
28
61
|
return state.guest || val === state.password;
|
|
29
62
|
},
|
|
30
63
|
{
|
|
31
|
-
message: "
|
|
64
|
+
message: "Die Passwörter stimmen nicht überein.",
|
|
32
65
|
},
|
|
33
66
|
),
|
|
34
|
-
billingAddress:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
!state.isShippingAddressDifferent ||
|
|
99
|
-
(val !== undefined && val !== "")
|
|
100
|
-
);
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
message: "Erforderlich.",
|
|
104
|
-
},
|
|
105
|
-
),
|
|
106
|
-
department: z.string().optional(),
|
|
107
|
-
phoneNumber: z
|
|
108
|
-
.string()
|
|
109
|
-
.optional()
|
|
110
|
-
.refine(
|
|
111
|
-
(val) => {
|
|
112
|
-
return (
|
|
113
|
-
!state.isShippingAddressDifferent ||
|
|
114
|
-
(val !== undefined && val !== "")
|
|
115
|
-
);
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
message: "Erforderlich.",
|
|
119
|
-
},
|
|
120
|
-
),
|
|
121
|
-
street: z
|
|
122
|
-
.string()
|
|
123
|
-
.optional()
|
|
124
|
-
.refine(
|
|
125
|
-
(val) => {
|
|
126
|
-
return (
|
|
127
|
-
!state.isShippingAddressDifferent ||
|
|
128
|
-
(val !== undefined && val !== "")
|
|
129
|
-
);
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
message: "Erforderlich.",
|
|
133
|
-
},
|
|
134
|
-
),
|
|
135
|
-
zipcode: z.string().min(1).optional(),
|
|
136
|
-
city: z
|
|
137
|
-
.string()
|
|
138
|
-
.optional()
|
|
139
|
-
.refine(
|
|
140
|
-
(val) => {
|
|
141
|
-
return (
|
|
142
|
-
!state.isShippingAddressDifferent ||
|
|
143
|
-
(val !== undefined && val !== "")
|
|
144
|
-
);
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
message: "Erforderlich.",
|
|
148
|
-
},
|
|
149
|
-
),
|
|
150
|
-
countryId: z.string().min(1),
|
|
151
|
-
}),
|
|
67
|
+
billingAddress: baseAddressSchema.refine(
|
|
68
|
+
(data) => {
|
|
69
|
+
return (
|
|
70
|
+
state.accountType !== "business" ||
|
|
71
|
+
(data.company !== undefined && data.company !== "")
|
|
72
|
+
);
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
message: "Als Geschäftskunde ist der Firmenname ein Pflichtfeld.",
|
|
76
|
+
path: ["company"],
|
|
77
|
+
},
|
|
78
|
+
),
|
|
79
|
+
shippingAddress: baseAddressSchema
|
|
80
|
+
.extend({
|
|
81
|
+
firstName: z.string().optional(),
|
|
82
|
+
lastName: z.string().optional(),
|
|
83
|
+
company: z.string().optional(),
|
|
84
|
+
phoneNumber: z.string().optional(),
|
|
85
|
+
street: z.string().optional(),
|
|
86
|
+
city: z.string().optional(),
|
|
87
|
+
zipcode: z.string().optional(),
|
|
88
|
+
countryId: z.string().optional(),
|
|
89
|
+
})
|
|
90
|
+
.superRefine((data, ctx) => {
|
|
91
|
+
if (!state.isShippingAddressDifferent) return;
|
|
92
|
+
|
|
93
|
+
const requiredFields: (keyof typeof data)[] = [
|
|
94
|
+
"firstName",
|
|
95
|
+
"lastName",
|
|
96
|
+
"company",
|
|
97
|
+
"phoneNumber",
|
|
98
|
+
"street",
|
|
99
|
+
"city",
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
const fieldMessages: Record<string, string> = {
|
|
103
|
+
firstName: "Bitte geben Sie den Vornamen für die Lieferadresse an.",
|
|
104
|
+
lastName: "Bitte geben Sie den Nachnamen für die Lieferadresse an.",
|
|
105
|
+
company: "Bitte geben Sie den Firmennamen für die Lieferadresse an.",
|
|
106
|
+
phoneNumber:
|
|
107
|
+
"Bitte geben Sie eine Telefonnummer für die Lieferadresse an.",
|
|
108
|
+
street: "Bitte geben Sie die Straße für die Lieferadresse an.",
|
|
109
|
+
city: "Bitte geben Sie den Ort für die Lieferadresse an.",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
requiredFields.forEach((field) => {
|
|
113
|
+
if (field === "company" && state.accountType !== "business") return;
|
|
114
|
+
|
|
115
|
+
if (!data[field] || data[field] === "") {
|
|
116
|
+
ctx.addIssue({
|
|
117
|
+
code: z.ZodIssueCode.custom,
|
|
118
|
+
message: fieldMessages[field] || "Dieses Feld ist erforderlich.",
|
|
119
|
+
path: [field],
|
|
120
|
+
});
|
|
121
|
+
} else if (field === "street" && !/[0-9]/.test(data.street || "")) {
|
|
122
|
+
ctx.addIssue({
|
|
123
|
+
code: z.ZodIssueCode.custom,
|
|
124
|
+
message: "Bitte geben Sie Ihre Straße und Hausnummer an.",
|
|
125
|
+
path: ["street"],
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
})
|
|
130
|
+
.optional(),
|
|
152
131
|
});
|
|
153
132
|
|
|
154
133
|
export type RegistrationSchema = z.infer<
|
|
155
134
|
ReturnType<typeof createRegistrationSchema>
|
|
156
135
|
>;
|
|
136
|
+
|
|
137
|
+
export type AddressSchema = z.infer<typeof baseAddressSchema>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Zahlungs- und Lieferinformationen
|
|
2
|
+
|
|
3
|
+
### Liefermethoden
|
|
4
|
+
|
|
5
|
+
Wir bieten Ihnen zwei Möglichkeiten, Ihre Bestellung zu erhalten:
|
|
6
|
+
|
|
7
|
+
- **Lieferung:** Wir bringen Ihre Bestellung direkt zu Ihnen nach Hause (beachten Sie bitte unsere Liefergebiete).
|
|
8
|
+
- **Abholung:** Sie können Ihre Bestellung auch direkt bei uns abholen (Kantstraße 6, 63179 Obertshausen).
|
|
9
|
+
|
|
10
|
+
### Zahlungsmöglichkeiten
|
|
11
|
+
|
|
12
|
+
- **Bei Lieferung:** Die Zahlung erfolgt ausschließlich **bar**.
|
|
13
|
+
- **Bei Abholung:** Sie können bar oder elektronisch mit **EC-Karte** bezahlen.
|
|
14
|
+
|
|
15
|
+
### Lieferbedingungen
|
|
16
|
+
|
|
17
|
+
Wir liefern ausschließlich in folgende Gebiete:
|
|
18
|
+
|
|
19
|
+
- **63179 Obertshausen** (Stadtteile Obertshausen und Hausen)
|
|
20
|
+
- **63165 Mühlheim** (Stadtteil Lämmerspiel)
|
|
21
|
+
|
|
22
|
+
### Lieferkosten
|
|
23
|
+
|
|
24
|
+
- **Ab 15,00 € Bestellwert:** Lieferung frei Haus (versandkostenfrei).
|
|
25
|
+
- **Unter 15,00 € Bestellwert:** Wir erheben eine Lieferpauschale von **2,00 €**.
|
|
26
|
+
|
|
27
|
+
### Lieferzeit
|
|
28
|
+
|
|
29
|
+
Die Lieferzeit beträgt in der Regel ca. **30 Minuten**. Bitte beachten Sie, dass es zu Stoßzeiten zu Abweichungen kommen kann.
|
package/nuxt.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default defineEventHandler(async (event) => {
|
|
2
|
+
const config = useRuntimeConfig();
|
|
3
|
+
const apiKey = config.geoapifyApiKey;
|
|
4
|
+
const query = getQuery(event);
|
|
5
|
+
|
|
6
|
+
if (!apiKey) {
|
|
7
|
+
return { features: [] };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { text, lang, limit, filter } = query;
|
|
11
|
+
|
|
12
|
+
if (!text) {
|
|
13
|
+
return { features: [] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const geoapifyUrl = new URL(
|
|
17
|
+
"https://api.geoapify.com/v1/geocode/autocomplete",
|
|
18
|
+
);
|
|
19
|
+
geoapifyUrl.searchParams.set("apiKey", apiKey);
|
|
20
|
+
geoapifyUrl.searchParams.set("text", text as string);
|
|
21
|
+
if (lang) geoapifyUrl.searchParams.set("lang", lang as string);
|
|
22
|
+
if (limit) geoapifyUrl.searchParams.set("limit", limit as string);
|
|
23
|
+
if (filter) geoapifyUrl.searchParams.set("filter", filter as string);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return await $fetch(geoapifyUrl.toString());
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: error.response?.status || 500,
|
|
30
|
+
statusMessage: error.message || "Error fetching address suggestions",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { reactive, ref } from "vue";
|
|
3
|
+
import { mountSuspended, mockNuxtImport } from "@nuxt/test-utils/runtime";
|
|
4
|
+
import Fields from "~/components/Address/Fields.vue";
|
|
5
|
+
|
|
6
|
+
const { mockGetSuggestions } = vi.hoisted(() => ({
|
|
7
|
+
mockGetSuggestions: vi.fn().mockResolvedValue([]),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
mockNuxtImport("useAddressAutocomplete", () => () => ({
|
|
11
|
+
getSuggestions: mockGetSuggestions,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
describe("AddressFields", () => {
|
|
15
|
+
const defaultProps = {
|
|
16
|
+
modelValue: {
|
|
17
|
+
firstName: "",
|
|
18
|
+
lastName: "",
|
|
19
|
+
street: "",
|
|
20
|
+
zipcode: "",
|
|
21
|
+
city: "",
|
|
22
|
+
phoneNumber: "",
|
|
23
|
+
},
|
|
24
|
+
prefix: "billingAddress",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
it("renders basic address fields", async () => {
|
|
28
|
+
const wrapper = await mountSuspended(Fields, {
|
|
29
|
+
props: defaultProps,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
expect(wrapper.find('input[name="billingAddress.street"]').exists()).toBe(
|
|
33
|
+
true,
|
|
34
|
+
);
|
|
35
|
+
expect(wrapper.find('input[name="billingAddress.zipcode"]').exists()).toBe(
|
|
36
|
+
true,
|
|
37
|
+
);
|
|
38
|
+
expect(wrapper.find('input[name="billingAddress.city"]').exists()).toBe(
|
|
39
|
+
true,
|
|
40
|
+
);
|
|
41
|
+
expect(
|
|
42
|
+
wrapper.find('input[name="billingAddress.phoneNumber"]').exists(),
|
|
43
|
+
).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("renders name fields when showNames is true", async () => {
|
|
47
|
+
const wrapper = await mountSuspended(Fields, {
|
|
48
|
+
props: {
|
|
49
|
+
...defaultProps,
|
|
50
|
+
showNames: true,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(
|
|
55
|
+
wrapper.find('input[name="billingAddress.firstName"]').exists(),
|
|
56
|
+
).toBe(true);
|
|
57
|
+
expect(wrapper.find('input[name="billingAddress.lastName"]').exists()).toBe(
|
|
58
|
+
true,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("does not render name fields when showNames is false", async () => {
|
|
63
|
+
const wrapper = await mountSuspended(Fields, {
|
|
64
|
+
props: {
|
|
65
|
+
...defaultProps,
|
|
66
|
+
showNames: false,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
wrapper.find('input[name="billingAddress.firstName"]').exists(),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
expect(wrapper.find('input[name="billingAddress.lastName"]').exists()).toBe(
|
|
74
|
+
false,
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders business fields when accountType is business", async () => {
|
|
79
|
+
const wrapper = await mountSuspended(Fields, {
|
|
80
|
+
props: {
|
|
81
|
+
...defaultProps,
|
|
82
|
+
accountType: "business",
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
expect(wrapper.find('input[name="billingAddress.company"]').exists()).toBe(
|
|
87
|
+
true,
|
|
88
|
+
);
|
|
89
|
+
expect(
|
|
90
|
+
wrapper.find('input[name="billingAddress.department"]').exists(),
|
|
91
|
+
).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("updates modelValue when input changes", async () => {
|
|
95
|
+
const modelValue = reactive({
|
|
96
|
+
firstName: "",
|
|
97
|
+
lastName: "",
|
|
98
|
+
street: "",
|
|
99
|
+
zipcode: "",
|
|
100
|
+
city: "",
|
|
101
|
+
phoneNumber: "",
|
|
102
|
+
});
|
|
103
|
+
const wrapper = await mountSuspended(Fields, {
|
|
104
|
+
props: {
|
|
105
|
+
...defaultProps,
|
|
106
|
+
modelValue,
|
|
107
|
+
"onUpdate:modelValue": (val: any) => Object.assign(modelValue, val),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
await wrapper
|
|
112
|
+
.find('input[name="billingAddress.street"]')
|
|
113
|
+
.setValue("New Street");
|
|
114
|
+
expect(modelValue.street).toBe("New Street");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("shows warning when an invalid city is selected for shipping address", async () => {
|
|
118
|
+
const modelValue = reactive({
|
|
119
|
+
firstName: "John",
|
|
120
|
+
lastName: "Doe",
|
|
121
|
+
street: "Main St 1",
|
|
122
|
+
zipcode: "12345",
|
|
123
|
+
city: "InvalidCity",
|
|
124
|
+
phoneNumber: "12345678",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const wrapper = await mountSuspended(Fields, {
|
|
128
|
+
props: {
|
|
129
|
+
...defaultProps,
|
|
130
|
+
modelValue,
|
|
131
|
+
isShipping: true,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(wrapper.text()).toContain(
|
|
136
|
+
"An diese Adresse können wir leider nicht liefern.",
|
|
137
|
+
);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("does not show warning for valid city in shipping address", async () => {
|
|
141
|
+
const modelValue = reactive({
|
|
142
|
+
firstName: "John",
|
|
143
|
+
lastName: "Doe",
|
|
144
|
+
street: "Main St 1",
|
|
145
|
+
zipcode: "63179",
|
|
146
|
+
city: "Obertshausen",
|
|
147
|
+
phoneNumber: "12345678",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const wrapper = await mountSuspended(Fields, {
|
|
151
|
+
props: {
|
|
152
|
+
...defaultProps,
|
|
153
|
+
modelValue,
|
|
154
|
+
isShipping: true,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
expect(wrapper.text()).not.toContain(
|
|
159
|
+
"An diese Adresse können wir leider nicht liefern.",
|
|
160
|
+
);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("does not show warning for billing address even if city is 'invalid'", async () => {
|
|
164
|
+
const modelValue = reactive({
|
|
165
|
+
firstName: "John",
|
|
166
|
+
lastName: "Doe",
|
|
167
|
+
street: "Main St 1",
|
|
168
|
+
zipcode: "12345",
|
|
169
|
+
city: "InvalidCity",
|
|
170
|
+
phoneNumber: "12345678",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const wrapper = await mountSuspended(Fields, {
|
|
174
|
+
props: {
|
|
175
|
+
...defaultProps,
|
|
176
|
+
modelValue,
|
|
177
|
+
isShipping: false,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(wrapper.text()).not.toContain(
|
|
182
|
+
"An diese Adresse können wir leider nicht liefern.",
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("shows correction automatically on input after debounce", async () => {
|
|
187
|
+
mockGetSuggestions.mockResolvedValue([
|
|
188
|
+
{
|
|
189
|
+
street: "Corrected Street 123",
|
|
190
|
+
city: "Corrected City",
|
|
191
|
+
zipcode: "54321",
|
|
192
|
+
label: "Corrected Street 123, 54321 Corrected City",
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
const modelValue = reactive({
|
|
197
|
+
firstName: "John",
|
|
198
|
+
lastName: "Doe",
|
|
199
|
+
street: "Musterstr 1",
|
|
200
|
+
zipcode: "12345",
|
|
201
|
+
city: "Musterstadt",
|
|
202
|
+
phoneNumber: "12345678",
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const wrapper = await mountSuspended(Fields, {
|
|
206
|
+
props: {
|
|
207
|
+
...defaultProps,
|
|
208
|
+
modelValue,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Change input to trigger watch
|
|
213
|
+
await wrapper
|
|
214
|
+
.find('input[name="billingAddress.street"]')
|
|
215
|
+
.setValue("Musterstr 2");
|
|
216
|
+
|
|
217
|
+
// Wait for debounce and async check
|
|
218
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
219
|
+
|
|
220
|
+
expect(wrapper.text()).toContain(
|
|
221
|
+
"Meinten Sie: Corrected Street 123, 54321 Corrected City?",
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("shows correction when checkAddress is called", async () => {
|
|
226
|
+
const modelValue = reactive({
|
|
227
|
+
firstName: "John",
|
|
228
|
+
lastName: "Doe",
|
|
229
|
+
street: "Musterstr 1",
|
|
230
|
+
zipcode: "12345",
|
|
231
|
+
city: "Musterstadt",
|
|
232
|
+
phoneNumber: "12345678",
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const wrapper = await mountSuspended(Fields, {
|
|
236
|
+
props: {
|
|
237
|
+
...defaultProps,
|
|
238
|
+
modelValue,
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Mock getSuggestions to return a correction
|
|
243
|
+
// Note: In a real test we might need to mock useAddressAutocomplete
|
|
244
|
+
// But let's see if we can just call the exposed method
|
|
245
|
+
|
|
246
|
+
// For this to work reliably in test, we might need to mock useAddressAutocomplete
|
|
247
|
+
// since it's used inside checkAddress
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("shows correction automatically on input after debounce even for short street names", async () => {
|
|
251
|
+
mockGetSuggestions.mockResolvedValueOnce([
|
|
252
|
+
{
|
|
253
|
+
street: "B 1",
|
|
254
|
+
city: "Berlin",
|
|
255
|
+
zipcode: "10115",
|
|
256
|
+
label: "B 1, 10115 Berlin",
|
|
257
|
+
},
|
|
258
|
+
]);
|
|
259
|
+
|
|
260
|
+
const modelValue = reactive({
|
|
261
|
+
firstName: "John",
|
|
262
|
+
lastName: "Doe",
|
|
263
|
+
street: "",
|
|
264
|
+
zipcode: "10115",
|
|
265
|
+
city: "Berlin",
|
|
266
|
+
phoneNumber: "12345678",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const wrapper = await mountSuspended(Fields, {
|
|
270
|
+
props: {
|
|
271
|
+
...defaultProps,
|
|
272
|
+
modelValue,
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Change input to trigger watch
|
|
277
|
+
await wrapper.find('input[name="billingAddress.street"]').setValue("B1");
|
|
278
|
+
|
|
279
|
+
// Wait for debounce and async check
|
|
280
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
281
|
+
|
|
282
|
+
expect(wrapper.text()).toContain("Meinten Sie: B 1, 10115 Berlin?");
|
|
283
|
+
});
|
|
284
|
+
});
|