feat(planning): ship holiday-aware planning and assistant upgrades

This commit is contained in:
2026-03-28 22:49:28 +01:00
parent 2a005794e7
commit 4f48afe7b4
151 changed files with 17738 additions and 1940 deletions
+71 -11
View File
@@ -1,4 +1,12 @@
import { expect, test } from "@playwright/test";
import { expect, test, type Page } from "@playwright/test";
async function signInAsAdmin(page: Page) {
await page.goto("/auth/signin");
await page.fill('input[type="email"]', "admin@capakraken.dev");
await page.fill('input[type="password"]', "admin123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/\/(dashboard|resources)/);
}
test.describe("Timeline", () => {
test.describe.configure({ mode: "serial" });
@@ -7,11 +15,7 @@ test.describe("Timeline", () => {
await page.addInitScript(() => {
localStorage.setItem("capakraken_theme", JSON.stringify({ mode: "dark" }));
});
await page.goto("/auth/signin");
await page.fill('input[type="email"]', "admin@capakraken.dev");
await page.fill('input[type="password"]', "admin123");
await page.click('button[type="submit"]');
await expect(page).toHaveURL(/\/(dashboard|resources)/);
await signInAsAdmin(page);
await page.goto("/timeline");
});
@@ -87,8 +91,13 @@ test.describe("Timeline", () => {
.first();
const allocationPopoverField = page.getByText("Hours / day");
const resourceHoverTarget = page.locator(".relative.overflow-hidden.touch-none").first();
await resourceHoverTarget.hover({ position: { x: 120, y: 20 } });
const resourceHoverTarget = page.getByTestId("timeline-resource-row-canvas").first();
const resourceHoverBox = await resourceHoverTarget.boundingBox();
expect(resourceHoverBox).not.toBeNull();
if (!resourceHoverBox) {
throw new Error("Expected a resource timeline row canvas to be available");
}
await page.mouse.move(resourceHoverBox.x + 120, resourceHoverBox.y + 20);
await expect(heatmapTooltip).toBeVisible();
await expect
.poll(async () => {
@@ -109,8 +118,19 @@ test.describe("Timeline", () => {
await expect(page.getByText(/projects/)).toBeVisible();
await page.waitForTimeout(500);
const projectHoverTarget = page.locator(".relative.overflow-hidden.touch-none").first();
await projectHoverTarget.hover({ position: { x: 120, y: 20 } });
const projectHoverTarget = page.getByTestId("timeline-project-resource-row-canvas").first();
const projectHoverBox = await projectHoverTarget.boundingBox();
const projectAllocation = page.locator("div[style*='top: 2px'][style*='bottom: 2px']").nth(1);
const projectAllocationBox = await projectAllocation.boundingBox();
expect(projectHoverBox).not.toBeNull();
expect(projectAllocationBox).not.toBeNull();
if (!projectHoverBox) {
throw new Error("Expected a project timeline row canvas to be available");
}
if (!projectAllocationBox) {
throw new Error("Expected a project allocation block to be available");
}
await page.mouse.move(projectAllocationBox.x + (projectAllocationBox.width / 2), projectHoverBox.y + 20);
await expect(heatmapTooltip).toBeVisible();
await expect
.poll(async () => {
@@ -118,8 +138,48 @@ test.describe("Timeline", () => {
})
.toBe("rgba(3, 7, 18, 0.96)");
const projectAllocation = page.locator("div[style*='top: 2px'][style*='bottom: 2px']").nth(1);
await projectAllocation.click({ button: "right" });
await expect(allocationPopoverField).toBeVisible();
});
test("shows resolved holiday overlays in the resource timeline and exposes the holiday name in the tooltip", async ({
page,
}) => {
await page.goto("/timeline?startDate=2026-04-01&days=14&eids=bruce.banner", {
waitUntil: "domcontentloaded",
});
const row = page.locator('[data-testid="timeline-resource-row-canvas"][data-resource-eid="bruce.banner"]').first();
await expect(row).toBeVisible();
const holidayBlock = row.locator(
'[data-testid="timeline-vacation-block"][data-vacation-type="PUBLIC_HOLIDAY"][data-vacation-note="Karfreitag"]',
).first();
await expect(holidayBlock).toBeVisible();
const rowBox = await row.boundingBox();
const holidayBox = await holidayBlock.boundingBox();
expect(rowBox).not.toBeNull();
expect(holidayBox).not.toBeNull();
if (!rowBox || !holidayBox) {
throw new Error("Expected timeline row and holiday block bounding boxes to be available");
}
await row.hover({
position: {
x: holidayBox.x - rowBox.x + holidayBox.width / 2,
y: holidayBox.y - rowBox.y + Math.min(holidayBox.height / 2, rowBox.height - 4),
},
});
const holidayTooltip = page
.locator("div.fixed.pointer-events-none.rounded-xl.border.border-amber-700\\/50")
.or(page.locator("div.fixed.pointer-events-none.rounded-xl").filter({ hasText: "Karfreitag" }))
.first();
await expect(holidayTooltip).toBeVisible();
await expect(holidayTooltip).toContainText("Karfreitag");
await expect(holidayTooltip).toContainText("3 April 2026");
});
});