Add Prettier for TypeScript formatting

- Install prettier
- Configure .prettierrc.json and .prettierignore
- Add npm scripts: format, format:check
- Add Makefile target: format-frontend
- Format all frontend files
This commit is contained in:
counterweight 2025-12-21 21:59:26 +01:00
parent 4b394b0698
commit 37de6f70e0
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
44 changed files with 906 additions and 856 deletions

View file

@ -2,7 +2,7 @@ import { test, expect, Page } from "@playwright/test";
/**
* Profile E2E tests
*
*
* These tests verify that:
* 1. Regular users can access and use the profile page
* 2. Admin users cannot access the profile page
@ -51,8 +51,8 @@ async function loginUser(page: Page, email: string, password: string) {
// Helper to clear profile data via API
async function clearProfileData(page: Page) {
const cookies = await page.context().cookies();
const authCookie = cookies.find(c => c.name === "auth_token");
const authCookie = cookies.find((c) => c.name === "auth_token");
if (authCookie) {
await page.request.put(`${API_URL}/api/profile`, {
headers: {
@ -77,10 +77,10 @@ test.describe("Profile - Regular User Access", () => {
test("can navigate to profile page from counter", async ({ page }) => {
await page.goto("/");
// Should see My Profile link
await expect(page.getByText("My Profile")).toBeVisible();
// Click to navigate
await page.click('a[href="/profile"]');
await expect(page).toHaveURL("/profile");
@ -88,10 +88,10 @@ test.describe("Profile - Regular User Access", () => {
test("can navigate to profile page from sum", async ({ page }) => {
await page.goto("/sum");
// Should see My Profile link
await expect(page.getByText("My Profile")).toBeVisible();
// Click to navigate
await page.click('a[href="/profile"]');
await expect(page).toHaveURL("/profile");
@ -99,17 +99,17 @@ test.describe("Profile - Regular User Access", () => {
test("profile page displays correct elements", async ({ page }) => {
await page.goto("/profile");
// Should see page title
await expect(page.getByRole("heading", { name: "My Profile" })).toBeVisible();
// Should see login email label with read-only badge
await expect(page.getByText("Login EmailRead only")).toBeVisible();
// Should see contact details section
await expect(page.getByText("Contact Details")).toBeVisible();
await expect(page.getByText(/communication purposes only/i)).toBeVisible();
// Should see all form fields
await expect(page.getByLabel("Contact Email")).toBeVisible();
await expect(page.getByLabel("Telegram")).toBeVisible();
@ -119,7 +119,7 @@ test.describe("Profile - Regular User Access", () => {
test("login email is displayed and read-only", async ({ page }) => {
await page.goto("/profile");
// Login email should show the user's email
const loginEmailInput = page.locator('input[type="email"][disabled]');
await expect(loginEmailInput).toHaveValue(REGULAR_USER.email);
@ -128,7 +128,7 @@ test.describe("Profile - Regular User Access", () => {
test("navigation shows Counter, Sum, and My Profile", async ({ page }) => {
await page.goto("/profile");
// Should see all nav items (Counter and Sum as links)
await expect(page.locator('a[href="/"]')).toBeVisible();
await expect(page.locator('a[href="/sum"]')).toBeVisible();
@ -147,7 +147,7 @@ test.describe("Profile - Form Behavior", () => {
test("new user has empty profile fields", async ({ page }) => {
await page.goto("/profile");
// All editable fields should be empty
await expect(page.getByLabel("Contact Email")).toHaveValue("");
await expect(page.getByLabel("Telegram")).toHaveValue("");
@ -157,7 +157,7 @@ test.describe("Profile - Form Behavior", () => {
test("save button is disabled when no changes", async ({ page }) => {
await page.goto("/profile");
// Save button should be disabled
const saveButton = page.getByRole("button", { name: /save changes/i });
await expect(saveButton).toBeDisabled();
@ -165,10 +165,10 @@ test.describe("Profile - Form Behavior", () => {
test("save button is enabled after making changes", async ({ page }) => {
await page.goto("/profile");
// Make a change
await page.fill("#telegram", "@testhandle");
// Save button should be enabled
const saveButton = page.getByRole("button", { name: /save changes/i });
await expect(saveButton).toBeEnabled();
@ -176,22 +176,22 @@ test.describe("Profile - Form Behavior", () => {
test("can save profile and values persist", async ({ page }) => {
await page.goto("/profile");
// Fill in all fields
await page.fill("#contact_email", "contact@test.com");
await page.fill("#telegram", "@testuser");
await page.fill("#signal", "signal.42");
await page.fill("#nostr_npub", VALID_NPUB);
// Save
await page.click('button:has-text("Save Changes")');
// Should see success message
await expect(page.getByText(/saved successfully/i)).toBeVisible();
// Reload and verify values persist
await page.reload();
await expect(page.getByLabel("Contact Email")).toHaveValue("contact@test.com");
await expect(page.getByLabel("Telegram")).toHaveValue("@testuser");
await expect(page.getByLabel("Signal")).toHaveValue("signal.42");
@ -200,20 +200,20 @@ test.describe("Profile - Form Behavior", () => {
test("can clear a field and save", async ({ page }) => {
await page.goto("/profile");
// First set a value
await page.fill("#telegram", "@initial");
await page.click('button:has-text("Save Changes")');
await expect(page.getByText(/saved successfully/i)).toBeVisible();
// Wait for toast to disappear
await expect(page.getByText(/saved successfully/i)).not.toBeVisible({ timeout: 5000 });
// Clear the field
await page.fill("#telegram", "");
await page.click('button:has-text("Save Changes")');
await expect(page.getByText(/saved successfully/i)).toBeVisible();
// Reload and verify it's cleared
await page.reload();
await expect(page.getByLabel("Telegram")).toHaveValue("");
@ -229,26 +229,26 @@ test.describe("Profile - Validation", () => {
test("auto-prepends @ for telegram when starting with letter", async ({ page }) => {
await page.goto("/profile");
// Type a letter without @ - should auto-prepend @
await page.fill("#telegram", "testhandle");
// Should show @testhandle in the input
await expect(page.locator("#telegram")).toHaveValue("@testhandle");
});
test("shows error for telegram handle with no characters after @", async ({ page }) => {
await page.goto("/profile");
// Enter telegram with @ but nothing after (needs at least 1 char)
await page.fill("#telegram", "@");
// Wait for debounced validation
await page.waitForTimeout(600);
// Should show error about needing at least one character
await expect(page.getByText(/at least one character after @/i)).toBeVisible();
// Save button should be disabled
const saveButton = page.getByRole("button", { name: /save changes/i });
await expect(saveButton).toBeDisabled();
@ -256,13 +256,13 @@ test.describe("Profile - Validation", () => {
test("shows error for invalid npub", async ({ page }) => {
await page.goto("/profile");
// Enter invalid npub
await page.fill("#nostr_npub", "invalidnpub");
// Should show error
await expect(page.getByText(/must start with 'npub1'/i)).toBeVisible();
// Save button should be disabled
const saveButton = page.getByRole("button", { name: /save changes/i });
await expect(saveButton).toBeDisabled();
@ -270,38 +270,38 @@ test.describe("Profile - Validation", () => {
test("can fix validation error and save", async ({ page }) => {
await page.goto("/profile");
// Enter invalid telegram (just @ with no handle)
await page.fill("#telegram", "@");
// Wait for debounced validation
await page.waitForTimeout(600);
await expect(page.getByText(/at least one character after @/i)).toBeVisible();
// Fix it
await page.fill("#telegram", "@validhandle");
// Wait for debounced validation
await page.waitForTimeout(600);
// Error should disappear
await expect(page.getByText(/at least one character after @/i)).not.toBeVisible();
// Should be able to save
const saveButton = page.getByRole("button", { name: /save changes/i });
await expect(saveButton).toBeEnabled();
await page.click('button:has-text("Save Changes")');
await expect(page.getByText(/saved successfully/i)).toBeVisible();
});
test("shows error for invalid email format", async ({ page }) => {
await page.goto("/profile");
// Enter invalid email
await page.fill("#contact_email", "not-an-email");
// Should show error
await expect(page.getByText(/valid email/i)).toBeVisible();
});
@ -315,25 +315,25 @@ test.describe("Profile - Admin User Access", () => {
test("admin does not see My Profile link", async ({ page }) => {
await page.goto("/audit");
// Should be on audit page
await expect(page).toHaveURL("/audit");
// Should NOT see My Profile link
await expect(page.locator('a[href="/profile"]')).toHaveCount(0);
});
test("admin cannot access profile page - redirected to audit", async ({ page }) => {
await page.goto("/profile");
// Should be redirected to audit
await expect(page).toHaveURL("/audit");
});
test("admin API call to profile returns 403", async ({ page, request }) => {
const cookies = await page.context().cookies();
const authCookie = cookies.find(c => c.name === "auth_token");
const authCookie = cookies.find((c) => c.name === "auth_token");
if (authCookie) {
// Try to call profile API directly
const response = await request.get(`${API_URL}/api/profile`, {
@ -341,7 +341,7 @@ test.describe("Profile - Admin User Access", () => {
Cookie: `auth_token=${authCookie.value}`,
},
});
expect(response.status()).toBe(403);
}
});
@ -362,4 +362,3 @@ test.describe("Profile - Unauthenticated Access", () => {
expect(response.status()).toBe(401);
});
});