Files
MikrotikManager/tests/e2e/large-statistics-pivot.spec.ts
T

63 lines
6.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from "@playwright/test"
import axe from "axe-core"
import { mkdirSync } from "node:fs"
import { statisticsDtoSchema, statisticsPivotDtoSchema } from "../../packages/contracts/src/statistics"
for (const width of [1440, 320]) test("Pivot51×16: границы, сортировка, прокрутка и срез/" + width, async ({ page }, info) => {
await page.setViewportSize({ width, height: 1000 })
const columns = Array.from({ length: 16 }, (_, i) => ({ id: i === 15 ? "__other__" : "service-" + (i + 1), label: i === 15 ? "Прочие сервисы" : "Сервис " + (i + 1), total: 0 }))
const rows = Array.from({ length: 51 }, (_, i) => ({ id: i === 50 ? "__other__" : String(i + 1), label: i === 50 ? "Прочие клиенты" : "Клиент " + String(i + 1).padStart(4, "0"), total: 0, cells: Object.fromEntries(columns.map((c, j) => [c.id, i === 0 && j === 0 ? 0 : (i + 1) * (j + 1)])) }))
for (const row of rows) row.total = Object.values(row.cells).reduce((sum, n) => sum + n, 0)
for (const col of columns) col.total = rows.reduce((sum, row) => sum + row.cells[col.id], 0)
const pivot = statisticsPivotDtoSchema.parse({ rowDim: "user", colDim: "service", metric: "bytes", rows, columns, otherBytes: rows.at(-1)!.total + columns.at(-1)!.total - rows.at(-1)!.cells.__other__ })
const reads: URL[] = [], writes: string[] = []
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
await page.route("**/api/**", r => {
const url = new URL(r.request().url()); reads.push(url)
if (r.request().method() !== "GET") writes.push(url.pathname)
return r.fulfill({ json: url.pathname === "/api/statistics/pivot" ? pivot : url.pathname === "/api/statistics" ? statisticsDtoSchema.parse({ from: url.searchParams.get("from"), to: url.searchParams.get("to"), grain: "day", kpis: { bytes: rows.reduce((sum, row) => sum + row.total, 0), packets: 0, avgBps: 0, users: 51, servers: 0, ifaces: 0, topCountry: "", topService: "" }, series: [], users: [], servers: [], interfaces: [], countries: [], services: [], asns: [] }) : url.pathname === "/api/auth/config" ? { required: false } : url.pathname === "/api/servers" ? [] : {} })
})
const target = "/statistics?from=2026-09-01&to=2026-09-03&view=pivot&pivotRow=user&pivotCol=service"
const start = Date.now(); await page.goto(target)
const rendered = page.locator("tr[data-row-id]")
await expect(rendered).toHaveCount(50)
const sample = { fixtureRows: 51, fixtureColumns: 16, fixtureBytes: Buffer.byteLength(JSON.stringify(pivot)), renderedRows: await rendered.count(), domNodes: await page.locator("*").count(), readyMs: Date.now() - start, width }
console.log(JSON.stringify(sample)); await info.attach("wide-pivot-metrics", { body: JSON.stringify(sample), contentType: "application/json" })
expect(sample.domNodes).toBeLessThan(10000)
await expect(page.getByText("1–50 из 51", { exact: true })).toBeVisible()
await page.getByRole("button", { name: "Следующая страница", exact: true }).click()
await expect(rendered).toHaveCount(1); await expect(rendered.first()).toContainText("Прочие клиенты")
await page.getByRole("button", { name: "Предыдущая страница", exact: true }).click()
await page.getByRole("table").getByRole("button", { name: "Итого", exact: true }).click()
await expect(rendered.first()).toHaveAttribute("data-row-id", "__other__")
await expect(rendered.nth(1)).toHaveAttribute("data-row-id", "50")
const scroll = page.locator('[data-slot="data-grid"] [data-slot="scroll-area-viewport"]')
await expect(scroll).toHaveCount(1)
expect(await scroll.evaluate(el => el.scrollWidth > el.clientWidth)).toBe(true)
await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth })
await expect.poll(() => scroll.evaluate(el => el.scrollLeft)).toBeGreaterThan(0)
const pinned = rendered.first().getByRole("cell").first()
expect(await pinned.evaluate(el => { const cell = el.getBoundingClientRect(), viewport = el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(); return cell.left >= viewport.left - 1 && cell.right <= viewport.right + 1 })).toBe(true)
expect(await rendered.first().getByRole("cell").last().evaluate(el => { const total = el.getBoundingClientRect(), viewport = el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(), pinned = el.closest("tr")!.querySelector("td")!.getBoundingClientRect(); return total.left >= pinned.right - 1 && total.right <= viewport.right + 1 })).toBe(true)
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
await expect(rendered.first().getByRole("button")).toHaveCount(0)
await expect(rendered.nth(1).getByRole("cell").nth(16).getByRole("button")).toHaveCount(0)
await page.addScriptTag({ content: axe.source })
const violations = await page.evaluate(async () => (await (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[data-slot="data-grid"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } })).violations)
expect(violations).toEqual([])
await page.addStyleTag({ content: "nextjs-portal { display: none !important; }" })
mkdirSync("docs/ux-ui/screens", { recursive: true })
await page.screenshot({ path: "docs/ux-ui/screens/wide-statistics-pivot-" + width + ".png", animations: "disabled" })
const lastCell = rendered.nth(1).getByRole("cell").nth(15).getByRole("button")
await lastCell.focus()
expect(await lastCell.evaluate(el => { const value = el.getBoundingClientRect(), viewport = el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(), pinned = el.closest("tr")!.querySelector("td")!.getBoundingClientRect(); return value.left >= pinned.right - 1 && value.right <= viewport.right + 1 })).toBe(true)
await page.keyboard.press("Enter")
await expect(page).toHaveURL(/view=explore/)
const selected = new URL(page.url()); expect(selected.searchParams.get("userId")).toBe("50"); expect(selected.searchParams.get("service")).toBe("service-15")
expect(reads.some(url => url.pathname === "/api/statistics" && url.searchParams.get("userId") === "50" && url.searchParams.get("service") === "service-15")).toBe(true)
await page.goBack(); await expect(rendered).toHaveCount(50)
await page.reload(); await expect(rendered).toHaveCount(50)
await expect(page.getByRole("button", { name: "Следующая страница", exact: true })).toBeVisible()
expect(writes).toEqual([])
})