feat: consolidate shared UI and harden browser authority for release

This commit is contained in:
2026-09-08 01:35:05 +02:00
parent ac40774785
commit b75ca34295
143 changed files with 8664 additions and 752 deletions
@@ -0,0 +1,199 @@
import { expect, test, type Page } from "@playwright/test";
const archive = {
id: "fixture-archive", tenant_id: "fixture-tenant", owner_type: "user", owner_id: "fixture-user",
display_path: "small-archive.zip", filename: "small-archive.zip", size_bytes: 280,
content_type: "application/zip", checksum_sha256: "a".repeat(64), version_id: "fixture-version-7",
created_at: "2026-01-01T12:00:00Z", updated_at: "2026-01-01T12:00:00Z", audit_relevant: false,
shares: [], metadata: {}, deleted_at: null
};
async function fixtures(page: Page, connector = false) {
const reads: string[] = [];
const forbidden: string[] = [];
const state = { fail: false };
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
const request = route.request();
const url = new URL(request.url());
const path = url.pathname;
const readOnlyPattern = request.method() === "POST" && path === "/api/v1/files/resolve-patterns";
if (request.method() !== "GET" && !readOnlyPattern) {
forbidden.push(`${request.method()} ${path}`);
await route.fulfill({ status: 403, json: { detail: "Fixture refuses all writes" } });
return;
}
reads.push(`${request.method()} ${path}${url.search}`);
if (state.fail && (path === "/api/v1/files" || path.endsWith("/browse"))) {
await route.fulfill({ status: 503, json: { detail: "Fixture listing temporarily unavailable" } });
return;
}
if (path === "/api/v1/files/spaces") {
await route.fulfill({ json: { spaces: [
{ id: "user:fixture-user", label: "My files", owner_type: "user", owner_id: "fixture-user", space_type: "managed" },
...(connector ? [{ id: "connector:fixture-remote", label: "Remote archive", owner_type: "user", owner_id: "fixture-user", space_type: "connector", connector_space_id: "fixture-remote", connector_profile_id: "fixture-profile", remote_path: "source", provider: "s3", read_only: true, sync_mode: "manual" }] : [])
] } });
} else if (path === "/api/v1/files/folders") {
await route.fulfill({ json: { folders: [], total: 0, next_cursor: null } });
} else if (path === "/api/v1/files") {
await route.fulfill({ json: { files: [archive], total: 1, next_cursor: null } });
} else if (readOnlyPattern) {
await route.fulfill({ json: { patterns: [{ pattern: "*.zip", matches: [archive] }], unmatched: [] } });
} else if (path === "/api/v1/files/connectors/profiles") {
await route.fulfill({ json: { profiles: [] } });
} else if (path.endsWith("/fixture-profile/browse")) {
const folder = url.searchParams.get("path") ?? "source";
await route.fulfill({ json: { profile_id: "fixture-profile", provider: "s3", path: folder, library_id: null,
read_only: true, has_more: false, decision: { allowed: true }, items: folder.endsWith("/nested")
? [{ kind: "file", name: "remote-letter.pdf", path: "source/nested/remote-letter.pdf", metadata: {}, size_bytes: 42 }]
: [{ kind: "folder", name: "nested", path: "source/nested", metadata: {} }]
} });
} else {
forbidden.push(`${request.method()} ${path}`);
await route.fulfill({ status: 404, json: { detail: "Unconfigured fixture read" } });
}
});
return { reads, forbidden, state };
}
const header = (page: Page) => page.locator('[data-interface-id="files.workspace.actions"]');
const archiveRow = (page: Page) => page.locator(".file-row").filter({ hasText: "small-archive.zip" });
test("Files global Reload, Create folder and primary Upload remain above both workspace panes", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&language=en");
await expect(archiveRow(page)).toBeVisible();
const toolbar = header(page);
await expect(toolbar).toHaveAttribute("data-workspace-action-scope", "workspace");
await expect(toolbar.getByRole("link", { name: "Open user documentation", exact: true })).toBeVisible();
await expect(toolbar.locator('[data-page-action-group="trailing"] button')).toHaveText(["Reload", /Create Folder/, /Upload/]);
const [bar, reload, create, upload, tree] = await Promise.all([
toolbar.boundingBox(), toolbar.getByRole("button", { name: "Reload", exact: true }).boundingBox(),
toolbar.getByRole("button", { name: "Create Folder", exact: true }).boundingBox(),
toolbar.getByRole("button", { name: "Upload", exact: true }).boundingBox(), page.locator(".file-tree-panel").boundingBox()
]);
expect(bar && reload && create && upload && tree).toBeTruthy();
expect(reload!.x + reload!.width).toBeLessThanOrEqual(create!.x);
expect(create!.x + create!.width).toBeLessThanOrEqual(upload!.x);
expect(upload!.x + upload!.width).toBeGreaterThan(bar!.x + bar!.width - 24);
expect(bar!.y + bar!.height).toBeLessThanOrEqual(tree!.y + 1);
await expect(toolbar.getByRole("button", { name: "Upload", exact: true })).toHaveClass(/primary/);
await expect(page.locator('.file-list-sticky [data-workspace-action-scope="workspace"]')).toHaveCount(0);
expect(fixture.forbidden).toEqual([]);
});
test("Files selection tools preserve organization, access and confirmed destructive actions", async ({ page }) => {
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&language=en");
await archiveRow(page).click();
await expect(page.getByRole("button", { name: "Unpack archive", exact: true })).toBeEnabled();
await page.getByRole("button", { name: "Manage selection", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "Manage selection", exact: true });
await expect(dialog).toContainText("1 file");
for (const name of ["Move", "Copy", "Rename", "Manage shares", "Explain access"]) {
await expect(dialog.getByRole("button", { name, exact: true })).toBeEnabled();
}
const remove = dialog.locator('[data-page-action-separation="destructive"]').getByRole("button", { name: "Delete", exact: true });
await remove.click();
await expect(dialog).toHaveCount(0);
await expect(page.getByRole("alertdialog")).toContainText(/delete|Delete/);
await page.getByRole("alertdialog").getByRole("button", { name: "Cancel", exact: true }).click();
await archiveRow(page).click({ button: "right" });
await expect(page.getByRole("menuitem", { name: "Unpack archive", exact: true })).toBeVisible();
expect(fixture.forbidden).toEqual([]);
});
test("Files Connections and imports exposes explicit tools without starting synchronization", async ({ page }) => {
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&language=en");
await expect(archiveRow(page)).toBeVisible();
const initialReads = fixture.reads.length;
await header(page).getByRole("button", { name: "Connections and imports", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "Connections and imports", exact: true });
await expect(dialog).toContainText("never synchronizes or imports");
await expect(dialog.getByRole("button", { name: "Sync", exact: true })).toBeEnabled();
await dialog.getByRole("button", { name: "Add Space", exact: true }).click();
await expect(page.getByRole("dialog", { name: "Add connector space", exact: true })).toBeVisible();
expect(fixture.reads.slice(initialReads).every((read) => read.startsWith("GET "))).toBe(true);
expect(fixture.forbidden).toEqual([]);
});
test("Files Reload preserves active pattern and property filters and retains loaded data after failure", async ({ page }) => {
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&language=en");
await expect(archiveRow(page)).toBeVisible();
await page.locator(".file-search-row input:not([type=checkbox])").fill("*.zip");
await page.locator(".file-search-row").getByRole("button", { name: "Search", exact: true }).click();
await page.getByRole("combobox", { name: "Campaign use", exact: true }).selectOption("linked");
await page.getByRole("button", { name: "Apply filters", exact: true }).click();
await expect(page.getByRole("button", { name: "Clear filters", exact: true })).toBeVisible();
const before = fixture.reads.length;
await header(page).getByRole("button", { name: "Reload", exact: true }).click();
await expect(header(page).getByRole("button", { name: "Reload", exact: true })).toBeEnabled();
await expect.poll(() => fixture.reads.slice(before).some((read) => read.startsWith("POST /api/v1/files/resolve-patterns"))).toBe(true);
await expect.poll(() => fixture.reads.slice(before).some((read) => read.includes("campaign_usage=linked"))).toBe(true);
await expect(header(page).getByRole("button", { name: "Reload", exact: true })).toBeEnabled();
await expect(page.locator(".file-search-row input:not([type=checkbox])")).toHaveValue("*.zip");
await expect(page.getByRole("combobox", { name: "Campaign use", exact: true })).toHaveValue("linked");
fixture.state.fail = true;
await header(page).getByRole("button", { name: "Reload", exact: true }).click();
await expect(page.getByText(/Fixture listing temporarily unavailable/)).toBeVisible();
await expect(archiveRow(page)).toBeVisible();
await expect(page.getByRole("button", { name: "Clear filters", exact: true })).toBeVisible();
expect(fixture.forbidden).toEqual([]);
});
test("Files reader keeps creation visible with permission reasons and cannot use selected write tools", async ({ page }) => {
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&read-only&language=en");
await expect(archiveRow(page)).toBeVisible();
const upload = header(page).getByRole("button", { name: "Upload", exact: true });
await expect(upload).toBeDisabled();
await header(page).locator(".disabled-action-tooltip").filter({ has: page.getByRole("button", { name: "Upload", exact: true }) }).focus();
await expect(page.getByRole("tooltip")).toContainText(/upload permission/i);
await expect(header(page).getByRole("button", { name: "Create Folder", exact: true })).toBeDisabled();
await archiveRow(page).click();
await expect(page.getByRole("button", { name: "Unpack archive", exact: true })).toBeDisabled();
await page.getByRole("button", { name: "Manage selection", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "Manage selection", exact: true });
for (const name of ["Move", "Copy", "Rename", "Manage shares", "Explain access", "Delete"]) {
await expect(dialog.getByRole("button", { name, exact: true })).toBeDisabled();
}
expect(fixture.forbidden).toEqual([]);
});
test("Files connector Reload only re-browses the selected remote folder and retains it on failure", async ({ page }) => {
const fixture = await fixtures(page, true);
await page.goto("/?files-toolbar&language=en");
await expect(archiveRow(page)).toBeVisible();
await page.locator(".file-tree-root").filter({ hasText: "Remote archive" }).click();
await page.locator(".file-list-panel").getByText("nested", { exact: true }).dblclick();
await expect(page.getByText("remote-letter.pdf", { exact: true })).toBeVisible();
const before = fixture.reads.length;
await header(page).getByRole("button", { name: "Reload", exact: true }).click();
await expect(header(page).getByRole("button", { name: "Reload", exact: true })).toBeEnabled();
await expect.poll(() => fixture.reads.slice(before)).toEqual(["GET /api/v1/files/connectors/profiles/fixture-profile/browse?path=source%2Fnested"]);
await expect(header(page).getByRole("button", { name: "Reload", exact: true })).toBeEnabled();
fixture.state.fail = true;
await header(page).getByRole("button", { name: "Reload", exact: true }).click();
await expect(page.getByText(/Fixture listing temporarily unavailable/)).toBeVisible();
await expect(page.getByText("remote-letter.pdf", { exact: true })).toBeVisible();
expect(fixture.forbidden).toEqual([]);
});
test("Files grouped tools and creation remain reachable without horizontal overflow on narrow German screens", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
const fixture = await fixtures(page);
await page.goto("/?files-toolbar&language=de");
await expect(archiveRow(page)).toBeVisible();
await expect(header(page).getByRole("button", { name: "Hochladen", exact: true })).toBeVisible();
await header(page).getByRole("button", { name: "Verbindungen und Importe", exact: true }).click();
const dialog = page.getByRole("dialog", { name: "Verbindungen und Importe", exact: true });
await expect(dialog).toBeVisible();
const overflow = await dialog.evaluate((element) => element.scrollWidth - element.clientWidth);
expect(overflow).toBeLessThanOrEqual(1);
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
await page.keyboard.press("Escape");
await expect(dialog).toHaveCount(0);
expect(fixture.forbidden).toEqual([]);
});