fix(ui): ограничить preview OSPF без изменения применения
This commit is contained in:
1 parent
9f74cfebfe
commit
be54666661
10 files changed
+138
-15
No files matched your search
@@ -903,6 +903,7 @@ function RouteOptimizerContent({requestedServer,onSelectServer}:{requestedServer
|
||||
</span>
|
||||
</div>
|
||||
<RouteOptimizerOspfPreviewDataGrid
|
||||
key={previewKey}
|
||||
rows={ospfPreview?.interfaces ?? []}
|
||||
error={ospfPreviewError || null}
|
||||
loading={ospfPreviewLoading}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client"
|
||||
|
||||
import { dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
|
||||
import { DataGridContainer, DataGridScrollArea, DataGridTable, dataGridFeatures, type DataGridFeatures } from "@/components/reui/data-grid"
|
||||
|
||||
import { useMemo } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import type {OspfOptimizerObserved} from "@mmapp/contracts"
|
||||
import {
|
||||
type ColumnDef,
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
DATA_GRID_CELL_PAD_LAST,
|
||||
} from "@/components/data-grids/shared/data-grid-layout"
|
||||
import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { EmptyState } from "@/components/empty-state"
|
||||
import { NetworkIcon } from "lucide-react"
|
||||
|
||||
@@ -48,15 +49,21 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
error,
|
||||
loading,
|
||||
}: RouteOptimizerOspfPreviewDataGridProps) {
|
||||
const [query, setQuery] = useState("")
|
||||
const filteredRows = useMemo(() => {
|
||||
const value = query.trim().toLowerCase()
|
||||
return value ? rows.filter(row => `${row.interface} ${row.id}`.toLowerCase().includes(value)) : rows
|
||||
}, [rows, query])
|
||||
const columns = useMemo<ColumnDef<DataGridFeatures, OspfPreviewInterfaceRow>[]>(
|
||||
() => [
|
||||
{
|
||||
id: "interface",
|
||||
size: 120,
|
||||
accessorKey: "interface",
|
||||
header: ({ column }) => (
|
||||
<DataGridSortHeader column={column} title="Интерфейс" className="ml-1" />
|
||||
),
|
||||
cell: ({ row }) => <span className="font-mono">{row.original.interface}</span>,
|
||||
cell: ({ row }) => <span className="block truncate font-mono" title={row.original.interface}>{row.original.interface}</span>,
|
||||
meta: {
|
||||
headerTitle: "Интерфейс",
|
||||
headerClassName: DATA_GRID_CELL_PAD_FIRST,
|
||||
@@ -65,6 +72,7 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
},
|
||||
{
|
||||
id: "cost",
|
||||
size: 144,
|
||||
accessorKey: "currentCost",
|
||||
header: ({ column }) => <DataGridSortHeader column={column} title="Cost" />,
|
||||
cell: ({ row }) => {
|
||||
@@ -89,6 +97,7 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
},
|
||||
{
|
||||
id: "score",
|
||||
size: 96,
|
||||
accessorKey: "score",
|
||||
header: ({ column }) => <DataGridSortHeader column={column} title="Score" />,
|
||||
cell: ({ row }) => <span className="font-mono">{row.original.score}</span>,
|
||||
@@ -96,6 +105,7 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
},
|
||||
{
|
||||
id: "pingMs",
|
||||
size: 288,
|
||||
accessorKey: "pingMs",
|
||||
header: ({ column }) => <DataGridSortHeader column={column} title="Ping модели" />,
|
||||
cell: ({ row }) => <div><span className="font-mono">{row.original.pingMs}ms</span><p className="text-[10px] text-muted-foreground">{describeObserved(row.original.observed?.ping,"мс")}</p></div>,
|
||||
@@ -103,6 +113,7 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
},
|
||||
{
|
||||
id: "speed",
|
||||
size: 336,
|
||||
header: () => <span className="text-xs font-medium text-muted-foreground">Speed (dl/ul) · модель</span>,
|
||||
enableSorting: false,
|
||||
cell: ({ row }) => {
|
||||
@@ -121,12 +132,20 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
manualPagination: true,
|
||||
data: rows,
|
||||
manualPagination: false,
|
||||
autoResetPageIndex: false,
|
||||
initialState: { pagination: { pageIndex: 0, pageSize: 50 }, columnPinning: { start: ["interface"], end: [] } },
|
||||
data: filteredRows,
|
||||
columns,
|
||||
getRowId: (row) => row.id,
|
||||
})
|
||||
|
||||
const { pageIndex, pageSize } = table.state.pagination
|
||||
useEffect(() => {
|
||||
const lastPage = Math.max(0, Math.ceil(filteredRows.length / pageSize) - 1)
|
||||
if (pageIndex > lastPage) table.setPageIndex(lastPage)
|
||||
}, [filteredRows.length, pageIndex, pageSize, table])
|
||||
|
||||
if (error && rows.length === 0) {
|
||||
return (
|
||||
<p role="alert" className="px-3 py-2 text-xs text-destructive">Ошибка preview: {error}</p>
|
||||
@@ -144,18 +163,28 @@ function RouteOptimizerOspfPreviewDataGrid({
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section aria-label="Preview интерфейсов OSPF" className="min-w-0">
|
||||
{error && <p role="alert" className="px-3 py-2 text-xs text-warning">Ошибка preview: {error}. Последний снимок; актуальность неизвестна.</p>}
|
||||
{loading && rows.length > 0 && <p role="status" className="px-3 py-2 text-xs text-muted-foreground">Обновление preview OSPF…</p>}
|
||||
<div className="flex min-w-0 flex-col gap-2 px-3 py-3">
|
||||
<Input aria-label="Поиск интерфейсов preview OSPF" placeholder="Имя интерфейса…" value={query} onChange={event => { setQuery(event.target.value); table.setPageIndex(0) }} />
|
||||
<p role="status" className="text-xs text-muted-foreground">Найдено {filteredRows.length} из {rows.length} интерфейсов. Фильтр меняет только отображение preview.</p>
|
||||
</div>
|
||||
<DataGridShell
|
||||
table={table}
|
||||
recordCount={rows.length}
|
||||
recordCount={filteredRows.length}
|
||||
pagination={filteredRows.length > 50 || pageSize !== 50}
|
||||
emptyMessage="Нет интерфейсов по запросу"
|
||||
isLoading={loading && rows.length === 0}
|
||||
loadingMode="skeleton"
|
||||
tableClassNames={{ headerRow: "border-b bg-muted/30", bodyRow: "group/row" }}
|
||||
tableLayout={{ dense: true }}
|
||||
/>
|
||||
</>
|
||||
tableLayout={{ dense: true, width: "fixed", columnsPinnable: true }}
|
||||
>
|
||||
<DataGridContainer border={false}>
|
||||
<DataGridScrollArea orientation="horizontal"><DataGridTable /></DataGridScrollArea>
|
||||
</DataGridContainer>
|
||||
</DataGridShell>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
## Оставшиеся специализированные grids
|
||||
|
||||
Аудит12.3.36 от2026-10-04: точный поиск `manualPagination: true` в components/data-grids —9 файлов после12.3.49. Это не9 доказанных дефектов: есть фиксированный источник, demo, неподключённый компонент и ограниченные срезы. Файлы ниже сопоставлены с действующими JSX-потребителями; ограничения установлены чтением кода, не нагрузочной приёмкой API.
|
||||
Аудит12.3.36 от2026-10-04: точный поиск `manualPagination: true` в components/data-grids —8 файлов после12.3.53. Это не8 доказанных дефектов: есть фиксированный источник, demo, неподключённый компонент и ограниченные срезы. Файлы ниже сопоставлены с действующими JSX-потребителями; ограничения установлены чтением кода, не нагрузочной приёмкой API.
|
||||
|
||||
| Файл | Источник и необходимая проверка |
|
||||
| --- | --- |
|
||||
@@ -24,12 +24,11 @@
|
||||
| [probes-schedule-data-grid.tsx](../components/data-grids/probes-schedule-data-grid.tsx) | Только demo ScheduleTab/INIT_RULES3; live использует ProbesSpeedSchedule. Demo список расширяется пользователем; не доказательство live API. |
|
||||
| [route-optimizer-comm-recs-data-grid.tsx](../components/data-grids/route-optimizer-comm-recs-data-grid.tsx) | Live commRecs slice(0,24) на HomeCard, не на страницу. Проверить количество HomeCard и честность ограничения рекомендаций. |
|
||||
| [route-optimizer-full-routes-data-grid.tsx](../components/data-grids/route-optimizer-full-routes-data-grid.tsx) | Live сочетания WAN×jumpHost×exitNode на HomeCard; ограничение DOM не устраняет стоимость расчёта полного массива. |
|
||||
| [route-optimizer-ospf-preview-data-grid.tsx](../components/data-grids/route-optimizer-ospf-preview-data-grid.tsx) | Полный interfaces из preview; проверить global сортировку/unknown и apply всего подтверждённого preview. |
|
||||
| [route-optimizer-wan-matrix-data-grid.tsx](../components/data-grids/route-optimizer-wan-matrix-data-grid.tsx) | Live сочетания WAN×jumpHost на HomeCard; динамические столбцы/много HomeCard требуют собственного профиля. |
|
||||
| [subusers-data-grid.tsx](../components/data-grids/subusers-data-grid.tsx) | Поиск импортов/JSX в app/components не нашёл потребителя: компонент экспортируется, но не подключён. Не доказывает дефект экрана /users. |
|
||||
| [traffic-flows-data-grid.tsx](../components/data-grids/traffic-flows-data-grid.tsx) | FlowAnalyticsPanel/conversationsList: backend traffic-flow-analytics.ts top=min(50,max(10,settings.topN)), slice(0,top). Это top slice, не полный каталог/серверная pagination. Проверить сообщение об ограничении50 и связывание с графиками. |
|
||||
|
||||
Очередь: OSPF topology/node details → optimizer preview/комбинаторика. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope.
|
||||
Очередь: many HomeRouterCard/optimizer матрицы/комбинаторика. OSPF topology/node details/preview приняты локально в12.3.50–53. Scheduler11, traffic cap50 и commRecs24 проверять на реальной границе, не искусственно подставлять1000 в недоступный DTO. Demo и неиспользуемый Subusers отделены от live приёмки. Все новые профили должны сохранять search/sort/refresh/clamp, точный target и права,320px и URL scope.
|
||||
|
||||
Принятые после ранней карты: OSPF routes/BFD, WireGuard interfaces/peers/detail, VXLAN/GRE tunnels. Pagination сохраняет полный DTO в памяти и не доказывает серверную производительность. Текущий журнал: [ux-ui-progress.md](ux-ui-progress.md).
|
||||
|
||||
@@ -100,3 +99,5 @@ Statistics pivot в backend/src/services/statistics-aggregate.ts огранич
|
||||
12.3.51: OSPF node=1/1000 neighbors+1000 interfaces →50+50 rows/635 panel nodes/2335 page DOM.4+15+1 browser/type/lint/build. Search/full counts/refresh/clamp/320px/axe приняты; parallel link labels далее12.3.52.
|
||||
|
||||
12.3.52: OSPF parallel1000 between2 nodes →8 raw edges per page/1 visual line, полный источник/state/cost сохранён.9+1 browser/type/lint/build; screenshot1440 принят. Далее optimizer preview.
|
||||
|
||||
12.3.53: OSPF preview1000 interfaces/245293 bytes →50 rows/924 region nodes/1610 page DOM.47+16 main/8 context/type/lint/build. Полный POST/receipt1000 при search1, sort/cache/clamp/320px приняты.8 manualPagination:true; далее many HomeRouterCard/матрицы.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# План реализации UX/UI по пятичасовым окнам
|
||||
|
||||
Актуальное продолжение 2026-10-04: 12.3.52 принят локально — параллельные связи OSPF объединены визуально без общего выдуманного cost/state;9+1 browser/type/lint/build. Далее12.3.53 — большой OSPF preview оптимизатора; затем остальные матрицы/общая регрессия. Внешняя12.5 отдельно.
|
||||
Актуальное продолжение 2026-10-04: 12.3.53 принят локально: OSPF preview оптимизатора1000→50 строк, полный поиск/sort/apply/refresh/cache/clamp/320px.47+16 main/8 context/type/lint/build. Далее12.3.54 — профиль множества HomeRouterCard; затем ограничение карточек/матриц и общая регрессия.8 specialized manualPagination:true остаются; внешняя12.5 отдельно.
|
||||
|
||||
Общий план проекта MikrotikManager. Основан на согласованном плане из задачи; точка продолжения обновлена 2026-10-04. Источник фактического прогресса, проверок и ограничений — [журнал реализации](ux-ui-progress.md). Этот документ задаёт объём и критерии завершения, журнал фиксирует результат.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# UX/UI: журнал реализации
|
||||
|
||||
Актуальное продолжение 2026-10-04: 12.3.52 принят локально — параллельные связи OSPF объединены визуально без общего выдуманного cost/state;9+1 browser/type/lint/build. Далее12.3.53 — большой OSPF preview оптимизатора; затем остальные матрицы/общая регрессия. Внешняя12.5 отдельно.
|
||||
Актуальное продолжение 2026-10-04: 12.3.53 принят локально: OSPF preview оптимизатора1000→50 строк, полный поиск/sort/apply/refresh/cache/clamp/320px.47+16 main/8 context/type/lint/build. Далее12.3.54 — профиль множества HomeRouterCard; затем ограничение карточек/матриц и общая регрессия.8 specialized manualPagination:true остаются; внешняя12.5 отдельно.
|
||||
|
||||
Общий план: [UX/UI по этапам и пятичасовым окнам](ux-ui-plan.md). План задаёт объём, этот журнал фиксирует выполненные блоки и ограничения.
|
||||
|
||||
|
||||
@@ -621,3 +621,12 @@ TopologyGraph группирует видимые edges по неориенти
|
||||
9/9 browser (large topology/details/identity/states/layout/keyboard/reduced motion) и финальный parallel1/1 прошли.1000 links between2 nodes, page8 links →одна visual group, aria-label Full/cost1 и Down/cost2, подпись8 связей. TypeScript без incremental/ESLint страницы/scope/теста и окончательный production build прошли. [Группировка1440px](ux-ui/screens/ospf-parallel-links-1440.png) сохранена и просмотрена; PNG после проверки показал недостаточный opacity, исправлен и повторно проверен.
|
||||
|
||||
Следующий12.3.53: OSPF preview оптимизатора1000 rows/полная apply-ветка при pagination/search; затем матрицы и many HomeRouterCard.9 manualPagination:true specialized grids остаются. Общая main/context регрессия после delta и внешняя12.5 отдельно. Локальный коммит без push.
|
||||
|
||||
|
||||
## 12.3.53 — большой OSPF preview оптимизатора (2026-10-04)
|
||||
|
||||
Штатные ReUI pages50/client sorting/clamp и ScrollArea, закреплённый interface120px оставляет место cost на320px. Именованная область preview включает поиск полного массива id/interface, полные counts и явное пояснение, что фильтр меняет только отображение. Контекстный key server/weight сбрасывает поиск/страницу при смене устройства; фоновый refresh сохраняет их. Применение не использует фильтр/видимую страницу, существующий POST pingWeight и строгий receipt сохранены.
|
||||
|
||||
47/47 профильных main,16/16 final preview/large/new context-search и8/8 backend/mode context прошли.1000 interfaces/245293 JSON bytes →50 rows/924 preview region DOM/1610 page DOM. Search ether1000→1 row/receipt1000 applied; Cost sort full array, page2/refresh/cache503/disabled apply/shrink1000→1/server+keep,320px/pinning/axe, смена server очищает no-match query. TypeScript без incremental/полный ESLint grid/page/двух тестов/production build прошли. [Профиль](ux-ui/acceptance/optimizer-ospf-preview-2026-10-04.json), [320px](ux-ui/screens/optimizer-ospf-preview-320.png) сохранены и просмотрены. API fixture receipt исправлен по существующему контракту currentCost=from; контракт не ослаблен. Это проверка HTTP live-ветки с подменёнными ответами, не реальное применение RouterOS. Полный массив остаётся в памяти.
|
||||
|
||||
Осталось8 manualPagination:true specialized grids. Следующий12.3.54 — исходный профиль1000 HomeRouterCard с полным servers DTO; затем bounded cards/full-route/wan-matrix, честность24 commRecs. Общая main/context после delta и внешняя12.5 отдельно. Локальный коммит без push.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"count": 1000,
|
||||
"bytes": 245293,
|
||||
"rendered": 50,
|
||||
"gridNodes": 924,
|
||||
"pageNodes": 1610
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
@@ -0,0 +1,64 @@
|
||||
import { test, expect } from "@playwright/test"
|
||||
import { writeFileSync } from "node:fs"
|
||||
import axe from "axe-core"
|
||||
import { optimizerOspfPreviewSchema } from "../../lib/route-optimizer-response"
|
||||
|
||||
test("Оптимизатор1000 preview: полный apply при поиске, страницы и кэш", async ({ page }, info) => {
|
||||
test.setTimeout(90000)
|
||||
let interfaces = Array.from({ length: 1000 }, (_, i) => ({ id: `i${i+1}`, interface: `ether${i+1}`, currentCost: i+1, optimalCost: i+1001, score: i+1, pingMs: 10, dlMbps: 100, ulMbps: 50 }))
|
||||
let applied = false, failed = false, previews = 0, writes = 0
|
||||
const preview = () => optimizerOspfPreviewSchema.parse({ serverId: 1, serverName: "Роутер 1", pingWeight: 60, interfacesTotal: interfaces.length, changedCount: applied ? 0 : interfaces.length, unchangedCount: applied ? interfaces.length : 0, interfaces: interfaces.map(row => applied ? { ...row, currentCost: row.optimalCost } : row), changes: applied ? [] : interfaces })
|
||||
await page.route("**/health", r => r.fulfill({ json: { ok: true } }))
|
||||
await page.route("**/api/**", r => {
|
||||
const path = new URL(r.request().url()).pathname
|
||||
if (path.endsWith("/ospf/optimize/preview")) { previews++; return r.fulfill(failed ? { status: 503, json: { error: "Preview недоступен1000" } } : { json: preview() }) }
|
||||
if (path.endsWith("/ospf/optimize")) {
|
||||
writes++; expect(r.request().postDataJSON()).toEqual({ pingWeight: 60 }); applied = true
|
||||
return r.fulfill({ json: { serverId: 1, serverName: "Роутер 1", pingWeight: 60, optimizedCount: interfaces.length, applied: interfaces.map(row => ({ interface: row.interface, from: row.currentCost, to: row.optimalCost })), interfaces } })
|
||||
}
|
||||
return r.fulfill({ json: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? [{ id: 1, name: "Роутер 1", host: "192.0.2.1", type: "home-router", country: "RU", enabled: true, status: null }] : path === "/api/filters/rules" ? { rulesets: [] } : path === "/api/uptime/speed-probes" ? { probes: [] } : path === "/api/ospf/catalog" ? { interfaces: [], instances: [], neighbors: [], bfdSessions: [], routes: [], failures: [] } : {} })
|
||||
})
|
||||
await page.goto("/route-optimizer?server=1&keep=1")
|
||||
const grid = page.getByRole("table").filter({ hasText: "Speed (dl/ul)" })
|
||||
const rows = grid.locator("tr[data-row-id]")
|
||||
await expect(rows).toHaveCount(50)
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", "i1")
|
||||
const root = page.getByRole("region", { name: "Preview интерфейсов OSPF", exact: true })
|
||||
const profile = { count: interfaces.length, bytes: Buffer.byteLength(JSON.stringify(preview())), rendered: await rows.count(), gridNodes: await root.locator("*").count(), pageNodes: await page.locator("*").count() }
|
||||
console.log("Optimizer preview", JSON.stringify(profile)); writeFileSync(info.outputPath("optimizer-ospf-preview-profile.json"), JSON.stringify(profile, null, 2))
|
||||
await root.getByRole("button", { name: "Следующая страница", exact: true }).click()
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", "i51")
|
||||
const before = previews; await page.getByRole("button", { name: "Обновить preview OSPF", exact: true }).click()
|
||||
await expect.poll(() => previews).toBeGreaterThan(before); await expect(rows.first()).toHaveAttribute("data-row-id", "i51")
|
||||
const search = page.getByRole("textbox", { name: "Поиск интерфейсов preview OSPF" })
|
||||
await search.fill("ether1000"); await expect(rows).toHaveCount(1); await expect(rows.first()).toHaveAttribute("data-row-id", "i1000")
|
||||
await expect(page.getByRole("status").filter({ hasText: "Фильтр меняет только отображение" })).toContainText("Найдено 1 из 1000")
|
||||
await page.getByRole("button", { name: "Оптимизировать OSPF", exact: true }).click()
|
||||
await expect(page.getByRole("status").filter({ hasText: "Последнее подтверждение API" })).toContainText("изменено интерфейсов — 1000")
|
||||
expect(writes).toBe(1); await expect(page.getByText("0 из 1000 изменятся", { exact: true })).toBeVisible()
|
||||
await expect(rows).toHaveCount(1)
|
||||
await search.fill(""); await expect(rows).toHaveCount(50)
|
||||
await grid.getByRole("button", { name: "Cost", exact: true }).click()
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", "i1000")
|
||||
await root.getByRole("button", { name: "Следующая страница", exact: true }).click()
|
||||
const previous = await rows.first().getAttribute("data-row-id")
|
||||
failed = true; await page.getByRole("button", { name: "Обновить preview OSPF", exact: true }).click()
|
||||
await expect(page.getByRole("alert").filter({ hasText: "Последний снимок" })).toBeVisible()
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", previous!)
|
||||
await expect(page.getByRole("button", { name: "Оптимизировать OSPF", exact: true })).toBeDisabled()
|
||||
await page.setViewportSize({ width: 320, height: 1000 })
|
||||
const scroll = root.locator('[data-slot="scroll-area-viewport"]')
|
||||
await scroll.evaluate(el => { el.scrollLeft = el.scrollWidth })
|
||||
expect(await rows.first().getByRole("cell").first().evaluate(el => { const a=el.getBoundingClientRect(), v=el.closest('[data-slot="scroll-area-viewport"]')!.getBoundingClientRect(); return a.left>=v.left-1 && a.right<=v.right+1 })).toBe(true)
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(320)
|
||||
await scroll.evaluate(el => { el.scrollLeft = 0 })
|
||||
await grid.evaluate(el => el.scrollIntoView({ block: "start" }))
|
||||
await page.addStyleTag({ content: "nextjs-portal{display:none!important}" })
|
||||
await page.screenshot({ path: info.outputPath("optimizer-ospf-preview-320.png"), animations: "disabled" })
|
||||
await page.addScriptTag({ content: axe.source })
|
||||
expect((await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[aria-label="Preview интерфейсов OSPF"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"] } }))).violations.map(v => ({ id: v.id, nodes: v.nodes.map(n => n.target) }))).toEqual([])
|
||||
failed = false; interfaces = interfaces.slice(0, 1)
|
||||
await page.getByRole("button", { name: "Обновить preview OSPF", exact: true }).click()
|
||||
await expect(rows).toHaveCount(1); await expect(rows.first()).toHaveAttribute("data-row-id", "i1")
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "1" && url.searchParams.get("keep") === "1")
|
||||
})
|
||||
@@ -63,3 +63,15 @@ for(const server of ["999","3","abc","01",""])test("Оптимизатор: не
|
||||
await expect(page.getByText("0 iface · 0 area",{exact:true})).toBeVisible()
|
||||
await expect(page.getByRole("button",{name:"Оптимизировать OSPF",exact:true})).toBeEnabled()
|
||||
})
|
||||
|
||||
|
||||
test("Оптимизатор: поиск preview сбрасывается при смене устройства", async ({ page }) => {
|
||||
await base(page); await page.goto("/route-optimizer?server=1&keep=value")
|
||||
const search = page.getByRole("textbox", { name: "Поиск интерфейсов preview OSPF" })
|
||||
await search.fill("несуществующий")
|
||||
await expect(page.getByText("Нет интерфейсов по запросу", { exact: true })).toBeVisible()
|
||||
await page.getByRole("combobox", { name: "Устройство OSPF", exact: true }).selectOption("2")
|
||||
await expect(search).toHaveValue("")
|
||||
await expect(page.getByRole("table").filter({ hasText: "Speed (dl/ul)" }).locator("tr[data-row-id]")).toHaveCount(1)
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "2" && url.searchParams.get("keep") === "value")
|
||||
})
|
||||
Reference in new issue
Block a user