import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen, fireEvent, waitFor } from '@testing-library/react'; import { render } from '../utils'; import { SpoolInfoCard, UnknownTagCard } from '../../components/spoolbuddy/SpoolInfoCard'; import type { MatchedSpool } from '../../hooks/useSpoolBuddyState'; const mockUpdateSpoolWeight = vi.fn(); vi.mock('../../api/client', () => ({ api: { getSettings: vi.fn().mockResolvedValue({}), getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }), }, spoolbuddyApi: { updateSpoolWeight: (...args: unknown[]) => mockUpdateSpoolWeight(...args), }, })); const mockSpool: MatchedSpool = { id: 42, tag_uid: 'AABBCCDD11223344', material: 'PLA', subtype: 'Matte', color_name: 'Jade White', rgba: 'E8F5E9FF', brand: 'Bambu', label_weight: 1000, core_weight: 250, weight_used: 200, }; describe('SpoolInfoCard', () => { beforeEach(() => { vi.clearAllMocks(); mockUpdateSpoolWeight.mockResolvedValue({ status: 'ok', weight_used: 300 }); }); it('renders spool material, brand, color name', () => { render(); expect(screen.getByText('Jade White')).toBeInTheDocument(); expect(screen.getByText(/Bambu/)).toBeInTheDocument(); expect(screen.getByText(/PLA/)).toBeInTheDocument(); }); it('shows spool color circle with correct hex color', () => { const { container } = render(); // SpoolIcon renders an SVG circle with fill=colorHex const circle = container.querySelector('circle[fill="#E8F5E9"]'); expect(circle).toBeInTheDocument(); }); it('shows remaining weight and fill percentage', () => { // scaleWeight=900g, core=250g → remaining = 900-250 = 650g // fillPercent = round(650/1000 * 100) = 65% render(); expect(screen.getByText('650g')).toBeInTheDocument(); expect(screen.getByText('65%')).toBeInTheDocument(); }); it('calls onAssignToAms when "Assign to AMS" button clicked', () => { const onAssign = vi.fn(); render( ); fireEvent.click(screen.getByText('Assign to AMS')); expect(onAssign).toHaveBeenCalledTimes(1); }); it('calls onSyncWeight when sync button clicked', async () => { const onSync = vi.fn(); render( ); fireEvent.click(screen.getByText('Sync Weight')); await waitFor(() => { expect(mockUpdateSpoolWeight).toHaveBeenCalledWith(42, 800); }); }); it('calls onClose when close button clicked', () => { const onClose = vi.fn(); render( ); fireEvent.click(screen.getByText('Close')); expect(onClose).toHaveBeenCalledTimes(1); }); it('disables "Assign to AMS" button when isAssigned=true', () => { render( ); expect(screen.getByText('Assign to AMS')).toBeDisabled(); }); it('enables "Assign to AMS" button when isAssigned=false', () => { render( ); expect(screen.getByText('Assign to AMS')).not.toBeDisabled(); }); it('shows Unassign button when isAssigned=true and onUnassignFromAms is provided', () => { render( ); expect(screen.getByText(/unassign/i)).toBeInTheDocument(); }); it('does not show Unassign button when onUnassignFromAms is not provided', () => { render( ); expect(screen.queryByText(/unassign/i)).not.toBeInTheDocument(); }); it('calls onUnassignFromAms when Unassign button is clicked', () => { const onUnassign = vi.fn(); render( ); fireEvent.click(screen.getByText(/unassign/i)); expect(onUnassign).toHaveBeenCalledTimes(1); }); }); describe('UnknownTagCard', () => { it('renders tag UID', () => { render(); expect(screen.getByText('DEADBEEF12345678')).toBeInTheDocument(); expect(screen.getByText('New Tag Detected')).toBeInTheDocument(); }); it('shows "Add to Inventory" button', () => { const onAdd = vi.fn(); render( ); const btn = screen.getByText('Add to Inventory'); expect(btn).toBeInTheDocument(); fireEvent.click(btn); expect(onAdd).toHaveBeenCalledTimes(1); }); });