101 lines
6.3 KiB
TypeScript
101 lines
6.3 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
async function mockAddresses(page: Page) {
|
|
const errors: string[] = [];
|
|
const writes: string[] = [];
|
|
page.on("pageerror", (error) => errors.push(error.message));
|
|
const timestamp = "2026-09-01T12:00:00Z";
|
|
await page.route(url => url.pathname.startsWith("/api/"), async route => {
|
|
const request = route.request();
|
|
const path = new URL(request.url()).pathname;
|
|
if (request.method() !== "GET") writes.push(path);
|
|
let body: unknown = {};
|
|
if (path === "/api/v1/addresses/address-books") body = { address_books: [{
|
|
id: "book-fixture", name: "Fixture address book", scope_type: "tenant",
|
|
source_kind: "local", read_only: false, contact_count: 0, created_at: timestamp, updated_at: timestamp
|
|
}] };
|
|
else if (path === "/api/v1/addresses/address-lists") body = { address_lists: [{
|
|
id: "list-fixture", address_book_id: "book-fixture", name: "Fixture address list",
|
|
source_kind: "local", read_only: false, entry_count: 0, created_at: timestamp, updated_at: timestamp
|
|
}] };
|
|
else if (path === "/api/v1/addresses/sync-sources") body = { sync_sources: [] };
|
|
else if (path === "/api/v1/addresses/contacts") body = { contacts: [], total: 0, limit: 50, offset: 0 };
|
|
else if (path.endsWith("/entries")) body = { entries: [] };
|
|
return route.fulfill({ json: body });
|
|
});
|
|
return { errors, writes };
|
|
}
|
|
|
|
test("Addressbook keeps Reload/New together and moves specialist actions out of the narrow tree header", async ({ page }) => {
|
|
const fixture = await mockAddresses(page);
|
|
await page.setViewportSize({ width: 1360, height: 900 });
|
|
await page.goto("/?address-explorer&language=en&theme=light");
|
|
const toolbar = page.getByRole("toolbar", { name: "Address book actions", exact: true });
|
|
const reload = toolbar.locator('[data-page-action-slot="reload"] button');
|
|
const create = toolbar.getByRole("button", { name: "Add address book", exact: true });
|
|
await expect(reload).toBeEnabled();
|
|
await expect(create).toBeVisible();
|
|
const [reloadBox, createBox] = await Promise.all([reload.boundingBox(), create.boundingBox()]);
|
|
expect(createBox!.x).toBeGreaterThan(1100);
|
|
expect(Math.abs(createBox!.y - reloadBox!.y)).toBeLessThan(3);
|
|
expect(createBox!.x - reloadBox!.x - reloadBox!.width).toBeLessThan(20);
|
|
await expect(page.locator('.address-tree-header button')).toHaveCount(1);
|
|
await toolbar.getByRole("button", { name: "Import / export", exact: true }).click();
|
|
const transfer = page.getByRole("dialog", { name: "Import / export", exact: true });
|
|
await expect(transfer.getByRole("button", { name: "Import contacts", exact: true })).toBeVisible();
|
|
await expect(transfer.getByRole("button", { name: "Export address book", exact: true })).toBeVisible();
|
|
await expect(transfer.getByRole("combobox", { name: "vCard export version" })).toBeVisible();
|
|
await page.keyboard.press("Escape");
|
|
await toolbar.getByRole("button", { name: "Connections", exact: true }).click();
|
|
const connections = page.getByRole("dialog", { name: "Connections", exact: true });
|
|
await expect(connections.getByRole("button", { name: "Connect CardDAV", exact: true })).toBeVisible();
|
|
await expect(connections.getByRole("button", { name: "Connect LDAP or Active Directory", exact: true })).toBeVisible();
|
|
await expect(connections.getByRole("button", { name: "Run sync", exact: true })).toBeDisabled();
|
|
expect(fixture.errors).toEqual([]);
|
|
expect(fixture.writes).toEqual([]);
|
|
});
|
|
|
|
test("Addressbook labels select only; folder buttons toggle, including after Reload", async ({ page }) => {
|
|
const fixture = await mockAddresses(page);
|
|
await page.goto("/?address-explorer&language=en&theme=light");
|
|
const root = page.locator('.address-tree-list > .explorer-tree-children > div').first();
|
|
const label = root.locator(':scope > .explorer-tree-node-wrap > .explorer-tree-node');
|
|
const folder = root.locator(':scope > .explorer-tree-node-wrap > .explorer-tree-toggle');
|
|
await expect(folder).toHaveAttribute("aria-expanded", "true");
|
|
await label.click();
|
|
await expect(label).toHaveAttribute("aria-current", "true");
|
|
await expect(folder).toHaveAttribute("aria-expanded", "true");
|
|
await expect(page.getByText("Select an address book in this group.", { exact: false })).toBeVisible();
|
|
await folder.click();
|
|
await expect(folder).toHaveAttribute("aria-expanded", "false");
|
|
await label.click();
|
|
await expect(folder).toHaveAttribute("aria-expanded", "false");
|
|
await page.locator('[data-page-action-slot="reload"] button').click();
|
|
await expect(page.locator('[data-page-action-slot="reload"] button')).toBeEnabled();
|
|
await expect(folder).toHaveAttribute("aria-expanded", "false");
|
|
await expect(label).toHaveAttribute("aria-current", "true");
|
|
await folder.click();
|
|
const book = page.locator('.address-tree-list .explorer-tree-node').filter({ hasText: "Fixture address book" });
|
|
await book.click();
|
|
await expect(book).toHaveAttribute("aria-current", "true");
|
|
await page.locator('.address-tree-header').getByRole("button", { name: "Manage", exact: true }).click();
|
|
const manage = page.getByRole("dialog", { name: "Manage selected book or list", exact: true });
|
|
await expect(manage.getByRole("button", { name: "Add address list", exact: true })).toBeVisible();
|
|
await expect(manage.getByRole("button", { name: "Edit address book", exact: true })).toBeVisible();
|
|
await expect(manage.locator('.form-section-separated').getByRole("button", { name: "Delete address book", exact: true })).toBeVisible();
|
|
expect(fixture.errors).toEqual([]);
|
|
expect(fixture.writes).toEqual([]);
|
|
});
|
|
|
|
test("Addressbook specialist controls retain permission blockers in German", async ({ page }) => {
|
|
const fixture = await mockAddresses(page);
|
|
await page.goto("/?address-explorer&language=de&read-only&theme=light");
|
|
await expect(page.getByRole("button", { name: "Adressbuch hinzufügen", exact: true })).toBeDisabled();
|
|
await page.getByRole("button", { name: "Import / Export", exact: true }).click();
|
|
const dialog = page.getByRole("dialog", { name: "Import / Export", exact: true });
|
|
await expect(dialog.getByRole("button", { name: "Kontakte importieren", exact: true })).toBeDisabled();
|
|
await expect(dialog.getByRole("heading", { name: "In das ausgewählte Adressbuch importieren", exact: true })).toBeVisible();
|
|
expect(fixture.errors).toEqual([]);
|
|
expect(fixture.writes).toEqual([]);
|
|
});
|