63 lines
6.4 KiB
TypeScript
63 lines
6.4 KiB
TypeScript
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([])
|
||
})
|