TableControlPage.test.tsx 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import { describe, it, expect, vi, beforeEach } from 'vitest'
  2. import { renderWithProviders, screen, waitFor, userEvent } from '../../test/utils'
  3. import { server } from '../../test/mocks/server'
  4. import { http, HttpResponse } from 'msw'
  5. import { TableControlPage } from '../../pages/TableControlPage'
  6. describe('TableControlPage', () => {
  7. beforeEach(() => {
  8. vi.clearAllMocks()
  9. })
  10. describe('Rendering', () => {
  11. it('renders page title and description', async () => {
  12. renderWithProviders(<TableControlPage />)
  13. await waitFor(() => {
  14. expect(screen.getByText('Table Control')).toBeInTheDocument()
  15. expect(screen.getByText(/manual controls for your sand table/i)).toBeInTheDocument()
  16. })
  17. })
  18. it('renders primary action buttons', async () => {
  19. renderWithProviders(<TableControlPage />)
  20. await waitFor(() => {
  21. // Home, Stop, Reset buttons should be visible
  22. expect(screen.getByText('Home')).toBeInTheDocument()
  23. expect(screen.getByText('Stop')).toBeInTheDocument()
  24. expect(screen.getByText('Reset')).toBeInTheDocument()
  25. })
  26. })
  27. it('renders position control buttons', async () => {
  28. renderWithProviders(<TableControlPage />)
  29. await waitFor(() => {
  30. // Center and Perimeter buttons
  31. expect(screen.getByText('Center')).toBeInTheDocument()
  32. expect(screen.getByText('Perimeter')).toBeInTheDocument()
  33. })
  34. })
  35. it('renders speed control section', async () => {
  36. renderWithProviders(<TableControlPage />)
  37. await waitFor(() => {
  38. expect(screen.getByText('Speed')).toBeInTheDocument()
  39. expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
  40. })
  41. })
  42. })
  43. describe('Homing Control', () => {
  44. it('home button calls send_home API', async () => {
  45. const user = userEvent.setup()
  46. let homeCalled = false
  47. server.use(
  48. http.post('/send_home', () => {
  49. homeCalled = true
  50. return HttpResponse.json({ success: true })
  51. })
  52. )
  53. renderWithProviders(<TableControlPage />)
  54. await waitFor(() => {
  55. expect(screen.getByText('Home')).toBeInTheDocument()
  56. })
  57. const homeButton = screen.getByText('Home').closest('button')!
  58. await user.click(homeButton)
  59. await waitFor(() => {
  60. expect(homeCalled).toBe(true)
  61. })
  62. })
  63. })
  64. describe('Stop Control', () => {
  65. it('stop button calls stop_execution API', async () => {
  66. const user = userEvent.setup()
  67. let stopCalled = false
  68. server.use(
  69. http.post('/stop_execution', () => {
  70. stopCalled = true
  71. return HttpResponse.json({ success: true })
  72. })
  73. )
  74. renderWithProviders(<TableControlPage />)
  75. await waitFor(() => {
  76. expect(screen.getByText('Stop')).toBeInTheDocument()
  77. })
  78. const stopButton = screen.getByText('Stop').closest('button')!
  79. await user.click(stopButton)
  80. await waitFor(() => {
  81. expect(stopCalled).toBe(true)
  82. })
  83. })
  84. })
  85. describe('Reset Control', () => {
  86. it('reset button is clickable', async () => {
  87. const user = userEvent.setup()
  88. renderWithProviders(<TableControlPage />)
  89. await waitFor(() => {
  90. expect(screen.getByText('Reset')).toBeInTheDocument()
  91. })
  92. const resetButton = screen.getByText('Reset').closest('button')!
  93. expect(resetButton).toBeEnabled()
  94. // Click should not throw
  95. await expect(user.click(resetButton)).resolves.not.toThrow()
  96. })
  97. it('reset button triggers dialog trigger', async () => {
  98. const user = userEvent.setup()
  99. renderWithProviders(<TableControlPage />)
  100. await waitFor(() => {
  101. expect(screen.getByText('Reset')).toBeInTheDocument()
  102. })
  103. // The Reset button is a DialogTrigger - check its aria attributes
  104. const resetButton = screen.getByText('Reset').closest('button')!
  105. expect(resetButton).toHaveAttribute('aria-haspopup', 'dialog')
  106. await user.click(resetButton)
  107. // After clicking, aria-expanded should change
  108. await waitFor(() => {
  109. // The button should have triggered the dialog
  110. // Note: Radix Dialog renders to a portal, may need to check document.body
  111. const dialog = document.querySelector('[role="dialog"]')
  112. if (dialog) {
  113. expect(dialog).toBeInTheDocument()
  114. }
  115. })
  116. })
  117. })
  118. describe('Movement Controls', () => {
  119. it('move to center button calls API', async () => {
  120. const user = userEvent.setup()
  121. let moveCalled = false
  122. server.use(
  123. http.post('/move_to_center', () => {
  124. moveCalled = true
  125. return HttpResponse.json({ success: true })
  126. })
  127. )
  128. renderWithProviders(<TableControlPage />)
  129. await waitFor(() => {
  130. expect(screen.getByText('Center')).toBeInTheDocument()
  131. })
  132. const centerButton = screen.getByText('Center').closest('button')!
  133. await user.click(centerButton)
  134. await waitFor(() => {
  135. expect(moveCalled).toBe(true)
  136. })
  137. })
  138. it('move to perimeter button calls API', async () => {
  139. const user = userEvent.setup()
  140. let moveCalled = false
  141. server.use(
  142. http.post('/move_to_perimeter', () => {
  143. moveCalled = true
  144. return HttpResponse.json({ success: true })
  145. })
  146. )
  147. renderWithProviders(<TableControlPage />)
  148. await waitFor(() => {
  149. expect(screen.getByText('Perimeter')).toBeInTheDocument()
  150. })
  151. const perimeterButton = screen.getByText('Perimeter').closest('button')!
  152. await user.click(perimeterButton)
  153. await waitFor(() => {
  154. expect(moveCalled).toBe(true)
  155. })
  156. })
  157. })
  158. describe('Speed Control', () => {
  159. it('speed input submits to API on Set click', async () => {
  160. const user = userEvent.setup()
  161. let speedSet: number | null = null
  162. server.use(
  163. http.post('/set_speed', async ({ request }) => {
  164. const body = await request.json() as { speed: number }
  165. speedSet = body.speed
  166. return HttpResponse.json({ success: true })
  167. })
  168. )
  169. renderWithProviders(<TableControlPage />)
  170. await waitFor(() => {
  171. expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
  172. })
  173. const speedInput = screen.getByPlaceholderText(/mm\/s/i)
  174. await user.type(speedInput, '250')
  175. // Find the Set button - it's near the speed input
  176. const speedCard = speedInput.closest('.p-6')
  177. const setButton = speedCard?.querySelector('button')
  178. expect(setButton).toBeTruthy()
  179. await user.click(setButton!)
  180. await waitFor(() => {
  181. expect(speedSet).toBe(250)
  182. })
  183. })
  184. it('speed input submits on Enter key', async () => {
  185. const user = userEvent.setup()
  186. let speedSet: number | null = null
  187. server.use(
  188. http.post('/set_speed', async ({ request }) => {
  189. const body = await request.json() as { speed: number }
  190. speedSet = body.speed
  191. return HttpResponse.json({ success: true })
  192. })
  193. )
  194. renderWithProviders(<TableControlPage />)
  195. await waitFor(() => {
  196. expect(screen.getByPlaceholderText(/mm\/s/i)).toBeInTheDocument()
  197. })
  198. const speedInput = screen.getByPlaceholderText(/mm\/s/i)
  199. await user.type(speedInput, '300{Enter}')
  200. await waitFor(() => {
  201. expect(speedSet).toBe(300)
  202. })
  203. })
  204. it('shows speed badge with current speed', async () => {
  205. renderWithProviders(<TableControlPage />)
  206. await waitFor(() => {
  207. // The speed badge shows "-- mm/s" when no speed is set
  208. expect(screen.getByText(/mm\/s/)).toBeInTheDocument()
  209. })
  210. })
  211. })
  212. })