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