Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion e2e/dashboard-widgets.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ test("dashboard widgets render with mocked metrics", async ({ page }) => {
await expect(page.getByRole("heading", { name: /dashboard/i })).toBeVisible({ timeout: 30000 });
await expect(page.getByRole("heading", { name: "Your Commits" })).toBeVisible({ timeout: 10000 });
await expect(page.getByRole("heading", { name: "PR Analytics" })).toBeVisible({ timeout: 10000 });
await expect(page.getByRole("heading", { name: "Goals" })).toBeVisible({ timeout: 10000 });
await expect(page.getByRole("heading", { name: "Goals", exact: true })).toBeVisible({ timeout: 10000 });
await expect(page.getByText("Make 10 commits")).toBeVisible({ timeout: 10000 });
});

Expand Down
5 changes: 3 additions & 2 deletions e2e/landing.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ test("[Landing E2E] landing has dashboard link", async ({ page }) => {
await expect(page.getByRole("link", { name: "Dashboard" })).toBeVisible();
});

test("[Landing E2E] landing shows footer via test-id", async ({ page }) => {
test("[Landing E2E] landing shows footer", async ({ page }) => {
await page.goto("/");
await expect(page.locator('[data-testid="landing-footer"]')).toBeVisible();
// Check that the global footer is rendered (e.g. looking for the copyright text)
await expect(page.getByText(/DevTrack. Built for open-source contributors/i)).toBeVisible();
});
3 changes: 1 addition & 2 deletions e2e/notifications.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { expect, test } from "@playwright/test";
import { encode } from "next-auth/jwt";

const authSecret =
process.env.NEXTAUTH_SECRET ||
"test-nextauth-secret-for-playwright-tests";
process.env.NEXTAUTH_SECRET || "test-nextauth-secret-for-playwright-tests";

/** Returns a properly-shaped mock response for each metric endpoint. */
function mockMetricResponse(url) {
Expand Down
49 changes: 44 additions & 5 deletions e2e/theme.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { expect, test } from "@playwright/test";
import { encode } from "next-auth/jwt";

test.beforeEach(async ({ page }) => {
const authSecret =
process.env.NEXTAUTH_SECRET ||
"test-nextauth-secret-for-playwright-tests";
const authSecret =
process.env.NEXTAUTH_SECRET ||
"test-nextauth-secret-for-playwright-tests";

test.beforeEach(async ({ page }) => {
const token = await encode({
secret: authSecret,
token: {
Expand Down Expand Up @@ -54,7 +54,8 @@ test.beforeEach(async ({ page }) => {
test("theme toggle switches between dark and light mode", async ({ page }) => {
await page.goto("/dashboard");

const themeToggle = page.getByRole("button", { name: "Toggle theme" });
// The DashboardHeader provides the ThemeToggle on the dashboard
const themeToggle = page.getByRole("button", { name: "Toggle theme" }).first();
await expect(themeToggle).toBeVisible();

const initialPressed = await themeToggle.getAttribute("aria-pressed");
Expand All @@ -66,3 +67,41 @@ test("theme toggle switches between dark and light mode", async ({ page }) => {
initialPressed === "true" ? "false" : "true"
);
});

/**
* Issue #964: Public profile page should have a theme toggle.
* The toggle must work without login and persist to localStorage.
* We navigate to the profile-not-found page because no real user exists
* in the test DB — but the layout (ThemeProvider + ThemeToggle) still renders.
*/
test("public profile page theme toggle works without authentication", async ({
page,
}) => {
// Clear cookies so visitor is unauthenticated
await page.context().clearCookies();

// Navigate to any public profile URL — will show "Profile Not Found"
// but the full layout (including ThemeToggle) still renders
await page.goto("/u/no-such-user-for-e2e-test", { waitUntil: "load" });

// Confirm we're on the public profile route (no auth redirect)
await expect(page).toHaveURL(/\/u\//);

// ThemeToggle must be present in the AppNavbar and functional without login
const themeToggle = page.getByRole("banner").getByRole("button", { name: "Toggle theme" });
await expect(themeToggle).toBeVisible({ timeout: 10000 });

const initialPressed = await themeToggle.getAttribute("aria-pressed");

await themeToggle.click();

// Toggle state must have flipped
await expect(themeToggle).toHaveAttribute(
"aria-pressed",
initialPressed === "true" ? "false" : "true"
);

// Theme preference must be persisted to localStorage
const stored = await page.evaluate(() => localStorage.getItem("theme"));
expect(stored === "dark" || stored === "light").toBe(true);
});
Loading
Loading