Procházet zdrojové kódy

test(printers): repair speed + AMS load/unload tests after #1661 refactor

  PR #1661 swapped the visible speed badge ("100%") for an icon-only Gauge
  button and replaced the kebab "Slot options" button with hover-card
  actions inside FilamentHoverCard. The two existing test files weren't
  updated alongside the refactor and stayed broken on dev.

  - Add data-testid="speed-control" to the Gauge button and rewrite the
    Speed tests around it; assertions on the percentage text are gone
    because that text no longer renders. Parametrize the four-mode API
    call test.
  - Add data-testid="filament-slot" to FilamentHoverCard's trigger
    wrapper and rewrite the AMS load/unload tests around
    fireEvent.mouseEnter → portaled actions. Replace the "hides menu
    while RUNNING" assertion with "Load/Unload buttons exist but are
    disabled" — matches the new UX shape.
maziggy před 2 měsíci
rodič
revize
a1cd86880c

+ 37 - 34
frontend/src/__tests__/pages/PrintersPageAmsLoadUnload.test.tsx

@@ -1,13 +1,15 @@
 /**
 /**
  * Tests for the AMS slot load / unload buttons on PrintersPage (#891).
  * 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 { 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 userEvent from '@testing-library/user-event';
 import { render } from '../utils';
 import { render } from '../utils';
 import { PrintersPage } from '../../pages/PrintersPage';
 import { PrintersPage } from '../../pages/PrintersPage';
@@ -88,6 +90,14 @@ const mockRunningStatus = {
   state: 'RUNNING',
   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)', () => {
 describe('PrintersPage - AMS load/unload (#891)', () => {
   beforeEach(() => {
   beforeEach(() => {
     server.use(
     server.use(
@@ -112,18 +122,12 @@ describe('PrintersPage - AMS load/unload (#891)', () => {
     render(<PrintersPage />);
     render(<PrintersPage />);
 
 
     await waitFor(() => {
     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
     // Slot 2 (third one, slotIdx=2) → expected tray_id = 0*4 + 2 = 2
-    const menuButtons = document.querySelectorAll<HTMLButtonElement>('[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 user.click(screen.getByText('Load'));
 
 
     await waitFor(() => {
     await waitFor(() => {
@@ -147,16 +151,11 @@ describe('PrintersPage - AMS load/unload (#891)', () => {
     render(<PrintersPage />);
     render(<PrintersPage />);
 
 
     await waitFor(() => {
     await waitFor(() => {
-      expect(document.querySelectorAll('[title="Slot options"]').length).toBeGreaterThan(0);
-    });
-
-    const menuButtons = document.querySelectorAll<HTMLButtonElement>('[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 user.click(screen.getByText('Unload'));
 
 
     await waitFor(() => {
     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(
     server.use(
       http.get('/api/v1/printers/:id/status', () => HttpResponse.json(mockRunningStatus)),
       http.get('/api/v1/printers/:id/status', () => HttpResponse.json(mockRunningStatus)),
     );
     );
 
 
     render(<PrintersPage />);
     render(<PrintersPage />);
 
 
-    // Wait for the page to render the printer card.
     await waitFor(() => {
     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 () => {
   it('external spool slot exposes Load and posts tray_id=254', async () => {
@@ -201,16 +209,11 @@ describe('PrintersPage - AMS load/unload (#891)', () => {
     render(<PrintersPage />);
     render(<PrintersPage />);
 
 
     await waitFor(() => {
     await waitFor(() => {
-      expect(document.querySelectorAll('[title="Slot options"]').length).toBeGreaterThan(0);
-    });
-
-    const menuButtons = document.querySelectorAll<HTMLButtonElement>('[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 user.click(screen.getByText('Load'));
 
 
     await waitFor(() => {
     await waitFor(() => {

+ 38 - 82
frontend/src/__tests__/pages/PrintersPageSpeed.test.tsx

@@ -1,8 +1,11 @@
 /**
 /**
  * Tests for the print speed control feature on the PrintersPage.
  * 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';
 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(
       server.use(
         http.get('/api/v1/printers/:id/status', () => {
         http.get('/api/v1/printers/:id/status', () => {
           return HttpResponse.json(mockPrintingStatus);
           return HttpResponse.json(mockPrintingStatus);
@@ -90,54 +93,13 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(<PrintersPage />);
-
-      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(<PrintersPage />);
-
-      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(<PrintersPage />);
-
-      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(
       server.use(
         http.get('/api/v1/printers/:id/status', () => {
         http.get('/api/v1/printers/:id/status', () => {
           return HttpResponse.json(mockIdleStatus);
           return HttpResponse.json(mockIdleStatus);
@@ -147,12 +109,9 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(() => {
       await waitFor(() => {
         expect(screen.getByText('Silent (50%)')).toBeInTheDocument();
         expect(screen.getByText('Silent (50%)')).toBeInTheDocument();
@@ -195,11 +153,10 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(() => {
       await waitFor(() => {
         const options = [
         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();
       const user = userEvent.setup();
       let capturedMode: number | null = null;
       let capturedMode: number | null = null;
 
 
@@ -231,18 +188,15 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(() => {
       await waitFor(() => {
         expect(screen.getByText('Sport (124%)')).toBeInTheDocument();
         expect(screen.getByText('Sport (124%)')).toBeInTheDocument();
       });
       });
 
 
-      // Select "Sport" speed
       await user.click(screen.getByText('Sport (124%)'));
       await user.click(screen.getByText('Sport (124%)'));
 
 
       await waitFor(() => {
       await waitFor(() => {
@@ -265,33 +219,38 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(() => {
       await waitFor(() => {
         expect(screen.getByText('Silent (50%)')).toBeInTheDocument();
         expect(screen.getByText('Silent (50%)')).toBeInTheDocument();
       });
       });
 
 
-      // Select an option
       await user.click(screen.getByText('Silent (50%)'));
       await user.click(screen.getByText('Silent (50%)'));
 
 
-      // Menu should close - speed labels should no longer be visible
       await waitFor(() => {
       await waitFor(() => {
         expect(screen.queryByText('Silent (50%)')).not.toBeInTheDocument();
         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();
       const user = userEvent.setup();
+      let capturedMode: number | null = null;
 
 
       server.use(
       server.use(
         http.get('/api/v1/printers/:id/status', () => {
         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' });
           return HttpResponse.json({ success: true, message: 'Speed set' });
         })
         })
       );
       );
@@ -299,22 +258,19 @@ describe('PrintersPage - Print Speed Control', () => {
       render(<PrintersPage />);
       render(<PrintersPage />);
 
 
       await waitFor(() => {
       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(() => {
       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(() => {
       await waitFor(() => {
-        expect(screen.getByText('166%')).toBeInTheDocument();
+        expect(capturedMode).toBe(mode);
       });
       });
     });
     });
   });
   });

+ 1 - 0
frontend/src/components/FilamentHoverCard.tsx

@@ -177,6 +177,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
   return (
   return (
     <div
     <div
       ref={triggerRef}
       ref={triggerRef}
+      data-testid="filament-slot"
       className={`relative ${className}`}
       className={`relative ${className}`}
       onMouseEnter={handleMouseEnter}
       onMouseEnter={handleMouseEnter}
       onMouseLeave={handleMouseLeave}
       onMouseLeave={handleMouseLeave}

+ 1 - 0
frontend/src/pages/PrintersPage.tsx

@@ -4054,6 +4054,7 @@ function PrinterCard({
                       {(() => (
                       {(() => (
                         <div className="relative">
                         <div className="relative">
                           <button
                           <button
+                            data-testid="speed-control"
                             onClick={() => setShowSpeedMenu(showSpeedMenu === printer.id ? null : printer.id)}
                             onClick={() => setShowSpeedMenu(showSpeedMenu === printer.id ? null : printer.id)}
                             disabled={!isPrinting || !hasPermission('printers:control')}
                             disabled={!isPrinting || !hasPermission('printers:control')}
                             className={`${iconControlClass} ${
                             className={`${iconControlClass} ${

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
static/assets/index-DzdK7n82.js


+ 1 - 1
static/index.html

@@ -26,7 +26,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-BEllJfyV.js"></script>
+    <script type="module" crossorigin src="/assets/index-DzdK7n82.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-iyQgdQ5p.css">
     <link rel="stylesheet" crossorigin href="/assets/index-iyQgdQ5p.css">
   </head>
   </head>
   <body>
   <body>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů