BugReportBubble.test.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. /**
  2. * Tests for the BugReportBubble component.
  3. */
  4. import { describe, it, expect } from 'vitest';
  5. import { render, screen, waitFor } from '../utils';
  6. import userEvent from '@testing-library/user-event';
  7. import { http, HttpResponse } from 'msw';
  8. import { server } from '../mocks/server';
  9. import { BugReportBubble } from '../../components/BugReportBubble';
  10. function getDescriptionTextarea() {
  11. return document.querySelector('textarea') as HTMLTextAreaElement;
  12. }
  13. function getSubmitButton() {
  14. const buttons = screen.getAllByRole('button');
  15. return buttons.find(
  16. (b) =>
  17. b.className.includes('bg-red-500') &&
  18. !b.className.includes('rounded-full') &&
  19. b.textContent !== ''
  20. );
  21. }
  22. describe('BugReportBubble', () => {
  23. it('renders the floating bug button', () => {
  24. render(<BugReportBubble />);
  25. const button = screen.getByRole('button');
  26. expect(button).toBeInTheDocument();
  27. });
  28. it('opens panel when bubble is clicked', async () => {
  29. const user = userEvent.setup();
  30. render(<BugReportBubble />);
  31. await user.click(screen.getByRole('button'));
  32. expect(getDescriptionTextarea()).toBeInTheDocument();
  33. });
  34. it('closes panel when X button is clicked', async () => {
  35. const user = userEvent.setup();
  36. render(<BugReportBubble />);
  37. // Open
  38. await user.click(screen.getByRole('button'));
  39. expect(getDescriptionTextarea()).toBeInTheDocument();
  40. // Close via the X button
  41. const buttons = screen.getAllByRole('button');
  42. const closeButton = buttons.find((b) => b.querySelector('.lucide-x'));
  43. if (closeButton) await user.click(closeButton);
  44. await waitFor(() => {
  45. expect(document.querySelector('textarea')).not.toBeInTheDocument();
  46. });
  47. });
  48. it('disables submit when description is empty', async () => {
  49. const user = userEvent.setup();
  50. render(<BugReportBubble />);
  51. await user.click(screen.getByRole('button'));
  52. expect(getSubmitButton()).toBeDisabled();
  53. });
  54. it('enables submit when description is provided', async () => {
  55. const user = userEvent.setup();
  56. render(<BugReportBubble />);
  57. await user.click(screen.getByRole('button'));
  58. await user.type(getDescriptionTextarea(), 'Something is broken');
  59. expect(getSubmitButton()).not.toBeDisabled();
  60. });
  61. it('shows collecting state with countdown after submit', async () => {
  62. const user = userEvent.setup();
  63. // Delay the API response so we can see collecting state
  64. server.use(
  65. http.post('*/bug-report/submit', async () => {
  66. await new Promise((resolve) => setTimeout(resolve, 60000));
  67. return HttpResponse.json({ success: true, message: 'ok', issue_url: null, issue_number: null });
  68. })
  69. );
  70. render(<BugReportBubble />);
  71. await user.click(screen.getByRole('button'));
  72. await user.type(getDescriptionTextarea(), 'Test bug report');
  73. const submitBtn = getSubmitButton();
  74. if (submitBtn) await user.click(submitBtn);
  75. // Should show collecting state
  76. await waitFor(() => {
  77. const collectingText = screen.queryByText(/collecting|Collecting|収集|Sammeln|Collecte|Raccolta|Coletando|收集/i);
  78. expect(collectingText).toBeInTheDocument();
  79. });
  80. });
  81. it('shows success state after successful submission', async () => {
  82. const user = userEvent.setup();
  83. server.use(
  84. http.post('*/bug-report/submit', () => {
  85. return HttpResponse.json({
  86. success: true,
  87. message: 'Bug report submitted successfully!',
  88. issue_url: 'https://github.com/maziggy/bambuddy/issues/42',
  89. issue_number: 42,
  90. });
  91. })
  92. );
  93. render(<BugReportBubble />);
  94. await user.click(screen.getByRole('button'));
  95. await user.type(getDescriptionTextarea(), 'Test bug');
  96. const submitBtn = getSubmitButton();
  97. if (submitBtn) await user.click(submitBtn);
  98. await waitFor(
  99. () => {
  100. expect(screen.getByText(/#42/)).toBeInTheDocument();
  101. },
  102. { timeout: 35000 }
  103. );
  104. });
  105. it('shows error state after failed submission', async () => {
  106. const user = userEvent.setup();
  107. server.use(
  108. http.post('*/bug-report/submit', () => {
  109. return HttpResponse.json({
  110. success: false,
  111. message: 'Relay not available',
  112. issue_url: null,
  113. issue_number: null,
  114. });
  115. })
  116. );
  117. render(<BugReportBubble />);
  118. await user.click(screen.getByRole('button'));
  119. await user.type(getDescriptionTextarea(), 'Test bug');
  120. const submitBtn = getSubmitButton();
  121. if (submitBtn) await user.click(submitBtn);
  122. await waitFor(
  123. () => {
  124. expect(screen.getByText(/Relay not available/)).toBeInTheDocument();
  125. },
  126. { timeout: 35000 }
  127. );
  128. });
  129. it('has expandable data collection notice', async () => {
  130. const user = userEvent.setup();
  131. render(<BugReportBubble />);
  132. await user.click(screen.getByRole('button'));
  133. const details = document.querySelector('details');
  134. expect(details).toBeInTheDocument();
  135. });
  136. });