mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
refactor(settings): Spool Catalog now identical in internal and Spoolman modes
Drop the Spoolman-mode hijack that replaced the local spool tare catalog
with an inline filament editor — two unrelated concepts that should never
have shared a card. Spool Catalog now renders the same way in both modes;
Spoolman users edit filament name and spool_weight in Spoolman's own UI.
Also eliminates the GET /spoolman/inventory/filaments 400 probe that fired
on the Filament settings page whenever Spoolman was disabled.
- frontend/src/components/SpoolCatalogSettings.tsx rewritten (752 -> 444 lines)
- frontend/src/components/SpoolWeightUpdateModal.tsx deleted (orphan)
- SpoolCatalogSettings test file rewritten to match the simplified component
- PATCH /spoolman/inventory/filaments/{id} backend route left in place
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
import { render } from '../utils';
|
||||
import { SpoolCatalogSettings } from '../../components/SpoolCatalogSettings';
|
||||
|
||||
@@ -20,17 +19,6 @@ vi.mock('../../api/client', () => ({
|
||||
api: {
|
||||
getSettings: vi.fn().mockResolvedValue({}),
|
||||
getSpoolCatalog: vi.fn().mockResolvedValue([]),
|
||||
getSpoolmanInventoryFilaments: vi.fn().mockResolvedValue([]),
|
||||
patchSpoolmanFilament: vi.fn().mockResolvedValue({
|
||||
id: 1,
|
||||
name: 'PLA Basic',
|
||||
material: 'PLA',
|
||||
color_hex: 'FF0000',
|
||||
color_name: 'Red',
|
||||
weight: 1000,
|
||||
spool_weight: 196,
|
||||
vendor: { id: 1, name: 'Bambu Lab' },
|
||||
}),
|
||||
},
|
||||
ApiError: class ApiError extends Error {
|
||||
status: number;
|
||||
@@ -41,87 +29,15 @@ vi.mock('../../api/client', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
import { api, ApiError } from '../../api/client';
|
||||
import { api } from '../../api/client';
|
||||
|
||||
const sampleFilament = {
|
||||
id: 1,
|
||||
name: 'PLA Basic',
|
||||
material: 'PLA',
|
||||
color_hex: 'FF0000',
|
||||
color_name: 'Red',
|
||||
weight: 1000,
|
||||
spool_weight: 196,
|
||||
vendor: { id: 1, name: 'Bambu Lab' },
|
||||
};
|
||||
|
||||
describe('SpoolCatalogSettings — mode switching', () => {
|
||||
describe('SpoolCatalogSettings — local catalog UI', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(api.getSpoolCatalog).mockResolvedValue([]);
|
||||
});
|
||||
|
||||
it('hides Spoolman table and shows local CRUD buttons when Spoolman is disabled (400)', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockRejectedValue(
|
||||
new ApiError('disabled', 400)
|
||||
);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Local mode: Add button visible
|
||||
expect(screen.getByText('common.add')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Spoolman table columns must NOT appear
|
||||
expect(screen.queryByText('settings.catalog.material')).toBeNull();
|
||||
expect(screen.queryByText('settings.catalog.spoolWeight')).toBeNull();
|
||||
// Spoolman catalog title must NOT appear
|
||||
expect(screen.queryByText('settings.spoolmanFilamentCatalogTitle')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows Spoolman error row when Spoolman is unreachable (503)', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockRejectedValue(
|
||||
new ApiError('unreachable', 503)
|
||||
);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('inventory.spoolmanCatalogLoadFailed')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Local CRUD buttons must NOT appear in Spoolman mode
|
||||
expect(screen.queryByText('common.add')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows empty state when Spoolman returns an empty list', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('inventory.noSpoolmanFilaments')).toBeTruthy();
|
||||
});
|
||||
|
||||
// Local CRUD buttons must NOT appear
|
||||
expect(screen.queryByText('common.add')).toBeNull();
|
||||
});
|
||||
|
||||
it('renders Spoolman filament rows with vendor and name combined', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('(local mode) shows Export, Import, Reset, Add buttons when Spoolman disabled', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockRejectedValue(
|
||||
new ApiError('disabled', 400)
|
||||
);
|
||||
|
||||
it('shows local CRUD buttons regardless of Spoolman state', async () => {
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -133,341 +49,20 @@ describe('SpoolCatalogSettings — mode switching', () => {
|
||||
expect(screen.getByText('common.reset')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('(spoolman mode) hides Export, Import, Reset, Add buttons when Spoolman is enabled', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
it('renders the local Spool Catalog header and column layout', async () => {
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
expect(screen.getByText('settings.catalog.spoolCatalog')).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('common.add')).toBeNull();
|
||||
expect(screen.queryByText('common.export')).toBeNull();
|
||||
expect(screen.queryByText('common.import')).toBeNull();
|
||||
expect(screen.queryByText('common.reset')).toBeNull();
|
||||
});
|
||||
|
||||
it('(spoolman mode) renders correct column headers — Name, Material, Weight, Spool Weight', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('common.name')).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.getByText('settings.catalog.material')).toBeTruthy();
|
||||
expect(screen.getByText('common.name')).toBeTruthy();
|
||||
expect(screen.getByText('settings.catalog.weight')).toBeTruthy();
|
||||
expect(screen.getByText('settings.catalog.spoolWeight')).toBeTruthy();
|
||||
});
|
||||
expect(screen.getByText('settings.catalog.type')).toBeTruthy();
|
||||
|
||||
it('(spoolman mode) renders all data fields for a filament row', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
|
||||
// Material column
|
||||
expect(screen.getByText('PLA')).toBeTruthy();
|
||||
// Filament weight
|
||||
expect(screen.getByText('1000g')).toBeTruthy();
|
||||
// Spool (empty) weight
|
||||
expect(screen.getByText('196g')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('(spoolman mode) renders color swatch with correct background color', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([
|
||||
{ ...sampleFilament, color_hex: 'FF5500' },
|
||||
]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
|
||||
const swatch = screen.getByLabelText('inventory.spoolmanFilamentColorSwatch');
|
||||
const bg = (swatch as HTMLElement).style.backgroundColor;
|
||||
// Accepts both hex-like and rgb() representations
|
||||
expect(bg).toBeTruthy();
|
||||
expect(bg).not.toBe('');
|
||||
});
|
||||
|
||||
it('(spoolman mode) renders fallback color when color_hex is null', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([
|
||||
{ ...sampleFilament, color_hex: null },
|
||||
]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
|
||||
const swatch = screen.getByLabelText('inventory.spoolmanFilamentColorSwatch');
|
||||
expect((swatch as HTMLElement).style.backgroundColor).toContain('128');
|
||||
});
|
||||
|
||||
it('(spoolman mode) renders dash for null material, weight, and spool_weight', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([
|
||||
{ ...sampleFilament, material: null, weight: null, spool_weight: null },
|
||||
]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
|
||||
// All three nullable fields must show '—', not 'nullg' or empty string
|
||||
const dashes = screen.getAllByText('—');
|
||||
expect(dashes.length).toBeGreaterThanOrEqual(3);
|
||||
});
|
||||
|
||||
it('(spoolman mode) shows Spoolman catalog title, not local catalog title', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.spoolmanFilamentCatalogTitle')).toBeTruthy();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('settings.catalog.spoolCatalog')).toBeNull();
|
||||
});
|
||||
|
||||
it('(spoolman mode) shows pencil edit button in each filament row', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Bambu Lab — PLA Basic/)).toBeTruthy();
|
||||
});
|
||||
|
||||
const editButtons = screen.getAllByLabelText('common.edit');
|
||||
expect(editButtons.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('(spoolman mode) clicking pencil shows name and weight inputs', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.name')).toBeTruthy();
|
||||
expect(screen.getByLabelText('settings.catalog.spoolWeight')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) name input is pre-filled with current name', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
const nameInput = screen.getByLabelText('common.name') as HTMLInputElement;
|
||||
expect(nameInput.value).toBe('PLA Basic');
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) weight input is pre-filled with current spool_weight', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
const weightInput = screen.getByLabelText('settings.catalog.spoolWeight') as HTMLInputElement;
|
||||
expect(weightInput.value).toBe('196');
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) cancel edit restores read-only display', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.cancel')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.cancel'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByLabelText('common.name')).toBeNull();
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) empty name input disables save button', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.name')).toBeTruthy();
|
||||
});
|
||||
|
||||
const nameInput = screen.getByLabelText('common.name');
|
||||
fireEvent.change(nameInput, { target: { value: '' } });
|
||||
|
||||
const saveBtn = screen.getByLabelText('common.save') as HTMLButtonElement;
|
||||
expect(saveBtn.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('(spoolman mode) saving name-only calls patchSpoolmanFilament without modal', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.name')).toBeTruthy();
|
||||
});
|
||||
|
||||
const nameInput = screen.getByLabelText('common.name');
|
||||
fireEvent.change(nameInput, { target: { value: 'PLA Basic Renamed' } });
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.save'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(api.patchSpoolmanFilament)).toHaveBeenCalledWith(1, { name: 'PLA Basic Renamed' });
|
||||
});
|
||||
|
||||
// Modal must NOT appear
|
||||
expect(screen.queryByText('settings.catalog.updateSpoolWeight')).toBeNull();
|
||||
});
|
||||
|
||||
it('(spoolman mode) saving changed spool_weight opens SpoolWeightUpdateModal', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('common.edit')).toBeTruthy();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('settings.catalog.spoolWeight')).toBeTruthy();
|
||||
});
|
||||
|
||||
const weightInput = screen.getByLabelText('settings.catalog.spoolWeight');
|
||||
fireEvent.change(weightInput, { target: { value: '100' } });
|
||||
|
||||
fireEvent.click(screen.getByLabelText('common.save'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('settings.catalog.updateSpoolWeight')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) confirming option B calls patchSpoolmanFilament with keep_existing_spools=false', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('common.edit')).toBeTruthy(); });
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('settings.catalog.spoolWeight')).toBeTruthy(); });
|
||||
fireEvent.change(screen.getByLabelText('settings.catalog.spoolWeight'), { target: { value: '100' } });
|
||||
fireEvent.click(screen.getByLabelText('common.save'));
|
||||
|
||||
await waitFor(() => { expect(screen.getByText('settings.catalog.updateSpoolWeight')).toBeTruthy(); });
|
||||
|
||||
// Confirm with option B selected by default
|
||||
fireEvent.click(screen.getByText('common.confirm'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(api.patchSpoolmanFilament)).toHaveBeenCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ spool_weight: 100, keep_existing_spools: false }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) confirming option A calls patchSpoolmanFilament with keep_existing_spools=true', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('common.edit')).toBeTruthy(); });
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('settings.catalog.spoolWeight')).toBeTruthy(); });
|
||||
fireEvent.change(screen.getByLabelText('settings.catalog.spoolWeight'), { target: { value: '100' } });
|
||||
fireEvent.click(screen.getByLabelText('common.save'));
|
||||
|
||||
await waitFor(() => { expect(screen.getByText('settings.catalog.updateSpoolWeight')).toBeTruthy(); });
|
||||
|
||||
// Select option A (keep existing)
|
||||
const radios = screen.getAllByRole('radio');
|
||||
fireEvent.click(radios[1]); // Option A = second radio = keepExisting=true
|
||||
|
||||
fireEvent.click(screen.getByText('common.confirm'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(vi.mocked(api.patchSpoolmanFilament)).toHaveBeenCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ spool_weight: 100, keep_existing_spools: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('(spoolman mode) negative weight input disables save button', async () => {
|
||||
vi.mocked(api.getSpoolmanInventoryFilaments).mockResolvedValue([sampleFilament]);
|
||||
|
||||
render(<SpoolCatalogSettings />);
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('common.edit')).toBeTruthy(); });
|
||||
fireEvent.click(screen.getByLabelText('common.edit'));
|
||||
|
||||
await waitFor(() => { expect(screen.getByLabelText('settings.catalog.spoolWeight')).toBeTruthy(); });
|
||||
|
||||
fireEvent.change(screen.getByLabelText('settings.catalog.spoolWeight'), { target: { value: '-5' } });
|
||||
|
||||
const saveBtn = screen.getByLabelText('common.save') as HTMLButtonElement;
|
||||
expect(saveBtn.disabled).toBe(true);
|
||||
// No Spoolman-only columns leak in
|
||||
expect(screen.queryByText('settings.catalog.material')).toBeNull();
|
||||
expect(screen.queryByText('settings.catalog.spoolWeight')).toBeNull();
|
||||
expect(screen.queryByText('settings.spoolmanFilamentCatalogTitle')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { screen, fireEvent } from '@testing-library/react';
|
||||
import { render } from '../utils';
|
||||
import { SpoolWeightUpdateModal } from '../../components/SpoolWeightUpdateModal';
|
||||
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => key,
|
||||
}),
|
||||
}));
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
filamentName: 'PLA Basic',
|
||||
oldWeight: 250,
|
||||
newWeight: 196,
|
||||
onConfirm: vi.fn(),
|
||||
onClose: vi.fn(),
|
||||
};
|
||||
|
||||
describe('SpoolWeightUpdateModal', () => {
|
||||
it('renders filament name, old weight, and new weight', () => {
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('settings.catalog.updateSpoolWeight')).toBeTruthy();
|
||||
expect(screen.getByText(/PLA Basic/)).toBeTruthy();
|
||||
expect(screen.getByText(/250g → 196g/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders option labels', () => {
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText('settings.catalog.applyToAllSpools')).toBeTruthy();
|
||||
expect(screen.getByText('settings.catalog.keepExistingSpoolWeight')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('option B (apply to all) is selected by default', () => {
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} />);
|
||||
|
||||
const radios = screen.getAllByRole('radio') as HTMLInputElement[];
|
||||
// First radio = apply-to-all (Option B, keepExisting=false)
|
||||
expect(radios[0].checked).toBe(true);
|
||||
expect(radios[1].checked).toBe(false);
|
||||
});
|
||||
|
||||
it('calls onConfirm(false) when option B is selected and Confirm clicked', () => {
|
||||
const onConfirm = vi.fn();
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} onConfirm={onConfirm} />);
|
||||
|
||||
fireEvent.click(screen.getByText('common.confirm'));
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it('calls onConfirm(true) when option A is selected and Confirm clicked', () => {
|
||||
const onConfirm = vi.fn();
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} onConfirm={onConfirm} />);
|
||||
|
||||
const radios = screen.getAllByRole('radio');
|
||||
fireEvent.click(radios[1]); // Option A: keep existing
|
||||
|
||||
fireEvent.click(screen.getByText('common.confirm'));
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('calls onClose on Cancel click', () => {
|
||||
const onClose = vi.fn();
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} onClose={onClose} />);
|
||||
|
||||
fireEvent.click(screen.getByText('common.cancel'));
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not call onConfirm on Cancel click', () => {
|
||||
const onConfirm = vi.fn();
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} onConfirm={onConfirm} />);
|
||||
|
||||
fireEvent.click(screen.getByText('common.cancel'));
|
||||
|
||||
expect(onConfirm).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders dash when oldWeight is null', () => {
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} oldWeight={null} />);
|
||||
|
||||
expect(screen.getByText(/— → 196g/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns null when isOpen is false', () => {
|
||||
render(<SpoolWeightUpdateModal {...defaultProps} isOpen={false} />);
|
||||
|
||||
expect(screen.queryByText('settings.catalog.updateSpoolWeight')).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user