InstallAppButton.test.tsx 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /**
  2. * Tests for InstallAppButton - the in-app PWA install trigger (#1460).
  3. */
  4. import { describe, it, expect, vi } from 'vitest';
  5. import { act } from 'react';
  6. import { render, screen, waitFor } from '../utils';
  7. import userEvent from '@testing-library/user-event';
  8. import { InstallAppButton } from '../../components/InstallAppButton';
  9. // Scoped so the assertion ignores unrelated buttons (e.g. a toast's dismiss).
  10. const INSTALL_BUTTON = { name: 'Install app' } as const;
  11. /** Build a fake beforeinstallprompt event with a controllable userChoice. */
  12. function makeInstallPromptEvent(outcome: 'accepted' | 'dismissed') {
  13. const event = new Event('beforeinstallprompt') as Event & {
  14. prompt: () => Promise<void>;
  15. userChoice: Promise<{ outcome: string; platform: string }>;
  16. };
  17. event.prompt = vi.fn().mockResolvedValue(undefined);
  18. event.userChoice = Promise.resolve({ outcome, platform: 'web' });
  19. return event;
  20. }
  21. describe('InstallAppButton', () => {
  22. it('renders nothing until the browser fires beforeinstallprompt', () => {
  23. render(<InstallAppButton />);
  24. expect(screen.queryByRole('button', INSTALL_BUTTON)).toBeNull();
  25. });
  26. it('shows the install button once beforeinstallprompt fires', async () => {
  27. render(<InstallAppButton />);
  28. await act(async () => {
  29. window.dispatchEvent(makeInstallPromptEvent('accepted'));
  30. });
  31. expect(await screen.findByRole('button', INSTALL_BUTTON)).toBeInTheDocument();
  32. });
  33. it('fires the captured prompt on click and hides itself afterwards', async () => {
  34. const user = userEvent.setup();
  35. const event = makeInstallPromptEvent('accepted');
  36. render(<InstallAppButton />);
  37. await act(async () => {
  38. window.dispatchEvent(event);
  39. });
  40. await user.click(await screen.findByRole('button', INSTALL_BUTTON));
  41. expect(event.prompt).toHaveBeenCalledTimes(1);
  42. // A captured prompt can only be used once, so the button must disappear.
  43. await waitFor(() =>
  44. expect(screen.queryByRole('button', INSTALL_BUTTON)).toBeNull()
  45. );
  46. });
  47. it('hides itself even when the user dismisses the prompt', async () => {
  48. const user = userEvent.setup();
  49. const event = makeInstallPromptEvent('dismissed');
  50. render(<InstallAppButton />);
  51. await act(async () => {
  52. window.dispatchEvent(event);
  53. });
  54. await user.click(await screen.findByRole('button', INSTALL_BUTTON));
  55. await waitFor(() =>
  56. expect(screen.queryByRole('button', INSTALL_BUTTON)).toBeNull()
  57. );
  58. });
  59. it('hides the button when the app reports it was installed', async () => {
  60. render(<InstallAppButton />);
  61. await act(async () => {
  62. window.dispatchEvent(makeInstallPromptEvent('accepted'));
  63. });
  64. expect(await screen.findByRole('button', INSTALL_BUTTON)).toBeInTheDocument();
  65. await act(async () => {
  66. window.dispatchEvent(new Event('appinstalled'));
  67. });
  68. await waitFor(() =>
  69. expect(screen.queryByRole('button', INSTALL_BUTTON)).toBeNull()
  70. );
  71. });
  72. });