isMatchingCalibration.test.ts 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. /**
  2. * Tests for K-profile matching helpers (#1688 + #1689).
  3. *
  4. * `isMatchingCalibration` is the predicate used by both the spool form's
  5. * PA-profile suggester and ConfigureAmsSlotModal's K-profile filter. The
  6. * #1688 enhancement adds a filament_id-first match path with id
  7. * normalisation; the old name-parsing logic stays as the fallback.
  8. */
  9. import { describe, it, expect } from 'vitest';
  10. import {
  11. isMatchingCalibration,
  12. toFilamentId,
  13. isGenericFilamentId,
  14. materialForGenericFilamentId,
  15. genericFilamentIdMatchesMaterial,
  16. } from '../../../components/spool-form/utils';
  17. describe('toFilamentId', () => {
  18. it('strips the variant suffix', () => {
  19. expect(toFilamentId('GFG98_09')).toBe('GFG98');
  20. });
  21. it('strips the "S" infix from a setting_id', () => {
  22. // Spool stores setting_id "GFSG98", K-profile stores filament_id "GFG98".
  23. // Normalisation has to drop the "S" so both sides agree.
  24. expect(toFilamentId('GFSG98')).toBe('GFG98');
  25. });
  26. it('strips both the "S" infix and the variant suffix', () => {
  27. expect(toFilamentId('GFSG98_09')).toBe('GFG98');
  28. });
  29. it('returns bare filament_id unchanged', () => {
  30. expect(toFilamentId('GFL05')).toBe('GFL05');
  31. });
  32. it('uppercases the result', () => {
  33. expect(toFilamentId('gfsg98_09')).toBe('GFG98');
  34. });
  35. it('returns empty string for null/undefined/empty', () => {
  36. expect(toFilamentId(null)).toBe('');
  37. expect(toFilamentId(undefined)).toBe('');
  38. expect(toFilamentId('')).toBe('');
  39. });
  40. it('passes through non-Bambu IDs (numeric local-preset, Orca UUID) without crashing', () => {
  41. // Numeric local-preset ID — caller falls through to name match, no crash.
  42. expect(toFilamentId('42')).toBe('42');
  43. // Orca UUID — same.
  44. expect(toFilamentId('orca-uuid-abc')).toBe('ORCA-UUID-ABC');
  45. });
  46. });
  47. describe('isGenericFilamentId', () => {
  48. it('flags GFx99 patterns as generic', () => {
  49. expect(isGenericFilamentId('GFL99')).toBe(true);
  50. expect(isGenericFilamentId('GFG99')).toBe(true);
  51. expect(isGenericFilamentId('GFB99')).toBe(true);
  52. });
  53. it('does not flag non-generic IDs', () => {
  54. expect(isGenericFilamentId('GFL05')).toBe(false);
  55. expect(isGenericFilamentId('GFG98')).toBe(false);
  56. });
  57. it('returns false for null/undefined/empty', () => {
  58. expect(isGenericFilamentId(null)).toBe(false);
  59. expect(isGenericFilamentId(undefined)).toBe(false);
  60. expect(isGenericFilamentId('')).toBe(false);
  61. });
  62. });
  63. describe('materialForGenericFilamentId (#2710)', () => {
  64. it('resolves the material each generic id stands for', () => {
  65. expect(materialForGenericFilamentId('GFL99')).toBe('PLA');
  66. expect(materialForGenericFilamentId('GFG99')).toBe('PETG');
  67. expect(materialForGenericFilamentId('gfu99')).toBe('TPU');
  68. });
  69. it('returns empty for specific ids and for nothing', () => {
  70. expect(materialForGenericFilamentId('GFL05')).toBe('');
  71. expect(materialForGenericFilamentId(null)).toBe('');
  72. expect(materialForGenericFilamentId('')).toBe('');
  73. });
  74. });
  75. describe('genericFilamentIdMatchesMaterial (#2710)', () => {
  76. it('agrees when the generic id describes that material', () => {
  77. expect(genericFilamentIdMatchesMaterial('GFL99', 'PLA')).toBe(true);
  78. expect(genericFilamentIdMatchesMaterial('GFL99', 'pla')).toBe(true);
  79. });
  80. it('treats Nylon and PA as the same material', () => {
  81. expect(genericFilamentIdMatchesMaterial('GFN99', 'Nylon')).toBe(true);
  82. });
  83. it('rejects a mismatched material, a specific id, or a missing material', () => {
  84. expect(genericFilamentIdMatchesMaterial('GFL99', 'PETG')).toBe(false);
  85. expect(genericFilamentIdMatchesMaterial('GFL05', 'PLA')).toBe(false);
  86. expect(genericFilamentIdMatchesMaterial('GFL99', '')).toBe(false);
  87. });
  88. });
  89. describe('isMatchingCalibration (#1688)', () => {
  90. const formData = {
  91. material: 'PETG',
  92. brand: 'Generic',
  93. subtype: '',
  94. slicer_filament: 'GFSG98_09', // spool's setting_id form
  95. };
  96. it('matches by filament_id when ids agree after normalisation', () => {
  97. // K-profile stores bare filament_id; spool stores setting_id. Both
  98. // normalise to "GFG98" — match without any name parsing.
  99. expect(
  100. isMatchingCalibration(
  101. { name: 'My Custom K Profile', filament_id: 'GFG98' },
  102. formData,
  103. ),
  104. ).toBe(true);
  105. });
  106. it('id-match wins even when the K-profile name would not parse to anything sensible', () => {
  107. expect(
  108. isMatchingCalibration(
  109. { name: 'literal-garbage-no-material', filament_id: 'GFG98' },
  110. formData,
  111. ),
  112. ).toBe(true);
  113. });
  114. it('skips id-match when a generic id contradicts the spool material', () => {
  115. // GFL99 is generic *PLA* but the spool says PETG — the ids agreeing is not
  116. // enough, the material has to agree too. Name parsing then drives the
  117. // decision and rejects it.
  118. const result = isMatchingCalibration(
  119. { name: 'Random thing with no PETG in it', filament_id: 'GFL99' },
  120. { ...formData, slicer_filament: 'GFL99' },
  121. );
  122. expect(result).toBe(false);
  123. });
  124. it('matches a generic id when the material agrees and the spool claims no brand (#2710)', () => {
  125. // Reporter's printer: every K-profile calibrated under Generic PLA and
  126. // named after the colour. No name parsing can tie "Dark Brown" to PLA, so
  127. // the shared GFL99 is the only signal there is.
  128. expect(
  129. isMatchingCalibration(
  130. { name: 'Dark Brown', filament_id: 'GFL99' },
  131. { material: 'PLA', brand: '', subtype: '', slicer_filament: 'GFL99' },
  132. ),
  133. ).toBe(true);
  134. });
  135. it('treats "Generic" as no brand on the generic id path', () => {
  136. expect(
  137. isMatchingCalibration(
  138. { name: 'Marble', filament_id: 'GFL99' },
  139. { material: 'PLA', brand: 'Generic', subtype: '', slicer_filament: 'GFL99' },
  140. ),
  141. ).toBe(true);
  142. });
  143. it('keeps generic id matches brand-specific when the spool names a brand', () => {
  144. // A spool that says "Sunlu" should still get Sunlu-specific suggestions
  145. // rather than the printer's whole generic-PLA table.
  146. expect(
  147. isMatchingCalibration(
  148. { name: 'Dark Brown', filament_id: 'GFL99' },
  149. { material: 'PLA', brand: 'Sunlu', subtype: '', slicer_filament: 'GFL99' },
  150. ),
  151. ).toBe(false);
  152. });
  153. it('accepts Nylon/PA as the same material on the generic id path', () => {
  154. expect(
  155. isMatchingCalibration(
  156. { name: 'spool-of-doom', filament_id: 'GFN99' },
  157. { material: 'Nylon', brand: '', subtype: '', slicer_filament: 'GFN99' },
  158. ),
  159. ).toBe(true);
  160. });
  161. it('falls through to name match when spool has no slicer_filament', () => {
  162. expect(
  163. isMatchingCalibration(
  164. { name: 'Generic PETG', filament_id: 'GFG98' },
  165. { material: 'PETG', brand: 'Generic', subtype: '' },
  166. ),
  167. ).toBe(true);
  168. });
  169. it('falls through to name match when K-profile has no filament_id', () => {
  170. expect(
  171. isMatchingCalibration(
  172. { name: 'Generic PETG' },
  173. formData,
  174. ),
  175. ).toBe(true);
  176. });
  177. it('falls through to name match when normalised ids differ', () => {
  178. // Spool says GFG98, K-profile says GFL05 — id-match fails, fall to name.
  179. expect(
  180. isMatchingCalibration(
  181. { name: 'Generic PETG', filament_id: 'GFL05' },
  182. formData,
  183. ),
  184. ).toBe(true);
  185. expect(
  186. isMatchingCalibration(
  187. { name: 'Bambu PLA Basic', filament_id: 'GFL05' },
  188. formData,
  189. ),
  190. ).toBe(false);
  191. });
  192. it('rejects calibrations whose material does not match (name fallback path)', () => {
  193. expect(
  194. isMatchingCalibration(
  195. { name: 'Bambu PLA Basic', filament_id: 'GFL05' },
  196. { material: 'PETG', brand: '', subtype: '' },
  197. ),
  198. ).toBe(false);
  199. });
  200. it('returns false when formData has no material', () => {
  201. expect(
  202. isMatchingCalibration(
  203. { name: 'PETG', filament_id: 'GFG98' },
  204. { material: '', brand: '', subtype: '' },
  205. ),
  206. ).toBe(false);
  207. });
  208. });