inventorySearch.test.ts 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import { describe, it, expect } from 'vitest';
  2. import { spoolMatchesQuery, filterSpoolsByQuery } from '../../utils/inventorySearch';
  3. import type { InventorySpool } from '../../api/client';
  4. function makeSpool(overrides: Partial<InventorySpool> = {}): InventorySpool {
  5. return {
  6. id: 1,
  7. material: 'PLA',
  8. subtype: 'Basic',
  9. color_name: 'Red',
  10. rgba: 'FF0000FF',
  11. brand: 'Bambu Lab',
  12. label_weight: 1000,
  13. core_weight: 250,
  14. core_weight_catalog_id: null,
  15. weight_used: 0,
  16. slicer_filament: null,
  17. slicer_filament_name: null,
  18. nozzle_temp_min: 220,
  19. nozzle_temp_max: 240,
  20. note: null,
  21. added_full: null,
  22. last_used: null,
  23. encode_time: null,
  24. tag_uid: null,
  25. tray_uuid: null,
  26. data_origin: 'local',
  27. tag_type: null,
  28. archived_at: null,
  29. created_at: '2024-01-01T00:00:00Z',
  30. updated_at: '2024-01-01T00:00:00Z',
  31. cost_per_kg: null,
  32. last_scale_weight: null,
  33. last_weighed_at: null,
  34. category: null,
  35. low_stock_threshold_pct: null,
  36. ...overrides,
  37. };
  38. }
  39. describe('spoolMatchesQuery', () => {
  40. it('returns true for empty query', () => {
  41. expect(spoolMatchesQuery(makeSpool(), '')).toBe(true);
  42. });
  43. it('matches on material (case-insensitive)', () => {
  44. const spool = makeSpool({ material: 'PETG' });
  45. expect(spoolMatchesQuery(spool, 'petg')).toBe(true);
  46. expect(spoolMatchesQuery(spool, 'PET')).toBe(true);
  47. expect(spoolMatchesQuery(spool, 'pla')).toBe(false);
  48. });
  49. it('matches on brand (case-insensitive)', () => {
  50. const spool = makeSpool({ brand: 'Prusament' });
  51. expect(spoolMatchesQuery(spool, 'prusa')).toBe(true);
  52. expect(spoolMatchesQuery(spool, 'PRUSA')).toBe(true);
  53. });
  54. it('matches on color_name (case-insensitive)', () => {
  55. const spool = makeSpool({ color_name: 'Galaxy Black' });
  56. expect(spoolMatchesQuery(spool, 'galaxy')).toBe(true);
  57. expect(spoolMatchesQuery(spool, 'GALAXY')).toBe(true);
  58. expect(spoolMatchesQuery(spool, 'blue')).toBe(false);
  59. });
  60. it('matches on subtype (case-insensitive)', () => {
  61. const spool = makeSpool({ subtype: 'Matte' });
  62. expect(spoolMatchesQuery(spool, 'matt')).toBe(true);
  63. expect(spoolMatchesQuery(spool, 'MATTE')).toBe(true);
  64. });
  65. it('returns false when null optional fields do not match', () => {
  66. const spool = makeSpool({ brand: null, color_name: null, subtype: null });
  67. expect(spoolMatchesQuery(spool, 'bambu')).toBe(false);
  68. });
  69. it('matches on numeric id (#1336)', () => {
  70. const spool = makeSpool({ id: 42, material: 'PLA', brand: null, color_name: null, subtype: null, note: null });
  71. expect(spoolMatchesQuery(spool, '42')).toBe(true);
  72. expect(spoolMatchesQuery(spool, '4')).toBe(true);
  73. expect(spoolMatchesQuery(spool, '99')).toBe(false);
  74. });
  75. it('treats a hash-prefixed number as an exact spool ID (#2978)', () => {
  76. expect(spoolMatchesQuery(makeSpool({ id: 3 }), '#3')).toBe(true);
  77. expect(spoolMatchesQuery(makeSpool({ id: 13 }), '#3')).toBe(false);
  78. expect(spoolMatchesQuery(makeSpool({ id: 30 }), '#3')).toBe(false);
  79. expect(spoolMatchesQuery(makeSpool({ id: 3 }), ' #3 ')).toBe(true);
  80. });
  81. it('does not treat #ID text in other fields as an exact ID match (#2978)', () => {
  82. expect(spoolMatchesQuery(makeSpool({ id: 13, color_name: 'Batch #3' }), '#3')).toBe(false);
  83. expect(spoolMatchesQuery(makeSpool({ id: 13, note: 'Use after spool #3' }), '#3')).toBe(false);
  84. });
  85. it('keeps plain numeric ID searches as partial matches (#2978)', () => {
  86. expect(spoolMatchesQuery(makeSpool({ id: 3 }), '3')).toBe(true);
  87. expect(spoolMatchesQuery(makeSpool({ id: 13 }), '3')).toBe(true);
  88. expect(spoolMatchesQuery(makeSpool({ id: 30 }), '3')).toBe(true);
  89. });
  90. });
  91. describe('filterSpoolsByQuery', () => {
  92. const spools = [
  93. makeSpool({ id: 1, material: 'PLA', brand: 'Bambu Lab', color_name: 'Red' }),
  94. makeSpool({ id: 2, material: 'PETG', brand: 'Prusament', color_name: 'Blue' }),
  95. makeSpool({ id: 3, material: 'ABS', brand: null, color_name: 'Black', subtype: 'Matte' }),
  96. ];
  97. it('returns all spools for empty query', () => {
  98. expect(filterSpoolsByQuery(spools, '')).toHaveLength(3);
  99. });
  100. it('filters by material', () => {
  101. const result = filterSpoolsByQuery(spools, 'pla');
  102. expect(result).toHaveLength(1);
  103. expect(result[0].id).toBe(1);
  104. });
  105. it('filters by brand', () => {
  106. const result = filterSpoolsByQuery(spools, 'prusa');
  107. expect(result).toHaveLength(1);
  108. expect(result[0].id).toBe(2);
  109. });
  110. it('filters by subtype', () => {
  111. const result = filterSpoolsByQuery(spools, 'matte');
  112. expect(result).toHaveLength(1);
  113. expect(result[0].id).toBe(3);
  114. });
  115. it('returns empty array when no match', () => {
  116. expect(filterSpoolsByQuery(spools, 'nylon')).toHaveLength(0);
  117. });
  118. it('returns only the exact spool for a #ID query (#2978)', () => {
  119. const result = filterSpoolsByQuery(spools, '#3');
  120. expect(result.map((spool) => spool.id)).toEqual([3]);
  121. });
  122. });