mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-21 10:15:04 +02:00
Merge branch 'dev' into feature/slicer-multi-button
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* The active-cycle badge on the AMS card (#2759).
|
||||
*
|
||||
* Bambu never echoes back which filament or temperature a drying cycle is
|
||||
* running, so the backend hands us two independent fields: `dry_filament`,
|
||||
* which it can also infer from a uniformly loaded unit, and `dry_target_temp`,
|
||||
* which it only knows from the target it cached when sending the command. The
|
||||
* temperature can therefore go missing while the filament survives, and the
|
||||
* badge has to render that pairing rather than dropping both.
|
||||
*
|
||||
* The reporter's second AMS held only PLA and was drying at the 45°C they
|
||||
* picked; with no cached target the badge previously showed the RFID
|
||||
* recommendation and read "PLA @ 55°C".
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { render } from '../utils';
|
||||
import { PrintersPage } from '../../pages/PrintersPage';
|
||||
import { http, HttpResponse } from 'msw';
|
||||
import { server } from '../mocks/server';
|
||||
|
||||
const mockPrinter = {
|
||||
id: 1,
|
||||
name: 'X1C',
|
||||
ip_address: '192.168.1.100',
|
||||
serial_number: '01P00A000000001',
|
||||
access_code: '12345678',
|
||||
model: 'X1C',
|
||||
enabled: true,
|
||||
nozzle_diameter: 0.4,
|
||||
nozzle_type: 'stainless_steel',
|
||||
location: 'Workshop',
|
||||
auto_archive: true,
|
||||
created_at: '2024-01-01T00:00:00Z',
|
||||
updated_at: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const baseTray = {
|
||||
tray_color: 'FF0000FF',
|
||||
tray_type: 'PLA',
|
||||
tray_sub_brands: 'PLA Basic',
|
||||
tray_id_name: 'A00-R0',
|
||||
tray_info_idx: 'GFA00',
|
||||
remain: 80,
|
||||
k: 0.02,
|
||||
cali_idx: null,
|
||||
tag_uid: null,
|
||||
tray_uuid: null,
|
||||
nozzle_temp_min: 190,
|
||||
nozzle_temp_max: 230,
|
||||
drying_temp: 55,
|
||||
drying_time: 8,
|
||||
state: 3,
|
||||
};
|
||||
|
||||
/** An AMS 2 Pro twelve hours into a cycle, with the badge fields under test. */
|
||||
function makeStatus(target: { dry_filament: string | null; dry_target_temp: number | null }) {
|
||||
return {
|
||||
connected: true,
|
||||
state: 'IDLE',
|
||||
progress: 0,
|
||||
layer_num: 0,
|
||||
total_layers: 0,
|
||||
temperatures: { nozzle: 25, bed: 25, chamber: 25 },
|
||||
remaining_time: 0,
|
||||
filename: null,
|
||||
wifi_signal: -29,
|
||||
speed_level: 2,
|
||||
supports_drying: true,
|
||||
drying_screen_only: false,
|
||||
vt_tray: [],
|
||||
ams: [
|
||||
{
|
||||
id: 0,
|
||||
humidity: 30,
|
||||
temp: 33,
|
||||
is_ams_ht: false,
|
||||
serial_number: 'AMS00',
|
||||
sw_ver: '03.00.21.29',
|
||||
dry_time: 719,
|
||||
dry_status: 2,
|
||||
dry_sub_status: 0,
|
||||
dry_sf_reason: [],
|
||||
module_type: 'n3f',
|
||||
...target,
|
||||
tray: [0, 1, 2, 3].map((id) => ({ id, ...baseTray })),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function renderWith(target: { dry_filament: string | null; dry_target_temp: number | null }) {
|
||||
server.use(
|
||||
http.get('/api/v1/printers/', () => HttpResponse.json([mockPrinter])),
|
||||
http.get('/api/v1/printers/:id/status', () => HttpResponse.json(makeStatus(target))),
|
||||
http.get('/api/v1/queue/', () => HttpResponse.json([])),
|
||||
);
|
||||
render(<PrintersPage />);
|
||||
}
|
||||
|
||||
describe('PrintersPage — AMS drying badge (#2759)', () => {
|
||||
beforeEach(() => {
|
||||
server.use(http.get('/api/v1/queue/', () => HttpResponse.json([])));
|
||||
});
|
||||
|
||||
it('names the filament and the temperature when the cycle target is known', async () => {
|
||||
renderWith({ dry_filament: 'PLA', dry_target_temp: 45 });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('PLA @ 45°C').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('still names the filament when only the temperature is unknown', async () => {
|
||||
renderWith({ dry_filament: 'PLA', dry_target_temp: null });
|
||||
|
||||
// The filament survives on its own — dropping it too would leave the badge
|
||||
// showing a bare countdown for a cycle we can still describe.
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText('PLA').length).toBeGreaterThan(0);
|
||||
});
|
||||
// And it must not fall back to the trays' RFID recommendation (55°C here),
|
||||
// which is what the user's chosen 45°C was being overwritten with. Scoped
|
||||
// to the badge's own "<filament> @ <temp>°C" shape — the card carries
|
||||
// unrelated nozzle and bed readings in °C.
|
||||
expect(screen.queryByText(/@ \d+°C/)).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the countdown alone when the unit gives no evidence at all', async () => {
|
||||
renderWith({ dry_filament: null, dry_target_temp: null });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByText(/11h 59m/).length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(screen.queryByText(/@ \d+°C/)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -4911,9 +4911,15 @@ function PrinterCard({
|
||||
<div className="flex items-center gap-2 rounded-lg bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-[length:var(--pc-t9,9px)]">
|
||||
<Flame className="w-[var(--pc-i3,0.75rem)] h-[var(--pc-i3,0.75rem)] text-amber-600 dark:text-amber-400 shrink-0" />
|
||||
<span className="text-amber-700 dark:text-amber-400 font-medium">{t('printers.drying.active')}</span>
|
||||
{ams.dry_filament && ams.dry_target_temp != null && (
|
||||
{/* The temperature is only ever known from the target we
|
||||
cached when sending the command — the filament can also
|
||||
be read off a uniformly loaded unit, so it can outlive
|
||||
the temperature (#2759). */}
|
||||
{ams.dry_filament && (
|
||||
<span className="text-amber-700/80 dark:text-amber-300/70">
|
||||
{t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })}
|
||||
{ams.dry_target_temp != null
|
||||
? t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })
|
||||
: ams.dry_filament}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-amber-700/80 dark:text-amber-300/70">
|
||||
@@ -5453,9 +5459,11 @@ function PrinterCard({
|
||||
{ams.dry_time > 0 && (
|
||||
<div className="flex items-center gap-1.5 overflow-hidden whitespace-nowrap rounded-lg bg-amber-50 dark:bg-amber-500/10 px-2 py-1 text-[length:var(--pc-t9,9px)]">
|
||||
<Flame className="w-[var(--pc-i3,0.75rem)] h-[var(--pc-i3,0.75rem)] text-amber-600 dark:text-amber-400 shrink-0" />
|
||||
{ams.dry_filament && ams.dry_target_temp != null && (
|
||||
{ams.dry_filament && (
|
||||
<span className="text-amber-700/80 dark:text-amber-300/70 text-[length:var(--pc-t8,8px)] truncate">
|
||||
{t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })}
|
||||
{ams.dry_target_temp != null
|
||||
? t('printers.drying.targetSummary', { filament: ams.dry_filament, temp: ams.dry_target_temp })
|
||||
: ams.dry_filament}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-amber-700/80 dark:text-amber-300/70 text-[length:var(--pc-t8,8px)] truncate">
|
||||
|
||||
Reference in New Issue
Block a user