AmsUnitCard.test.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * Tests for AmsUnitCard component:
  3. * - Renders slot circles for a 4-slot AMS
  4. * - Shows slot labels (1, 2, 3, 4)
  5. * - Shows fill level bars
  6. */
  7. import { describe, it, expect, vi } from 'vitest';
  8. import { render, screen } from '@testing-library/react';
  9. import React from 'react';
  10. import { AmsUnitCard } from '../../../components/spoolbuddy/AmsUnitCard';
  11. import type { AMSUnit, AMSTray } from '../../../api/client';
  12. vi.mock('../../../utils/amsHelpers', () => ({
  13. getFillBarColor: (fill: number) => {
  14. if (fill > 50) return '#00ae42';
  15. if (fill >= 15) return '#f59e0b';
  16. return '#ef4444';
  17. },
  18. }));
  19. function makeTray(overrides: Partial<AMSTray> = {}): AMSTray {
  20. return {
  21. id: 0,
  22. tray_color: 'FF0000FF',
  23. tray_type: 'PLA',
  24. tray_sub_brands: null,
  25. tray_id_name: null,
  26. tray_info_idx: null,
  27. remain: 80,
  28. k: null,
  29. cali_idx: null,
  30. tag_uid: null,
  31. tray_uuid: null,
  32. nozzle_temp_min: null,
  33. nozzle_temp_max: null,
  34. drying_temp: null,
  35. drying_time: null,
  36. ...overrides,
  37. };
  38. }
  39. function makeUnit(overrides: Partial<AMSUnit> = {}): AMSUnit {
  40. return {
  41. id: 0,
  42. humidity: 30,
  43. temp: 25,
  44. is_ams_ht: false,
  45. tray: [
  46. makeTray({ id: 0, tray_color: 'FF0000FF', tray_type: 'PLA', remain: 80 }),
  47. makeTray({ id: 1, tray_color: '00FF00FF', tray_type: 'PETG', remain: 50 }),
  48. makeTray({ id: 2, tray_color: '0000FFFF', tray_type: 'ABS', remain: 10 }),
  49. // state=9 = firmware-confirmed empty (#1694: vs state=null which would
  50. // be "spool loaded but unconfigured", labelled "?" in the UI).
  51. makeTray({ id: 3, tray_color: null, tray_type: '', remain: -1, state: 9 } as Partial<AMSTray> & { state: number }),
  52. ],
  53. serial_number: 'AMS001',
  54. sw_ver: '1.0.0',
  55. dry_time: 0,
  56. dry_status: 0,
  57. dry_sub_status: 0,
  58. ...overrides,
  59. };
  60. }
  61. describe('AmsUnitCard', () => {
  62. it('renders 4 slot positions for a regular AMS', () => {
  63. const { container } = render(
  64. <AmsUnitCard unit={makeUnit()} activeSlot={null} />
  65. );
  66. // 4 slot numbers should be visible (1, 2, 3, 4)
  67. expect(screen.getByText('1')).toBeDefined();
  68. expect(screen.getByText('2')).toBeDefined();
  69. expect(screen.getByText('3')).toBeDefined();
  70. expect(screen.getByText('4')).toBeDefined();
  71. // grid-cols-4 class should be present
  72. const grid = container.querySelector('.grid-cols-4');
  73. expect(grid).not.toBeNull();
  74. });
  75. it('renders AMS name in header', () => {
  76. render(<AmsUnitCard unit={makeUnit({ id: 0 })} activeSlot={null} />);
  77. expect(screen.getByText('AMS A')).toBeDefined();
  78. });
  79. it('shows material types for populated slots', () => {
  80. render(<AmsUnitCard unit={makeUnit()} activeSlot={null} />);
  81. expect(screen.getByText('PLA')).toBeDefined();
  82. expect(screen.getByText('PETG')).toBeDefined();
  83. expect(screen.getByText('ABS')).toBeDefined();
  84. });
  85. it('shows "Empty" for firmware-confirmed empty slot (state 9/10)', () => {
  86. render(<AmsUnitCard unit={makeUnit()} activeSlot={null} />);
  87. expect(screen.getByText('Empty')).toBeDefined();
  88. });
  89. it('shows "?" for loaded-but-unconfigured slot (#1694)', () => {
  90. // No state reported by firmware + empty tray_type = spool loaded into the
  91. // slot but no material assigned. Reporter on a 3-AMS P1S saw these slots
  92. // mislabelled as "Empty" because the prior logic only checked tray_type.
  93. const unit = makeUnit({
  94. tray: [
  95. makeTray({ id: 0, tray_type: 'PLA', remain: 80 }),
  96. makeTray({ id: 1, tray_color: null, tray_type: '', remain: -1 } as Partial<AMSTray> & { state?: number }),
  97. makeTray({ id: 2, tray_type: 'ABS', remain: 10 }),
  98. makeTray({ id: 3, tray_color: null, tray_type: '', remain: -1, state: 9 } as Partial<AMSTray> & { state: number }),
  99. ],
  100. });
  101. render(<AmsUnitCard unit={unit} activeSlot={null} />);
  102. expect(screen.getByText('?')).toBeDefined();
  103. // The firmware-empty slot still reads "Empty" — the two states are visually
  104. // distinct, not collapsed.
  105. expect(screen.getByText('Empty')).toBeDefined();
  106. });
  107. it('renders fill level bars for slots with filament', () => {
  108. const { container } = render(
  109. <AmsUnitCard unit={makeUnit()} activeSlot={null} />
  110. );
  111. // Look for fill bar elements (they have style width set to fill%)
  112. const fillBars = container.querySelectorAll('.h-full.rounded-full.transition-all');
  113. // 3 populated slots should have fill bars (slot 4 is empty)
  114. expect(fillBars.length).toBe(3);
  115. });
  116. it('renders only 1 slot for AMS-HT', () => {
  117. const htUnit = makeUnit({
  118. is_ams_ht: true,
  119. tray: [makeTray({ id: 0, tray_type: 'PLA', remain: 90 })],
  120. });
  121. const { container } = render(
  122. <AmsUnitCard unit={htUnit} activeSlot={null} />
  123. );
  124. const grid = container.querySelector('.grid-cols-1');
  125. expect(grid).not.toBeNull();
  126. expect(screen.getByText('1')).toBeDefined();
  127. });
  128. it('shows humidity and temperature indicators', () => {
  129. render(<AmsUnitCard unit={makeUnit({ humidity: 45, temp: 30 })} activeSlot={null} />);
  130. expect(screen.getByText('45%')).toBeDefined();
  131. });
  132. it('highlights active slot with ring', () => {
  133. const { container } = render(
  134. <AmsUnitCard unit={makeUnit()} activeSlot={1} />
  135. );
  136. const activeSlot = container.querySelector('.ring-2.ring-bambu-green');
  137. expect(activeSlot).not.toBeNull();
  138. });
  139. });