buildFilamentOptions.test.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /**
  2. * Regression tests for buildFilamentOptions (#1248).
  3. *
  4. * The original bug was precedence-based merging: cloud presets, when present,
  5. * fully replaced the local-presets branch and silently hid Local Profiles.
  6. *
  7. * The follow-up clarification: the spool form is printer-agnostic, so it must
  8. * show every per-printer / per-nozzle variant of a preset as its own entry —
  9. * unlike the AMS Slot modal which is per-printer and filters down to the
  10. * active printer model. Both surfaces should render the same set of presets
  11. * if you summed the AMS Slot's per-printer-filtered output across all
  12. * printers; the spool form just shows the union directly.
  13. */
  14. import { describe, it, expect } from 'vitest';
  15. import { buildFilamentOptions } from '../../../components/spool-form/utils';
  16. import type { SlicerSetting, LocalPreset, BuiltinFilament } from '../../../api/client';
  17. const cloudPreset = (overrides: Partial<SlicerSetting> = {}): SlicerSetting => ({
  18. setting_id: 'GFSL00@P1S',
  19. name: 'Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle',
  20. type: 'filament',
  21. version: null,
  22. user_id: null,
  23. updated_time: null,
  24. is_custom: false,
  25. ...overrides,
  26. });
  27. const localPreset = (overrides: Partial<LocalPreset> = {}): LocalPreset => ({
  28. id: 1,
  29. name: 'My Custom PETG @Bambu Lab P2S 0.4 nozzle',
  30. preset_type: 'filament',
  31. source: 'local',
  32. filament_type: 'GFG00',
  33. filament_vendor: 'Acme',
  34. nozzle_temp_min: 230,
  35. nozzle_temp_max: 260,
  36. pressure_advance: null,
  37. default_filament_colour: null,
  38. filament_cost: null,
  39. filament_density: null,
  40. compatible_printers: null,
  41. inherits: null,
  42. version: null,
  43. created_at: '2026-01-01T00:00:00Z',
  44. updated_at: '2026-01-01T00:00:00Z',
  45. ...overrides,
  46. });
  47. const builtin = (overrides: Partial<BuiltinFilament> = {}): BuiltinFilament => ({
  48. filament_id: 'GFA00',
  49. name: 'Bambu ASA Basic',
  50. ...overrides,
  51. });
  52. describe('buildFilamentOptions', () => {
  53. it('returns one entry per cloud setting_id (no @printer collapse)', () => {
  54. const options = buildFilamentOptions(
  55. [
  56. cloudPreset({ setting_id: 'GFSL00@P1S', name: 'Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle' }),
  57. cloudPreset({ setting_id: 'GFSL00@X1C', name: 'Bambu PLA Basic @Bambu Lab X1C 0.4 nozzle' }),
  58. cloudPreset({ setting_id: 'GFSL00@A1', name: 'Bambu PLA Basic @Bambu Lab A1 0.4 nozzle' }),
  59. ],
  60. new Set(),
  61. );
  62. expect(options).toHaveLength(3);
  63. expect(options.map(o => o.code)).toEqual([
  64. 'GFSL00@A1',
  65. 'GFSL00@P1S',
  66. 'GFSL00@X1C',
  67. ]);
  68. });
  69. it('keeps the @printer suffix in displayName so users can tell variants apart', () => {
  70. const options = buildFilamentOptions(
  71. [cloudPreset({ name: 'Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle' })],
  72. new Set(),
  73. );
  74. expect(options[0].displayName).toBe('Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle');
  75. });
  76. it('merges local profiles even when cloud has presets (#1248 regression)', () => {
  77. const options = buildFilamentOptions(
  78. [cloudPreset()],
  79. new Set(),
  80. [localPreset()],
  81. );
  82. const names = options.map(o => o.name);
  83. expect(names).toContain('Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle');
  84. expect(names).toContain('My Custom PETG @Bambu Lab P2S 0.4 nozzle');
  85. });
  86. it('merges built-in filaments alongside cloud and local sources', () => {
  87. const options = buildFilamentOptions(
  88. [cloudPreset()],
  89. new Set(),
  90. [localPreset()],
  91. [builtin()],
  92. );
  93. const names = options.map(o => o.name);
  94. expect(names).toContain('Bambu PLA Basic @Bambu Lab P1S 0.4 nozzle');
  95. expect(names).toContain('My Custom PETG @Bambu Lab P2S 0.4 nozzle');
  96. expect(names).toContain('Bambu ASA Basic');
  97. });
  98. it('lists each local preset individually (no @printer collapse)', () => {
  99. const options = buildFilamentOptions(
  100. [],
  101. new Set(),
  102. [
  103. localPreset({ id: 1, name: 'My PETG @Bambu Lab P2S 0.4 nozzle' }),
  104. localPreset({ id: 2, name: 'My PETG @Bambu Lab X1C 0.4 nozzle' }),
  105. localPreset({ id: 3, name: 'My PETG @Bambu Lab P2S 0.6 nozzle' }),
  106. ],
  107. );
  108. expect(options).toHaveLength(3);
  109. expect(options.map(o => o.name).sort()).toEqual([
  110. 'My PETG @Bambu Lab P2S 0.4 nozzle',
  111. 'My PETG @Bambu Lab P2S 0.6 nozzle',
  112. 'My PETG @Bambu Lab X1C 0.4 nozzle',
  113. ]);
  114. });
  115. it('local-preset allCodes carries both filament_type and the row id for findPresetOption', () => {
  116. const options = buildFilamentOptions(
  117. [],
  118. new Set(),
  119. [localPreset({ id: 42, filament_type: 'GFL00' })],
  120. );
  121. expect(options[0].allCodes).toEqual(expect.arrayContaining(['GFL00', '42']));
  122. });
  123. it('falls back to hardcoded list only when every source is empty', () => {
  124. const options = buildFilamentOptions([], new Set(), [], []);
  125. expect(options.length).toBeGreaterThan(0);
  126. expect(options.map(o => o.name)).toContain('Generic PLA');
  127. });
  128. it('skips a built-in whose setting_id is already covered by cloud', () => {
  129. const options = buildFilamentOptions(
  130. [cloudPreset({ setting_id: 'GFSA00', name: 'Bambu ASA Basic' })],
  131. new Set(),
  132. undefined,
  133. [builtin()],
  134. );
  135. const asaCount = options.map(o => o.name).filter(n => n === 'Bambu ASA Basic').length;
  136. expect(asaCount).toBe(1);
  137. });
  138. it('result is sorted alphabetically by displayName', () => {
  139. const options = buildFilamentOptions(
  140. [],
  141. new Set(),
  142. [
  143. localPreset({ id: 1, name: 'Zebra PLA' }),
  144. localPreset({ id: 2, name: 'Alpha PLA' }),
  145. ],
  146. );
  147. expect(options.map(o => o.name)).toEqual(['Alpha PLA', 'Zebra PLA']);
  148. });
  149. });