Files
technitium-console/src/screens/blocking/StatusPanel.test.tsx
T
DenozordecandClaude Sonnet 5.5 f3531c64f9 Move the console onto ReUI PRO
Install ReUI (MCP, skill, @reui registry, Tailwind v4, shadcn base-nova) and move
the shared UI onto it: Button, Tag, Check, Input/Textarea, Segmented, Alert, Panel,
Dialog, Tooltip, Menu and Select (Popover), Toast (sonner), the Table frame
(DataGrid), the Dashboard KPI strip, the shnt Ops palette, Geist and the type scale.

Design only: behaviour, texts and roles are unchanged. Tests that asserted the old
DOM were updated; the tests now format numbers in English wherever they run.
docs/reui-migration-plan.md records the plan and its decisions.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-27 00:06:06 +07:00

166 lines
7.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import { act, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as settings from '../../api/settings'
import type { DnsSettings } from '../../api/settings'
import { StatusPanel } from './StatusPanel'
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
})
function serve(s: Partial<DnsSettings>) {
return vi.spyOn(settings, 'getSettings').mockResolvedValue(s as DnsSettings)
}
describe('StatusPanel', () => {
it('says blocking is enabled', async () => {
serve({ enableBlocking: true })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Blocking is enabled')).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Blocking options' })).toHaveTextContent('Disable')
})
it('says until when it is paused', async () => {
serve({ enableBlocking: false, temporaryDisableBlockingTill: '2099-01-01T21:15:00Z' })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Blocking is paused')).toBeInTheDocument()
expect(screen.getByText(/^Until /)).toBeInTheDocument()
})
it('enabled, it carries the Active pill in the ok tone', async () => {
serve({ enableBlocking: true })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
const pill = await screen.findByText('Active')
expect(pill.className).toMatch(/ok/)
expect(screen.getByText('Queries matching a block list or your rules are answered as blocked.')).toBeInTheDocument()
})
it('paused until later today, the pill gives the hour alone, in amber, and the sentence stays', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
vi.setSystemTime(new Date(2026, 9, 1, 20, 45, 0))
serve({ enableBlocking: false, temporaryDisableBlockingTill: new Date(2026, 9, 1, 21, 15, 0).toISOString() })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
const pill = await screen.findByText('Until 21:15')
expect(pill.className).toMatch(/acc/)
expect(screen.getByText('Resumes automatically.')).toBeInTheDocument()
})
it('paused past today, the pill gives the date as well', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
vi.setSystemTime(new Date(2026, 9, 1, 23, 30, 0))
serve({ enableBlocking: false, temporaryDisableBlockingTill: new Date(2026, 9, 2, 1, 30, 0).toISOString() })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Until 2026-10-02 01:30')).toBeInTheDocument()
})
it('paused without Settings.canModify, the pill is neutral: the padlock wins', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
vi.setSystemTime(new Date(2026, 9, 1, 20, 45, 0))
serve({ enableBlocking: false, temporaryDisableBlockingTill: new Date(2026, 9, 1, 21, 15, 0).toISOString() })
render(
<StatusPanel
token="T"
permissions={{ Settings: { canView: true, canModify: false, canDelete: false } }}
onNotice={() => {}}
/>,
)
const pill = await screen.findByText('Until 21:15')
expect(pill.className).not.toMatch(/acc/)
})
it('says until when to the minute, without seconds', async () => {
const till = new Date(2099, 0, 1, 21, 15, 42).toISOString()
serve({ enableBlocking: false, temporaryDisableBlockingTill: till })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Until 2099-01-01 21:15')).toBeInTheDocument()
})
it('reads again when the pause ends, and stops saying paused', async () => {
vi.useFakeTimers({ shouldAdvanceTime: true })
const till = new Date(Date.now() + 60_000).toISOString()
const read = vi
.spyOn(settings, 'getSettings')
.mockResolvedValueOnce({ enableBlocking: false, temporaryDisableBlockingTill: till } as DnsSettings)
.mockResolvedValue({ enableBlocking: true } as DnsSettings)
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Blocking is paused')).toBeInTheDocument()
expect(read).toHaveBeenCalledTimes(1)
await act(() => vi.advanceTimersByTimeAsync(30_000))
expect(read).toHaveBeenCalledTimes(1)
await act(() => vi.advanceTimersByTimeAsync(35_000))
expect(read).toHaveBeenCalledTimes(2)
expect(await screen.findByText('Blocking is enabled')).toBeInTheDocument()
})
it('when the state cannot be read it says so, continuous, with a Retry that reads again', async () => {
const read = vi
.spyOn(settings, 'getSettings')
.mockResolvedValueOnce(null)
.mockResolvedValue({ enableBlocking: true } as DnsSettings)
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Could not read the blocking state.')).toBeInTheDocument()
expect(screen.queryByText('Loading…')).not.toBeInTheDocument()
await userEvent.click(screen.getByRole('button', { name: 'Retry' }))
expect(await screen.findByText('Blocking is enabled')).toBeInTheDocument()
expect(read).toHaveBeenCalledTimes(2)
})
it('a paused mark is amber only when the user can act on it: the padlock wins', async () => {
const till = '2099-01-01T21:15:00Z'
serve({ enableBlocking: false, temporaryDisableBlockingTill: till })
const { container, unmount } = render(
<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />,
)
await screen.findByText('Blocking is paused')
expect(container.querySelector('[data-tone]')).toHaveAttribute('data-tone', 'paused')
unmount()
const locked = render(
<StatusPanel
token="T"
permissions={{ Settings: { canView: true, canModify: false, canDelete: false } }}
onNotice={() => {}}
/>,
)
expect(await screen.findByText('Blocking is paused')).toBeInTheDocument()
expect(screen.getByText(/^Until /)).toBeInTheDocument()
expect(locked.container.querySelector('[data-tone]')).toHaveAttribute('data-tone', 'off')
})
it('says it is disabled when there is no pause in the future', async () => {
serve({ enableBlocking: false, temporaryDisableBlockingTill: '2000-01-01T00:00:00Z' })
render(<StatusPanel token="T" permissions={undefined} onNotice={() => {}} />)
expect(await screen.findByText('Blocking is disabled')).toBeInTheDocument()
})
it('without Settings.canModify the control is disabled with its padlock', async () => {
serve({ enableBlocking: true })
render(
<StatusPanel
token="T"
permissions={{ Settings: { canView: true, canModify: false, canDelete: false } }}
onNotice={() => {}}
/>,
)
expect(await screen.findByRole('button', { name: /Disable/ })).toBeDisabled()
})
it('without Settings.canView the panel is locked and nothing is read', () => {
const read = serve({ enableBlocking: true })
render(
<StatusPanel
token="T"
permissions={{ Settings: { canView: false, canModify: false, canDelete: false } }}
onNotice={() => {}}
/>,
)
expect(screen.getByText('Requires Settings: View')).toBeInTheDocument()
expect(read).not.toHaveBeenCalled()
})
})