@cosmicdrift/kumiko-bundled-features 0.207.0 → 0.208.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/package.json +9 -8
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +1 -1
- package/src/admin-shell/i18n.ts +17 -39
- package/src/admin-shell/web/i18n.ts +6 -11
- package/src/audit/i18n.ts +25 -41
- package/src/audit/web/i18n.ts +5 -9
- package/src/auth-email-password/__tests__/email-templates.test.ts +4 -0
- package/src/auth-email-password/email-templates.ts +97 -149
- package/src/auth-email-password/i18n.ts +6 -265
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +7 -7
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +14 -14
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +23 -25
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +60 -68
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +24 -24
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +20 -20
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +9 -9
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +11 -11
- package/src/auth-email-password/web/__tests__/test-utils.tsx +2 -2
- package/src/auth-email-password/web/__tests__/user-menu.test.tsx +3 -3
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +3 -3
- package/src/auth-email-password/web/invite-accept-screen.tsx +3 -1
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +2 -4
- package/src/auth-mfa/i18n.ts +1 -3
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +15 -15
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +45 -49
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +10 -10
- package/src/auth-mfa/web/i18n.ts +0 -126
- package/src/cap-counter/i18n.ts +5 -11
- package/src/compliance-profiles/i18n.ts +3 -11
- package/src/compliance-profiles/web/i18n.ts +0 -20
- package/src/config/i18n.ts +5 -5
- package/src/config/web/i18n.ts +1 -19
- package/src/custom-fields/web/i18n.ts +1 -18
- package/src/delivery/i18n.ts +3 -7
- package/src/delivery/web/i18n.ts +0 -20
- package/src/feature-toggles/i18n.ts +3 -11
- package/src/feature-toggles/web/i18n.ts +0 -24
- package/src/folders/web/i18n.ts +1 -51
- package/src/jobs/i18n.ts +40 -68
- package/src/jobs/web/i18n.ts +5 -9
- package/src/managed-pages/i18n.ts +23 -67
- package/src/managed-pages/web/i18n.ts +6 -11
- package/src/notes-history/i18n.ts +1 -1
- package/src/notes-history/web/i18n.ts +1 -17
- package/src/personal-access-tokens/i18n.ts +2 -6
- package/src/personal-access-tokens/web/i18n.ts +0 -72
- package/src/sessions/i18n.ts +17 -21
- package/src/tags/i18n.ts +2 -2
- package/src/tags/web/i18n.ts +1 -52
- package/src/template-resolver/web/__tests__/client-plugin.test.tsx +2 -2
- package/src/template-resolver/web/__tests__/editor-read-only.test.tsx +5 -5
- package/src/template-resolver/web/i18n.ts +0 -28
- package/src/tenant/__tests__/members-screens.boot.test.ts +0 -2
- package/src/tenant/i18n.ts +13 -27
- package/src/tenant/web/i18n.ts +0 -38
- package/src/tier-engine/i18n.ts +3 -39
- package/src/user/i18n.ts +14 -32
- package/src/user-data-rights/__tests__/default-mailers.test.ts +4 -0
- package/src/user-data-rights/__tests__/email-templates.test.ts +4 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +4 -0
- package/src/user-data-rights/email-templates.ts +55 -91
- package/src/user-data-rights/i18n.ts +18 -70
- package/src/user-data-rights/lib/default-mailers.ts +4 -1
- package/src/user-data-rights/web/__tests__/deletion-screens.test.tsx +11 -11
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +5 -5
- package/src/user-data-rights/web/i18n.ts +9 -157
- package/src/user-profile/i18n.ts +1 -71
|
@@ -19,23 +19,23 @@ describe("LoginScreen", () => {
|
|
|
19
19
|
|
|
20
20
|
test("renders translated title + email + password labels (de)", () => {
|
|
21
21
|
renderWithProviders(<LoginScreen />);
|
|
22
|
-
expect(screen.
|
|
23
|
-
expect(screen.getByLabelText(/^
|
|
24
|
-
expect(screen.getByLabelText(/^
|
|
25
|
-
expect(screen.getByRole("button", { name: "
|
|
22
|
+
expect(screen.getByRole("heading", { name: "Sign in" })).toBeTruthy();
|
|
23
|
+
expect(screen.getByLabelText(/^Email/)).toBeTruthy();
|
|
24
|
+
expect(screen.getByLabelText(/^Password/)).toBeTruthy();
|
|
25
|
+
expect(screen.getByRole("button", { name: "Sign in" })).toBeTruthy();
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test("submit calls session.login with form values", async () => {
|
|
29
29
|
const session = makeSessionApi({ status: "unauthenticated", user: null });
|
|
30
30
|
renderWithProviders(<LoginScreen />, { session });
|
|
31
31
|
|
|
32
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
32
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
33
33
|
target: { value: "demo@example.com" },
|
|
34
34
|
});
|
|
35
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
35
|
+
fireEvent.change(screen.getByLabelText(/^Password/), {
|
|
36
36
|
target: { value: "secret" },
|
|
37
37
|
});
|
|
38
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
38
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
39
39
|
|
|
40
40
|
await waitFor(() => {
|
|
41
41
|
expect(session.login).toHaveBeenCalledWith({
|
|
@@ -56,17 +56,17 @@ describe("LoginScreen", () => {
|
|
|
56
56
|
});
|
|
57
57
|
renderWithProviders(<LoginScreen />, { session });
|
|
58
58
|
|
|
59
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
59
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
60
60
|
target: { value: "wrong@example.com" },
|
|
61
61
|
});
|
|
62
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
62
|
+
fireEvent.change(screen.getByLabelText(/^Password/), {
|
|
63
63
|
target: { value: "x" },
|
|
64
64
|
});
|
|
65
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
65
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
66
66
|
|
|
67
67
|
await waitFor(() => {
|
|
68
68
|
expect(screen.getByRole("alert")).toBeTruthy();
|
|
69
|
-
expect(screen.getByRole("alert").textContent).toBe("
|
|
69
|
+
expect(screen.getByRole("alert").textContent).toBe("Invalid email or password.");
|
|
70
70
|
});
|
|
71
71
|
});
|
|
72
72
|
|
|
@@ -81,30 +81,30 @@ describe("LoginScreen", () => {
|
|
|
81
81
|
});
|
|
82
82
|
renderWithProviders(<LoginScreen />, { session });
|
|
83
83
|
|
|
84
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
84
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
85
85
|
target: { value: "x@example.com" },
|
|
86
86
|
});
|
|
87
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
87
|
+
fireEvent.change(screen.getByLabelText(/^Password/), {
|
|
88
88
|
target: { value: "x" },
|
|
89
89
|
});
|
|
90
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
90
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
91
91
|
|
|
92
92
|
await waitFor(() => {
|
|
93
|
-
// 540s → 9
|
|
94
|
-
expect(screen.getByRole("alert").textContent).toMatch(/9
|
|
93
|
+
// 540s → 9 minutes (Math.ceil)
|
|
94
|
+
expect(screen.getByRole("alert").textContent).toMatch(/9 minutes/);
|
|
95
95
|
});
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
test("forgotPasswordHref-prop → Link rendert mit korrektem href", () => {
|
|
99
99
|
renderWithProviders(<LoginScreen forgotPasswordHref="/forgot-password" />);
|
|
100
|
-
const link = screen.getByRole("link", { name: /
|
|
100
|
+
const link = screen.getByRole("link", { name: /Forgot password/i });
|
|
101
101
|
expect(link).toBeTruthy();
|
|
102
102
|
expect(link.getAttribute("href")).toBe("/forgot-password");
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
test("ohne forgotPasswordHref → KEIN Link (Login bleibt minimalistisch)", () => {
|
|
106
106
|
renderWithProviders(<LoginScreen />);
|
|
107
|
-
expect(screen.queryByRole("link", { name: /
|
|
107
|
+
expect(screen.queryByRole("link", { name: /Forgot password/i })).toBeNull();
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
// Bug-Bash 2026-06-07 (Bug 1): Login-Screen ist oft die einzige
|
|
@@ -133,16 +133,16 @@ describe("LoginScreen", () => {
|
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
// Resend-Flow: bei email_not_verified bietet der LoginScreen einen
|
|
136
|
-
// "
|
|
136
|
+
// "Send verification email again"-Link im Fehler-Banner an.
|
|
137
137
|
describe("resend verification on email_not_verified", () => {
|
|
138
138
|
async function loginUntilEmailNotVerified(): Promise<void> {
|
|
139
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
139
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
140
140
|
target: { value: "demo@example.com" },
|
|
141
141
|
});
|
|
142
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
142
|
+
fireEvent.change(screen.getByLabelText(/^Password/), {
|
|
143
143
|
target: { value: "secret" },
|
|
144
144
|
});
|
|
145
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
145
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
146
146
|
await waitFor(() => {
|
|
147
147
|
expect(screen.getByRole("alert")).toBeTruthy();
|
|
148
148
|
});
|
|
@@ -164,7 +164,7 @@ describe("LoginScreen", () => {
|
|
|
164
164
|
// Vor Submit gibt es keinen Resend-Trigger
|
|
165
165
|
expect(screen.queryByRole("button", { name: /erneut senden/i })).toBeNull();
|
|
166
166
|
await loginUntilEmailNotVerified();
|
|
167
|
-
expect(screen.getByRole("button", { name: "
|
|
167
|
+
expect(screen.getByRole("button", { name: "Send verification email again" })).toBeTruthy();
|
|
168
168
|
});
|
|
169
169
|
|
|
170
170
|
test("click → calls requestEmailVerification with form email and shows success banner", async () => {
|
|
@@ -172,16 +172,14 @@ describe("LoginScreen", () => {
|
|
|
172
172
|
renderWithProviders(<LoginScreen />, { session: unverifiedSession() });
|
|
173
173
|
await loginUntilEmailNotVerified();
|
|
174
174
|
|
|
175
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
175
|
+
fireEvent.click(screen.getByRole("button", { name: "Send verification email again" }));
|
|
176
176
|
|
|
177
177
|
await waitFor(() => {
|
|
178
178
|
expect(requestEmailVerificationMock).toHaveBeenCalledWith("demo@example.com");
|
|
179
179
|
});
|
|
180
180
|
// Banner variant="info" setzt kein role — wir suchen per Text
|
|
181
181
|
await waitFor(() => {
|
|
182
|
-
expect(
|
|
183
|
-
screen.getByText("Wir haben dir eine neue Bestätigungs-Mail geschickt."),
|
|
184
|
-
).toBeTruthy();
|
|
182
|
+
expect(screen.getByText("We've sent you a new verification email.")).toBeTruthy();
|
|
185
183
|
});
|
|
186
184
|
// Fehler-Banner (role=alert) ist weg, Success-Banner ist da
|
|
187
185
|
expect(screen.queryByRole("alert")).toBeNull();
|
|
@@ -195,15 +193,13 @@ describe("LoginScreen", () => {
|
|
|
195
193
|
renderWithProviders(<LoginScreen />, { session: unverifiedSession() });
|
|
196
194
|
await loginUntilEmailNotVerified();
|
|
197
195
|
|
|
198
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
196
|
+
fireEvent.click(screen.getByRole("button", { name: "Send verification email again" }));
|
|
199
197
|
|
|
200
198
|
await waitFor(() => {
|
|
201
|
-
expect(screen.getByRole("alert").textContent).toMatch(
|
|
202
|
-
/Bitte warte kurz und versuche es erneut/,
|
|
203
|
-
);
|
|
199
|
+
expect(screen.getByRole("alert").textContent).toMatch(/Please wait a moment and try again/);
|
|
204
200
|
});
|
|
205
201
|
// Original-Fehler bleibt sichtbar
|
|
206
|
-
expect(screen.getByRole("alert").textContent).toMatch(/
|
|
202
|
+
expect(screen.getByRole("alert").textContent).toMatch(/Email address not yet verified/);
|
|
207
203
|
});
|
|
208
204
|
|
|
209
205
|
test("network/unknown error → generischer inline hint", async () => {
|
|
@@ -211,23 +207,21 @@ describe("LoginScreen", () => {
|
|
|
211
207
|
renderWithProviders(<LoginScreen />, { session: unverifiedSession() });
|
|
212
208
|
await loginUntilEmailNotVerified();
|
|
213
209
|
|
|
214
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
210
|
+
fireEvent.click(screen.getByRole("button", { name: "Send verification email again" }));
|
|
215
211
|
|
|
216
212
|
await waitFor(() => {
|
|
217
|
-
expect(screen.getByRole("alert").textContent).toMatch(
|
|
218
|
-
/Konnte nicht senden. Bitte erneut versuchen/,
|
|
219
|
-
);
|
|
213
|
+
expect(screen.getByRole("alert").textContent).toMatch(/Could not send. Please try again/);
|
|
220
214
|
});
|
|
221
215
|
});
|
|
222
216
|
|
|
223
217
|
test("Email-Änderung nach Failure → Resend-Button verschwindet (anti-typo-Falle)", async () => {
|
|
224
218
|
renderWithProviders(<LoginScreen />, { session: unverifiedSession() });
|
|
225
219
|
await loginUntilEmailNotVerified();
|
|
226
|
-
expect(screen.getByRole("button", { name: "
|
|
220
|
+
expect(screen.getByRole("button", { name: "Send verification email again" })).toBeTruthy();
|
|
227
221
|
|
|
228
222
|
// User korrigiert die Email-Eingabe — Resend-Button darf nicht mehr
|
|
229
223
|
// sichtbar sein, damit kein silent-send an typo-Adresse passiert
|
|
230
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
224
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
231
225
|
target: { value: "typo@example.com" },
|
|
232
226
|
});
|
|
233
227
|
expect(screen.queryByRole("button", { name: /erneut senden/i })).toBeNull();
|
|
@@ -258,24 +252,24 @@ describe("LoginScreen", () => {
|
|
|
258
252
|
})),
|
|
259
253
|
});
|
|
260
254
|
renderWithProviders(<LoginScreen unlockAccountHref="/unlock" />, { session });
|
|
261
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
262
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
263
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
255
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
256
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
257
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
264
258
|
await waitFor(() => {
|
|
265
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
259
|
+
expect(screen.getByRole("alert").textContent).toContain("temporarily locked");
|
|
266
260
|
});
|
|
267
|
-
expect(screen.getByRole("link", { name: /
|
|
261
|
+
expect(screen.getByRole("link", { name: /Unlock account/i }).getAttribute("href")).toBe(
|
|
268
262
|
"/unlock",
|
|
269
263
|
);
|
|
270
264
|
});
|
|
271
265
|
|
|
272
266
|
test("reasonToKey covers no_membership, rate_limited, invalid_body, default", async () => {
|
|
273
267
|
const cases: Array<{ reason: string; needle: string }> = [
|
|
274
|
-
{ reason: "no_membership", needle: "
|
|
275
|
-
{ reason: "rate_limited", needle: "
|
|
276
|
-
{ reason: "invalid_body", needle: "
|
|
277
|
-
{ reason: "weird_unknown", needle: "Login
|
|
278
|
-
{ reason: "mfa_setup_required", needle: "
|
|
268
|
+
{ reason: "no_membership", needle: "no tenant access" },
|
|
269
|
+
{ reason: "rate_limited", needle: "Too many login attempts" },
|
|
270
|
+
{ reason: "invalid_body", needle: "Invalid input" },
|
|
271
|
+
{ reason: "weird_unknown", needle: "Login failed" },
|
|
272
|
+
{ reason: "mfa_setup_required", needle: "Two-factor authentication required" },
|
|
279
273
|
];
|
|
280
274
|
for (const { reason, needle } of cases) {
|
|
281
275
|
const session = makeSessionApi({
|
|
@@ -287,9 +281,9 @@ describe("LoginScreen", () => {
|
|
|
287
281
|
})),
|
|
288
282
|
});
|
|
289
283
|
const { unmount } = renderWithProviders(<LoginScreen />, { session });
|
|
290
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
291
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
292
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
284
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
285
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
286
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
293
287
|
await waitFor(() => {
|
|
294
288
|
expect(screen.getByRole("alert").textContent).toContain(needle);
|
|
295
289
|
});
|
|
@@ -308,9 +302,9 @@ describe("LoginScreen", () => {
|
|
|
308
302
|
})),
|
|
309
303
|
});
|
|
310
304
|
renderWithProviders(<LoginScreen onMfaChallenge={onMfaChallenge} />, { session });
|
|
311
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
312
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
313
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
305
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
306
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
307
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
314
308
|
await waitFor(() => {
|
|
315
309
|
expect(onMfaChallenge).toHaveBeenCalledWith("chal-1");
|
|
316
310
|
});
|
|
@@ -327,11 +321,11 @@ describe("LoginScreen", () => {
|
|
|
327
321
|
})),
|
|
328
322
|
});
|
|
329
323
|
renderWithProviders(<LoginScreen />, { session });
|
|
330
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
331
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
332
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
324
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
325
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
326
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
333
327
|
await waitFor(() => {
|
|
334
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
328
|
+
expect(screen.getByRole("alert").textContent).toContain("doesn't support two-factor");
|
|
335
329
|
});
|
|
336
330
|
});
|
|
337
331
|
|
|
@@ -349,9 +343,9 @@ describe("LoginScreen", () => {
|
|
|
349
343
|
<LoginScreen onMfaSetupRequired={onMfaSetupRequired} />,
|
|
350
344
|
{ session: sessionOk },
|
|
351
345
|
);
|
|
352
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
353
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
354
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
346
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
347
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
348
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
355
349
|
await waitFor(() => {
|
|
356
350
|
expect(onMfaSetupRequired).toHaveBeenCalledWith("setup-token-value", "a@b.c");
|
|
357
351
|
});
|
|
@@ -366,19 +360,17 @@ describe("LoginScreen", () => {
|
|
|
366
360
|
})),
|
|
367
361
|
});
|
|
368
362
|
renderWithProviders(<LoginScreen />, { session: sessionBare });
|
|
369
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
370
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
371
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
363
|
+
fireEvent.change(screen.getByLabelText(/^Email/), { target: { value: "a@b.c" } });
|
|
364
|
+
fireEvent.change(screen.getByLabelText(/^Password/), { target: { value: "x" } });
|
|
365
|
+
fireEvent.click(screen.getByRole("button", { name: "Sign in" }));
|
|
372
366
|
await waitFor(() => {
|
|
373
|
-
expect(screen.getByRole("alert").textContent).toContain(
|
|
374
|
-
"Zwei-Faktor-Authentifizierung erforderlich",
|
|
375
|
-
);
|
|
367
|
+
expect(screen.getByRole("alert").textContent).toContain("Two-factor authentication required");
|
|
376
368
|
});
|
|
377
369
|
});
|
|
378
370
|
|
|
379
371
|
test("signupHref renders signup link", () => {
|
|
380
372
|
renderWithProviders(<LoginScreen signupHref="/signup" />);
|
|
381
|
-
const link = screen.getByRole("link", { name: "
|
|
373
|
+
const link = screen.getByRole("link", { name: "Create account" });
|
|
382
374
|
expect(link.getAttribute("href")).toBe("/signup");
|
|
383
375
|
});
|
|
384
376
|
});
|
|
@@ -9,9 +9,9 @@ installFetchMock();
|
|
|
9
9
|
describe("RequestAccountUnlockScreen", () => {
|
|
10
10
|
test("renders title + email input + submit (de)", () => {
|
|
11
11
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
12
|
-
expect(screen.getByText("
|
|
13
|
-
expect(screen.getByLabelText(/^
|
|
14
|
-
expect(screen.getByRole("button", { name: "
|
|
12
|
+
expect(screen.getByText("Unlock account")).toBeTruthy();
|
|
13
|
+
expect(screen.getByLabelText(/^Email/)).toBeTruthy();
|
|
14
|
+
expect(screen.getByRole("button", { name: "Request link" })).toBeTruthy();
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
test("submit posts /api/auth/request-account-unlock with email", async () => {
|
|
@@ -19,10 +19,10 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
19
19
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
20
20
|
|
|
21
21
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
22
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
22
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
23
23
|
target: { value: "user@example.com" },
|
|
24
24
|
});
|
|
25
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
25
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
26
26
|
|
|
27
27
|
await waitFor(() => {
|
|
28
28
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
@@ -39,15 +39,15 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
39
39
|
|
|
40
40
|
test("success → silent-success banner + back-to-login link", async () => {
|
|
41
41
|
renderWithProviders(<RequestAccountUnlockScreen loginHref="/signin" />);
|
|
42
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
42
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
43
43
|
target: { value: "user@example.com" },
|
|
44
44
|
});
|
|
45
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
45
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
46
46
|
|
|
47
47
|
await waitFor(() => {
|
|
48
|
-
expect(screen.getByText("
|
|
48
|
+
expect(screen.getByText("Email sent")).toBeTruthy();
|
|
49
49
|
});
|
|
50
|
-
expect(screen.getByRole("link", { name: /
|
|
50
|
+
expect(screen.getByRole("link", { name: /Back to sign in/i }).getAttribute("href")).toBe(
|
|
51
51
|
"/signin",
|
|
52
52
|
);
|
|
53
53
|
});
|
|
@@ -61,15 +61,15 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
61
61
|
) as unknown as typeof fetch;
|
|
62
62
|
|
|
63
63
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
64
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
64
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
65
65
|
target: { value: "user@example.com" },
|
|
66
66
|
});
|
|
67
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
67
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
68
68
|
|
|
69
69
|
await waitFor(() => {
|
|
70
|
-
expect(screen.getByRole("alert").textContent).toContain("3
|
|
70
|
+
expect(screen.getByRole("alert").textContent).toContain("3 minutes");
|
|
71
71
|
});
|
|
72
|
-
expect(screen.queryByText("
|
|
72
|
+
expect(screen.queryByText("Email sent")).toBeNull();
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test("429 without retryAfterSeconds → generic rateLimited banner", async () => {
|
|
@@ -78,13 +78,13 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
78
78
|
) as unknown as typeof fetch;
|
|
79
79
|
|
|
80
80
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
81
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
81
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
82
82
|
target: { value: "user@example.com" },
|
|
83
83
|
});
|
|
84
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
84
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
85
85
|
|
|
86
86
|
await waitFor(() => {
|
|
87
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
87
|
+
expect(screen.getByRole("alert").textContent).toContain("Too many login attempts");
|
|
88
88
|
});
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -94,16 +94,16 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
94
94
|
) as unknown as typeof fetch;
|
|
95
95
|
|
|
96
96
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
97
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
97
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
98
98
|
target: { value: "user@example.com" },
|
|
99
99
|
});
|
|
100
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
100
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
101
101
|
|
|
102
102
|
await waitFor(() => {
|
|
103
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
103
|
+
expect(screen.getByRole("alert").textContent).toContain("wrong");
|
|
104
104
|
});
|
|
105
|
-
expect(screen.queryByText("
|
|
106
|
-
expect(screen.getByLabelText(/^
|
|
105
|
+
expect(screen.queryByText("Email sent")).toBeNull();
|
|
106
|
+
expect(screen.getByLabelText(/^Email/)).toBeTruthy();
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
test("fetch throw → unknownError banner", async () => {
|
|
@@ -112,13 +112,13 @@ describe("RequestAccountUnlockScreen", () => {
|
|
|
112
112
|
}) as unknown as typeof fetch;
|
|
113
113
|
|
|
114
114
|
renderWithProviders(<RequestAccountUnlockScreen />);
|
|
115
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
115
|
+
fireEvent.change(screen.getByLabelText(/^Email/), {
|
|
116
116
|
target: { value: "user@example.com" },
|
|
117
117
|
});
|
|
118
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
118
|
+
fireEvent.click(screen.getByRole("button", { name: "Request link" }));
|
|
119
119
|
|
|
120
120
|
await waitFor(() => {
|
|
121
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
121
|
+
expect(screen.getByRole("alert").textContent).toContain("wrong");
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
124
|
|
|
@@ -10,14 +10,14 @@ describe("ResetPasswordScreen", () => {
|
|
|
10
10
|
test("ohne Token in URL UND ohne token-Prop → missing-token-Page", () => {
|
|
11
11
|
// jsdom default location is "about:blank" → search = ""
|
|
12
12
|
renderWithProviders(<ResetPasswordScreen />);
|
|
13
|
-
expect(screen.getByText(/
|
|
13
|
+
expect(screen.getByText(/missing a token/i)).toBeTruthy();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test("mit token-Prop → Form rendert", () => {
|
|
17
17
|
renderWithProviders(<ResetPasswordScreen token="abc-token" />);
|
|
18
|
-
expect(screen.getByLabelText(/^
|
|
19
|
-
expect(screen.getByLabelText(/^
|
|
20
|
-
expect(screen.getByRole("button", { name: "
|
|
18
|
+
expect(screen.getByLabelText(/^New password/)).toBeTruthy();
|
|
19
|
+
expect(screen.getByLabelText(/^Confirm password/)).toBeTruthy();
|
|
20
|
+
expect(screen.getByRole("button", { name: "Save password" })).toBeTruthy();
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
test("Passwort < 8 Zeichen → client-side error, kein fetch-Call", async () => {
|
|
@@ -25,28 +25,28 @@ describe("ResetPasswordScreen", () => {
|
|
|
25
25
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
26
26
|
|
|
27
27
|
renderWithProviders(<ResetPasswordScreen token="abc" />);
|
|
28
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
29
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
30
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
28
|
+
fireEvent.change(screen.getByLabelText(/^New password/), { target: { value: "short" } });
|
|
29
|
+
fireEvent.change(screen.getByLabelText(/^Confirm password/), { target: { value: "short" } });
|
|
30
|
+
fireEvent.click(screen.getByRole("button", { name: "Save password" }));
|
|
31
31
|
|
|
32
32
|
await waitFor(() => {
|
|
33
|
-
expect(screen.getByRole("alert").textContent).toContain("8
|
|
33
|
+
expect(screen.getByRole("alert").textContent).toContain("8 characters");
|
|
34
34
|
});
|
|
35
35
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test("mismatch zwischen Passwort und Confirm → client-side error", async () => {
|
|
39
39
|
renderWithProviders(<ResetPasswordScreen token="abc" />);
|
|
40
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
40
|
+
fireEvent.change(screen.getByLabelText(/^New password/), {
|
|
41
41
|
target: { value: "validpass1" },
|
|
42
42
|
});
|
|
43
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
43
|
+
fireEvent.change(screen.getByLabelText(/^Confirm password/), {
|
|
44
44
|
target: { value: "differentpass" },
|
|
45
45
|
});
|
|
46
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
46
|
+
fireEvent.click(screen.getByRole("button", { name: "Save password" }));
|
|
47
47
|
|
|
48
48
|
await waitFor(() => {
|
|
49
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
49
|
+
expect(screen.getByRole("alert").textContent).toContain("do not match");
|
|
50
50
|
});
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -55,13 +55,13 @@ describe("ResetPasswordScreen", () => {
|
|
|
55
55
|
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
|
56
56
|
|
|
57
57
|
renderWithProviders(<ResetPasswordScreen token="abc-token" />);
|
|
58
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
58
|
+
fireEvent.change(screen.getByLabelText(/^New password/), {
|
|
59
59
|
target: { value: "validpass1" },
|
|
60
60
|
});
|
|
61
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
61
|
+
fireEvent.change(screen.getByLabelText(/^Confirm password/), {
|
|
62
62
|
target: { value: "validpass1" },
|
|
63
63
|
});
|
|
64
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
64
|
+
fireEvent.click(screen.getByRole("button", { name: "Save password" }));
|
|
65
65
|
|
|
66
66
|
await waitFor(() => {
|
|
67
67
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
@@ -71,7 +71,7 @@ describe("ResetPasswordScreen", () => {
|
|
|
71
71
|
body: JSON.stringify({ token: "abc-token", newPassword: "validpass1" }),
|
|
72
72
|
}),
|
|
73
73
|
);
|
|
74
|
-
expect(screen.getByText("
|
|
74
|
+
expect(screen.getByText("Password set")).toBeTruthy();
|
|
75
75
|
});
|
|
76
76
|
});
|
|
77
77
|
|
|
@@ -84,16 +84,16 @@ describe("ResetPasswordScreen", () => {
|
|
|
84
84
|
) as unknown as typeof fetch;
|
|
85
85
|
|
|
86
86
|
renderWithProviders(<ResetPasswordScreen token="bad" />);
|
|
87
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
87
|
+
fireEvent.change(screen.getByLabelText(/^New password/), {
|
|
88
88
|
target: { value: "validpass1" },
|
|
89
89
|
});
|
|
90
|
-
fireEvent.change(screen.getByLabelText(/^
|
|
90
|
+
fireEvent.change(screen.getByLabelText(/^Confirm password/), {
|
|
91
91
|
target: { value: "validpass1" },
|
|
92
92
|
});
|
|
93
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
93
|
+
fireEvent.click(screen.getByRole("button", { name: "Save password" }));
|
|
94
94
|
|
|
95
95
|
await waitFor(() => {
|
|
96
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
96
|
+
expect(screen.getByRole("alert").textContent).toContain("invalid");
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
});
|
|
@@ -33,14 +33,14 @@ function fillPasswords(password: string, confirm: string): void {
|
|
|
33
33
|
describe("SignupCompleteScreen", () => {
|
|
34
34
|
test("ohne Token in URL UND ohne token-Prop → missing-token-Page", () => {
|
|
35
35
|
renderWithProviders(<SignupCompleteScreen />);
|
|
36
|
-
expect(screen.getByText(/
|
|
36
|
+
expect(screen.getByText(/missing a token/i)).toBeTruthy();
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
test("mit token-Prop → Form rendert", () => {
|
|
40
40
|
renderWithProviders(<SignupCompleteScreen token="abc-token" />);
|
|
41
41
|
expect(document.getElementById("signup-password")).toBeTruthy();
|
|
42
42
|
expect(document.getElementById("signup-confirm-password")).toBeTruthy();
|
|
43
|
-
expect(screen.getByRole("button", { name: "
|
|
43
|
+
expect(screen.getByRole("button", { name: "Activate account" })).toBeTruthy();
|
|
44
44
|
});
|
|
45
45
|
|
|
46
46
|
test("Passwort < 8 Zeichen → client-side error, kein fetch-Call", async () => {
|
|
@@ -49,10 +49,10 @@ describe("SignupCompleteScreen", () => {
|
|
|
49
49
|
|
|
50
50
|
renderWithProviders(<SignupCompleteScreen token="abc" />);
|
|
51
51
|
fillPasswords("short", "short");
|
|
52
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
52
|
+
fireEvent.click(screen.getByRole("button", { name: "Activate account" }));
|
|
53
53
|
|
|
54
54
|
await waitFor(() => {
|
|
55
|
-
expect(screen.getByRole("alert").textContent).toContain("8
|
|
55
|
+
expect(screen.getByRole("alert").textContent).toContain("8 characters");
|
|
56
56
|
});
|
|
57
57
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
58
58
|
});
|
|
@@ -84,7 +84,7 @@ describe("SignupCompleteScreen", () => {
|
|
|
84
84
|
/>,
|
|
85
85
|
);
|
|
86
86
|
fillPasswords("validpass1", "validpass1");
|
|
87
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
87
|
+
fireEvent.click(screen.getByRole("button", { name: "Activate account" }));
|
|
88
88
|
|
|
89
89
|
await waitFor(() => {
|
|
90
90
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
@@ -107,10 +107,10 @@ describe("SignupCompleteScreen", () => {
|
|
|
107
107
|
|
|
108
108
|
renderWithProviders(<SignupCompleteScreen token="abc" />);
|
|
109
109
|
fillPasswords("validpass1", "differentpass");
|
|
110
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
110
|
+
fireEvent.click(screen.getByRole("button", { name: "Activate account" }));
|
|
111
111
|
|
|
112
112
|
await waitFor(() => {
|
|
113
|
-
expect(screen.getByRole("alert").textContent).toContain("
|
|
113
|
+
expect(screen.getByRole("alert").textContent).toContain("do not match");
|
|
114
114
|
});
|
|
115
115
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
116
116
|
});
|
|
@@ -125,10 +125,10 @@ describe("SignupCompleteScreen", () => {
|
|
|
125
125
|
|
|
126
126
|
renderWithProviders(<SignupCompleteScreen token="bad" />);
|
|
127
127
|
fillPasswords("validpass1", "validpass1");
|
|
128
|
-
fireEvent.click(screen.getByRole("button", { name: "
|
|
128
|
+
fireEvent.click(screen.getByRole("button", { name: "Activate account" }));
|
|
129
129
|
|
|
130
130
|
await waitFor(() => {
|
|
131
|
-
expect(screen.getByRole("alert").textContent).toMatch(/
|
|
131
|
+
expect(screen.getByRole("alert").textContent).toMatch(/invalid|expired/i);
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
134
|
});
|