|
1 | | -import { describe, it, expect, afterEach } from 'vitest' |
| 1 | +import { describe, it, expect, afterEach, vi } from 'vitest' |
2 | 2 | import { render, screen, cleanup, within } from '@testing-library/react' |
3 | 3 | import BackendCapabilitiesBanner from './BackendCapabilitiesBanner' |
4 | 4 | import type { CapabilityNotice } from '../hooks/useReadinessReport' |
5 | 5 |
|
| 6 | +vi.mock('../hooks/queries/useReadinessQuery', () => ({ |
| 7 | + useReadinessQuery: vi.fn(), |
| 8 | +})) |
| 9 | + |
| 10 | +import { useReadinessQuery } from '../hooks/queries/useReadinessQuery' |
| 11 | + |
6 | 12 | afterEach(cleanup) |
7 | 13 |
|
8 | | -const defaultProps = { loadError: false, loading: false, belowRealtimeBar: false } |
| 14 | +const mockQuery = useReadinessQuery as unknown as ReturnType<typeof vi.fn> |
| 15 | + |
| 16 | +function mockReturn(overrides: Partial<ReturnType<typeof useReadinessQuery>> = {}) { |
| 17 | + mockQuery.mockReturnValue({ |
| 18 | + notices: [], |
| 19 | + loadError: false, |
| 20 | + loading: false, |
| 21 | + report: null, |
| 22 | + refresh: vi.fn(), |
| 23 | + ...overrides, |
| 24 | + }) |
| 25 | +} |
9 | 26 |
|
10 | 27 | function notice(id: string, kind: CapabilityNotice['kind'] = 'disabled', text = 'Some issue.'): CapabilityNotice { |
11 | 28 | return { id, kind, text } |
12 | 29 | } |
13 | 30 |
|
14 | 31 | describe('BackendCapabilitiesBanner', () => { |
| 32 | + afterEach(() => { |
| 33 | + mockQuery.mockReset() |
| 34 | + }) |
| 35 | + |
15 | 36 | it('renders nothing when no notices and no error', () => { |
16 | | - const { container } = render(<BackendCapabilitiesBanner {...defaultProps} notices={[]} />) |
| 37 | + mockReturn() |
| 38 | + const { container } = render(<BackendCapabilitiesBanner />) |
17 | 39 | expect(container.firstChild).toBeNull() |
18 | 40 | }) |
19 | 41 |
|
20 | 42 | it('renders load-error message when loadError is true and no notices', () => { |
21 | | - render(<BackendCapabilitiesBanner {...defaultProps} loadError notices={[]} />) |
| 43 | + mockReturn({ loadError: true, notices: [] }) |
| 44 | + render(<BackendCapabilitiesBanner />) |
22 | 45 | expect(screen.getByRole('status')).toHaveTextContent(/could not load backend service status/i) |
23 | 46 | }) |
24 | 47 |
|
25 | 48 | it('renders notice text', () => { |
26 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('database', 'limited', 'DB is down.')]} />) |
| 49 | + mockReturn({ notices: [notice('database', 'limited', 'DB is down.')] }) |
| 50 | + render(<BackendCapabilitiesBanner />) |
27 | 51 | expect(screen.getByText(/DB is down\./)).toBeInTheDocument() |
28 | 52 | }) |
29 | 53 |
|
30 | 54 | it('attaches a doc link for database notice', () => { |
31 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('database', 'limited')]} />) |
| 55 | + mockReturn({ notices: [notice('database', 'limited')] }) |
| 56 | + render(<BackendCapabilitiesBanner />) |
32 | 57 | const link = screen.getByRole('link', { name: /database setup/i }) |
33 | 58 | expect(link).toHaveAttribute('href', expect.stringContaining('#database-setup')) |
34 | 59 | expect(link).toHaveAttribute('target', '_blank') |
35 | 60 | expect(link).toHaveAttribute('rel', 'noopener noreferrer') |
36 | 61 | }) |
37 | 62 |
|
38 | 63 | it('attaches a doc link for queue-workers notice', () => { |
39 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('queue-workers')]} />) |
| 64 | + mockReturn({ notices: [notice('queue-workers')] }) |
| 65 | + render(<BackendCapabilitiesBanner />) |
40 | 66 | const link = screen.getByRole('link', { name: /redis \/ worker setup/i }) |
41 | 67 | expect(link).toHaveAttribute('href', expect.stringContaining('CONTRIBUTING')) |
42 | 68 | }) |
43 | 69 |
|
44 | 70 | it('attaches a doc link for indexer notice', () => { |
45 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('indexer')]} />) |
| 71 | + mockReturn({ notices: [notice('indexer')] }) |
| 72 | + render(<BackendCapabilitiesBanner />) |
46 | 73 | const link = screen.getByRole('link', { name: /environment setup/i }) |
47 | 74 | expect(link).toHaveAttribute('href', expect.stringContaining('ENVIRONMENT')) |
48 | 75 | }) |
49 | 76 |
|
50 | 77 | it('attaches a doc link for auto-rebalancer notice', () => { |
51 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('auto-rebalancer')]} />) |
| 78 | + mockReturn({ notices: [notice('auto-rebalancer')] }) |
| 79 | + render(<BackendCapabilitiesBanner />) |
52 | 80 | const link = screen.getByRole('link', { name: /environment setup/i }) |
53 | 81 | expect(link).toBeInTheDocument() |
54 | 82 | }) |
55 | 83 |
|
56 | 84 | it('renders multiple notices each with their own link', () => { |
57 | 85 | const notices = [notice('database', 'limited'), notice('queue-workers')] |
58 | | - const { container } = render(<BackendCapabilitiesBanner {...defaultProps} notices={notices} />) |
| 86 | + mockReturn({ notices }) |
| 87 | + const { container } = render(<BackendCapabilitiesBanner />) |
59 | 88 | const banner = container.querySelector('[role="status"]')! |
60 | 89 | expect(within(banner as HTMLElement).getByRole('link', { name: /database setup/i })).toBeInTheDocument() |
61 | 90 | expect(within(banner as HTMLElement).getByRole('link', { name: /redis \/ worker setup/i })).toBeInTheDocument() |
62 | 91 | }) |
63 | 92 |
|
64 | 93 | it('applies top-14 class when belowRealtimeBar is true', () => { |
65 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('database', 'limited')]} belowRealtimeBar />) |
| 94 | + mockReturn({ notices: [notice('database', 'limited')] }) |
| 95 | + render(<BackendCapabilitiesBanner belowRealtimeBar />) |
66 | 96 | expect(screen.getByRole('status').className).toContain('top-14') |
67 | 97 | }) |
68 | 98 |
|
69 | 99 | it('applies top-0 class when belowRealtimeBar is false', () => { |
70 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('database', 'limited')]} />) |
| 100 | + mockReturn({ notices: [notice('database', 'limited')] }) |
| 101 | + render(<BackendCapabilitiesBanner />) |
71 | 102 | expect(screen.getByRole('status').className).toContain('top-0') |
72 | 103 | }) |
73 | 104 |
|
74 | 105 | it('uses amber styling when any notice is limited', () => { |
75 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('database', 'limited')]} />) |
| 106 | + mockReturn({ notices: [notice('database', 'limited')] }) |
| 107 | + render(<BackendCapabilitiesBanner />) |
76 | 108 | expect(screen.getByRole('status').className).toContain('amber') |
77 | 109 | }) |
78 | 110 |
|
79 | 111 | it('uses slate styling when all notices are disabled', () => { |
80 | | - render(<BackendCapabilitiesBanner {...defaultProps} notices={[notice('queue-workers', 'disabled')]} />) |
| 112 | + mockReturn({ notices: [notice('queue-workers', 'disabled')] }) |
| 113 | + render(<BackendCapabilitiesBanner />) |
81 | 114 | expect(screen.getByRole('status').className).not.toContain('amber') |
82 | 115 | }) |
| 116 | + |
| 117 | + it('updates banner after a simulated capability change on subsequent poll', () => { |
| 118 | + mockReturn({ notices: [notice('database', 'limited', 'Initial issue.')] }) |
| 119 | + const { unmount } = render(<BackendCapabilitiesBanner />) |
| 120 | + expect(screen.getByText(/Initial issue\./)).toBeInTheDocument() |
| 121 | + unmount() |
| 122 | + |
| 123 | + mockQuery.mockReturnValue({ |
| 124 | + notices: [notice('database', 'limited', 'Updated issue.')], |
| 125 | + loadError: false, |
| 126 | + loading: false, |
| 127 | + report: null, |
| 128 | + refresh: vi.fn(), |
| 129 | + }) |
| 130 | + render(<BackendCapabilitiesBanner />) |
| 131 | + expect(screen.getByText(/Updated issue\./)).toBeInTheDocument() |
| 132 | + }) |
83 | 133 | }) |
0 commit comments