/** * The drying popover has to name a material the printer will recognise. * * It seeds two things from one lookup: the temperature it prefills, and the * filament name the start command carries. The dropdown rendering that name * falls back to its first option when the value isn't in its list -- silently * -- so a lookup that can return something outside the table shows one material * and sends another. An AMS-HT holding Support for PLA/PETG displayed PLA and * told the printer PLA-S (#2774). These tests pin the invariant that closes * that: whatever comes back is a key the table has. */ import { describe, it, expect } from 'vitest'; import { resolveDryingPresetKey, type DryingPreset } from '../../utils/dryingPresets'; // The shipped table, as PrintersPage.tsx defines it. const PRESETS: Record = { 'PLA': { n3f: 45, n3s: 45, n3f_hours: 12, n3s_hours: 12 }, 'PETG': { n3f: 65, n3s: 65, n3f_hours: 12, n3s_hours: 12 }, 'TPU': { n3f: 65, n3s: 75, n3f_hours: 12, n3s_hours: 18 }, 'ABS': { n3f: 65, n3s: 80, n3f_hours: 12, n3s_hours: 8 }, 'ASA': { n3f: 65, n3s: 80, n3f_hours: 12, n3s_hours: 8 }, 'PA': { n3f: 65, n3s: 85, n3f_hours: 12, n3s_hours: 12 }, 'PC': { n3f: 65, n3s: 80, n3f_hours: 12, n3s_hours: 8 }, 'PVA': { n3f: 65, n3s: 85, n3f_hours: 12, n3s_hours: 18 }, }; describe('resolveDryingPresetKey', () => { it('always answers with a key the table has', () => { // The invariant behind #2774. Anything outside the table reaches the // dropdown as a value it will not display and the printer as a material // the user never chose. const trayTypes = [ 'PLA', 'PETG', 'ABS', 'ASA', 'TPU', 'PA', 'PC', 'PVA', 'PLA-S', 'PLA-CF', 'PETG-CF', 'PET-CF', 'ABS-GF', 'ASA-CF', 'PAHT-CF', 'PA6-CF', 'PPS-CF', 'PPA-CF', 'HIPS', 'PP', 'PE', 'EVA', 'PHA', 'PCTG', 'Nylon', 'TPU for AMS', '', ' ', 'wildly unknown', ]; for (const trayType of trayTypes) { expect(PRESETS).toHaveProperty(resolveDryingPresetKey(trayType, PRESETS)); } }); it('passes a listed material straight through', () => { expect(resolveDryingPresetKey('PETG', PRESETS)).toBe('PETG'); expect(resolveDryingPresetKey('PVA', PRESETS)).toBe('PVA'); }); it('is case-insensitive and ignores a trailing qualifier', () => { expect(resolveDryingPresetKey('petg', PRESETS)).toBe('PETG'); expect(resolveDryingPresetKey('TPU for AMS', PRESETS)).toBe('TPU'); }); it('dries a support material as its base', () => { // Support for PLA/PETG reports as PLA-S -- the case from the report. expect(resolveDryingPresetKey('PLA-S', PRESETS)).toBe('PLA'); }); it('dries a composite as its base rather than defaulting to PLA', () => { // The reason the suffix is stripped instead of just falling back: PETG-CF // wants PETG's 65 degrees, and landing on PLA's 45 would quietly waste the // cycle. expect(resolveDryingPresetKey('PETG-CF', PRESETS)).toBe('PETG'); expect(resolveDryingPresetKey('PLA-CF', PRESETS)).toBe('PLA'); expect(resolveDryingPresetKey('ABS-GF', PRESETS)).toBe('ABS'); expect(resolveDryingPresetKey('ASA-CF', PRESETS)).toBe('ASA'); }); it('recognises the polyamide family under its own spellings', () => { // The grades have to be listed: "PA12" shares no prefix rule with "PA", so // dropping the suffix alone leaves it unrecognised. #3067 reported PA6 and // named PA11 and PA12 alongside it. expect(resolveDryingPresetKey('PA6-CF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('PA6-GF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('PA11-CF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('PA12-CF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('PAHT-CF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('Nylon', PRESETS)).toBe('PA'); // Polyphthalamide is a distinct polymer rather than a nylon grade, so this // one is a judgement: an aromatic polyamide that takes up moisture the same // way, dried on the hottest row the table has. expect(resolveDryingPresetKey('PPA-CF', PRESETS)).toBe('PA'); expect(resolveDryingPresetKey('PPA-GF', PRESETS)).toBe('PA'); }); it('falls back to the coolest row for an unknown material', () => { // Under-drying an exotic filament wastes a cycle; PA's 85 degrees would // deform a PLA spool. So an unrecognised material must never inherit a // hotter row than PLA's. for (const unknown of ['PPS-CF', 'PEEK', 'wildly unknown']) { expect(resolveDryingPresetKey(unknown, PRESETS)).toBe('PLA'); } }); it('handles an empty tray', () => { // No spool loaded -- the popover still has to open on something. expect(resolveDryingPresetKey(undefined, PRESETS)).toBe('PLA'); expect(resolveDryingPresetKey(null, PRESETS)).toBe('PLA'); expect(resolveDryingPresetKey('', PRESETS)).toBe('PLA'); }); it('respects a custom preset table', () => { // Users can override the table from settings, so the lookup answers about // the table it was handed, not the shipped one. const custom = { ...PRESETS, 'PLA-S': { n3f: 55, n3s: 55, n3f_hours: 8, n3s_hours: 8 } }; expect(resolveDryingPresetKey('PLA-S', custom)).toBe('PLA-S'); }); });