Files
ui/src/features/admin/__tests__/AdminPage.gps-settings.test.tsx
T
Armen Rohalov 434854bf3c admin v2: implement design from ui_design/v2/plugin/admin.html
- Design system: v2 CSS variables (surface-0/1/2, border-hair, accent-amber/cyan/red/green/blue)
  and utility classes (.btn, .inp, .pill, .chip, .bracket, .panel, .seg, .swatch,
  .type-sq, .grid-bg, .ibtn, .checkbox, .tab); v1 az-* names aliased to v2 vars
  so other pages still render. Google Fonts (IBM Plex Sans + JetBrains Mono)
  loaded via <link> in index.html <head> to avoid FOUT.
- Header rebuilt to v2: amber wordmark + // divider, amber-bordered flight pill
  with cyan live dot, tab-style nav with amber underline on active, LINK status
  pill, cog + sign-out icon buttons.
- AdminPage rewritten to 3-column layout (340 / flex / 280):
  - Detection Classes: search + ADD button, table with #/Name/Hex/Ops columns,
    name-only inline edit with ringed swatch, sibling-row error alert.
  - AI Recognition Engine + GPS Device Link panels with corner-bracket borders,
    number steppers, segmented protocol control, dashed telemetry footers.
    Hooks (useAiSettings, useGpsSettings) seed factory defaults so the UI is
    interactive when GET fails (no backend).
  - Default Aircrafts: P/C/F type chips, isDefault star toggle, + ADD AIRCRAFT
    modal with model/type/resolution/maxMinutes/default fields.
- Co-located components: Modal (backdrop + ESC + body-scroll-lock),
  NumberStepper (▲▼ with clamp on click but not on typing), ClassEditRow.
- Types: Aircraft extended with FixedWing + optional resolution/maxMinutes;
  new AiRecognitionSettings/Telemetry, GpsDeviceSettings/Telemetry, GpsProtocol.
- Endpoints: /api/admin/ai-settings, /api/admin/gps-settings (+ /ping, /reconnect).
  POST /api/flights/aircrafts (plural REST collection).
- MSW: stateful admin-settings handler with resetAdminSettingsSeed() wired into
  tests/setup.ts. Aircraft seed expanded to 6 entries matching the mockup.
- i18n: full admin.{classes,aiEngine,gpsDevice,aircrafts} key sets in en+ua;
  nav.dataset shortened to "Dataset"; obsolete users-management keys removed.
- Tests: new AdminPage AI/GPS/aircraft test suites; admin_class_edit selectors
  updated for the name-only inline editor and the modal-based add flow.
2026-05-19 02:01:20 +03:00

80 lines
3.0 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { http } from 'msw'
import { server } from '../../../../tests/msw/server'
import { jsonResponse } from '../../../../tests/msw/helpers'
import { renderWithProviders, screen, waitFor, userEvent } from '../../../../tests/helpers/render'
import { seedBearer, clearBearer } from '../../../../tests/helpers/auth'
import { AdminPage } from '..'
// v2 admin — GPS Device Link panel.
//
// AI and GPS share APPLY label; GPS is the SECOND APPLY in DOM order.
function gpsApplyButton(): HTMLElement {
return screen.getAllByRole('button', { name: /apply/i })[1]
}
beforeEach(() => {
seedBearer()
})
afterEach(() => {
clearBearer()
})
describe('AdminPage — GPS Device Link', () => {
it('renders initial settings + telemetry from GET /api/admin/gps-settings', async () => {
renderWithProviders(<AdminPage />)
expect(await screen.findByDisplayValue('192.168.1.100')).toBeInTheDocument()
expect(screen.getByDisplayValue('9001')).toBeInTheDocument()
expect(screen.getByText('UDP/192.168.1.100:9001')).toBeInTheDocument()
})
it('protocol segmented control switches active value and APPLY PATCHes', async () => {
const calls: { body: unknown }[] = []
server.use(
http.patch('/api/admin/gps-settings', async ({ request }) => {
const body = await request.json()
calls.push({ body })
return jsonResponse({
settings: { ...(body as object), address: '192.168.1.100', port: 9001 },
telemetry: { socket: 'UDP/192.168.1.100:9001', connected: true, fix: '3D', satellites: 11, hdop: 0.82, lastPacketMs: 12 },
})
}),
)
renderWithProviders(<AdminPage />)
await screen.findByDisplayValue('192.168.1.100')
const ubxBtn = screen.getByRole('button', { name: 'UBX' })
await userEvent.click(ubxBtn)
expect(ubxBtn).toHaveAttribute('aria-pressed', 'true')
await userEvent.click(gpsApplyButton())
await waitFor(() => expect(calls.length).toBe(1))
expect((calls[0].body as { protocol: string }).protocol).toBe('UBX')
})
it('PING and RECONNECT fire their dedicated endpoints', async () => {
let pingHits = 0
let reconnectHits = 0
server.use(
http.post('/api/admin/gps-settings/ping', () => { pingHits += 1; return new Response(null, { status: 204 }) }),
http.post('/api/admin/gps-settings/reconnect', () => {
reconnectHits += 1
return jsonResponse({
settings: { address: '192.168.1.100', port: 9001, protocol: 'NMEA' },
telemetry: { socket: 'UDP/192.168.1.100:9001', connected: true, fix: '3D', satellites: 11, hdop: 0.82, lastPacketMs: 0 },
})
}),
)
renderWithProviders(<AdminPage />)
await screen.findByDisplayValue('192.168.1.100')
await userEvent.click(screen.getByRole('button', { name: /^ping$/i }))
await waitFor(() => expect(pingHits).toBe(1))
await userEvent.click(screen.getByRole('button', { name: /reconnect/i }))
await waitFor(() => expect(reconnectHits).toBe(1))
})
})