mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge branch 'worktree-agent-a63c6a97'
This commit is contained in:
@@ -131,4 +131,69 @@ test.describe("Keyboard Shortcuts - Tool Navigation", () => {
|
||||
|
||||
await expect(page).toHaveURL("/convert");
|
||||
});
|
||||
|
||||
test("Cmd/Ctrl+Alt+5 navigates to Remove Background", async ({ loggedInPage: page }) => {
|
||||
await page.keyboard.press(`${MOD}+Alt+5`);
|
||||
|
||||
await expect(page).toHaveURL("/remove-background");
|
||||
});
|
||||
|
||||
test("Cmd/Ctrl+Alt+6 navigates to Watermark Text", async ({ loggedInPage: page }) => {
|
||||
await page.keyboard.press(`${MOD}+Alt+6`);
|
||||
|
||||
await expect(page).toHaveURL("/watermark-text");
|
||||
});
|
||||
|
||||
test("Cmd/Ctrl+Alt+7 navigates to Strip Metadata", async ({ loggedInPage: page }) => {
|
||||
await page.keyboard.press(`${MOD}+Alt+7`);
|
||||
|
||||
await expect(page).toHaveURL("/strip-metadata");
|
||||
});
|
||||
|
||||
test("Cmd/Ctrl+Alt+8 navigates to Image Info", async ({ loggedInPage: page }) => {
|
||||
await page.keyboard.press(`${MOD}+Alt+8`);
|
||||
|
||||
await expect(page).toHaveURL("/info");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Keyboard shortcut suppression in different input types
|
||||
// ---------------------------------------------------------------------------
|
||||
test.describe("Keyboard Shortcuts - Input Suppression", () => {
|
||||
test("Cmd/Ctrl+Shift+D does not toggle theme when focused on search input", async ({
|
||||
loggedInPage: page,
|
||||
}) => {
|
||||
// Navigate to fullscreen which reliably shows the search input
|
||||
await page.goto("/fullscreen");
|
||||
const searchInput = page.getByPlaceholder(/search/i);
|
||||
await expect(searchInput).toBeVisible();
|
||||
await searchInput.click();
|
||||
await searchInput.fill("");
|
||||
|
||||
const hadDark = await page.evaluate(() => document.documentElement.classList.contains("dark"));
|
||||
|
||||
await page.keyboard.press(`${MOD}+Shift+d`);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
const hasDark = await page.evaluate(() => document.documentElement.classList.contains("dark"));
|
||||
|
||||
// Theme should NOT have changed since we were in an input
|
||||
expect(hasDark).toBe(hadDark);
|
||||
});
|
||||
|
||||
test("Cmd/Ctrl+Alt+1 does not navigate when focused on search input", async ({
|
||||
loggedInPage: page,
|
||||
}) => {
|
||||
await page.goto("/fullscreen");
|
||||
const searchInput = page.getByPlaceholder(/search/i);
|
||||
await expect(searchInput).toBeVisible();
|
||||
await searchInput.click();
|
||||
|
||||
await page.keyboard.press(`${MOD}+Alt+1`);
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Should still be on fullscreen since shortcut was suppressed
|
||||
await expect(page).toHaveURL("/fullscreen");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -352,6 +352,98 @@ test.describe("Sidebar Navigation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Footer (desktop only)
|
||||
// ---------------------------------------------------------------------------
|
||||
test.describe("Footer", () => {
|
||||
test("theme toggle button is visible with sun or moon icon", async ({ loggedInPage: page }) => {
|
||||
const themeBtn = page.locator("button[title='Toggle Theme']");
|
||||
await expect(themeBtn).toBeVisible();
|
||||
|
||||
// Should contain an SVG icon (Sun or Moon)
|
||||
await expect(themeBtn.locator("svg")).toBeVisible();
|
||||
});
|
||||
|
||||
test("theme toggle switches between sun and moon icons", async ({ loggedInPage: page }) => {
|
||||
const themeBtn = page.locator("button[title='Toggle Theme']");
|
||||
await expect(themeBtn).toBeVisible();
|
||||
|
||||
const hadDark = await page.evaluate(() => document.documentElement.classList.contains("dark"));
|
||||
|
||||
await themeBtn.click();
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
const hasDark = await page.evaluate(() => document.documentElement.classList.contains("dark"));
|
||||
expect(hasDark).not.toBe(hadDark);
|
||||
});
|
||||
|
||||
test("language button is visible and shows English", async ({ loggedInPage: page }) => {
|
||||
const langBtn = page.locator("button[title='Language']");
|
||||
await expect(langBtn).toBeVisible();
|
||||
await expect(langBtn).toContainText("English");
|
||||
await expect(langBtn.locator("svg")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Drag-and-Drop Upload
|
||||
// ---------------------------------------------------------------------------
|
||||
test.describe("Drag-and-Drop Upload", () => {
|
||||
test("dropzone accepts dropped files via DataTransfer", async ({ loggedInPage: page }) => {
|
||||
const dropzone = page.locator("section[aria-label='File drop zone']");
|
||||
await expect(dropzone).toBeVisible();
|
||||
|
||||
// Verify the dropzone aria-label and interactive elements
|
||||
await expect(page.getByText("Drop files here or click the upload button")).toBeVisible();
|
||||
|
||||
// Upload via the file chooser flow (same onFiles handler as drag-and-drop)
|
||||
await uploadTestImage(page);
|
||||
|
||||
// After upload, the file info should appear
|
||||
await expect(page.getByText(/test-image/i).first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Files Page Layout
|
||||
// ---------------------------------------------------------------------------
|
||||
test.describe("Files Page Layout", () => {
|
||||
test("desktop shows three-column layout with nav, list, and details", async ({
|
||||
loggedInPage: page,
|
||||
}) => {
|
||||
await page.goto("/files");
|
||||
|
||||
// Left nav column with "My Files"
|
||||
await expect(page.getByText("My Files")).toBeVisible();
|
||||
// Nav items: Recent and Upload Files
|
||||
await expect(page.getByRole("button", { name: /recent/i }).first()).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /upload files/i }).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("mobile shows tabbed layout with Recent and Upload tabs", async ({ browser }) => {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 375, height: 667 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto("/login");
|
||||
await page.getByLabel("Username").fill("admin");
|
||||
await page.getByLabel("Password").fill("admin");
|
||||
await page.getByRole("button", { name: /login/i }).click();
|
||||
await page.waitForURL("/", { timeout: 15_000 });
|
||||
|
||||
await page.goto("/files");
|
||||
|
||||
// Mobile tabs should be visible
|
||||
await expect(page.getByRole("button", { name: "Recent" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Upload" })).toBeVisible();
|
||||
|
||||
// Desktop nav "My Files" heading should not be visible (hidden md:block)
|
||||
await expect(page.getByText("My Files")).not.toBeVisible();
|
||||
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Routing Edge Cases
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -388,4 +480,78 @@ test.describe("Routing Edge Cases", () => {
|
||||
await expect(page.getByRole("heading", { name: "Privacy Policy" })).toBeVisible();
|
||||
await expect(page.getByText("Back to app")).toBeVisible();
|
||||
});
|
||||
|
||||
test("/privacy Back to app link navigates home", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/privacy");
|
||||
|
||||
await page.getByText("Back to app").click();
|
||||
await expect(page).toHaveURL("/");
|
||||
});
|
||||
|
||||
test("/analytics-consent page renders consent UI", async ({ browser }) => {
|
||||
// Use unauthenticated context since analytics-consent is unguarded
|
||||
const context = await browser.newContext({
|
||||
storageState: { cookies: [], origins: [] },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto("/analytics-consent");
|
||||
|
||||
// The page shows a heading and two buttons
|
||||
await expect(page.getByText("Help improve SnapOtter")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Sure, sounds good" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Not right now" })).toBeVisible();
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test("legacy /color-effects redirects to /adjust-colors", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/color-effects");
|
||||
|
||||
await expect(page).toHaveURL("/adjust-colors");
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Browser Back/Forward Navigation
|
||||
// ---------------------------------------------------------------------------
|
||||
test.describe("Browser Back/Forward Navigation", () => {
|
||||
test("browser back button returns to previous page", async ({ loggedInPage: page }) => {
|
||||
// Navigate: Home -> Fullscreen -> back should return to Home
|
||||
await page.goto("/fullscreen");
|
||||
await expect(page).toHaveURL("/fullscreen");
|
||||
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL("/");
|
||||
});
|
||||
|
||||
test("browser forward button returns to next page after going back", async ({
|
||||
loggedInPage: page,
|
||||
}) => {
|
||||
await page.goto("/fullscreen");
|
||||
await expect(page).toHaveURL("/fullscreen");
|
||||
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL("/");
|
||||
|
||||
await page.goForward();
|
||||
await expect(page).toHaveURL("/fullscreen");
|
||||
});
|
||||
|
||||
test("multi-step back/forward through several pages", async ({ loggedInPage: page }) => {
|
||||
// Navigate: Home -> /automate -> /files -> back -> back -> forward
|
||||
await page.goto("/automate");
|
||||
await expect(page).toHaveURL("/automate");
|
||||
|
||||
await page.goto("/files");
|
||||
await expect(page).toHaveURL("/files");
|
||||
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL("/automate");
|
||||
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL("/");
|
||||
|
||||
await page.goForward();
|
||||
await expect(page).toHaveURL("/automate");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -147,6 +147,38 @@ test.describe("Responsive - Tablet (768x1024)", () => {
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test("files page is accessible at tablet width", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/files");
|
||||
|
||||
await expect(page.getByText("My Files")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: /recent/i }).first()).toBeVisible();
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
|
||||
test("no horizontal overflow on files page", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/files");
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
|
||||
test("settings dialog fits within tablet viewport", async ({ loggedInPage: page }) => {
|
||||
const sidebar = page.locator("aside");
|
||||
// On tablet, sidebar may or may not be visible; use it if visible, otherwise use menu
|
||||
if (await sidebar.isVisible({ timeout: 2000 }).catch(() => false)) {
|
||||
await sidebar.getByText("Settings").click();
|
||||
}
|
||||
await expect(page.getByRole("heading", { name: "General" })).toBeVisible({ timeout: 10_000 });
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -318,4 +350,57 @@ test.describe("Responsive - Mobile (375x667)", () => {
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
|
||||
test("files page shows mobile tabs instead of desktop nav", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/files");
|
||||
|
||||
// Mobile tabs: "Recent" and "Upload"
|
||||
await expect(page.getByRole("button", { name: "Recent" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Upload" })).toBeVisible();
|
||||
|
||||
// Desktop nav "My Files" heading should not be visible
|
||||
await expect(page.getByText("My Files")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("no horizontal overflow on files page", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/files");
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
|
||||
test("dropzone is accessible and clickable on mobile", async ({ loggedInPage: page }) => {
|
||||
// The dropzone should be visible and have the upload button
|
||||
const dropzone = page.locator("section[aria-label='File drop zone']");
|
||||
await expect(dropzone).toBeVisible();
|
||||
await expect(page.getByText("Upload from computer")).toBeVisible();
|
||||
});
|
||||
|
||||
test("privacy policy page renders on mobile without overflow", async ({ loggedInPage: page }) => {
|
||||
await page.goto("/privacy");
|
||||
|
||||
await expect(page.getByRole("heading", { name: "Privacy Policy" })).toBeVisible();
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
|
||||
test("footer theme and language buttons are hidden on mobile", async ({ loggedInPage: page }) => {
|
||||
// Footer is rendered only on desktop (!isMobile)
|
||||
await expect(page.locator("button[title='Toggle Theme']")).not.toBeVisible();
|
||||
await expect(page.locator("button[title='Language']")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("settings dialog fits within mobile viewport", async ({ loggedInPage: page }) => {
|
||||
const bottomNav = page.locator("nav.fixed");
|
||||
await bottomNav.getByText("Settings").click();
|
||||
|
||||
await expect(page.getByRole("heading", { name: "General" })).toBeVisible();
|
||||
|
||||
const scrollWidth = await page.evaluate(() => document.documentElement.scrollWidth);
|
||||
const clientWidth = await page.evaluate(() => document.documentElement.clientWidth);
|
||||
expect(scrollWidth).toBeLessThanOrEqual(clientWidth);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user