mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 11:12:35 +02:00
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.
This commit is contained in:
@@ -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(<PrintersPage />);
|
||||
|
||||
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<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 waitFor(() => {
|
||||
@@ -147,16 +151,11 @@ describe('PrintersPage - AMS load/unload (#891)', () => {
|
||||
render(<PrintersPage />);
|
||||
|
||||
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 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(<PrintersPage />);
|
||||
|
||||
// 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(<PrintersPage />);
|
||||
|
||||
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 waitFor(() => {
|
||||
|
||||
@@ -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(<PrintersPage />);
|
||||
|
||||
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(
|
||||
http.get('/api/v1/printers/:id/status', () => {
|
||||
return HttpResponse.json(mockIdleStatus);
|
||||
@@ -147,12 +109,9 @@ describe('PrintersPage - Print Speed Control', () => {
|
||||
render(<PrintersPage />);
|
||||
|
||||
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 />);
|
||||
|
||||
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(<PrintersPage />);
|
||||
|
||||
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(<PrintersPage />);
|
||||
|
||||
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(<PrintersPage />);
|
||||
|
||||
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(<PrintersPage />);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,6 +177,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
return (
|
||||
<div
|
||||
ref={triggerRef}
|
||||
data-testid="filament-slot"
|
||||
className={`relative ${className}`}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
|
||||
@@ -4054,6 +4054,7 @@ function PrinterCard({
|
||||
{(() => (
|
||||
<div className="relative">
|
||||
<button
|
||||
data-testid="speed-control"
|
||||
onClick={() => setShowSpeedMenu(showSpeedMenu === printer.id ? null : printer.id)}
|
||||
disabled={!isPrinting || !hasPermission('printers:control')}
|
||||
className={`${iconControlClass} ${
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user