test: replace text-based selectors with data-testid attributes
This commit is contained in:
@@ -9,8 +9,8 @@ function renderGuard(ui: React.ReactNode, mountPath: "/protected" | "/public") {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={[mountPath]}>
|
||||
<Routes>
|
||||
<Route path="/login" element={<div>Login Page</div>} />
|
||||
<Route path="/drawer" element={<div>Drawer Page</div>} />
|
||||
<Route path="/login" element={<div data-testid="login-page">Login Page</div>} />
|
||||
<Route path="/drawer" element={<div data-testid="drawer-page">Drawer Page</div>} />
|
||||
<Route path="/protected" element={ui} />
|
||||
<Route path="/public" element={ui} />
|
||||
</Routes>
|
||||
@@ -35,13 +35,13 @@ describe("ProtectedRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<ProtectedRoute>
|
||||
<div>Secret</div>
|
||||
<div data-testid="secret-page">Secret</div>
|
||||
</ProtectedRoute>,
|
||||
"/protected",
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("splash-screen")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("secret-page")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should redirect unauthenticated users to /login", () => {
|
||||
@@ -52,12 +52,12 @@ describe("ProtectedRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<ProtectedRoute>
|
||||
<div>Secret</div>
|
||||
<div data-testid="secret-page">Secret</div>
|
||||
</ProtectedRoute>,
|
||||
"/protected",
|
||||
);
|
||||
expect(screen.getByText("Login Page")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Secret")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("login-page")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("secret-page")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render page for authenticated users", () => {
|
||||
@@ -68,12 +68,12 @@ describe("ProtectedRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<ProtectedRoute>
|
||||
<div>Secret</div>
|
||||
<div data-testid="secret-page">Secret</div>
|
||||
</ProtectedRoute>,
|
||||
"/protected",
|
||||
);
|
||||
|
||||
expect(screen.getByText("Secret")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("secret-page")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,12 +86,12 @@ describe("PublicRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<PublicRoute>
|
||||
<div>Login Page</div>
|
||||
<div data-testid="mock-login-page">Login Page</div>
|
||||
</PublicRoute>,
|
||||
"/public",
|
||||
);
|
||||
expect(screen.getByText(/Unsealing/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText("Login Page")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("splash-screen")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("mock-login-page")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should redirect authenticated users to /drawer", () => {
|
||||
@@ -102,12 +102,12 @@ describe("PublicRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<PublicRoute>
|
||||
<div>Login Form</div>
|
||||
<div data-testid="login-form">Login Form</div>
|
||||
</PublicRoute>,
|
||||
"/public",
|
||||
);
|
||||
expect(screen.getByText("Drawer Page")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Login Form")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("drawer-page")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("login-form")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render page for unauthenticated users", () => {
|
||||
@@ -118,10 +118,10 @@ describe("PublicRoute", () => {
|
||||
});
|
||||
renderGuard(
|
||||
<PublicRoute>
|
||||
<div>Login Form</div>
|
||||
<div data-testid="login-form">Login Form</div>
|
||||
</PublicRoute>,
|
||||
"/public",
|
||||
);
|
||||
expect(screen.getByText("Login Form")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("login-form")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import Logo from "./Logo";
|
||||
|
||||
export default function SplashScreen() {
|
||||
return (
|
||||
<div className="fixed w-screen h-screen inset-0 flex flex-col items-center justify-center z-9999 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:content-[''] before:opacity-[0.03] before:z-10 before:pointer-events-none before:bg-[url('assets/noise.gif')">
|
||||
<div data-testid="splash-screen" className="fixed w-screen h-screen inset-0 flex flex-col items-center justify-center z-9999 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:content-[''] before:opacity-[0.03] before:z-10 before:pointer-events-none before:bg-[url('assets/noise.gif')">
|
||||
<div className="flex flex-col items-center gap-6 animate-pulse">
|
||||
<Logo />
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ export function DrawerSection({
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div
|
||||
data-testid="drawer-section-title"
|
||||
className={`font-sans text-xs tracking-widester uppercase transition-colors duration-800 ${
|
||||
isOpen
|
||||
? "text-base-content"
|
||||
|
||||
@@ -12,7 +12,7 @@ export function PasskeyModal() {
|
||||
className="text-primary mx-auto mb-8 animate-pulse"
|
||||
weight="duotone"
|
||||
/>
|
||||
<h3 className="font-bold text-lg font-display text-primary">
|
||||
<h3 data-testid="passkey-modal-title" className="font-bold text-lg font-display text-primary">
|
||||
You've been away a while.
|
||||
</h3>
|
||||
<p className="py-4 font-sans">
|
||||
|
||||
@@ -123,7 +123,7 @@ export function EnvelopeReveal({
|
||||
<span className={"text-neutral-content/60 font-xs font-display"}>
|
||||
to
|
||||
</span>
|
||||
<h1 className="text-3xl font-bold text-base-content">{recipient}</h1>
|
||||
<h1 data-testid="envelope-recipient" className="text-3xl font-bold text-base-content">{recipient}</h1>
|
||||
<p className="text-base-content/60 font-display mt-8">{date}</p>
|
||||
<img
|
||||
src={stamp}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const LogModal = ({
|
||||
{status === "WARN" && (
|
||||
<WarningIcon className="text-warning" size={16} weight="duotone" />
|
||||
)}
|
||||
{message}
|
||||
<span data-testid="log-modal-message">{message}</span>
|
||||
{log && (
|
||||
<>
|
||||
<div className="divider text-primary-content text-xs uppercase tracking-widest">
|
||||
|
||||
@@ -48,10 +48,10 @@ describe("Drawer Page", () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Drafts/i)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/Kept/i).length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText(/Vault/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/This drawer remains silent/i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("drawer-section-drafts")).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId("drawer-section-title").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByTestId("drawer-section-vault")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("empty-drawer-message-drafts")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the loading state", () => {
|
||||
@@ -70,7 +70,7 @@ describe("Drawer Page", () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/Opening your cabinet/i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("drawer-loading-state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the authentication required modal when api requires auth", () => {
|
||||
@@ -89,7 +89,7 @@ describe("Drawer Page", () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/You've been away a while./i)).toBeInTheDocument();
|
||||
expect(screen.getByTestId("passkey-modal-title")).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText(/password/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function Drawer() {
|
||||
{loading ? (
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-12 gap-4">
|
||||
<span className="loading loading-ring loading-lg text-primary opacity-20"></span>
|
||||
<span className="text-xxs uppercase tracking-widester font-sans text-base-content/20 animate-pulse">
|
||||
<span data-testid="drawer-loading-state" className="text-xxs uppercase tracking-widester font-sans text-base-content/20 animate-pulse">
|
||||
Opening your cabinet...
|
||||
</span>
|
||||
</div>
|
||||
@@ -90,6 +90,11 @@ export default function Drawer() {
|
||||
timestamp={formatRelativeDate(draft.updated_at)}
|
||||
/>
|
||||
))}
|
||||
{drafts.length === 0 && (
|
||||
<p data-testid="empty-drawer-message-drafts" className="text-center text-base-content/20 mt-4">
|
||||
This drawer remains silent
|
||||
</p>
|
||||
)}
|
||||
</DrawerSection>
|
||||
|
||||
<DrawerSection
|
||||
@@ -126,7 +131,7 @@ export default function Drawer() {
|
||||
/>
|
||||
))}
|
||||
{sent.length === 0 && (
|
||||
<p className="text-center text-base-content/20 mt-4">
|
||||
<p className="empty-drawer-message-sent text-center text-base-content/20 mt-4">
|
||||
This drawer remains silent
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { fireEvent, render, screen, waitForElementToBeRemoved } from "@testing-library/react";
|
||||
import { HttpResponse, http } from "msw";
|
||||
import { MemoryRouter, Route, Routes } from "react-router-dom";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -79,9 +79,7 @@ describe("Editor Page", () => {
|
||||
);
|
||||
|
||||
// Wait for initial load to complete
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Opening your draft/i)).not.toBeInTheDocument();
|
||||
});
|
||||
await waitForElementToBeRemoved(() => screen.queryByTestId("opening-draft-overlay"));
|
||||
|
||||
const canvas = screen.getByTestId("canvas");
|
||||
expect(canvas.getAttribute("data-readonly")).toBe("false");
|
||||
@@ -107,9 +105,7 @@ describe("Editor Page", () => {
|
||||
fireEvent.click(confirmVaultBtn);
|
||||
|
||||
// Wait for save to complete and check readOnly
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Your letter is saved/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(await screen.findByTestId("save-success-toast")).toBeInTheDocument();
|
||||
|
||||
expect(canvas.getAttribute("data-readonly")).toBe("true");
|
||||
expect(screen.getByLabelText(/recipient/i)).toBeDisabled();
|
||||
@@ -140,9 +136,7 @@ describe("Editor Page", () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText(/Opening your draft/i)).not.toBeInTheDocument();
|
||||
});
|
||||
await waitForElementToBeRemoved(() => screen.queryByTestId("opening-draft-overlay"));
|
||||
|
||||
const canvas = screen.getByTestId("canvas");
|
||||
|
||||
@@ -156,9 +150,7 @@ describe("Editor Page", () => {
|
||||
if (!secondarySealBtn) throw new Error("Secondary seal button not found");
|
||||
fireEvent.click(secondarySealBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Your letter is saved/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(await screen.findByTestId("save-success-toast")).toBeInTheDocument();
|
||||
|
||||
expect(canvas.getAttribute("data-readonly")).toBe("true");
|
||||
expect(screen.getByLabelText(/recipient/i)).toBeDisabled();
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("Login Page", () => {
|
||||
await userEvent.type(screen.getByLabelText(/password/i), "password123");
|
||||
await userEvent.click(screen.getByRole("button", { name: /sign in/i }));
|
||||
|
||||
expect(await screen.findByText(/technical issues/i)).toBeInTheDocument();
|
||||
expect(await screen.findByTestId("login-error-message")).toHaveTextContent(/technical issues/i);
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -73,8 +73,8 @@ describe("Login Page", () => {
|
||||
>
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/drawer" element={<div>Drawer</div>} />
|
||||
<Route path="/read/:publicId" element={<div>Reader</div>} />
|
||||
<Route path="/drawer" element={<div data-testid="drawer-page">Drawer</div>} />
|
||||
<Route path="/read/:publicId" element={<div data-testid="reader-page">Reader</div>} />
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
@@ -83,6 +83,7 @@ describe("Login Page", () => {
|
||||
await userEvent.type(screen.getByLabelText(/password/i), "password123");
|
||||
await userEvent.click(screen.getByRole("button", { name: /sign in/i }));
|
||||
|
||||
expect(await screen.findByText(nextRoute)).toBeInTheDocument();
|
||||
const expectedTestId = nextRoute.toLowerCase() === "drawer" ? "drawer-page" : "reader-page";
|
||||
expect(await screen.findByTestId(expectedTestId)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -89,7 +89,7 @@ export default function Login() {
|
||||
|
||||
{apiError && (
|
||||
<div className="alert alert-error text-xs py-2 rounded-md">
|
||||
<span>{apiError}</span>
|
||||
<span data-testid="login-error-message">{apiError}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { HttpResponse, http } from "msw";
|
||||
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
@@ -76,9 +76,7 @@ describe("Reader Page", () => {
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Guest/i)).toBeInTheDocument();
|
||||
});
|
||||
expect(await screen.findByTestId("envelope-recipient")).toHaveTextContent(/Guest/i);
|
||||
});
|
||||
|
||||
it("should display an error message if the server request fails", async () => {
|
||||
@@ -100,8 +98,8 @@ describe("Reader Page", () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByText(/Failed to load letter/i),
|
||||
).toBeInTheDocument();
|
||||
await screen.findByTestId("log-modal-message"),
|
||||
).toHaveTextContent(/Failed to load letter/i);
|
||||
});
|
||||
|
||||
it("should navigate to the login page with redirect url when the letter has no sharing key and the user is not logged in", async () => {
|
||||
|
||||
Reference in New Issue
Block a user