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 jsonResponse,
7 mockData,
8 mockEndpoint,
9 setupFetchMock,
10} from "./mocks.ts";
11import { _testSetState, type SavedAccount } from "../lib/auth.svelte.ts";
12import {
13 unsafeAsAccessToken,
14 unsafeAsDid,
15 unsafeAsHandle,
16 unsafeAsRefreshToken,
17} from "../lib/types/branded.ts";
18
19describe("Login", () => {
20 beforeEach(() => {
21 clearMocks();
22 setupFetchMock();
23 mockEndpoint(
24 "/oauth/par",
25 () => jsonResponse({ request_uri: "urn:mock:request" }),
26 );
27 });
28
29 describe("initial render with no saved accounts", () => {
30 beforeEach(() => {
31 _testSetState({
32 session: null,
33 loading: false,
34 error: null,
35 savedAccounts: [],
36 });
37 });
38
39 it("renders login page with title and OAuth button", async () => {
40 render(Login);
41 await waitFor(() => {
42 expect(screen.getByRole("heading", { name: /sign in/i }))
43 .toBeInTheDocument();
44 expect(screen.getByRole("button", { name: /sign in/i }))
45 .toBeInTheDocument();
46 });
47 });
48
49 it("shows create account link", async () => {
50 render(Login);
51 await waitFor(() => {
52 expect(screen.getByText(/don't have an account/i)).toBeInTheDocument();
53 expect(screen.getByRole("link", { name: /create/i })).toHaveAttribute(
54 "href",
55 "/app/register",
56 );
57 });
58 });
59
60 it("shows forgot password and lost passkey links", async () => {
61 render(Login);
62 await waitFor(() => {
63 expect(screen.getByRole("link", { name: /forgot password/i }))
64 .toHaveAttribute("href", "/app/reset-password");
65 expect(screen.getByRole("link", { name: /lost passkey/i }))
66 .toHaveAttribute("href", "/app/request-passkey-recovery");
67 });
68 });
69 });
70
71 describe("with saved accounts", () => {
72 const savedAccounts: SavedAccount[] = [
73 {
74 did: unsafeAsDid("did:web:test.tranquil.dev:u:alice"),
75 handle: unsafeAsHandle("alice.test.tranquil.dev"),
76 accessJwt: unsafeAsAccessToken("mock-jwt-alice"),
77 refreshJwt: unsafeAsRefreshToken("mock-refresh-alice"),
78 },
79 {
80 did: unsafeAsDid("did:web:test.tranquil.dev:u:bob"),
81 handle: unsafeAsHandle("bob.test.tranquil.dev"),
82 accessJwt: unsafeAsAccessToken("mock-jwt-bob"),
83 refreshJwt: unsafeAsRefreshToken("mock-refresh-bob"),
84 },
85 ];
86
87 beforeEach(() => {
88 _testSetState({
89 session: null,
90 loading: false,
91 error: null,
92 savedAccounts,
93 });
94 mockEndpoint(
95 "com.atproto.server.getSession",
96 () =>
97 jsonResponse(
98 mockData.session({
99 handle: unsafeAsHandle("alice.test.tranquil.dev"),
100 }),
101 ),
102 );
103 });
104
105 it("displays saved accounts list", async () => {
106 render(Login);
107 await waitFor(() => {
108 expect(screen.getByText(/@alice\.test\.tranquil\.dev/))
109 .toBeInTheDocument();
110 expect(screen.getByText(/@bob\.test\.tranquil\.dev/))
111 .toBeInTheDocument();
112 });
113 });
114
115 it("shows sign in to another account option", async () => {
116 render(Login);
117 await waitFor(() => {
118 expect(screen.getByText(/sign in to another/i)).toBeInTheDocument();
119 });
120 });
121
122 it("can click on saved account to switch", async () => {
123 render(Login);
124 await waitFor(() => {
125 expect(screen.getByText(/@alice\.test\.tranquil\.dev/))
126 .toBeInTheDocument();
127 });
128 const aliceAccount = screen.getByText(/@alice\.test\.tranquil\.dev/)
129 .closest("[role='button']");
130 if (aliceAccount) {
131 await fireEvent.click(aliceAccount);
132 }
133 await waitFor(() => {
134 expect(globalThis.location.pathname).toBe("/app/dashboard");
135 });
136 });
137
138 it("can remove saved account with forget button", async () => {
139 render(Login);
140 await waitFor(() => {
141 expect(screen.getByText(/@alice\.test\.tranquil\.dev/))
142 .toBeInTheDocument();
143 const forgetButtons = screen.getAllByTitle(/remove/i);
144 expect(forgetButtons.length).toBe(2);
145 });
146 });
147 });
148
149 describe("error handling", () => {
150 it("displays error message when auth state has error", async () => {
151 _testSetState({
152 session: null,
153 loading: false,
154 error: "OAuth login failed",
155 savedAccounts: [],
156 });
157 render(Login);
158 await waitFor(() => {
159 expect(screen.getByText(/oauth login failed/i)).toBeInTheDocument();
160 expect(screen.getByText(/oauth login failed/i)).toHaveClass("error");
161 });
162 });
163 });
164
165 describe("verification flow", () => {
166 beforeEach(() => {
167 _testSetState({
168 session: null,
169 loading: false,
170 error: null,
171 savedAccounts: [],
172 });
173 });
174
175 it("shows verification form when pending verification exists", () => {
176 render(Login);
177 });
178 });
179
180 describe("loading state", () => {
181 it("shows loading state while auth is initializing", () => {
182 _testSetState({
183 session: null,
184 loading: true,
185 error: null,
186 savedAccounts: [],
187 });
188 render(Login);
189 });
190 });
191});