AssignSpoolModalIdSearch.test.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /**
  2. * Searching "#<number>" in Assign Spool finds that spool whatever the slot
  3. * holds (#3301).
  4. *
  5. * The search ran on what was left after the tray-match filter, so a spool of
  6. * another material never appeared for its exact number unless "Show all
  7. * spools" was ticked -- and the empty list read as if the spool were gone.
  8. * Typing a spool's number is choosing it: "#123" now skips the tray match
  9. * (the material-mismatch warning still asks on assign). A spool that stays
  10. * hidden says why: in another slot, archived, or no such spool.
  11. */
  12. import { describe, it, expect, vi, beforeEach } from 'vitest';
  13. import { screen, waitFor } from '@testing-library/react';
  14. import userEvent from '@testing-library/user-event';
  15. import { render } from '../utils';
  16. import { AssignSpoolModal } from '../../components/AssignSpoolModal';
  17. import { api } from '../../api/client';
  18. vi.mock('../../api/client', () => ({
  19. api: {
  20. getSpools: vi.fn(),
  21. getAssignments: vi.fn(),
  22. assignSpool: vi.fn(),
  23. assignSpoolmanSlot: vi.fn(),
  24. getSpoolmanInventorySpools: vi.fn(),
  25. getSpoolmanSlotAssignments: vi.fn().mockResolvedValue([]),
  26. getSettings: vi.fn().mockResolvedValue({}),
  27. getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }),
  28. refreshPrinterStatus: vi.fn().mockResolvedValue({ status: 'ok' }),
  29. },
  30. }));
  31. const spool = (id: number, material: string, brand: string, extra: Record<string, unknown> = {}) => ({
  32. id,
  33. material,
  34. subtype: 'Basic',
  35. brand,
  36. color_name: 'Black',
  37. rgba: '000000FF',
  38. label_weight: 1000,
  39. weight_used: 0,
  40. tag_uid: null,
  41. tray_uuid: null,
  42. slicer_filament_name: material,
  43. ...extra,
  44. });
  45. const SPOOLS = [
  46. spool(1, 'PLA', 'Polymaker'),
  47. spool(7, 'PETG', 'Sunlu'),
  48. spool(8, 'PLA', 'Elegoo', { archived_at: '2026-10-01T00:00:00Z' }),
  49. spool(9, 'PLA', 'Overture'),
  50. ];
  51. const props = {
  52. isOpen: true,
  53. onClose: vi.fn(),
  54. printerId: 1,
  55. amsId: 0,
  56. trayId: 0,
  57. trayInfo: { type: 'PLA', material: 'PLA', profile: 'PLA', color: '000000', location: 'AMS 1 - Slot 1' },
  58. };
  59. async function search(query: string) {
  60. const user = userEvent.setup();
  61. render(<AssignSpoolModal {...props} />);
  62. await screen.findByText(/Polymaker/);
  63. await user.type(screen.getByPlaceholderText(/search/i), query);
  64. return user;
  65. }
  66. describe('AssignSpoolModal "#<number>" search', () => {
  67. beforeEach(() => {
  68. vi.clearAllMocks();
  69. (api.getSpools as ReturnType<typeof vi.fn>).mockResolvedValue(SPOOLS);
  70. // Spool 9 sits in another slot of the same printer.
  71. (api.getAssignments as ReturnType<typeof vi.fn>).mockResolvedValue([
  72. { id: 1, spool_id: 9, printer_id: 1, ams_id: 0, tray_id: 2 },
  73. ]);
  74. });
  75. it('finds a spool of another material than the slot, and only that one', async () => {
  76. await search('#7');
  77. expect(await screen.findByText(/Sunlu/)).toBeInTheDocument();
  78. expect(screen.queryByText(/Polymaker/)).not.toBeInTheDocument();
  79. });
  80. it('still asks before assigning it', async () => {
  81. const user = await search('#7');
  82. await user.click(await screen.findByText(/Sunlu/));
  83. await user.click(screen.getByRole('button', { name: /assign spool/i }));
  84. expect(await screen.findByText('Material mismatch')).toBeInTheDocument();
  85. expect(api.assignSpool).not.toHaveBeenCalled();
  86. });
  87. it('leaves the tray match on for any other search', async () => {
  88. await search('Sunlu');
  89. await waitFor(() => expect(screen.queryByText(/Polymaker/)).not.toBeInTheDocument());
  90. expect(screen.queryByText(/Sunlu/)).not.toBeInTheDocument();
  91. });
  92. it('says so when the spool is in another slot, and still does not offer it', async () => {
  93. await search('#9');
  94. expect(await screen.findByText('Spool #9 is already assigned to another slot. Unassign it there first.')).toBeInTheDocument();
  95. expect(screen.queryByText(/Overture/)).not.toBeInTheDocument();
  96. });
  97. it('says so when the spool is archived', async () => {
  98. await search('#8');
  99. expect(await screen.findByText('Spool #8 is archived. Restore it to assign it.')).toBeInTheDocument();
  100. expect(screen.queryByText(/Elegoo/)).not.toBeInTheDocument();
  101. });
  102. it('says so when there is no such spool', async () => {
  103. await search('#99');
  104. expect(await screen.findByText('There is no spool #99.')).toBeInTheDocument();
  105. });
  106. it('with "Show all spools" ticked, a spool in another slot is offered as before', async () => {
  107. const user = await search('#9');
  108. await user.click(screen.getByLabelText(/show all spools/i));
  109. expect(await screen.findByText(/Overture/)).toBeInTheDocument();
  110. });
  111. });