PendingUploadsPanel.test.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. /**
  2. * Tests for PendingUploadsPanel — review-card name resolution (#1152 follow-up).
  3. *
  4. * The panel renders ``upload.display_name`` (the resolved name that mirrors
  5. * what the eventual archive's ``print_name`` will be) and falls back to
  6. * ``upload.filename`` when the display_name is missing — guards against a
  7. * pending row created before the column landed (or a serialiser bug) showing
  8. * a blank card.
  9. */
  10. import { describe, it, expect, beforeEach } from 'vitest';
  11. import { render } from '../utils';
  12. import { PendingUploadsPanel } from '../../components/PendingUploadsPanel';
  13. import { http, HttpResponse } from 'msw';
  14. import { server } from '../mocks/server';
  15. interface MockUpload {
  16. id: number;
  17. filename: string;
  18. display_name: string;
  19. file_size: number;
  20. source_ip: string | null;
  21. status: string;
  22. tags: string | null;
  23. notes: string | null;
  24. project_id: number | null;
  25. uploaded_at: string;
  26. }
  27. const baseUpload: MockUpload = {
  28. id: 1,
  29. filename: 'Plate_1.gcode.3mf',
  30. display_name: 'My Resolved Name',
  31. file_size: 12345,
  32. source_ip: '192.168.1.50',
  33. status: 'pending',
  34. tags: null,
  35. notes: null,
  36. project_id: null,
  37. uploaded_at: '2026-05-01T10:00:00Z',
  38. };
  39. describe('PendingUploadsPanel — display_name', () => {
  40. beforeEach(() => {
  41. server.use(
  42. http.get('/api/v1/projects/', () => HttpResponse.json([])),
  43. );
  44. });
  45. it('renders the resolved display_name on the review card', async () => {
  46. server.use(http.get('/api/v1/pending-uploads/', () => HttpResponse.json([baseUpload])));
  47. const { findByText } = render(<PendingUploadsPanel />);
  48. expect(await findByText('My Resolved Name')).toBeInTheDocument();
  49. });
  50. it('falls back to filename when display_name is an empty string', async () => {
  51. // Defensive: a bug in the resolver, a pre-migration row that was somehow
  52. // re-fetched, or a partial JSON deserialisation must not produce a blank
  53. // review card. The frontend keeps showing _something_ the user can click.
  54. server.use(
  55. http.get('/api/v1/pending-uploads/', () =>
  56. HttpResponse.json([{ ...baseUpload, display_name: '' }]),
  57. ),
  58. );
  59. const { findByText } = render(<PendingUploadsPanel />);
  60. expect(await findByText('Plate_1.gcode.3mf')).toBeInTheDocument();
  61. });
  62. it('exposes the raw filename via tooltip so the user can see what arrived over FTP', async () => {
  63. server.use(http.get('/api/v1/pending-uploads/', () => HttpResponse.json([baseUpload])));
  64. const { findByText } = render(<PendingUploadsPanel />);
  65. const nameEl = await findByText('My Resolved Name');
  66. expect(nameEl.getAttribute('title')).toBe('Plate_1.gcode.3mf');
  67. });
  68. });