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