fix(ui): ограничить боковой каталог устройств
This commit is contained in:
1 parent
2bdcc1621f
commit
1cce712833
6 files changed
+141
-10
No files matched your search
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from "react"
|
||||
import { useEffect, useMemo, useRef, useState, useId, type KeyboardEvent, type ReactNode } from "react"
|
||||
import type { ServerStatus, ServerType } from "@/lib/data"
|
||||
import { Flag } from "@/components/flag"
|
||||
import { StatusDot } from "@/components/status-dot"
|
||||
@@ -27,6 +27,9 @@ import {
|
||||
InputGroupInput,
|
||||
} from "@/components/ui/input-group"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useTable } from "@tanstack/react-table"
|
||||
import { DataGrid, DataGridPagination, dataGridFeatures } from "@/components/reui/data-grid"
|
||||
import { DATA_GRID_LABELS_RU } from "@/lib/data-grid-labels-ru"
|
||||
import { LayersIcon, SearchIcon, ServerIcon } from "lucide-react"
|
||||
|
||||
/** Preview: https://reui.io/preview/base/list-9 · https://reui.io/docs/components/base/icon-tile · https://reui.io/docs/components/base/frame · https://reui.io/docs/components/base/badge */
|
||||
@@ -63,7 +66,7 @@ function typeBadgeLabel(type: ServerType): string {
|
||||
|
||||
function ServerTypeBadge({ type }: { type: ServerType }) {
|
||||
return (
|
||||
<Badge variant={typeBadgeVariant(type)} size="xs" className="font-mono">
|
||||
<Badge variant={typeBadgeVariant(type)} size="xs" className="font-mono text-foreground">
|
||||
{typeBadgeLabel(type)}
|
||||
</Badge>
|
||||
)
|
||||
@@ -112,6 +115,7 @@ function ServerTileRail({
|
||||
preserveSelection?: boolean
|
||||
className?: string
|
||||
}) {
|
||||
const railId = useId()
|
||||
const [query, setQuery] = useState("")
|
||||
const q = query.trim().toLowerCase()
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
@@ -158,6 +162,35 @@ function ServerTileRail({
|
||||
if (resolvedId && resolvedId !== selectedId) onSelect(resolvedId)
|
||||
}, [onSelect, resolvedId, selectedId])
|
||||
|
||||
const orderedItems = useMemo(() => groups.flatMap(group => group.items), [groups])
|
||||
const table = useTable({
|
||||
features: dataGridFeatures,
|
||||
data: orderedItems,
|
||||
columns: [],
|
||||
getRowId: item => item.id,
|
||||
manualPagination: false,
|
||||
autoResetPageIndex: false,
|
||||
initialState: { pagination: { pageIndex: 0, pageSize: 50 } },
|
||||
})
|
||||
const { pageIndex, pageSize } = table.state.pagination
|
||||
useEffect(() => {
|
||||
const lastPage = Math.max(0, Math.ceil(orderedItems.length / pageSize) - 1)
|
||||
if (pageIndex > lastPage) table.setPageIndex(lastPage)
|
||||
}, [orderedItems.length, pageIndex, pageSize, table])
|
||||
const setPageIndex = table.setPageIndex
|
||||
const selectedIndex = orderedItems.findIndex(item => item.id === resolvedId)
|
||||
useEffect(() => {
|
||||
setPageIndex(selectedIndex < 0 ? 0 : Math.floor(selectedIndex / pageSize))
|
||||
}, [resolvedId, selectedIndex, q, pageSize, setPageIndex])
|
||||
useEffect(() => {
|
||||
const viewport = listRef.current?.closest<HTMLElement>('[data-slot="scroll-area-viewport"]')
|
||||
viewport?.scrollTo({ top: 0 })
|
||||
listRef.current?.querySelector<HTMLElement>('[aria-selected="true"]')?.scrollIntoView({ block: "nearest" })
|
||||
}, [pageIndex, resolvedId, q, pageSize])
|
||||
const visibleIds = new Set(table.getRowModel().rows.map(row => row.id))
|
||||
const visibleGroups = groups.map(group => ({ ...group, items: group.items.filter(item => visibleIds.has(item.id)) })).filter(group => group.items.length > 0)
|
||||
const activeId = resolvedId === ALL_SERVERS_ID && showAllTile || visibleIds.has(resolvedId) ? resolvedId : undefined
|
||||
|
||||
const selectableIds = useMemo(() => {
|
||||
const ids: string[] = []
|
||||
if (showAllTile) ids.push(ALL_SERVERS_ID)
|
||||
@@ -211,6 +244,7 @@ function ServerTileRail({
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGrid table={table} recordCount={orderedItems.length} i18n={{ labels: { ...DATA_GRID_LABELS_RU, goToPage: page => `Страница устройств ${page}` } }}>
|
||||
<Frame dense spacing="sm" className={cn("flex h-full min-h-0 w-full flex-col", className)}>
|
||||
<FramePanel className="flex min-h-0 flex-1 flex-col gap-0 p-0">
|
||||
<FrameHeader className="flex flex-col gap-2 border-b px-3 py-2">
|
||||
@@ -249,12 +283,13 @@ function ServerTileRail({
|
||||
className="flex flex-col gap-0.5 p-1.5"
|
||||
role="listbox"
|
||||
aria-label="Серверы"
|
||||
aria-activedescendant={resolvedId ? `server-tile-${resolvedId}` : undefined}
|
||||
aria-activedescendant={activeId ? `${railId}-server-tile-${activeId}` : undefined}
|
||||
tabIndex={0}
|
||||
onKeyDown={handleListKeyDown}
|
||||
>
|
||||
{showAllTile ? (
|
||||
<ServerTileButton
|
||||
id={`${railId}-server-tile-${ALL_SERVERS_ID}`}
|
||||
item={allItem}
|
||||
selected={resolvedId === ALL_SERVERS_ID}
|
||||
onSelect={() => onSelect(ALL_SERVERS_ID)}
|
||||
@@ -267,7 +302,7 @@ function ServerTileRail({
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
{groups.map((group) => (
|
||||
{visibleGroups.map((group) => (
|
||||
<div key={group.site || "__flat__"} className="flex flex-col gap-0.5">
|
||||
{groupBySite && group.site ? (
|
||||
<p className="sticky top-0 z-[1] bg-background/95 px-2 py-1 text-[10px] font-bold uppercase tracking-wide text-muted-foreground backdrop-blur-sm">
|
||||
@@ -277,6 +312,7 @@ function ServerTileRail({
|
||||
{group.items.map((item) => (
|
||||
<ServerTileButton
|
||||
key={item.id}
|
||||
id={`${railId}-server-tile-${item.id}`}
|
||||
item={item}
|
||||
selected={resolvedId === item.id}
|
||||
onSelect={() => onSelect(item.id)}
|
||||
@@ -307,12 +343,15 @@ function ServerTileRail({
|
||||
) : null}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
{(orderedItems.length > 50 || pageSize !== 50) && <DataGridPagination moreLimit={3} sizes={[25, 50, 100]} rowsPerPageLabel="Устройств на странице" previousPageLabel="Предыдущие устройства" nextPageLabel="Следующие устройства" className="grow-0 shrink-0 border-t px-2 sm:flex-col [&>div]:flex-col [&>div]:gap-2" />}
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
</DataGrid>
|
||||
)
|
||||
}
|
||||
|
||||
function ServerTileButton({
|
||||
id,
|
||||
item,
|
||||
selected,
|
||||
onSelect,
|
||||
@@ -321,6 +360,7 @@ function ServerTileButton({
|
||||
showSite = true,
|
||||
icon,
|
||||
}: {
|
||||
id: string
|
||||
item: ServerTileItem
|
||||
selected: boolean
|
||||
onSelect: () => void
|
||||
@@ -337,7 +377,7 @@ function ServerTileButton({
|
||||
|
||||
return (
|
||||
<Item
|
||||
id={`server-tile-${item.id}`}
|
||||
id={id}
|
||||
size="xs"
|
||||
variant={selected ? "muted" : "default"}
|
||||
render={
|
||||
|
||||
@@ -56,3 +56,5 @@
|
||||
12.3.8: ресурсы приняты на1000 CPU10% строках; глобальный поиск/URL/сортировка/страница20/refresh/1000→1/320px. Большой список1000 предупреждений ресурсов требует отдельной проверки12.3.9.
|
||||
|
||||
12.3.9:1000 предупреждений CPU90% — warning nodes2009→21, страница4739→2751 при закрытом списке.5→55→105→5→55 карточек по явному раскрытию; полный счётчик сохранён.9 browser и tagged axe прошли. Полное постепенное раскрытие допускает весь список, это не виртуализация.
|
||||
|
||||
12.3.11: ServerTileRail1001 устройств —50 options на странице (All отдельный aggregate), mobile Sheet тоже50. Speed probes + каталог:11265→1778 DOM nodes,739 nodes speed секции/50 строк. Поиск/URL/граница площадок/клавиатура/refresh/320px/axe обе темы проверены;47 browser и production build прошли. Полные DTO остаются в памяти.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
## Актуальная точка продолжения — 2026-10-04
|
||||
|
||||
Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. Следующий блок12.3.11:ServerTileRail1000 устройств/поиск/URL/клавиатура/mobile Sheet, затем schedule entries и остальные grids;12.4/12.5 ещё не завершены. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются.
|
||||
Этап 11Б завершён в границах локальной приёмки (11Б.7); внешний выпуск не проверен. 12.1 принят локально: доступность оболочки/клавиатура/health в описанных сценариях. 12.2.1 принят локально: strict BGP catalog/partial/nullable counters. 12.2.2 принят локально: strict traffic DTO и агрегация без выдуманных точек. 12.2.3 принят локально: observations для server traffic от backend до dashboard. 12.2.4 принят локально: strict servers/probes и unknown server status. 12.2.5 принят локально: реальные ping samples/unknown/ось RTT/Recharts. 12.2.6 принят локально: strict GRE/WireGuard/VXLAN и device failures. 12.2.7 принят локально: strict Internet path snapshot/fallback. 12.2.8 принят локально: подтверждение полного backend сбора. 12.2.9 принят локально: метрики участков и наблюдаемый WAN без эвристической подмены. 12.3.1 принят локально:1000 серверов/штатная пагинация. 12.3.2 принят локально: пагинация CompactDataGrid/1000 строк статистики. 12.3.3 принят локально: порционный DOM очереди проблем. 12.3.4 принят локально: большой BGP каталог/страница при обновлении. Production build и123 профильных browser приняты локально на3978976. 12.2.10 принят локально: обязательные RouterOS поля. 12.3.5 принят локально: страница CompactDataGrid сохраняется при опросе,89 browser и повторный production build прошли. 12.3.6 сохранён: baseline1000 speed probes (ограничение DOM пока не выполнено). 12.3.7 принят локально:50 строк speed-проб/сохранение страницы/глобальная сортировка/1000→1; каталог1001 устройства остаётся в DOM. 12.3.8 принят локально:50 строк ресурсов, URL/поиск/сохранение страницы/1000→1; исправлена pagination оболочки с children, production build и8 больших browser прошли. 12.3.9 принят локально:порционное раскрытие1000 предупреждений ресурсов/клавиатура/axe; исходная ошибка ESLint speedDraft подтверждена. 12.3.10 принят локально:недоступные интерфейсы очищаются в обработчиках,54 browser/полный ESLint/production build прошли. 12.3.11 принят локально:ServerTileRail50 устройств/URL/поиск/клавиатура/две площадки/mobile Sheet/axe обе темы;47 browser и production build прошли. Следующий блок12.3.12:BGP selected URL/ручная страница и стабильные зависимости эффекта, затем общая регрессия12.4 и остальные grids;12.4/12.5 ещё не завершены. См. docs/ux-ui-stage-12.md. Внешние ограничения 11Б сохраняются.
|
||||
|
||||
Реальные pg_dump/pg_restore, PG rollback и остановка UDP worker требуют внешней приёмки. Датированные продолжения ниже — история; точный следующий шаг также указан в начале [журнала](ux-ui-progress.md).
|
||||
|
||||
|
||||
+11
-1
@@ -29,13 +29,23 @@
|
||||
| 10 | Маршрутизация, диагностика, карта и терминал | Завершён в границах локальной приёмки 2026-10-01; блоки 10.1–10.71, внешние ограничения в 10.71 |
|
||||
| 11А | Пользователи, контейнеры, сертификаты и резервные копии | Завершён в границах локальной приёмки 2026-10-01; 11А.1–11А.33, внешние ограничения в 11А.33 |
|
||||
| 11Б | Оповещения, сбор данных, интеграции, релизы и настройки | Завершён в границах локальной приёмки 2026-10-03; ограничения и evidence в11Б.7 |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | В работе;12.1/12.2.1–12.2.10 и12.3.1–12.3.5 и12.3.7–12.3.10 приняты в описанных локальных границах |
|
||||
| 12 | Итоговая доступность, адаптивность, производительность и регрессия | В работе;12.1/12.2.1–12.2.10 и12.3.1–12.3.5 и12.3.7–12.3.11 приняты в описанных локальных границах |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 12.3.11 — ограничение DOM бокового каталога устройств (2026-10-04)
|
||||
|
||||
ServerTileRail сохраняет существующую композицию Frame/Item/List9, а страницы управляются штатными TanStack v9/DataGridPagination. До50 устройств в DOM, поиск по полному каталогу, группировка по площадкам и выбор из URL сохранены. Home/End/стрелки открывают страницу выбранного устройства; ручное перелистывание не меняет URL и сохраняется при повторном получении каталога. Эффект выбора использует стабильный setPageIndex: объект useTable в v9 меняется при рендере и иначе возвращал страницу назад. ID карточек уникальны для desktop/mobile, активный descendant указывается только для смонтированной карточки своего списка.
|
||||
|
||||
1000 speed probes/1001 устройства: вся страница11265→1778 DOM nodes,50 строк speed grid/739 nodes секции; dev ready1016ms в одном прогоне. Rail<=50 устройств,<=1200 nodes; открытый Sheet также ограничен50. Проверены последняя страница/URL, Home/End/ArrowDown, поиск записи1000, refresh страницы2, граница двух площадок,320px/возврат фокуса. На снимке исправлены растущий footer и длинное многоточие. Axe выявил контраст EN: ServerTypeBadge теперь text-foreground с прежним цветным фоном. Повторный WCAG-tagged audit mobile обеих тем прошёл; снимки просмотрены.
|
||||
|
||||
47 browser BGP/диагностика/collector-probes прошли после финальной правки, Typecheck/ESLint и frontend production/contracts build(34 страницы) прошли. ReUI validate_usage/checklist применены; primitives/theme не изменены. Полный список остаётся в памяти; это pagination, не серверная выборка/виртуализация. Общая регрессия ещё не завершена: main config содержит1091 тест в163 файлах.
|
||||
|
||||
Следующий блок12.3.12: проверить тот же эффект зависимости от объекта table в BGP при выбранной сессии из URL и ручном переходе страницы. Затем общая регрессия12.4 по группам и оставшиеся25 специализированных grids;12.5 внешняя PG/RouterOS приёмка отдельно. Без push.
|
||||
|
||||
## 12.3.10 — синхронизация интерфейсов формы speed-проб (2026-10-04)
|
||||
|
||||
Исходный set-state-in-effect устранён: недоступный интерфейс очищается при получении списка endpoint и при повторном открытии формы с кэшем. Проверка относится только к совпадающему src/dst serverId; активный интерфейс сохраняется. Effect с синхронным setSpeedDraft удалён, правило ESLint не отключалось. Права и подтверждение сохранения API сохранены.
|
||||
|
||||
@@ -205,3 +205,13 @@ UptimeResourcesDataGrid использует ReUI client pagination по50 ст
|
||||
54 browser uptime links прошли, включая2 новых сценария active=false/true с холодной загрузкой и повторным открытием/проверкой PUT. Typecheck и полный ESLint изменённой страницы/тестов прошли без ошибок (6 исходных unused warnings). Повторный frontend production/contracts build прошёл:34 страницы; включает ResourceWarnings12.3.9. Живые интерфейсы RouterOS не проверены.
|
||||
|
||||
Следующий блок12.3.11: ограничение DOM ServerTileRail на1000 устройствах с поиском по всему каталогу, выбранным сервером из URL, клавиатурой и мобильным Sheet. Затем оставшиеся25 grids: scheduler ограничен11 job keys, speed history backend limit200, остальные требуют проверки потребителя/API.12.4/12.5 не завершены. Коммит локальный, без push.
|
||||
|
||||
## 12.3.11 — ограничение DOM бокового каталога устройств (2026-10-04)
|
||||
|
||||
ServerTileRail сохраняет существующую композицию Frame/Item/List9, а страницы управляются штатными TanStack v9/DataGridPagination. До50 устройств в DOM, поиск по полному каталогу, группировка по площадкам и выбор из URL сохранены. Home/End/стрелки открывают страницу выбранного устройства; ручное перелистывание не меняет URL и сохраняется при повторном получении каталога. Эффект выбора использует стабильный setPageIndex: объект useTable в v9 меняется при рендере и иначе возвращал страницу назад. ID карточек уникальны для desktop/mobile, активный descendant указывается только для смонтированной карточки своего списка.
|
||||
|
||||
1000 speed probes/1001 устройства: вся страница11265→1778 DOM nodes,50 строк speed grid/739 nodes секции; dev ready1016ms в одном прогоне. Rail<=50 устройств,<=1200 nodes; открытый Sheet также ограничен50. Проверены последняя страница/URL, Home/End/ArrowDown, поиск записи1000, refresh страницы2, граница двух площадок,320px/возврат фокуса. На снимке исправлены растущий footer и длинное многоточие. Axe выявил контраст EN: ServerTypeBadge теперь text-foreground с прежним цветным фоном. Повторный WCAG-tagged audit mobile обеих тем прошёл; снимки просмотрены.
|
||||
|
||||
47 browser BGP/диагностика/collector-probes прошли после финальной правки, Typecheck/ESLint и frontend production/contracts build(34 страницы) прошли. ReUI validate_usage/checklist применены; primitives/theme не изменены. Полный список остаётся в памяти; это pagination, не серверная выборка/виртуализация. Общая регрессия ещё не завершена: main config содержит1091 тест в163 файлах.
|
||||
|
||||
Следующий блок12.3.12: проверить тот же эффект зависимости от объекта table в BGP при выбранной сессии из URL и ручном переходе страницы. Затем общая регрессия12.4 по группам и оставшиеся25 специализированных grids;12.5 внешняя PG/RouterOS приёмка отдельно. Без push.
|
||||
@@ -1,8 +1,9 @@
|
||||
import { SCHEDULER_JOB_KEYS } from "../../lib/scheduler-settings"
|
||||
import { test, expect, type Page } from "@playwright/test"
|
||||
import axe from "axe-core"
|
||||
import type { SpeedProbeApiRow } from "../../components/data-grids/probes-speed-probes-data-grid"
|
||||
|
||||
async function setup(page: Page, path: string, count = 12) {
|
||||
async function setup(page: Page, path: string, count = 12, grouped = false) {
|
||||
const probes: SpeedProbeApiRow[] = Array.from({ length: count }, (_, i) => ({
|
||||
id: String(i + 1), srcServerId: String(i + 1), dstServerId: String(count + 1), srcInterface: "ether1",
|
||||
dstInterface: "ether1", protocol: "tcp", direction: "both", durationSec: "10", enabled: true,
|
||||
@@ -17,7 +18,7 @@ async function setup(page: Page, path: string, count = 12) {
|
||||
if (path === "/api/scheduler/status") return route.fulfill({ json: { jobs } })
|
||||
if (path === "/api/scheduler/jobs/alert_engine/run-now") return route.fulfill({ json: { ok: true, result: { status: "ok", error: null, result: { v: 1, job: "alert_engine", sampledAt: "2026-10-03T00:00:00Z", rulesChecked: 0, standaloneFires: 0, groupFires: 0, skippedNoTelegram: false } }, status: { jobs } } })
|
||||
return route.fulfill({ json: path === "/api/uptime/speed-probes" ? { probes }
|
||||
: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? Array.from({length:count+1},(_,i)=>({id:i+1,name:i<count?`Источник ${i+1}`:"Цель",host:`192.0.2.${i%250+1}`,type:"exit-node",enabled:true,country:"RU",site:"TEST",asn:"",status:null,latency:null,os:"7",model:"CHR",sessions:0,wanUplinks:[]}))
|
||||
: path === "/api/auth/config" ? { required: false } : path === "/api/servers" ? Array.from({length:count+1},(_,i)=>({id:i+1,name:i<count?`Источник ${i+1}`:"Цель",host:`192.0.2.${i%250+1}`,type:"exit-node",enabled:true,country:"RU",site:grouped?(i%2?"Площадка B":"Площадка A"):"TEST",asn:"",status:null,latency:null,os:"7",model:"CHR",sessions:0,wanUplinks:[]}))
|
||||
: path === "/api/evobgp/settings" ? { enabled: false, baseUrl: "", secretConfigured: false }
|
||||
: path === "/api/traffic/flow/settings" ? {
|
||||
enabled: false, collectorIp: "10.0.0.1", flowListenPort: 2055, wgListenPort: 51820,
|
||||
@@ -83,7 +84,7 @@ test("Large speed probes: 50 строк, глобальная сортировк
|
||||
console.log(JSON.stringify(sample))
|
||||
await info.attach("large-speed-probes-metrics", { body: JSON.stringify(sample), contentType: "application/json" })
|
||||
expect(sample.scheduleNodes).toBeLessThan(2000)
|
||||
expect(sample.domNodes).toBeLessThan(12000)
|
||||
expect(sample.domNodes).toBeLessThan(2500)
|
||||
await page.getByRole("button", { name: "Следующая страница", exact: true }).click()
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", "51")
|
||||
await page.getByRole("button", { name: "Обновить пробы скорости", exact: true }).click()
|
||||
@@ -103,3 +104,71 @@ test("Large speed probes: 50 строк, глобальная сортировк
|
||||
await expect(rows).toHaveCount(1)
|
||||
await expect(rows.first()).toHaveAttribute("data-row-id", "1")
|
||||
})
|
||||
|
||||
for (const theme of ["dark", "light"]) test(`Large server rail: URL, страницы, поиск и клавиатура на1001 устройстве/${theme}`, async ({ page }, info) => {
|
||||
await page.addInitScript(value => localStorage.setItem("rl-theme", value), theme)
|
||||
await setup(page, "/probes?server=1000&keep=1", 1000)
|
||||
const rail = page.locator("aside").filter({ has: page.getByRole("listbox", { name: "Серверы", exact: true }) })
|
||||
const list = rail.getByRole("listbox", { name: "Серверы", exact: true })
|
||||
await expect(list.getByRole("option")).toHaveCount(50)
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Источник 1000")
|
||||
await expect(rail.getByText("951–1000 из 1001", { exact: true })).toBeVisible()
|
||||
expect(await rail.locator("*").count()).toBeLessThan(1200)
|
||||
await info.attach("large-rail-metrics", { body: JSON.stringify({ fixtureDevices: 1001, renderedOptions: await list.getByRole("option").count(), railNodes: await rail.locator("*").count(), domNodes: await page.locator("*").count() }), contentType: "application/json" })
|
||||
await list.focus()
|
||||
await page.keyboard.press("End")
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "1001" && url.searchParams.get("keep") === "1")
|
||||
await expect(list.getByRole("option")).toHaveCount(1)
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Цель")
|
||||
await page.keyboard.press("Home")
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "1" && url.searchParams.get("keep") === "1")
|
||||
await expect(list.getByRole("option")).toHaveCount(50)
|
||||
await page.keyboard.press("ArrowDown")
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "2" && url.searchParams.get("keep") === "1")
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Источник 2")
|
||||
await rail.getByRole("button", { name: "Следующие устройства", exact: true }).click()
|
||||
await expect(list.getByRole("option").first()).toContainText("Источник 51")
|
||||
await page.getByRole("button", { name: "Обновить устройства диагностики", exact: true }).click()
|
||||
await expect(page.getByRole("button", { name: "Обновить устройства диагностики", exact: true })).toBeEnabled()
|
||||
await expect(rail.getByText("51–100 из 1001", { exact: true })).toBeVisible()
|
||||
const search = rail.getByRole("textbox", { name: "Поиск сервера", exact: true })
|
||||
await search.fill("Источник 1000")
|
||||
await expect(list.getByRole("option")).toHaveCount(1)
|
||||
await list.getByRole("option").click()
|
||||
await expect(page).toHaveURL(url => url.searchParams.get("server") === "1000" && url.searchParams.get("keep") === "1")
|
||||
await search.fill("")
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Источник 1000")
|
||||
await page.setViewportSize({ width: 320, height: 900 })
|
||||
await page.getByRole("button", { name: "Серверы", exact: true }).click()
|
||||
const sheet = page.getByRole("dialog")
|
||||
await expect(sheet.getByRole("option")).toHaveCount(50)
|
||||
await expect(sheet.getByRole("option", { selected: true })).toContainText("Источник 1000")
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
||||
expect(await sheet.getByRole("button", { name: "Следующие устройства", exact: true }).evaluate(el => el.getBoundingClientRect().right <= innerWidth)).toBe(true)
|
||||
expect(await sheet.getByRole("listbox", { name: "Серверы", exact: true }).evaluate(el => { const id = el.getAttribute("aria-activedescendant"); return !!id && el.contains(document.getElementById(id)) })).toBe(true)
|
||||
await page.addScriptTag({ content: axe.source })
|
||||
const audit = await page.evaluate(async () => (window as unknown as { axe: typeof axe }).axe.run(document.querySelector('[role="dialog"]')!, { runOnly: { type: "tag", values: ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22aa"] } }))
|
||||
await info.attach("large-rail-axe", { body: JSON.stringify(audit), contentType: "application/json" })
|
||||
expect(audit.violations.map(violation => ({ id: violation.id, nodes: violation.nodes.map(node => node.target) }))).toEqual([])
|
||||
await page.screenshot({ path: `test-results/large-server-rail-mobile-${theme}.png`, animations: "disabled" })
|
||||
await page.keyboard.press("Escape")
|
||||
await expect(page.getByRole("button", { name: "Серверы", exact: true })).toBeFocused()
|
||||
})
|
||||
|
||||
test("Large server rail: выбранное устройство и граница площадок", async ({ page }) => {
|
||||
await setup(page, "/probes?server=1000", 1000, true)
|
||||
const rail = page.locator("aside").filter({ has: page.getByRole("listbox", { name: "Серверы", exact: true }) })
|
||||
const list = rail.getByRole("listbox", { name: "Серверы", exact: true })
|
||||
await expect(list.getByRole("option")).toHaveCount(1)
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Источник 1000")
|
||||
await expect(rail.getByText("Площадка B", { exact: true })).toBeVisible()
|
||||
await list.focus()
|
||||
await page.keyboard.press("End")
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Цель")
|
||||
await expect(list.getByRole("option")).toHaveCount(50)
|
||||
await expect(rail.getByText("Площадка A", { exact: true })).toBeVisible()
|
||||
await expect(rail.getByText("Площадка B", { exact: true })).toBeVisible()
|
||||
await page.keyboard.press("Home")
|
||||
await expect(list.getByRole("option", { selected: true })).toContainText("Источник 1")
|
||||
await expect(list.getByRole("option")).toHaveCount(50)
|
||||
})
|
||||
Reference in new issue
Block a user