AssignToAmsModal.test.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * Tests for AssignToAmsModal — verifies that spoolmanMode prop
  3. * routes to assignSpoolmanSlot (not assignSpool) when assigning.
  4. */
  5. import { describe, it, expect, vi, beforeEach } from 'vitest';
  6. import { screen, waitFor } from '@testing-library/react';
  7. import userEvent from '@testing-library/user-event';
  8. import { render } from '../utils';
  9. vi.mock('../../api/client', () => ({
  10. api: {
  11. getPrinterStatus: vi.fn(),
  12. getPrinter: vi.fn(),
  13. getSettings: vi.fn().mockResolvedValue({}),
  14. assignSpool: vi.fn(),
  15. assignSpoolmanSlot: vi.fn(),
  16. getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }),
  17. getAssignments: vi.fn().mockResolvedValue([]),
  18. getSpoolmanSlotAssignments: vi.fn().mockResolvedValue([]),
  19. },
  20. }));
  21. import { AssignToAmsModal } from '../../components/spoolbuddy/AssignToAmsModal';
  22. import { api } from '../../api/client';
  23. const SPOOL = {
  24. id: 42,
  25. material: 'PLA',
  26. subtype: 'Basic',
  27. brand: 'BrandX',
  28. color_name: 'Red',
  29. rgba: 'FF0000FF',
  30. label_weight: 1000,
  31. weight_used: 0,
  32. tag_uid: null,
  33. tray_uuid: null,
  34. slicer_filament_name: 'PLA',
  35. data_origin: 'spoolman',
  36. k_profiles: [],
  37. };
  38. const BLANK_TRAY = {
  39. tray_color: null,
  40. tray_type: null,
  41. tray_sub_brands: null,
  42. tray_id_name: null,
  43. tray_info_idx: null,
  44. remain: 100,
  45. k: null,
  46. cali_idx: null,
  47. tag_uid: null,
  48. tray_uuid: null,
  49. nozzle_temp_min: null,
  50. nozzle_temp_max: null,
  51. drying_temp: null,
  52. drying_time: null,
  53. state: null,
  54. };
  55. const PRINTER_STATUS_ONLINE = {
  56. connected: true,
  57. state: 'idle',
  58. ams: [
  59. {
  60. id: 0,
  61. humidity: null,
  62. temp: null,
  63. is_ams_ht: false,
  64. serial_number: '',
  65. sw_ver: '',
  66. dry_time: 0,
  67. dry_status: 0,
  68. dry_sub_status: 0,
  69. tray: [
  70. { id: 0, ...BLANK_TRAY },
  71. { id: 1, ...BLANK_TRAY },
  72. { id: 2, ...BLANK_TRAY },
  73. { id: 3, ...BLANK_TRAY },
  74. ],
  75. },
  76. ],
  77. nozzles: [{ nozzle_diameter: '0.4', nozzle_type: 'stainless' }],
  78. ams_extruder_map: { '0': 0 },
  79. dual_nozzle: false,
  80. };
  81. describe('AssignToAmsModal', () => {
  82. beforeEach(() => {
  83. vi.clearAllMocks();
  84. vi.mocked(api.getPrinterStatus).mockResolvedValue(PRINTER_STATUS_ONLINE as never);
  85. vi.mocked(api.getPrinter).mockResolvedValue({ id: 1, name: 'Test Printer' } as never);
  86. vi.mocked(api.assignSpool).mockResolvedValue({} as never);
  87. vi.mocked(api.assignSpoolmanSlot).mockResolvedValue({} as never);
  88. });
  89. it('renders modal when open', async () => {
  90. render(
  91. <AssignToAmsModal
  92. isOpen={true}
  93. onClose={vi.fn()}
  94. spool={SPOOL as never}
  95. printerId={1}
  96. spoolmanMode={false}
  97. />
  98. );
  99. await waitFor(() => {
  100. expect(screen.getByText(/Assign.*to AMS/i)).toBeInTheDocument();
  101. });
  102. });
  103. it('renders nothing when closed', () => {
  104. render(
  105. <AssignToAmsModal
  106. isOpen={false}
  107. onClose={vi.fn()}
  108. spool={SPOOL as never}
  109. printerId={1}
  110. spoolmanMode={false}
  111. />
  112. );
  113. expect(screen.queryByText(/Assign.*to AMS/i)).not.toBeInTheDocument();
  114. });
  115. describe('API routing based on spoolmanMode', () => {
  116. it('calls assignSpool when spoolmanMode is false', async () => {
  117. const user = userEvent.setup();
  118. render(
  119. <AssignToAmsModal
  120. isOpen={true}
  121. onClose={vi.fn()}
  122. spool={SPOOL as never}
  123. printerId={1}
  124. spoolmanMode={false}
  125. />
  126. );
  127. await waitFor(() => {
  128. expect(screen.queryAllByTitle(/AMS Slot/i).length).toBeGreaterThan(0);
  129. });
  130. // Click first available slot button
  131. const slotButtons = screen.queryAllByTitle(/AMS Slot/i);
  132. await user.click(slotButtons[0]);
  133. await waitFor(() => {
  134. expect(api.assignSpool).toHaveBeenCalledWith(
  135. expect.objectContaining({ spool_id: 42, printer_id: 1 })
  136. );
  137. });
  138. expect(api.assignSpoolmanSlot).not.toHaveBeenCalled();
  139. });
  140. it('calls assignSpoolmanSlot when spoolmanMode is true', async () => {
  141. const user = userEvent.setup();
  142. render(
  143. <AssignToAmsModal
  144. isOpen={true}
  145. onClose={vi.fn()}
  146. spool={SPOOL as never}
  147. printerId={1}
  148. spoolmanMode={true}
  149. />
  150. );
  151. await waitFor(() => {
  152. expect(screen.queryAllByTitle(/AMS Slot/i).length).toBeGreaterThan(0);
  153. });
  154. const slotButtons = screen.queryAllByTitle(/AMS Slot/i);
  155. await user.click(slotButtons[0]);
  156. await waitFor(() => {
  157. expect(api.assignSpoolmanSlot).toHaveBeenCalledWith(
  158. expect.objectContaining({ spoolman_spool_id: 42, printer_id: 1 })
  159. );
  160. });
  161. expect(api.assignSpool).not.toHaveBeenCalled();
  162. });
  163. });
  164. describe('query invalidation after successful assign', () => {
  165. it('calls assignSpoolmanSlot successfully — invalidation fires on success', async () => {
  166. const user = userEvent.setup();
  167. vi.mocked(api.assignSpoolmanSlot).mockResolvedValue({} as never);
  168. render(
  169. <AssignToAmsModal
  170. isOpen={true}
  171. onClose={vi.fn()}
  172. spool={SPOOL as never}
  173. printerId={1}
  174. spoolmanMode={true}
  175. />
  176. );
  177. await waitFor(() => {
  178. expect(screen.queryAllByTitle(/AMS Slot/i).length).toBeGreaterThan(0);
  179. });
  180. const slotButtons = screen.queryAllByTitle(/AMS Slot/i);
  181. await user.click(slotButtons[0]);
  182. await waitFor(() => {
  183. expect(api.assignSpoolmanSlot).toHaveBeenCalledWith(
  184. expect.objectContaining({ spoolman_spool_id: 42, printer_id: 1 })
  185. );
  186. });
  187. });
  188. });
  189. describe('slot highlighting', () => {
  190. it('shows no ring-bambu-green when spool is not assigned', async () => {
  191. vi.mocked(api.getSpoolmanSlotAssignments).mockResolvedValue([]);
  192. const { container } = render(
  193. <AssignToAmsModal
  194. isOpen={true}
  195. onClose={vi.fn()}
  196. spool={SPOOL as never}
  197. printerId={1}
  198. spoolmanMode={true}
  199. />
  200. );
  201. await waitFor(() => {
  202. expect(screen.queryAllByRole('button').length).toBeGreaterThan(0);
  203. });
  204. expect(container.querySelector('.ring-bambu-green')).toBeNull();
  205. });
  206. it('highlights assigned slot with ring-bambu-green when spool is assigned to tray 2', async () => {
  207. vi.mocked(api.getSpoolmanSlotAssignments).mockResolvedValue([
  208. { printer_id: 1, ams_id: 0, tray_id: 2, spoolman_spool_id: 42 },
  209. ] as never);
  210. const { container } = render(
  211. <AssignToAmsModal
  212. isOpen={true}
  213. onClose={vi.fn()}
  214. spool={SPOOL as never}
  215. printerId={1}
  216. spoolmanMode={true}
  217. />
  218. );
  219. await waitFor(() => {
  220. expect(container.querySelector('.ring-bambu-green')).toBeInTheDocument();
  221. });
  222. });
  223. });
  224. });