import { describe, it, expect, vi, beforeEach } from 'vitest'
import { renderWithProviders, screen, waitFor, userEvent } from '../../test/utils'
import { server } from '../../test/mocks/server'
import { http, HttpResponse } from 'msw'
import { TableControlPage } from '../../pages/TableControlPage'
describe('TableControlPage', () => {
beforeEach(() => {
vi.clearAllMocks()
})
describe('Rendering', () => {
it('renders page title and description', async () => {
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Table Control')).toBeInTheDocument()
expect(screen.getByText(/manual controls for your sand table/i)).toBeInTheDocument()
})
})
it('renders primary action buttons', async () => {
renderWithProviders()
await waitFor(() => {
// Home, Stop, Reset buttons should be visible
expect(screen.getByText('Home')).toBeInTheDocument()
expect(screen.getByText('Stop')).toBeInTheDocument()
expect(screen.getByText('Reset')).toBeInTheDocument()
})
})
it('renders position control buttons', async () => {
renderWithProviders()
await waitFor(() => {
// Center and Perimeter buttons
expect(screen.getByText('Center')).toBeInTheDocument()
expect(screen.getByText('Perimeter')).toBeInTheDocument()
})
})
it('renders speed control section', async () => {
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Speed')).toBeInTheDocument()
expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
})
})
})
describe('Homing Control', () => {
it('home button calls send_home API', async () => {
const user = userEvent.setup()
let homeCalled = false
server.use(
http.post('/send_home', () => {
homeCalled = true
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Home')).toBeInTheDocument()
})
const homeButton = screen.getByText('Home').closest('button')!
await user.click(homeButton)
await waitFor(() => {
expect(homeCalled).toBe(true)
})
})
})
describe('Stop Control', () => {
it('stop button calls stop_execution API', async () => {
const user = userEvent.setup()
let stopCalled = false
server.use(
http.post('/stop_execution', () => {
stopCalled = true
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Stop')).toBeInTheDocument()
})
const stopButton = screen.getByText('Stop').closest('button')!
await user.click(stopButton)
await waitFor(() => {
expect(stopCalled).toBe(true)
})
})
})
describe('Reset Control', () => {
it('reset button is clickable', async () => {
const user = userEvent.setup()
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Reset')).toBeInTheDocument()
})
const resetButton = screen.getByText('Reset').closest('button')!
expect(resetButton).toBeEnabled()
// Click should not throw
await expect(user.click(resetButton)).resolves.not.toThrow()
})
it('reset button triggers dialog trigger', async () => {
const user = userEvent.setup()
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Reset')).toBeInTheDocument()
})
// The Reset button is a DialogTrigger - check its aria attributes
const resetButton = screen.getByText('Reset').closest('button')!
expect(resetButton).toHaveAttribute('aria-haspopup', 'dialog')
await user.click(resetButton)
// After clicking, aria-expanded should change
await waitFor(() => {
// The button should have triggered the dialog
// Note: Radix Dialog renders to a portal, may need to check document.body
const dialog = document.querySelector('[role="dialog"]')
if (dialog) {
expect(dialog).toBeInTheDocument()
}
})
})
})
describe('Movement Controls', () => {
it('move to center button calls API', async () => {
const user = userEvent.setup()
let moveCalled = false
server.use(
http.post('/move_to_center', () => {
moveCalled = true
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Center')).toBeInTheDocument()
})
const centerButton = screen.getByText('Center').closest('button')!
await user.click(centerButton)
await waitFor(() => {
expect(moveCalled).toBe(true)
})
})
it('move to perimeter button calls API', async () => {
const user = userEvent.setup()
let moveCalled = false
server.use(
http.post('/move_to_perimeter', () => {
moveCalled = true
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByText('Perimeter')).toBeInTheDocument()
})
const perimeterButton = screen.getByText('Perimeter').closest('button')!
await user.click(perimeterButton)
await waitFor(() => {
expect(moveCalled).toBe(true)
})
})
})
describe('Speed Control', () => {
it('speed input submits to API on Set click', async () => {
const user = userEvent.setup()
let speedSet: number | null = null
server.use(
http.post('/set_speed', async ({ request }) => {
const body = await request.json() as { speed: number }
speedSet = body.speed
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
})
const speedInput = screen.getByPlaceholderText(/mm\/s/i)
await user.type(speedInput, '250')
// Find the Set button - it's near the speed input
const speedCard = speedInput.closest('.p-6')
const setButton = speedCard?.querySelector('button')
expect(setButton).toBeTruthy()
await user.click(setButton!)
await waitFor(() => {
expect(speedSet).toBe(250)
})
})
it('speed input submits on Enter key', async () => {
const user = userEvent.setup()
let speedSet: number | null = null
server.use(
http.post('/set_speed', async ({ request }) => {
const body = await request.json() as { speed: number }
speedSet = body.speed
return HttpResponse.json({ success: true })
})
)
renderWithProviders()
await waitFor(() => {
expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
})
const speedInput = screen.getByPlaceholderText(/mm\/s/i)
await user.type(speedInput, '300{Enter}')
await waitFor(() => {
expect(speedSet).toBe(300)
})
})
it('shows speed badge with current speed', async () => {
renderWithProviders()
await waitFor(() => {
// The speed badge shows "-- mm/s" when no speed is set
expect(screen.getByText(/mm\/s/)).toBeInTheDocument()
})
})
})
})