QueuePage.test.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /**
  2. * Tests for the QueuePage component.
  3. */
  4. import { describe, it, expect, beforeEach } from 'vitest';
  5. import { screen, waitFor } from '@testing-library/react';
  6. import userEvent from '@testing-library/user-event';
  7. import { render } from '../utils';
  8. import { QueuePage } from '../../pages/QueuePage';
  9. import { http, HttpResponse } from 'msw';
  10. import { server } from '../mocks/server';
  11. // Mock queue data
  12. const mockQueueItems = [
  13. {
  14. id: 1,
  15. printer_id: 1,
  16. archive_id: 1,
  17. position: 1,
  18. status: 'pending',
  19. scheduled_time: null,
  20. require_previous_success: false,
  21. auto_off_after: false,
  22. manual_start: false,
  23. ams_mapping: null,
  24. plate_id: null,
  25. bed_levelling: true,
  26. flow_cali: false,
  27. vibration_cali: true,
  28. layer_inspect: false,
  29. timelapse: false,
  30. use_ams: true,
  31. started_at: null,
  32. completed_at: null,
  33. error_message: null,
  34. created_at: '2024-01-01T00:00:00Z',
  35. archive_name: 'Test Print 1',
  36. archive_thumbnail: '/thumb1.png',
  37. printer_name: 'Test Printer',
  38. print_time_seconds: 3600,
  39. },
  40. {
  41. id: 2,
  42. printer_id: 1,
  43. archive_id: 2,
  44. position: 2,
  45. status: 'printing',
  46. scheduled_time: null,
  47. require_previous_success: false,
  48. auto_off_after: true,
  49. manual_start: false,
  50. ams_mapping: null,
  51. plate_id: null,
  52. bed_levelling: true,
  53. flow_cali: false,
  54. vibration_cali: true,
  55. layer_inspect: false,
  56. timelapse: false,
  57. use_ams: true,
  58. started_at: '2024-01-01T10:00:00Z',
  59. completed_at: null,
  60. error_message: null,
  61. created_at: '2024-01-01T00:00:00Z',
  62. archive_name: 'Active Print',
  63. archive_thumbnail: '/thumb2.png',
  64. printer_name: 'Test Printer',
  65. print_time_seconds: 7200,
  66. },
  67. {
  68. id: 3,
  69. printer_id: 1,
  70. archive_id: 3,
  71. position: 3,
  72. status: 'completed',
  73. scheduled_time: null,
  74. require_previous_success: false,
  75. auto_off_after: false,
  76. manual_start: false,
  77. ams_mapping: null,
  78. plate_id: null,
  79. bed_levelling: true,
  80. flow_cali: false,
  81. vibration_cali: true,
  82. layer_inspect: false,
  83. timelapse: false,
  84. use_ams: true,
  85. started_at: '2024-01-01T08:00:00Z',
  86. completed_at: '2024-01-01T09:00:00Z',
  87. error_message: null,
  88. created_at: '2024-01-01T00:00:00Z',
  89. archive_name: 'Completed Print',
  90. archive_thumbnail: '/thumb3.png',
  91. printer_name: 'Test Printer',
  92. print_time_seconds: 1800,
  93. },
  94. ];
  95. const mockPrinters = [
  96. {
  97. id: 1,
  98. name: 'Test Printer',
  99. ip_address: '192.168.1.100',
  100. serial_number: 'TESTSERIAL0001',
  101. access_code: '12345678',
  102. model: 'X1C',
  103. enabled: true,
  104. created_at: '2024-01-01T00:00:00Z',
  105. },
  106. ];
  107. describe('QueuePage', () => {
  108. beforeEach(() => {
  109. // Setup MSW handlers for this test
  110. server.use(
  111. http.get('/api/v1/queue/', () => {
  112. return HttpResponse.json(mockQueueItems);
  113. }),
  114. http.get('/api/v1/printers/', () => {
  115. return HttpResponse.json(mockPrinters);
  116. }),
  117. http.delete('/api/v1/queue/:id', () => {
  118. return HttpResponse.json({ success: true });
  119. }),
  120. http.post('/api/v1/queue/:id/cancel', () => {
  121. return HttpResponse.json({ success: true });
  122. }),
  123. http.post('/api/v1/queue/:id/start', () => {
  124. return HttpResponse.json({ success: true });
  125. }),
  126. http.post('/api/v1/queue/:id/stop', () => {
  127. return HttpResponse.json({ success: true });
  128. }),
  129. http.post('/api/v1/queue/reorder', () => {
  130. return HttpResponse.json({ success: true });
  131. })
  132. );
  133. });
  134. describe('rendering', () => {
  135. it('renders the page title', async () => {
  136. render(<QueuePage />);
  137. await waitFor(() => {
  138. expect(screen.getByText('Print Queue')).toBeInTheDocument();
  139. });
  140. });
  141. it('renders the page description', async () => {
  142. render(<QueuePage />);
  143. await waitFor(() => {
  144. expect(screen.getByText('Schedule and manage your print jobs')).toBeInTheDocument();
  145. });
  146. });
  147. it('shows summary cards', async () => {
  148. render(<QueuePage />);
  149. await waitFor(() => {
  150. // Check for the page title (Print Queue is the h1)
  151. expect(screen.getByText('Print Queue')).toBeInTheDocument();
  152. });
  153. });
  154. it('shows filter dropdowns', async () => {
  155. render(<QueuePage />);
  156. await waitFor(() => {
  157. expect(screen.getByText('All Printers')).toBeInTheDocument();
  158. expect(screen.getByText('All Status')).toBeInTheDocument();
  159. });
  160. });
  161. });
  162. describe('queue items display', () => {
  163. it('shows pending queue items', async () => {
  164. render(<QueuePage />);
  165. await waitFor(() => {
  166. expect(screen.getByText('Test Print 1')).toBeInTheDocument();
  167. });
  168. });
  169. it('shows active printing items', async () => {
  170. render(<QueuePage />);
  171. await waitFor(() => {
  172. expect(screen.getByText('Active Print')).toBeInTheDocument();
  173. expect(screen.getByText('Currently Printing')).toBeInTheDocument();
  174. });
  175. });
  176. it('shows completed items in history', async () => {
  177. render(<QueuePage />);
  178. await waitFor(() => {
  179. expect(screen.getByText('Completed Print')).toBeInTheDocument();
  180. });
  181. });
  182. it('shows status badges', async () => {
  183. render(<QueuePage />);
  184. await waitFor(() => {
  185. // Queue items should be visible with status indicators
  186. expect(screen.getByText('Test Print 1')).toBeInTheDocument();
  187. });
  188. });
  189. it('shows printer names', async () => {
  190. render(<QueuePage />);
  191. await waitFor(() => {
  192. const printerElements = screen.getAllByText('Test Printer');
  193. expect(printerElements.length).toBeGreaterThan(0);
  194. });
  195. });
  196. it('renders queue items with plate_id correctly', async () => {
  197. // Override with queue items that have plate_id set
  198. server.use(
  199. http.get('/api/v1/queue/', () => {
  200. return HttpResponse.json([
  201. {
  202. ...mockQueueItems[0],
  203. plate_id: 2,
  204. archive_name: 'Multi-plate Print',
  205. },
  206. ]);
  207. })
  208. );
  209. render(<QueuePage />);
  210. await waitFor(() => {
  211. expect(screen.getByText('Multi-plate Print')).toBeInTheDocument();
  212. });
  213. });
  214. });
  215. describe('empty state', () => {
  216. it('shows empty state when no queue items', async () => {
  217. server.use(
  218. http.get('/api/v1/queue/', () => {
  219. return HttpResponse.json([]);
  220. })
  221. );
  222. render(<QueuePage />);
  223. await waitFor(() => {
  224. expect(screen.getByText('No prints scheduled')).toBeInTheDocument();
  225. });
  226. });
  227. });
  228. describe('filtering', () => {
  229. it('has printer filter options', async () => {
  230. const user = userEvent.setup();
  231. render(<QueuePage />);
  232. await waitFor(() => {
  233. expect(screen.getByText('All Printers')).toBeInTheDocument();
  234. });
  235. const printerSelect = screen.getByDisplayValue('All Printers');
  236. await user.click(printerSelect);
  237. expect(screen.getByText('Unassigned')).toBeInTheDocument();
  238. });
  239. it('has status filter options', async () => {
  240. const user = userEvent.setup();
  241. render(<QueuePage />);
  242. await waitFor(() => {
  243. expect(screen.getByText('All Status')).toBeInTheDocument();
  244. });
  245. const statusSelect = screen.getByDisplayValue('All Status');
  246. await user.click(statusSelect);
  247. expect(screen.getByRole('option', { name: 'Pending' })).toBeInTheDocument();
  248. expect(screen.getByRole('option', { name: 'Printing' })).toBeInTheDocument();
  249. expect(screen.getByRole('option', { name: 'Completed' })).toBeInTheDocument();
  250. });
  251. });
  252. describe('queue actions', () => {
  253. it('shows edit button for pending items', async () => {
  254. render(<QueuePage />);
  255. await waitFor(() => {
  256. expect(screen.getByText('Test Print 1')).toBeInTheDocument();
  257. });
  258. // Find the edit button (Pencil icon)
  259. const editButtons = screen.getAllByTitle('Edit');
  260. expect(editButtons.length).toBeGreaterThan(0);
  261. });
  262. it('shows cancel button for pending items', async () => {
  263. render(<QueuePage />);
  264. await waitFor(() => {
  265. expect(screen.getByText('Test Print 1')).toBeInTheDocument();
  266. });
  267. const cancelButtons = screen.getAllByTitle('Cancel');
  268. expect(cancelButtons.length).toBeGreaterThan(0);
  269. });
  270. it('shows stop button for printing items', async () => {
  271. render(<QueuePage />);
  272. await waitFor(() => {
  273. expect(screen.getByText('Active Print')).toBeInTheDocument();
  274. });
  275. const stopButtons = screen.getAllByTitle('Stop Print');
  276. expect(stopButtons.length).toBeGreaterThan(0);
  277. });
  278. it('shows re-queue button for history items', async () => {
  279. render(<QueuePage />);
  280. await waitFor(() => {
  281. expect(screen.getByText('Completed Print')).toBeInTheDocument();
  282. });
  283. const requeueButtons = screen.getAllByTitle('Re-queue');
  284. expect(requeueButtons.length).toBeGreaterThan(0);
  285. });
  286. });
  287. describe('clear history', () => {
  288. it('shows clear history button when history exists', async () => {
  289. render(<QueuePage />);
  290. await waitFor(() => {
  291. expect(screen.getByText('Clear History')).toBeInTheDocument();
  292. });
  293. });
  294. it('opens confirm modal when clicking clear history', async () => {
  295. const user = userEvent.setup();
  296. render(<QueuePage />);
  297. await waitFor(() => {
  298. expect(screen.getByText('Clear History')).toBeInTheDocument();
  299. });
  300. const clearButton = screen.getByRole('button', { name: /clear history/i });
  301. await user.click(clearButton);
  302. await waitFor(() => {
  303. expect(screen.getByText(/Are you sure you want to remove all/i)).toBeInTheDocument();
  304. });
  305. });
  306. });
  307. describe('staged items', () => {
  308. it('shows staged badge for manual_start items', async () => {
  309. server.use(
  310. http.get('/api/v1/queue/', () => {
  311. return HttpResponse.json([
  312. {
  313. ...mockQueueItems[0],
  314. manual_start: true,
  315. },
  316. ]);
  317. })
  318. );
  319. render(<QueuePage />);
  320. await waitFor(() => {
  321. expect(screen.getByText('Staged')).toBeInTheDocument();
  322. });
  323. });
  324. it('shows start button for staged items', async () => {
  325. server.use(
  326. http.get('/api/v1/queue/', () => {
  327. return HttpResponse.json([
  328. {
  329. ...mockQueueItems[0],
  330. manual_start: true,
  331. },
  332. ]);
  333. })
  334. );
  335. render(<QueuePage />);
  336. await waitFor(() => {
  337. expect(screen.getByTitle('Start Print')).toBeInTheDocument();
  338. });
  339. });
  340. });
  341. describe('auto power off badge', () => {
  342. it('shows power off badge when auto_off_after is true', async () => {
  343. render(<QueuePage />);
  344. await waitFor(() => {
  345. expect(screen.getByText('Auto power off')).toBeInTheDocument();
  346. });
  347. });
  348. });
  349. });