CameraDiagnoseModal.test.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. /**
  2. * Tests for the camera diagnostic modal (#1395 follow-up).
  3. *
  4. * Covers the three observable behaviours that matter for user-facing
  5. * triage: the modal kicks off the diagnostic on mount, renders per-
  6. * stage results when the API replies, and maps the summary code to a
  7. * translated remediation hint. Each test mocks the API client so the
  8. * suite never actually opens a socket.
  9. */
  10. import { describe, it, expect, vi } from 'vitest';
  11. import { render, screen, waitFor, fireEvent } from '@testing-library/react';
  12. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  13. import { I18nextProvider } from 'react-i18next';
  14. import i18n from '../../i18n';
  15. import { CameraDiagnoseModal } from '../../components/CameraDiagnoseModal';
  16. import { api, type CameraDiagnoseResult } from '../../api/client';
  17. function renderModal() {
  18. const queryClient = new QueryClient({
  19. defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  20. });
  21. const onClose = vi.fn();
  22. render(
  23. <QueryClientProvider client={queryClient}>
  24. <I18nextProvider i18n={i18n}>
  25. <CameraDiagnoseModal printerId={1} printerName="Test P2S" onClose={onClose} />
  26. </I18nextProvider>
  27. </QueryClientProvider>,
  28. );
  29. return { onClose };
  30. }
  31. describe('CameraDiagnoseModal', () => {
  32. it('runs the diagnostic on mount and shows per-stage results', async () => {
  33. const okResult: CameraDiagnoseResult = {
  34. printer_id: 1,
  35. protocol: 'rtsp',
  36. port: 322,
  37. profile: 'P2S',
  38. overall_status: 'ok',
  39. stages: [
  40. { name: 'tcp_reachable', status: 'ok', duration_ms: 12, code: null },
  41. { name: 'first_frame', status: 'ok', duration_ms: 1230, code: null },
  42. ],
  43. summary_code: 'all_ok',
  44. };
  45. const spy = vi.spyOn(api, 'diagnoseCamera').mockResolvedValue(okResult);
  46. renderModal();
  47. // Mounted → API called once
  48. await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
  49. // Stage names render via i18n
  50. expect(await screen.findByText(/Network reachability/i)).toBeInTheDocument();
  51. expect(screen.getByText(/Frame capture/i)).toBeInTheDocument();
  52. // Per-stage duration is shown for support triage
  53. expect(screen.getByText(/12 ms/i)).toBeInTheDocument();
  54. expect(screen.getByText(/1230 ms/i)).toBeInTheDocument();
  55. // Summary remediation message is rendered translated
  56. expect(screen.getByText(/Camera is working/i)).toBeInTheDocument();
  57. // Metadata for support triage
  58. expect(screen.getByText('rtsp')).toBeInTheDocument();
  59. expect(screen.getByText('322')).toBeInTheDocument();
  60. expect(screen.getByText('P2S')).toBeInTheDocument();
  61. spy.mockRestore();
  62. });
  63. it('maps a failure summary code to a translated remediation hint', async () => {
  64. const failedResult: CameraDiagnoseResult = {
  65. printer_id: 1,
  66. protocol: 'rtsp',
  67. port: 322,
  68. profile: 'P2S',
  69. overall_status: 'failed',
  70. stages: [
  71. { name: 'tcp_reachable', status: 'failed', duration_ms: 3001, code: 'tcp_timeout' },
  72. { name: 'first_frame', status: 'skipped', duration_ms: 0, code: null },
  73. ],
  74. summary_code: 'printer_unreachable',
  75. };
  76. const spy = vi.spyOn(api, 'diagnoseCamera').mockResolvedValue(failedResult);
  77. renderModal();
  78. // The remediation hint for printer_unreachable mentions IP / network /
  79. // power — the user-facing fix-it instructions, not the raw summary code.
  80. expect(await screen.findByText(/IP address/i)).toBeInTheDocument();
  81. // The machine-readable stage code is also surfaced (small font) for
  82. // support triage so users can paste it into a ticket.
  83. expect(screen.getByText('tcp_timeout')).toBeInTheDocument();
  84. spy.mockRestore();
  85. });
  86. it('re-runs the diagnostic when the user clicks Run again', async () => {
  87. const okResult: CameraDiagnoseResult = {
  88. printer_id: 1,
  89. protocol: 'rtsp',
  90. port: 322,
  91. profile: 'P2S',
  92. overall_status: 'ok',
  93. stages: [{ name: 'tcp_reachable', status: 'ok', duration_ms: 12, code: null }],
  94. summary_code: 'all_ok',
  95. };
  96. const spy = vi.spyOn(api, 'diagnoseCamera').mockResolvedValue(okResult);
  97. renderModal();
  98. await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
  99. fireEvent.click(screen.getByText(/Run again/i));
  100. await waitFor(() => expect(spy).toHaveBeenCalledTimes(2));
  101. spy.mockRestore();
  102. });
  103. });