diff --git a/frontend/src/__tests__/pages/PrintersPageAmsLoadUnload.test.tsx b/frontend/src/__tests__/pages/PrintersPageAmsLoadUnload.test.tsx index 0a458e792..e3e919c36 100644 --- a/frontend/src/__tests__/pages/PrintersPageAmsLoadUnload.test.tsx +++ b/frontend/src/__tests__/pages/PrintersPageAmsLoadUnload.test.tsx @@ -1,13 +1,15 @@ /** * Tests for the AMS slot load / unload buttons on PrintersPage (#891). * - * Verifies that the menu in each AMS slot popover exposes Load and Unload, - * that clicking them POSTs to the right endpoint with the right tray_id, and - * that the buttons are hidden while the printer is RUNNING. + * The printer-card refactor in #1661 replaced the kebab "Slot options" + * button with a hover card (FilamentHoverCard) whose actions render on + * hover. Tests now `fireEvent.mouseEnter` on the slot trigger + * (`data-testid="filament-slot"`) and wait for the portaled card to + * appear in document.body before clicking Load / Unload. */ import { describe, it, expect, beforeEach } from 'vitest'; -import { screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { render } from '../utils'; import { PrintersPage } from '../../pages/PrintersPage'; @@ -88,6 +90,14 @@ const mockRunningStatus = { state: 'RUNNING', }; +/** Hover-card visibility flips after an 80ms timeout — wait it out. */ +async function hoverSlot(slot: Element) { + fireEvent.mouseEnter(slot); + await waitFor(() => { + expect(screen.getByText('Load')).toBeInTheDocument(); + }); +} + describe('PrintersPage - AMS load/unload (#891)', () => { beforeEach(() => { server.use( @@ -112,18 +122,12 @@ describe('PrintersPage - AMS load/unload (#891)', () => { render(); await waitFor(() => { - // The slot menu button is hidden until we hover. Pull it directly out of the DOM. - expect(document.querySelectorAll('[title="Slot options"]').length).toBeGreaterThan(0); + expect(screen.getAllByTestId('filament-slot').length).toBeGreaterThan(0); }); // Slot 2 (third one, slotIdx=2) → expected tray_id = 0*4 + 2 = 2 - const menuButtons = document.querySelectorAll('[title="Slot options"]'); - await user.click(menuButtons[2]); - - await waitFor(() => { - expect(screen.getByText('Load')).toBeInTheDocument(); - }); - + const slots = screen.getAllByTestId('filament-slot'); + await hoverSlot(slots[2]); await user.click(screen.getByText('Load')); await waitFor(() => { @@ -147,16 +151,11 @@ describe('PrintersPage - AMS load/unload (#891)', () => { render(); await waitFor(() => { - expect(document.querySelectorAll('[title="Slot options"]').length).toBeGreaterThan(0); - }); - - const menuButtons = document.querySelectorAll('[title="Slot options"]'); - await user.click(menuButtons[0]); - - await waitFor(() => { - expect(screen.getByText('Unload')).toBeInTheDocument(); + expect(screen.getAllByTestId('filament-slot').length).toBeGreaterThan(0); }); + const slots = screen.getAllByTestId('filament-slot'); + await hoverSlot(slots[0]); await user.click(screen.getByText('Unload')); await waitFor(() => { @@ -164,20 +163,29 @@ describe('PrintersPage - AMS load/unload (#891)', () => { }); }); - it('hides the slot menu while the printer is RUNNING', async () => { + it('disables Load / Unload while the printer is RUNNING', async () => { server.use( http.get('/api/v1/printers/:id/status', () => HttpResponse.json(mockRunningStatus)), ); render(); - // Wait for the page to render the printer card. await waitFor(() => { - expect(screen.getByText('X1 Carbon')).toBeInTheDocument(); + expect(screen.getAllByTestId('filament-slot').length).toBeGreaterThan(0); }); - // No "Slot options" menu trigger should be present at all while running. - expect(document.querySelectorAll('[title="Slot options"]').length).toBe(0); + // Hover to reveal the actions — they should be present but disabled + // while the printer is running (replaces the pre-#1661 behavior where + // the trigger button was hidden entirely). + const slots = screen.getAllByTestId('filament-slot'); + fireEvent.mouseEnter(slots[0]); + + await waitFor(() => { + expect(screen.getByText('Load')).toBeInTheDocument(); + }); + + expect(screen.getByText('Load').closest('button')).toBeDisabled(); + expect(screen.getByText('Unload').closest('button')).toBeDisabled(); }); it('external spool slot exposes Load and posts tray_id=254', async () => { @@ -201,16 +209,11 @@ describe('PrintersPage - AMS load/unload (#891)', () => { render(); await waitFor(() => { - expect(document.querySelectorAll('[title="Slot options"]').length).toBeGreaterThan(0); - }); - - const menuButtons = document.querySelectorAll('[title="Slot options"]'); - await user.click(menuButtons[0]); - - await waitFor(() => { - expect(screen.getByText('Load')).toBeInTheDocument(); + expect(screen.getAllByTestId('filament-slot').length).toBeGreaterThan(0); }); + const slots = screen.getAllByTestId('filament-slot'); + await hoverSlot(slots[0]); await user.click(screen.getByText('Load')); await waitFor(() => { diff --git a/frontend/src/__tests__/pages/PrintersPageSpeed.test.tsx b/frontend/src/__tests__/pages/PrintersPageSpeed.test.tsx index 1f9540e04..5920f1975 100644 --- a/frontend/src/__tests__/pages/PrintersPageSpeed.test.tsx +++ b/frontend/src/__tests__/pages/PrintersPageSpeed.test.tsx @@ -1,8 +1,11 @@ /** * Tests for the print speed control feature on the PrintersPage. * - * Verifies that the speed badge renders, the dropdown menu opens on click, - * speed options are displayed, and selecting an option calls the API. + * The printer-card refactor in #1661 replaced the visible "100%" / "50%" + * speed badge with an icon-only Gauge button. These tests now target the + * button via data-testid="speed-control" instead of the percentage text. + * The dropdown still shows the same translated labels (Silent (50%), + * Standard (100%), Sport (124%), Ludicrous (166%)). */ import { describe, it, expect, beforeEach } from 'vitest'; @@ -79,8 +82,8 @@ describe('PrintersPage - Print Speed Control', () => { ); }); - describe('speed badge rendering', () => { - it('shows speed badge with current speed percentage when printing', async () => { + describe('speed control button', () => { + it('renders and is enabled when printer is printing', async () => { server.use( http.get('/api/v1/printers/:id/status', () => { return HttpResponse.json(mockPrintingStatus); @@ -90,54 +93,13 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - // speed_level 2 = Standard = 100% - expect(screen.getByText('100%')).toBeInTheDocument(); + const button = screen.getByTestId('speed-control'); + expect(button).toBeInTheDocument(); + expect(button).toBeEnabled(); }); }); - it('shows speed badge with 50% for silent mode', async () => { - server.use( - http.get('/api/v1/printers/:id/status', () => { - return HttpResponse.json({ ...mockPrintingStatus, speed_level: 1 }); - }) - ); - - render(); - - await waitFor(() => { - expect(screen.getByText('50%')).toBeInTheDocument(); - }); - }); - - it('shows speed badge with 124% for sport mode', async () => { - server.use( - http.get('/api/v1/printers/:id/status', () => { - return HttpResponse.json({ ...mockPrintingStatus, speed_level: 3 }); - }) - ); - - render(); - - await waitFor(() => { - expect(screen.getByText('124%')).toBeInTheDocument(); - }); - }); - - it('shows speed badge with 166% for ludicrous mode', async () => { - server.use( - http.get('/api/v1/printers/:id/status', () => { - return HttpResponse.json({ ...mockPrintingStatus, speed_level: 4 }); - }) - ); - - render(); - - await waitFor(() => { - expect(screen.getByText('166%')).toBeInTheDocument(); - }); - }); - - it('disables speed badge button when printer is idle', async () => { + it('is disabled when printer is idle', async () => { server.use( http.get('/api/v1/printers/:id/status', () => { return HttpResponse.json(mockIdleStatus); @@ -147,12 +109,9 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + const button = screen.getByTestId('speed-control'); + expect(button).toBeDisabled(); }); - - // The button containing the speed percentage should be disabled - const speedBadge = screen.getByText('100%').closest('button'); - expect(speedBadge).toBeDisabled(); }); }); @@ -169,11 +128,10 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByTestId('speed-control')).toBeEnabled(); }); - const speedBadge = screen.getByText('100%').closest('button')!; - await user.click(speedBadge); + await user.click(screen.getByTestId('speed-control')); await waitFor(() => { expect(screen.getByText('Silent (50%)')).toBeInTheDocument(); @@ -195,11 +153,10 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByTestId('speed-control')).toBeEnabled(); }); - const speedBadge = screen.getByText('100%').closest('button')!; - await user.click(speedBadge); + await user.click(screen.getByTestId('speed-control')); await waitFor(() => { const options = [ @@ -213,7 +170,7 @@ describe('PrintersPage - Print Speed Control', () => { }); }); - it('calls the API when a speed option is selected', async () => { + it('calls the API with the correct mode when a speed option is selected', async () => { const user = userEvent.setup(); let capturedMode: number | null = null; @@ -231,18 +188,15 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByTestId('speed-control')).toBeEnabled(); }); - // Open the speed menu - const speedBadge = screen.getByText('100%').closest('button')!; - await user.click(speedBadge); + await user.click(screen.getByTestId('speed-control')); await waitFor(() => { expect(screen.getByText('Sport (124%)')).toBeInTheDocument(); }); - // Select "Sport" speed await user.click(screen.getByText('Sport (124%)')); await waitFor(() => { @@ -265,33 +219,38 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByTestId('speed-control')).toBeEnabled(); }); - const speedBadge = screen.getByText('100%').closest('button')!; - await user.click(speedBadge); + await user.click(screen.getByTestId('speed-control')); await waitFor(() => { expect(screen.getByText('Silent (50%)')).toBeInTheDocument(); }); - // Select an option await user.click(screen.getByText('Silent (50%)')); - // Menu should close - speed labels should no longer be visible await waitFor(() => { expect(screen.queryByText('Silent (50%)')).not.toBeInTheDocument(); }); }); - it('optimistically updates the speed display when selecting a new speed', async () => { + it.each([ + { mode: 1, label: 'Silent (50%)' }, + { mode: 2, label: 'Standard (100%)' }, + { mode: 3, label: 'Sport (124%)' }, + { mode: 4, label: 'Ludicrous (166%)' }, + ])('selecting $label sends mode=$mode', async ({ mode, label }) => { const user = userEvent.setup(); + let capturedMode: number | null = null; server.use( http.get('/api/v1/printers/:id/status', () => { - return HttpResponse.json(mockPrintingStatus); // speed_level: 2 (100%) + return HttpResponse.json({ ...mockPrintingStatus, speed_level: 2 }); }), - http.post('/api/v1/printers/:id/print-speed', () => { + http.post('/api/v1/printers/:id/print-speed', async ({ request }) => { + const url = new URL(request.url); + capturedMode = Number(url.searchParams.get('mode')); return HttpResponse.json({ success: true, message: 'Speed set' }); }) ); @@ -299,22 +258,19 @@ describe('PrintersPage - Print Speed Control', () => { render(); await waitFor(() => { - expect(screen.getByText('100%')).toBeInTheDocument(); + expect(screen.getByTestId('speed-control')).toBeEnabled(); }); - // Open the speed menu and select Ludicrous - const speedBadge = screen.getByText('100%').closest('button')!; - await user.click(speedBadge); + await user.click(screen.getByTestId('speed-control')); await waitFor(() => { - expect(screen.getByText('Ludicrous (166%)')).toBeInTheDocument(); + expect(screen.getByText(label)).toBeInTheDocument(); }); - await user.click(screen.getByText('Ludicrous (166%)')); + await user.click(screen.getByText(label)); - // The badge should optimistically update to show 166% await waitFor(() => { - expect(screen.getByText('166%')).toBeInTheDocument(); + expect(capturedMode).toBe(mode); }); }); }); diff --git a/frontend/src/components/FilamentHoverCard.tsx b/frontend/src/components/FilamentHoverCard.tsx index 4f80c0c1a..a04c26c76 100644 --- a/frontend/src/components/FilamentHoverCard.tsx +++ b/frontend/src/components/FilamentHoverCard.tsx @@ -177,6 +177,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp return (
(