|
@@ -18,6 +18,15 @@ vi.mock('../../api/client', () => ({
|
|
|
},
|
|
},
|
|
|
}));
|
|
}));
|
|
|
|
|
|
|
|
|
|
+const mockShowToast = vi.fn();
|
|
|
|
|
+vi.mock('../../contexts/ToastContext', async (importOriginal) => {
|
|
|
|
|
+ const actual = await importOriginal<typeof import('../../contexts/ToastContext')>();
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...actual,
|
|
|
|
|
+ useToast: () => ({ showToast: mockShowToast }),
|
|
|
|
|
+ };
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
const defaultProps = {
|
|
const defaultProps = {
|
|
|
isOpen: true,
|
|
isOpen: true,
|
|
|
onClose: vi.fn(),
|
|
onClose: vi.fn(),
|
|
@@ -323,6 +332,88 @@ describe('AssignSpoolModal', () => {
|
|
|
expect(api.refreshPrinterStatus).toHaveBeenCalledWith(7);
|
|
expect(api.refreshPrinterStatus).toHaveBeenCalledWith(7);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ // #1680: backend returns `pending_config=true` when the AMS slot was empty
|
|
|
|
|
+ // at assign time (firmware drops the MQTT push silently; on_ams_change
|
|
|
|
|
+ // re-fires the config when filament is detected). The toast MUST reflect
|
|
|
|
|
+ // that the slot hasn't been configured yet — saying "AMS slot configured"
|
|
|
|
|
+ // reads as a lie and is what the reporter hit.
|
|
|
|
|
+ it('shows the pending-insert toast when backend returns pending_config=true (#1680)', async () => {
|
|
|
|
|
+ const { default: userEvent } = await import('@testing-library/user-event');
|
|
|
|
|
+ const user = userEvent.setup();
|
|
|
|
|
+
|
|
|
|
|
+ (api.getSpools as ReturnType<typeof vi.fn>).mockResolvedValue([manualSpool]);
|
|
|
|
|
+ (api.assignSpool as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
|
|
|
+ id: 1, spool_id: 1, printer_id: 7, ams_id: 0, tray_id: 0,
|
|
|
|
|
+ pending_config: true,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(
|
|
|
|
|
+ <AssignSpoolModal
|
|
|
|
|
+ {...defaultProps}
|
|
|
|
|
+ printerId={7}
|
|
|
|
|
+ trayInfo={{ type: 'PLA', material: 'PLA', profile: 'PLA', color: 'FF0000', location: 'AMS 1 - Slot 1' }}
|
|
|
|
|
+ />
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText(/Polymaker/)).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+ await user.click(screen.getByText(/Polymaker/));
|
|
|
|
|
+ await user.click(screen.getByRole('button', { name: /assign spool/i }));
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('Slot will configure when you insert the spool'),
|
|
|
|
|
+ 'success',
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ // And the "AMS slot configured" toast must NOT also have fired.
|
|
|
|
|
+ expect(mockShowToast).not.toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('AMS slot configured'),
|
|
|
|
|
+ expect.anything(),
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Counterpart guard: when the backend did configure the slot
|
|
|
|
|
+ // (pending_config=false or absent), the existing success toast still
|
|
|
|
|
+ // fires. Without this regression test, the #1680 fix could silently
|
|
|
|
|
+ // overshoot and label every assign as pending.
|
|
|
|
|
+ it('shows the configured toast when backend returns pending_config=false (#1680)', async () => {
|
|
|
|
|
+ const { default: userEvent } = await import('@testing-library/user-event');
|
|
|
|
|
+ const user = userEvent.setup();
|
|
|
|
|
+
|
|
|
|
|
+ (api.getSpools as ReturnType<typeof vi.fn>).mockResolvedValue([manualSpool]);
|
|
|
|
|
+ (api.assignSpool as ReturnType<typeof vi.fn>).mockResolvedValue({
|
|
|
|
|
+ id: 1, spool_id: 1, printer_id: 7, ams_id: 0, tray_id: 0,
|
|
|
|
|
+ pending_config: false,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ render(
|
|
|
|
|
+ <AssignSpoolModal
|
|
|
|
|
+ {...defaultProps}
|
|
|
|
|
+ printerId={7}
|
|
|
|
|
+ trayInfo={{ type: 'PLA', material: 'PLA', profile: 'PLA', color: 'FF0000', location: 'AMS 1 - Slot 1' }}
|
|
|
|
|
+ />
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByText(/Polymaker/)).toBeInTheDocument();
|
|
|
|
|
+ });
|
|
|
|
|
+ await user.click(screen.getByText(/Polymaker/));
|
|
|
|
|
+ await user.click(screen.getByRole('button', { name: /assign spool/i }));
|
|
|
|
|
+
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('AMS slot configured'),
|
|
|
|
|
+ 'success',
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
|
|
+ expect(mockShowToast).not.toHaveBeenCalledWith(
|
|
|
|
|
+ expect.stringContaining('Slot will configure when you insert'),
|
|
|
|
|
+ expect.anything(),
|
|
|
|
|
+ );
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('AssignSpoolModal — Spoolman enabled (T-Gap 7)', () => {
|
|
describe('AssignSpoolModal — Spoolman enabled (T-Gap 7)', () => {
|