CameraTile.test.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2. import { act, screen } from '@testing-library/react';
  3. import { render } from '../utils';
  4. import { CameraTile } from '../../components/CameraTile';
  5. // The shared render() util mounts AuthProvider, which fires an async
  6. // /auth/me probe on mount. Each test absorbs that settle with a single
  7. // `await act(async () => {})` after render so the AuthProvider state
  8. // update doesn't bleed into the assertion phase as an act() warning.
  9. async function flushMicrotasks() {
  10. await act(async () => {
  11. await Promise.resolve();
  12. });
  13. }
  14. describe('CameraTile', () => {
  15. beforeEach(() => {
  16. vi.useFakeTimers();
  17. vi.spyOn(global, 'fetch').mockResolvedValue(new Response(null, { status: 200 }));
  18. });
  19. afterEach(() => {
  20. vi.useRealTimers();
  21. vi.restoreAllMocks();
  22. });
  23. it('renders the live stream URL in live mode', async () => {
  24. render(
  25. <CameraTile
  26. printerId={42}
  27. printerName="X1C-Lab"
  28. mode="live"
  29. snapshotIntervalMs={5000}
  30. connected
  31. />,
  32. );
  33. await flushMicrotasks();
  34. const img = screen.getByAltText('X1C-Lab') as HTMLImageElement;
  35. expect(img.src).toContain('/api/v1/printers/42/camera/stream');
  36. expect(img.src).toContain('fps=8');
  37. });
  38. it('renders the snapshot URL and refreshes on the interval', async () => {
  39. render(
  40. <CameraTile
  41. printerId={7}
  42. printerName="P1S-Garage"
  43. mode="snapshot"
  44. snapshotIntervalMs={1000}
  45. connected
  46. />,
  47. );
  48. await flushMicrotasks();
  49. const initial = (screen.getByAltText('P1S-Garage') as HTMLImageElement).src;
  50. expect(initial).toContain('/api/v1/printers/7/camera/snapshot');
  51. await act(async () => {
  52. vi.advanceTimersByTime(1500);
  53. });
  54. const refreshed = (screen.getByAltText('P1S-Garage') as HTMLImageElement).src;
  55. expect(refreshed).toContain('/api/v1/printers/7/camera/snapshot');
  56. expect(refreshed).not.toBe(initial);
  57. });
  58. it('shows an offline placeholder when not connected', async () => {
  59. render(
  60. <CameraTile
  61. printerId={1}
  62. printerName="A1-Offline"
  63. mode="live"
  64. snapshotIntervalMs={5000}
  65. connected={false}
  66. />,
  67. );
  68. await flushMicrotasks();
  69. expect(screen.queryByAltText('A1-Offline')).toBeNull();
  70. });
  71. it('shows the paused placeholder in paused mode', async () => {
  72. render(
  73. <CameraTile
  74. printerId={9}
  75. printerName="H2D-Booth"
  76. mode="paused"
  77. snapshotIntervalMs={5000}
  78. connected
  79. />,
  80. );
  81. await flushMicrotasks();
  82. expect(screen.queryByAltText('H2D-Booth')).toBeNull();
  83. });
  84. it('POSTs /camera/stop when leaving live mode', async () => {
  85. const fetchMock = vi.spyOn(global, 'fetch').mockResolvedValue(
  86. new Response(null, { status: 200 }),
  87. );
  88. const { rerender } = render(
  89. <CameraTile
  90. printerId={11}
  91. printerName="X1C-Stop"
  92. mode="live"
  93. snapshotIntervalMs={5000}
  94. connected
  95. />,
  96. );
  97. await flushMicrotasks();
  98. fetchMock.mockClear();
  99. await act(async () => {
  100. rerender(
  101. <CameraTile
  102. printerId={11}
  103. printerName="X1C-Stop"
  104. mode="snapshot"
  105. snapshotIntervalMs={5000}
  106. connected
  107. />,
  108. );
  109. });
  110. const stopCalls = fetchMock.mock.calls.filter(([url]) =>
  111. String(url).includes('/api/v1/printers/11/camera/stop'),
  112. );
  113. expect(stopCalls.length).toBeGreaterThan(0);
  114. });
  115. });