SpoolmanFilamentPicker.test.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import React from 'react';
  2. import { describe, it, expect, vi } from 'vitest';
  3. import { screen, fireEvent } from '@testing-library/react';
  4. import { render } from '../utils';
  5. import { SpoolmanFilamentPicker } from '../../components/spool-form/SpoolmanFilamentPicker';
  6. import type { SpoolmanFilamentEntry } from '../../api/client';
  7. vi.mock('react-i18next', () => ({
  8. useTranslation: () => ({
  9. t: (key: string, fallback?: string) => fallback ?? key,
  10. }),
  11. }));
  12. const FILAMENTS: SpoolmanFilamentEntry[] = [
  13. {
  14. id: 1,
  15. name: 'PLA Basic',
  16. material: 'PLA',
  17. color_hex: 'FF0000',
  18. color_name: 'Red',
  19. weight: 1000,
  20. spool_weight: 196,
  21. vendor: { id: 1, name: 'Bambu Lab' },
  22. },
  23. {
  24. id: 2,
  25. name: 'PETG',
  26. material: 'PETG',
  27. color_hex: '00FF00',
  28. color_name: 'Green',
  29. weight: 1000,
  30. spool_weight: null,
  31. vendor: { id: 2, name: 'Bambu Lab' },
  32. },
  33. {
  34. id: 3,
  35. name: 'ABS Basic',
  36. material: 'ABS',
  37. color_hex: null,
  38. color_name: null,
  39. weight: 1000,
  40. spool_weight: 250,
  41. vendor: null,
  42. },
  43. ];
  44. function openDropdown() {
  45. const trigger = screen.getByRole('button', { name: /spoolman/i });
  46. fireEvent.click(trigger);
  47. }
  48. describe('SpoolmanFilamentPicker', () => {
  49. it('renders the trigger button with catalog label when nothing selected', () => {
  50. render(
  51. <SpoolmanFilamentPicker
  52. filaments={FILAMENTS}
  53. isLoading={false}
  54. selectedId={null}
  55. onSelect={vi.fn()}
  56. />
  57. );
  58. expect(screen.getByText('inventory.spoolmanFilamentCatalog')).toBeTruthy();
  59. });
  60. it('shows all filaments when dropdown is opened', () => {
  61. render(
  62. <SpoolmanFilamentPicker
  63. filaments={FILAMENTS}
  64. isLoading={false}
  65. selectedId={null}
  66. onSelect={vi.fn()}
  67. />
  68. );
  69. openDropdown();
  70. expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
  71. expect(screen.getByText(/Bambu Lab — PETG/)).toBeTruthy();
  72. expect(screen.getByText(/ABS Basic/)).toBeTruthy();
  73. });
  74. it('filters by search term (material)', () => {
  75. render(
  76. <SpoolmanFilamentPicker
  77. filaments={FILAMENTS}
  78. isLoading={false}
  79. selectedId={null}
  80. onSelect={vi.fn()}
  81. />
  82. );
  83. openDropdown();
  84. const searchInput = screen.getByPlaceholderText('inventory.pickFromSpoolmanCatalog');
  85. fireEvent.change(searchInput, { target: { value: 'PETG' } });
  86. expect(screen.getAllByText(/PETG/).length).toBeGreaterThan(0);
  87. expect(screen.queryByText(/PLA Basic/)).toBeNull();
  88. expect(screen.queryByText(/ABS Basic/)).toBeNull();
  89. });
  90. it('filters by vendor name', () => {
  91. render(
  92. <SpoolmanFilamentPicker
  93. filaments={FILAMENTS}
  94. isLoading={false}
  95. selectedId={null}
  96. onSelect={vi.fn()}
  97. />
  98. );
  99. openDropdown();
  100. const searchInput = screen.getByPlaceholderText('inventory.pickFromSpoolmanCatalog');
  101. fireEvent.change(searchInput, { target: { value: 'Bambu' } });
  102. // ABS Basic has no vendor — should be filtered out
  103. expect(screen.queryByText(/ABS Basic/)).toBeNull();
  104. });
  105. it('calls onSelect with the correct filament when an item is clicked', () => {
  106. const onSelect = vi.fn();
  107. render(
  108. <SpoolmanFilamentPicker
  109. filaments={FILAMENTS}
  110. isLoading={false}
  111. selectedId={null}
  112. onSelect={onSelect}
  113. />
  114. );
  115. openDropdown();
  116. fireEvent.click(screen.getByText(/Bambu Lab — PLA Basic/));
  117. expect(onSelect).toHaveBeenCalledOnce();
  118. expect(onSelect).toHaveBeenCalledWith(FILAMENTS[0]);
  119. });
  120. it('shows empty state when no filaments match search', () => {
  121. render(
  122. <SpoolmanFilamentPicker
  123. filaments={FILAMENTS}
  124. isLoading={false}
  125. selectedId={null}
  126. onSelect={vi.fn()}
  127. />
  128. );
  129. openDropdown();
  130. const searchInput = screen.getByPlaceholderText('inventory.pickFromSpoolmanCatalog');
  131. fireEvent.change(searchInput, { target: { value: 'xyzzy-not-found' } });
  132. expect(screen.getByText('inventory.noSpoolmanFilaments')).toBeTruthy();
  133. });
  134. it('shows loading spinner when isLoading is true', () => {
  135. render(
  136. <SpoolmanFilamentPicker
  137. filaments={[]}
  138. isLoading={true}
  139. selectedId={null}
  140. onSelect={vi.fn()}
  141. />
  142. );
  143. // Trigger button shows spinner (Loader2 icon with animate-spin)
  144. const spinners = document.querySelectorAll('.animate-spin');
  145. expect(spinners.length).toBeGreaterThan(0);
  146. });
  147. it('shows selected filament name in trigger button', () => {
  148. render(
  149. <SpoolmanFilamentPicker
  150. filaments={FILAMENTS}
  151. isLoading={false}
  152. selectedId={1}
  153. onSelect={vi.fn()}
  154. />
  155. );
  156. // Should display the selected filament in the trigger
  157. expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
  158. });
  159. it('filters by color_name', () => {
  160. render(
  161. <SpoolmanFilamentPicker
  162. filaments={FILAMENTS}
  163. isLoading={false}
  164. selectedId={null}
  165. onSelect={vi.fn()}
  166. />
  167. );
  168. openDropdown();
  169. const searchInput = screen.getByPlaceholderText('inventory.pickFromSpoolmanCatalog');
  170. fireEvent.change(searchInput, { target: { value: 'Green' } });
  171. // PETG has color_name 'Green' — should match
  172. expect(screen.getAllByText(/PETG/).length).toBeGreaterThan(0);
  173. // PLA Basic has color_name 'Red' — should be filtered out
  174. expect(screen.queryByText(/PLA Basic/)).toBeNull();
  175. });
  176. });