diff --git a/frontend/src/__tests__/components/SliceModal.test.tsx b/frontend/src/__tests__/components/SliceModal.test.tsx index 133423a41..1c1e58113 100644 --- a/frontend/src/__tests__/components/SliceModal.test.tsx +++ b/frontend/src/__tests__/components/SliceModal.test.tsx @@ -26,6 +26,7 @@ vi.mock('../../api/client', () => ({ getArchivePlates: vi.fn(), getLibraryFileFilamentRequirements: vi.fn(), getArchiveFilamentRequirements: vi.fn(), + listSlicerBundles: vi.fn(), getSettings: vi.fn().mockResolvedValue({}), updateSettings: vi.fn().mockResolvedValue({}), }, @@ -40,6 +41,7 @@ const mockApi = api as unknown as { getArchivePlates: ReturnType; getLibraryFileFilamentRequirements: ReturnType; getArchiveFilamentRequirements: ReturnType; + listSlicerBundles: ReturnType; }; function makeUnified(overrides: Partial = {}): UnifiedPresetsResponse { @@ -119,6 +121,10 @@ describe('SliceModal', () => { plate_id: 1, filaments: [], }); + // Default: no bundles imported. Bundle-tier tests override this with a + // populated array; everything else inherits the empty default so the + // modal renders the original (preset-only) layout. + mockApi.listSlicerBundles.mockResolvedValue([]); }); it('auto-selects the highest-priority tier per slot on first load', async () => { @@ -957,4 +963,171 @@ describe('SliceModal', () => { expect(body.filament_presets[1]).toEqual({ source: 'cloud', id: 'F-GREY' }); }); }); + + // ------------------------------------------------------------------------- + // Bundle tier — picking an imported .bbscfg replaces the cloud/local/standard + // dropdown set with bundle-scoped pickers and routes the slice through the + // backend's bundle dispatch shape (no PresetRefs in the body). + // ------------------------------------------------------------------------- + + describe('Bundle tier', () => { + const sampleBundle = { + id: 'abc123def456abcd', + printer_preset_name: '# Bambu Lab H2D 0.4 nozzle', + printer: ['# Bambu Lab H2D 0.4 nozzle'], + process: [ + '# 0.20mm Standard @BBL H2D', + '# 0.16mm Standard @BBL H2D', + ], + filament: [ + '# Bambu PLA Basic @BBL H2D', + '# Bambu PETG HF @BBL H2D 0.4 nozzle', + ], + version: '02.06.00.50', + }; + + it('hides the bundle picker when no bundles are imported', async () => { + // Default beforeEach already returns []; assert the picker isn't + // rendered so users without bundles see the original layout. + renderWithTracker({ + source: { kind: 'libraryFile', id: 100, filename: 'Cube.stl' }, + onClose: vi.fn(), + }); + await waitFor(() => expect(screen.getByText('My Custom X1C')).toBeDefined()); + expect(screen.queryByText(/slicer bundle/i)).toBeNull(); + }); + + it('renders the bundle picker when at least one bundle is imported', async () => { + mockApi.listSlicerBundles.mockResolvedValue([sampleBundle]); + renderWithTracker({ + source: { kind: 'libraryFile', id: 100, filename: 'Cube.stl' }, + onClose: vi.fn(), + }); + await waitFor(() => + expect(screen.getByText(/slicer bundle/i)).toBeDefined(), + ); + // The bundle option is in the dropdown. + const bundleSelect = screen.getAllByRole('combobox')[0] as HTMLSelectElement; + expect( + Array.from(bundleSelect.options).map((o) => o.textContent), + ).toContain('# Bambu Lab H2D 0.4 nozzle'); + }); + + it('replaces preset dropdowns with bundle-scoped pickers when a bundle is selected', async () => { + mockApi.listSlicerBundles.mockResolvedValue([sampleBundle]); + renderWithTracker({ + source: { kind: 'libraryFile', id: 100, filename: 'Cube.stl' }, + onClose: vi.fn(), + }); + await waitFor(() => expect(screen.getByText('My Custom X1C')).toBeDefined()); + + const user = userEvent.setup(); + const selects = screen.getAllByRole('combobox') as HTMLSelectElement[]; + // First select is the bundle picker (new top-of-modal dropdown). + await user.selectOptions(selects[0], sampleBundle.id); + + // Wait for the bundle-mode UI to take over: process options should + // now reflect the bundle's process names. + await waitFor(() => { + expect( + screen.getByText('# 0.20mm Standard @BBL H2D'), + ).toBeDefined(); + }); + + // The static printer label shows the bundle's printer. Both the + //