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