this repo has no description
1import { beforeEach, describe, expect, it } from "vitest";
2import { fireEvent, render, screen, waitFor } from "@testing-library/svelte";
3import Login from "../routes/Login.svelte";
4import {
5 clearMocks,
6 errorResponse,
7 jsonResponse,
8 mockData,
9 mockEndpoint,
10 setupFetchMock,
11} from "./mocks";
12describe("Login", () => {
13 beforeEach(() => {
14 clearMocks();
15 setupFetchMock();
16 window.location.hash = "";
17 });
18 describe("initial render", () => {
19 it("renders login form with all elements and correct initial state", () => {
20 render(Login);
21 expect(screen.getByRole("heading", { name: /sign in/i }))
22 .toBeInTheDocument();
23 expect(screen.getByLabelText(/handle or email/i)).toBeInTheDocument();
24 expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
25 expect(screen.getByRole("button", { name: /sign in/i }))
26 .toBeInTheDocument();
27 expect(screen.getByRole("button", { name: /sign in/i })).toBeDisabled();
28 expect(screen.getByText(/don't have an account/i)).toBeInTheDocument();
29 expect(screen.getByRole("link", { name: /create one/i })).toHaveAttribute(
30 "href",
31 "#/register",
32 );
33 });
34 });
35 describe("form validation", () => {
36 it("enables submit button only when both fields are filled", async () => {
37 render(Login);
38 const identifierInput = screen.getByLabelText(/handle or email/i);
39 const passwordInput = screen.getByLabelText(/password/i);
40 const submitButton = screen.getByRole("button", { name: /sign in/i });
41 await fireEvent.input(identifierInput, { target: { value: "testuser" } });
42 expect(submitButton).toBeDisabled();
43 await fireEvent.input(identifierInput, { target: { value: "" } });
44 await fireEvent.input(passwordInput, {
45 target: { value: "password123" },
46 });
47 expect(submitButton).toBeDisabled();
48 await fireEvent.input(identifierInput, { target: { value: "testuser" } });
49 expect(submitButton).not.toBeDisabled();
50 });
51 });
52 describe("login submission", () => {
53 it("calls createSession with correct credentials", async () => {
54 let capturedBody: Record<string, string> | null = null;
55 mockEndpoint("com.atproto.server.createSession", (_url, options) => {
56 capturedBody = JSON.parse((options?.body as string) || "{}");
57 return jsonResponse(mockData.session());
58 });
59 render(Login);
60 await fireEvent.input(screen.getByLabelText(/handle or email/i), {
61 target: { value: "testuser@example.com" },
62 });
63 await fireEvent.input(screen.getByLabelText(/password/i), {
64 target: { value: "mypassword" },
65 });
66 await fireEvent.click(screen.getByRole("button", { name: /sign in/i }));
67 await waitFor(() => {
68 expect(capturedBody).toEqual({
69 identifier: "testuser@example.com",
70 password: "mypassword",
71 });
72 });
73 });
74 it("shows styled error message on invalid credentials", async () => {
75 mockEndpoint(
76 "com.atproto.server.createSession",
77 () =>
78 errorResponse(
79 "AuthenticationRequired",
80 "Invalid identifier or password",
81 401,
82 ),
83 );
84 render(Login);
85 await fireEvent.input(screen.getByLabelText(/handle or email/i), {
86 target: { value: "wronguser" },
87 });
88 await fireEvent.input(screen.getByLabelText(/password/i), {
89 target: { value: "wrongpassword" },
90 });
91 await fireEvent.click(screen.getByRole("button", { name: /sign in/i }));
92 await waitFor(() => {
93 const errorDiv = screen.getByText(/invalid identifier or password/i);
94 expect(errorDiv).toBeInTheDocument();
95 expect(errorDiv).toHaveClass("error");
96 });
97 });
98 it("navigates to dashboard on successful login", async () => {
99 mockEndpoint(
100 "com.atproto.server.createSession",
101 () => jsonResponse(mockData.session()),
102 );
103 render(Login);
104 await fireEvent.input(screen.getByLabelText(/handle or email/i), {
105 target: { value: "test" },
106 });
107 await fireEvent.input(screen.getByLabelText(/password/i), {
108 target: { value: "password" },
109 });
110 await fireEvent.click(screen.getByRole("button", { name: /sign in/i }));
111 await waitFor(() => {
112 expect(window.location.hash).toBe("#/dashboard");
113 });
114 });
115 });
116 describe("account verification flow", () => {
117 it("shows verification form with all controls when account is not verified", async () => {
118 mockEndpoint("com.atproto.server.createSession", () => ({
119 ok: false,
120 status: 401,
121 json: async () => ({
122 error: "AccountNotVerified",
123 message: "Account not verified",
124 did: "did:web:test.tranquil.dev:u:testuser",
125 }),
126 }));
127 render(Login);
128 await fireEvent.input(screen.getByLabelText(/handle or email/i), {
129 target: { value: "unverified@test.com" },
130 });
131 await fireEvent.input(screen.getByLabelText(/password/i), {
132 target: { value: "password" },
133 });
134 await fireEvent.click(screen.getByRole("button", { name: /sign in/i }));
135 await waitFor(() => {
136 expect(screen.getByRole("heading", { name: /verify your account/i }))
137 .toBeInTheDocument();
138 expect(screen.getByLabelText(/verification code/i)).toBeInTheDocument();
139 expect(screen.getByRole("button", { name: /resend code/i }))
140 .toBeInTheDocument();
141 expect(screen.getByRole("button", { name: /back to login/i }))
142 .toBeInTheDocument();
143 });
144 });
145 it("returns to login form when clicking back", async () => {
146 mockEndpoint("com.atproto.server.createSession", () => ({
147 ok: false,
148 status: 401,
149 json: async () => ({
150 error: "AccountNotVerified",
151 message: "Account not verified",
152 did: "did:web:test.tranquil.dev:u:testuser",
153 }),
154 }));
155 render(Login);
156 await fireEvent.input(screen.getByLabelText(/handle or email/i), {
157 target: { value: "test" },
158 });
159 await fireEvent.input(screen.getByLabelText(/password/i), {
160 target: { value: "password" },
161 });
162 await fireEvent.click(screen.getByRole("button", { name: /sign in/i }));
163 await waitFor(() => {
164 expect(screen.getByRole("button", { name: /back to login/i }))
165 .toBeInTheDocument();
166 });
167 await fireEvent.click(
168 screen.getByRole("button", { name: /back to login/i }),
169 );
170 await waitFor(() => {
171 expect(screen.getByRole("heading", { name: /sign in/i }))
172 .toBeInTheDocument();
173 expect(screen.queryByLabelText(/verification code/i)).not
174 .toBeInTheDocument();
175 });
176 });
177 });
178});