PrintLogModal.test.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { screen, waitFor, fireEvent } from '@testing-library/react';
  3. import { render } from '../utils';
  4. import { PrintLogModal } from '../../components/PrintLogModal';
  5. import { api } from '../../api/client';
  6. vi.mock('../../api/client', () => ({
  7. api: {
  8. getArchiveRuns: vi.fn(),
  9. getSettings: vi.fn().mockResolvedValue({}),
  10. getAuthStatus: vi.fn().mockResolvedValue({ auth_enabled: false }),
  11. },
  12. }));
  13. const sampleRuns = {
  14. total: 2,
  15. items: [
  16. {
  17. id: 99,
  18. archive_id: 42,
  19. print_name: 'Benchy',
  20. printer_name: 'X1C-01',
  21. printer_id: 3,
  22. status: 'failed',
  23. started_at: '2026-05-10T10:00:00Z',
  24. completed_at: '2026-05-10T10:05:00Z',
  25. duration_seconds: 300,
  26. filament_type: 'PLA',
  27. filament_color: '#FF0000',
  28. filament_used_grams: 10.0,
  29. cost: 0.25,
  30. energy_kwh: 0.05,
  31. energy_cost: 0.01,
  32. failure_reason: 'Cancelled by user',
  33. thumbnail_path: null,
  34. created_by_id: 1,
  35. created_by_username: 'admin',
  36. created_at: '2026-05-10T10:05:00Z',
  37. },
  38. {
  39. id: 50,
  40. archive_id: 42,
  41. print_name: 'Benchy',
  42. printer_name: 'X1C-01',
  43. printer_id: 3,
  44. status: 'completed',
  45. started_at: '2026-05-01T10:00:00Z',
  46. completed_at: '2026-05-01T11:00:00Z',
  47. duration_seconds: 3600,
  48. filament_type: 'PLA',
  49. filament_color: '#FF0000',
  50. filament_used_grams: 100.0,
  51. cost: 2.5,
  52. energy_kwh: 0.2,
  53. energy_cost: 0.05,
  54. failure_reason: null,
  55. thumbnail_path: null,
  56. created_by_id: 1,
  57. created_by_username: 'admin',
  58. created_at: '2026-05-01T11:00:00Z',
  59. },
  60. ],
  61. };
  62. describe('PrintLogModal', () => {
  63. beforeEach(() => {
  64. vi.clearAllMocks();
  65. vi.mocked(api.getArchiveRuns).mockResolvedValue(sampleRuns);
  66. });
  67. it('renders the archive name in the modal title', async () => {
  68. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={vi.fn()} />);
  69. await waitFor(() => {
  70. expect(screen.getByText(/Benchy/)).toBeInTheDocument();
  71. });
  72. });
  73. it('falls back to "this archive" when name is null', async () => {
  74. render(<PrintLogModal archiveId={42} archiveName={null} onClose={vi.fn()} />);
  75. await waitFor(() => {
  76. expect(screen.getByText(/this archive/i)).toBeInTheDocument();
  77. });
  78. });
  79. it('lists every run with its filament + cost', async () => {
  80. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={vi.fn()} />);
  81. await waitFor(() => {
  82. expect(screen.getByText(/100\.0 g/)).toBeInTheDocument();
  83. expect(screen.getByText(/10\.0 g/)).toBeInTheDocument();
  84. });
  85. });
  86. it('shows failure_reason under failed runs', async () => {
  87. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={vi.fn()} />);
  88. await waitFor(() => {
  89. expect(screen.getByText('Cancelled by user')).toBeInTheDocument();
  90. });
  91. });
  92. it('translates camelCase failure_reason keys (#1687 follow-up)', async () => {
  93. vi.mocked(api.getArchiveRuns).mockResolvedValue({
  94. total: 1,
  95. items: [
  96. {
  97. ...sampleRuns.items[0],
  98. failure_reason: 'filamentRunout',
  99. },
  100. ],
  101. });
  102. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={vi.fn()} />);
  103. await waitFor(() => {
  104. expect(screen.getByText('Filament runout')).toBeInTheDocument();
  105. });
  106. expect(screen.queryByText('filamentRunout')).not.toBeInTheDocument();
  107. });
  108. it('shows the empty state when there are no runs', async () => {
  109. vi.mocked(api.getArchiveRuns).mockResolvedValue({ total: 0, items: [] });
  110. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={vi.fn()} />);
  111. await waitFor(() => {
  112. expect(screen.getByText(/no print events/i)).toBeInTheDocument();
  113. });
  114. });
  115. it('calls onClose when Escape is pressed', async () => {
  116. const onClose = vi.fn();
  117. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={onClose} />);
  118. fireEvent.keyDown(window, { key: 'Escape' });
  119. expect(onClose).toHaveBeenCalledTimes(1);
  120. });
  121. it('calls onClose when backdrop is clicked', async () => {
  122. const onClose = vi.fn();
  123. const { container } = render(
  124. <PrintLogModal archiveId={42} archiveName="Benchy" onClose={onClose} />
  125. );
  126. const backdrop = container.querySelector('.fixed.inset-0');
  127. if (!backdrop) throw new Error('Backdrop not found');
  128. fireEvent.click(backdrop);
  129. expect(onClose).toHaveBeenCalledTimes(1);
  130. });
  131. it('does not close when clicking inside the modal body', async () => {
  132. const onClose = vi.fn();
  133. render(<PrintLogModal archiveId={42} archiveName="Benchy" onClose={onClose} />);
  134. await waitFor(() => {
  135. expect(screen.getByText(/Benchy/)).toBeInTheDocument();
  136. });
  137. // Click on the title text inside the modal
  138. fireEvent.click(screen.getByText(/Benchy/));
  139. expect(onClose).not.toHaveBeenCalled();
  140. });
  141. });