Initial curated itworx-knowledge-hub source from 3b1751d0d9dee551aa6994eee0093704dc334817
This commit is contained in:
commit
ad8ad252a9
825 files changed
+124216
No files matched your search
@@ -0,0 +1,232 @@
|
||||
import AxeBuilder from "@axe-core/playwright";
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import type {
|
||||
IngestionProposalDetail,
|
||||
IngestionProposalSummary,
|
||||
PaginatedIngestionProposals,
|
||||
} from "../../../src/frontend/src/api/intakeTypes";
|
||||
import type { SessionResponse } from "../../../src/frontend/src/api/types";
|
||||
import { authenticatedStatePath } from "../global-setup";
|
||||
|
||||
test.use({ storageState: authenticatedStatePath });
|
||||
|
||||
const proposalId = "8b8df97b-6015-4f7b-9bd0-9d9b2fb0e54a";
|
||||
const correlationId = "e2e-v3-intake-pilot";
|
||||
const proposalEndpoint = "/api/v1/ingestion-proposals";
|
||||
|
||||
function createProposalFixture(): IngestionProposalDetail {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
id: proposalId,
|
||||
correlationId,
|
||||
status: "ReadyForReview",
|
||||
revisionNumber: 7,
|
||||
sourceCount: 1,
|
||||
submitterId: "91d38469-1c14-43c5-ae32-c806b4906bd0",
|
||||
createdAt: now,
|
||||
destination: {
|
||||
pageId: "e3f5b858-cd07-4e6e-9bb8-63e1e1712ae4",
|
||||
parentPageId: null,
|
||||
title: "Runbook bestandsherstel",
|
||||
},
|
||||
metadata: {
|
||||
documentType: "Runbook",
|
||||
documentStatus: "Draft",
|
||||
projectId: null,
|
||||
risk: "medium",
|
||||
},
|
||||
mutationKind: "UpdatePage",
|
||||
manifest: "# Bestandsherstel\n\nControleer eerst de meest recente back-up.",
|
||||
analysis: "De wijziging voegt herstelstappen toe en verwijst naar een controleerbare back-up.",
|
||||
reviewerComment: "Controleer de herstelvolgorde en de genoemde revisie.",
|
||||
retryCount: 0,
|
||||
maxPublishRetries: 3,
|
||||
isRetryEligible: false,
|
||||
failedLifecycleStatus: null,
|
||||
failedProcessingStage: null,
|
||||
failureErrorType: null,
|
||||
failureMessage: null,
|
||||
sources: [{
|
||||
id: "6dce39d5-d022-48b6-90d6-6f1ea8ad0c93",
|
||||
fileName: "herstelstappen.md",
|
||||
mimeType: "text/markdown",
|
||||
byteSize: 512,
|
||||
digestSha256: "a".repeat(64),
|
||||
scanResult: "accepted",
|
||||
attachmentReferenceId: "08ab9571-5fb5-4a77-b151-8bdc4ef302cc",
|
||||
}],
|
||||
revisions: [],
|
||||
etag: "proposal-revision-7",
|
||||
publishedPageId: null,
|
||||
publishedPageRevisionNumber: null,
|
||||
attachments: [],
|
||||
expectedTargetETag: "page-revision-12",
|
||||
citations: [{
|
||||
label: "Back-upcontrole",
|
||||
pageId: "e3f5b858-cd07-4e6e-9bb8-63e1e1712ae4",
|
||||
excerpt: "Vergelijk de hash met de vorige volledige back-up.",
|
||||
confidence: 0.94,
|
||||
}],
|
||||
warnings: [{
|
||||
code: "manual-review",
|
||||
message: "Handmatige beoordeling vereist voor deze wijziging.",
|
||||
severity: "medium",
|
||||
}],
|
||||
duplicateCandidates: [],
|
||||
conflictCandidates: [{
|
||||
pageId: "e3f5b858-cd07-4e6e-9bb8-63e1e1712ae4",
|
||||
title: "Runbook bestandsherstel",
|
||||
kind: "target-page",
|
||||
}],
|
||||
diff: {
|
||||
before: "De huidige proceduretekst.",
|
||||
after: "De voorgestelde herstelprocedure met hashcontrole.",
|
||||
summary: "Voegt controle van de herstelhash toe.",
|
||||
},
|
||||
permissions: { canEdit: true, canDecide: true, canPublish: false },
|
||||
};
|
||||
}
|
||||
|
||||
async function installProposalFixture(page: Page): Promise<void> {
|
||||
let currentDetail = createProposalFixture();
|
||||
const toSummary = (detail: IngestionProposalDetail): IngestionProposalSummary => ({
|
||||
id: detail.id,
|
||||
correlationId: detail.correlationId,
|
||||
status: detail.status,
|
||||
revisionNumber: detail.revisionNumber,
|
||||
sourceCount: detail.sourceCount,
|
||||
submitterId: detail.submitterId ?? "91d38469-1c14-43c5-ae32-c806b4906bd0",
|
||||
createdAt: detail.createdAt ?? new Date().toISOString(),
|
||||
destination: detail.destination ?? null,
|
||||
metadata: detail.metadata,
|
||||
mutationKind: detail.mutationKind,
|
||||
});
|
||||
const queue: PaginatedIngestionProposals = {
|
||||
items: [toSummary(currentDetail)],
|
||||
nextCursor: null,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
total: 1,
|
||||
correlationId,
|
||||
};
|
||||
|
||||
// Keep the pilot flag disabled by default in the running service; this synthetic
|
||||
// UI contract explicitly opts the authenticated fixture session into the route.
|
||||
await page.route("**/api/v1/auth/session", async (route) => {
|
||||
const response = await route.fetch();
|
||||
if (!response.ok()) {
|
||||
await route.fulfill({ response });
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await response.json() as SessionResponse;
|
||||
await route.fulfill({
|
||||
response,
|
||||
json: { ...session, features: { ...session.features, ingestionProposals: true } },
|
||||
});
|
||||
});
|
||||
|
||||
await page.route(/\/api\/v1\/ingestion-proposals(?:\/|\?|$)/, async (route) => {
|
||||
const request = route.request();
|
||||
const pathname = new URL(request.url()).pathname;
|
||||
|
||||
if (request.method() === "GET" && pathname === proposalEndpoint) {
|
||||
await route.fulfill({ status: 200, json: { ...queue, items: [toSummary(currentDetail)] } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method() === "GET" && pathname === `${proposalEndpoint}/${proposalId}`) {
|
||||
await route.fulfill({ status: 200, json: currentDetail });
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.method() === "POST" && pathname === `${proposalEndpoint}/${proposalId}/approve`) {
|
||||
currentDetail = {
|
||||
...currentDetail,
|
||||
status: "Approved",
|
||||
revisionNumber: currentDetail.revisionNumber + 1,
|
||||
etag: "proposal-revision-8",
|
||||
};
|
||||
await route.fulfill({ status: 200, json: currentDetail });
|
||||
return;
|
||||
}
|
||||
|
||||
await route.continue();
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("V3 intakepilot: beoordeling op desktop en mobiel", () => {
|
||||
test("reviewer kan waarschuwingen, verschil, bestemming en beslissing toetsen met toetsenbord", async ({ page }, testInfo) => {
|
||||
const isMobile = testInfo.project.name === "chromium-mobile";
|
||||
await page.setViewportSize(isMobile ? { width: 390, height: 844 } : { width: 1440, height: 1000 });
|
||||
await installProposalFixture(page);
|
||||
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Operationeel overzicht" })).toBeVisible();
|
||||
await expect(page.getByRole("region", { name: "Kennisintake" })).toHaveCount(0);
|
||||
|
||||
if (isMobile) await page.getByRole("button", { name: "Navigatie openen" }).click();
|
||||
const intakeNavigationLink = page.getByRole("navigation", { name: "Hoofdnavigatie" }).getByRole("link", { name: "Intake", exact: true });
|
||||
await expect(intakeNavigationLink).toHaveAttribute("href", "/knowledge/intake");
|
||||
await intakeNavigationLink.click();
|
||||
await expect(page.getByRole("heading", { name: "Intakewachtrij" })).toBeVisible();
|
||||
const proposalLink = page.getByRole("link", { name: "Open voorstel Runbook bestandsherstel" });
|
||||
await proposalLink.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
|
||||
await expect(page.getByLabel("Voorsteloverzicht").getByRole("heading", { name: "Voorstel beoordelen" })).toBeVisible();
|
||||
await expect(page.locator('[data-status="ReadyForReview"]')).toContainText("Klaar voor beoordeling");
|
||||
await expect(page.getByRole("heading", { name: "Bronnen en herkomst" })).toBeVisible();
|
||||
|
||||
if (isMobile) {
|
||||
await expect(page.locator('[data-testid="ingestion-progress"]')).toHaveText("Stap 1 van 3: Bronnen");
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
|
||||
await page.getByRole("button", { name: "Volgende stap" }).press("Enter");
|
||||
await expect(page.locator('[data-testid="ingestion-progress"]')).toHaveText("Stap 2 van 3: Onderbouwing");
|
||||
await expect(page.locator('[data-mobile-step="sources"]')).toBeHidden();
|
||||
await expect(page.locator('[data-mobile-step="evidence"]')).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Waarschuwingen en conflicten" })).toBeVisible();
|
||||
await expect(page.getByText("Handmatige beoordeling vereist voor deze wijziging.")).toBeVisible();
|
||||
await expect(page.getByText("Tegenstrijdige pagina: Runbook bestandsherstel")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Voorgestelde inhoud" })).toBeVisible();
|
||||
await expect(page.getByText("De huidige proceduretekst.", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("De voorgestelde herstelprocedure met hashcontrole.", { exact: true })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Bestemming en beoordeling" })).toBeVisible();
|
||||
await expect(page.getByText("Runbook bestandsherstel", { exact: true })).toBeVisible();
|
||||
await expect(page.getByText("Verwachte paginaversie: page-revision-12")).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
|
||||
const evidenceAccessibility = await new AxeBuilder({ page }).analyze();
|
||||
expect(evidenceAccessibility.violations).toEqual([]);
|
||||
|
||||
// The mobile step moves focus to the active pane; normal Tab order then enters
|
||||
// the reviewer controls after the read-only warning and diff evidence.
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByRole("textbox", { name: "Voorstel (beoordelaar)" })).toBeFocused();
|
||||
await page.keyboard.press("Tab");
|
||||
await expect(page.getByRole("textbox", { name: "Onderbouwing (beoordelaar)" })).toBeFocused();
|
||||
|
||||
await page.getByRole("button", { name: "Volgende stap" }).press("Enter");
|
||||
await expect(page.locator('[data-testid="ingestion-progress"]')).toHaveText("Stap 3 van 3: Beslissing");
|
||||
await expect(page.locator('[data-mobile-step="evidence"]')).toBeHidden();
|
||||
await expect(page.locator('[data-mobile-step="decision"]')).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Goedkeuren" })).toBeVisible();
|
||||
|
||||
const decisionAccessibility = await new AxeBuilder({ page }).analyze();
|
||||
expect(decisionAccessibility.violations).toEqual([]);
|
||||
} else {
|
||||
await expect(page.getByRole("heading", { name: "Waarschuwingen en conflicten" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Voorgestelde inhoud" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Bestemming en beoordeling" })).toBeVisible();
|
||||
}
|
||||
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
const accessibility = await new AxeBuilder({ page }).analyze();
|
||||
expect(accessibility.violations).toEqual([]);
|
||||
|
||||
await page.getByRole("button", { name: "Goedkeuren" }).focus();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(page.locator('[data-status="Approved"]')).toContainText("Goedgekeurd");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user