diff --git a/apps/login/src/components/change-password-form.test.tsx b/apps/login/src/components/change-password-form.test.tsx new file mode 100644 index 000000000..36271fd3a --- /dev/null +++ b/apps/login/src/components/change-password-form.test.tsx @@ -0,0 +1,45 @@ +import { cleanup, render } from "@testing-library/react"; +import { afterEach, describe, expect, test, vi } from "vitest"; +import { ChangePasswordForm } from "./change-password-form"; +import { create } from "@zitadel/client"; +import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb"; + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/password", () => ({ + checkSessionAndSetPassword: vi.fn(), + sendPassword: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), +})); + +const defaultComplexitySettings = create(PasswordComplexitySettingsSchema, { + minLength: 8n, + requiresUppercase: false, + requiresLowercase: false, + requiresNumber: false, + requiresSymbol: false, +}); + +describe("ChangePasswordForm", () => { + afterEach(cleanup); + + test("should autofocus the current password input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("password-change-current-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/change-password-form.tsx b/apps/login/src/components/change-password-form.tsx index c8d1aff2b..cdcec0142 100644 --- a/apps/login/src/components/change-password-form.tsx +++ b/apps/login/src/components/change-password-form.tsx @@ -129,6 +129,7 @@ export function ChangePasswordForm({ passwordComplexitySettings, sessionId, logi ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/oidc", () => ({ + getDeviceAuthorizationRequest: vi.fn(), +})); + +describe("DeviceCodeForm", () => { + afterEach(cleanup); + + test("should autofocus the code input on mount", () => { + const { getByTestId } = render(); + expect(getByTestId("code-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/device-code-form.tsx b/apps/login/src/components/device-code-form.tsx index 12a44fd59..1e64190b8 100644 --- a/apps/login/src/components/device-code-form.tsx +++ b/apps/login/src/components/device-code-form.tsx @@ -65,6 +65,7 @@ export function DeviceCodeForm({ userCode }: { userCode?: string }) { { expect(input?.maxLength).toBe(10); expect(input?.type).toBe("email"); }); + + it("should support autoFocus", () => { + const { container } = render(); + const input = container.querySelector("input"); + expect(input).toHaveFocus(); + }); }); describe("Styling", () => { diff --git a/apps/login/src/components/ldap-username-password-form.test.tsx b/apps/login/src/components/ldap-username-password-form.test.tsx new file mode 100644 index 000000000..0d2dce091 --- /dev/null +++ b/apps/login/src/components/ldap-username-password-form.test.tsx @@ -0,0 +1,26 @@ +import { cleanup, render } from "@testing-library/react"; +import { afterEach, describe, expect, test, vi } from "vitest"; +import { LDAPUsernamePasswordForm } from "./ldap-username-password-form"; + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/idp", () => ({ + createNewSessionForLDAP: vi.fn(), +})); + +describe("LDAPUsernamePasswordForm", () => { + afterEach(cleanup); + + test("should autofocus the username input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("username-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/ldap-username-password-form.tsx b/apps/login/src/components/ldap-username-password-form.tsx index 669c673f7..342ae4b63 100644 --- a/apps/login/src/components/ldap-username-password-form.tsx +++ b/apps/login/src/components/ldap-username-password-form.tsx @@ -68,6 +68,7 @@ export function LDAPUsernamePasswordForm({ idpId, link }: Props) { ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/session", () => ({ + updateOrCreateSession: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), + completeFlowOrGetUrl: vi.fn(), +})); + +describe("LoginOTP", () => { + afterEach(cleanup); + + test("should autofocus the code input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("code-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/login-otp.tsx b/apps/login/src/components/login-otp.tsx index 2bf7f49ee..4c4f0fa94 100644 --- a/apps/login/src/components/login-otp.tsx +++ b/apps/login/src/components/login-otp.tsx @@ -241,6 +241,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,
({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/password", () => ({ + sendPassword: vi.fn(), + resetPassword: vi.fn(), +})); + +describe("PasswordForm", () => { + afterEach(cleanup); + + test("should autofocus the password input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("password-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/password-form.tsx b/apps/login/src/components/password-form.tsx index 7f767461d..23f66e91a 100644 --- a/apps/login/src/components/password-form.tsx +++ b/apps/login/src/components/password-form.tsx @@ -116,6 +116,7 @@ export function PasswordForm({ loginSettings, loginName, organization, defaultOr ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/register", () => ({ + registerUserAndLinkToIDP: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), +})); + +const defaultProps = { + organization: "org-1", + idpIntent: { idpIntentId: "intent-1", idpIntentToken: "token-1" }, + idpUserId: "user-1", + idpId: "idp-1", +}; + +describe("RegisterFormIDPIncomplete", () => { + afterEach(cleanup); + + test("should autofocus the username input when idpUserName is not provided", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("username-text-input")).toHaveFocus(); + }); + + test("should autofocus the firstname input when idpUserName is provided", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("firstname-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/register-form-idp-incomplete.tsx b/apps/login/src/components/register-form-idp-incomplete.tsx index d405ec1a2..318869532 100644 --- a/apps/login/src/components/register-form-idp-incomplete.tsx +++ b/apps/login/src/components/register-form-idp-incomplete.tsx @@ -100,6 +100,7 @@ export function RegisterFormIDPIncomplete({ ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/register", () => ({ + registerUser: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), +})); + +const defaultLegal = create(LegalAndSupportSettingsSchema, {}); + +describe("RegisterForm", () => { + afterEach(cleanup); + + test("should autofocus the firstname input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("firstname-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/register-form.tsx b/apps/login/src/components/register-form.tsx index 7352d5cfd..cd02c6b9d 100644 --- a/apps/login/src/components/register-form.tsx +++ b/apps/login/src/components/register-form.tsx @@ -122,6 +122,7 @@ export function RegisterForm({ ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/password", () => ({ + changePassword: vi.fn(), + resetPassword: vi.fn(), + sendPassword: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), +})); + +const defaultComplexitySettings = create(PasswordComplexitySettingsSchema, { + minLength: 8n, + requiresUppercase: false, + requiresLowercase: false, + requiresNumber: false, + requiresSymbol: false, +}); + +describe("SetPasswordForm", () => { + afterEach(cleanup); + + test("should autofocus the code input when codeRequired is true", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("code-text-input")).toHaveFocus(); + }); + + test("should autofocus the password input when codeRequired is false", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("password-set-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/set-password-form.tsx b/apps/login/src/components/set-password-form.tsx index 84466d4c0..25742106b 100644 --- a/apps/login/src/components/set-password-form.tsx +++ b/apps/login/src/components/set-password-form.tsx @@ -201,6 +201,7 @@ export function SetPasswordForm({
({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/register", () => ({ + registerUser: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), +})); + +const defaultComplexitySettings = create(PasswordComplexitySettingsSchema, { + minLength: 8n, + requiresUppercase: false, + requiresLowercase: false, + requiresNumber: false, + requiresSymbol: false, +}); + +describe("SetRegisterPasswordForm", () => { + afterEach(cleanup); + + test("should autofocus the password input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("password-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/set-register-password-form.tsx b/apps/login/src/components/set-register-password-form.tsx index 56ddd443c..debcc4f2d 100644 --- a/apps/login/src/components/set-register-password-form.tsx +++ b/apps/login/src/components/set-register-password-form.tsx @@ -106,6 +106,7 @@ export function SetRegisterPasswordForm({ ({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/verify", () => ({ + verifyTOTP: vi.fn(), +})); + +vi.mock("@/lib/client", () => ({ + handleServerActionResponse: vi.fn(), + completeFlowOrGetUrl: vi.fn(), +})); + +vi.mock("qrcode.react", () => ({ + QRCodeSVG: ({ value }: { value: string }) => , +})); + +describe("TotpRegister", () => { + afterEach(cleanup); + + test("should autofocus the code input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("code-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/totp-register.tsx b/apps/login/src/components/totp-register.tsx index 8a6424689..3a268ec6b 100644 --- a/apps/login/src/components/totp-register.tsx +++ b/apps/login/src/components/totp-register.tsx @@ -116,6 +116,7 @@ export function TotpRegister({ uri, loginName, sessionId, requestId, organizatio
({ + useRouter: () => ({ push: vi.fn() }), +})); + +vi.mock("next-intl", () => ({ + useTranslations: () => (key: string) => key, +})); + +vi.mock("@/lib/server/loginname", () => ({ + sendLoginname: vi.fn(), +})); + +describe("UsernameForm", () => { + afterEach(cleanup); + + test("should autofocus the loginName input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("username-text-input")).toHaveFocus(); + }); +}); diff --git a/apps/login/src/components/username-form.tsx b/apps/login/src/components/username-form.tsx index c98131c54..08ed561ec 100644 --- a/apps/login/src/components/username-form.tsx +++ b/apps/login/src/components/username-form.tsx @@ -101,6 +101,7 @@ export function UsernameForm({ { afterEach(cleanup); + describe("Input Focus", () => { + test("should autofocus the code input on mount", () => { + const { getByTestId } = render( + , + ); + expect(getByTestId("code-text-input")).toHaveFocus(); + }); + }); + describe("Auto-submit Behavior", () => { test("should call sendVerification automatically when submit=true", async () => { render(); diff --git a/apps/login/src/components/verify-form.tsx b/apps/login/src/components/verify-form.tsx index 5d303a8c4..3fd53cb07 100644 --- a/apps/login/src/components/verify-form.tsx +++ b/apps/login/src/components/verify-form.tsx @@ -138,6 +138,7 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i