UNPKG

datajunction-ui

Version:
329 lines (269 loc) 9.26 kB
import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { NotificationSubscriptionsSection } from '../NotificationSubscriptionsSection'; describe('NotificationSubscriptionsSection', () => { const mockOnUpdate = vi.fn(); const mockOnUnsubscribe = vi.fn(); const mockSubscriptions = [ { entity_name: 'default.orders_count', entity_type: 'node', node_type: 'metric', activity_types: ['update', 'delete'], status: 'valid', }, { entity_name: 'default.dim_customers', entity_type: 'node', node_type: 'dimension', activity_types: ['create'], status: 'invalid', }, ]; beforeEach(() => { vi.clearAllMocks(); }); it('renders empty state when no subscriptions', () => { render( <NotificationSubscriptionsSection subscriptions={[]} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText(/not watching any nodes/i)).toBeInTheDocument(); }); it('renders subscriptions list', () => { render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('default.orders_count')).toBeInTheDocument(); expect(screen.getByText('default.dim_customers')).toBeInTheDocument(); expect(screen.getByText('METRIC')).toBeInTheDocument(); expect(screen.getByText('DIMENSION')).toBeInTheDocument(); }); it('shows invalid badge for invalid status', () => { render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('INVALID')).toBeInTheDocument(); }); it('displays activity types as badges', () => { render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('update')).toBeInTheDocument(); expect(screen.getByText('delete')).toBeInTheDocument(); expect(screen.getByText('create')).toBeInTheDocument(); }); it('enters edit mode when edit button is clicked', () => { render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const editButtons = screen.getAllByTitle('Edit subscription'); fireEvent.click(editButtons[0]); expect(screen.getByText('Save')).toBeInTheDocument(); expect(screen.getByText('Cancel')).toBeInTheDocument(); expect(screen.getByLabelText('Update')).toBeInTheDocument(); }); it('cancels editing when cancel button is clicked', () => { render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const editButtons = screen.getAllByTitle('Edit subscription'); fireEvent.click(editButtons[0]); expect(screen.getByText('Cancel')).toBeInTheDocument(); fireEvent.click(screen.getByText('Cancel')); expect(screen.queryByText('Cancel')).not.toBeInTheDocument(); }); it('calls onUnsubscribe when unsubscribe is confirmed', async () => { window.confirm = vi.fn().mockReturnValue(true); mockOnUnsubscribe.mockResolvedValue(); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const unsubscribeButtons = screen.getAllByTitle('Unsubscribe'); fireEvent.click(unsubscribeButtons[0]); expect(window.confirm).toHaveBeenCalledWith( 'Unsubscribe from notifications for "default.orders_count"?', ); expect(mockOnUnsubscribe).toHaveBeenCalledWith(mockSubscriptions[0]); }); it('does not call onUnsubscribe when unsubscribe is cancelled', () => { window.confirm = vi.fn().mockReturnValue(false); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const unsubscribeButtons = screen.getAllByTitle('Unsubscribe'); fireEvent.click(unsubscribeButtons[0]); expect(mockOnUnsubscribe).not.toHaveBeenCalled(); }); it('shows alert when trying to save with no activity types', () => { window.alert = vi.fn(); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const editButtons = screen.getAllByTitle('Edit subscription'); fireEvent.click(editButtons[0]); // Uncheck all activity types const updateCheckbox = screen.getByLabelText('Update'); const deleteCheckbox = screen.getByLabelText('Delete'); fireEvent.click(updateCheckbox); fireEvent.click(deleteCheckbox); fireEvent.click(screen.getByText('Save')); expect(window.alert).toHaveBeenCalledWith( 'Please select at least one activity type', ); expect(mockOnUpdate).not.toHaveBeenCalled(); }); it('calls onUpdate with new activity types when saved', async () => { mockOnUpdate.mockResolvedValue(); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const editButtons = screen.getAllByTitle('Edit subscription'); fireEvent.click(editButtons[0]); // Add 'create' activity type const createCheckbox = screen.getByLabelText('Create'); fireEvent.click(createCheckbox); fireEvent.click(screen.getByText('Save')); await waitFor(() => { expect(mockOnUpdate).toHaveBeenCalledWith(mockSubscriptions[0], [ 'update', 'delete', 'create', ]); }); }); it('renders entity type badge when node_type is not available', () => { const subscriptionsWithoutNodeType = [ { entity_name: 'some_entity', entity_type: 'namespace', activity_types: ['create'], }, ]; render( <NotificationSubscriptionsSection subscriptions={subscriptionsWithoutNodeType} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('namespace')).toBeInTheDocument(); }); it('handles onUpdate error gracefully', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(); window.alert = vi.fn(); mockOnUpdate.mockRejectedValue(new Error('Update failed')); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const editButtons = screen.getAllByTitle('Edit subscription'); fireEvent.click(editButtons[0]); fireEvent.click(screen.getByText('Save')); await waitFor(() => { expect(consoleSpy).toHaveBeenCalled(); expect(window.alert).toHaveBeenCalledWith( 'Failed to update subscription', ); }); consoleSpy.mockRestore(); }); it('handles onUnsubscribe error gracefully', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(); window.confirm = vi.fn().mockReturnValue(true); mockOnUnsubscribe.mockRejectedValue(new Error('Unsubscribe failed')); render( <NotificationSubscriptionsSection subscriptions={mockSubscriptions} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); const unsubscribeButtons = screen.getAllByTitle('Unsubscribe'); fireEvent.click(unsubscribeButtons[0]); await waitFor(() => { expect(consoleSpy).toHaveBeenCalled(); }); consoleSpy.mockRestore(); }); it('shows "All" when subscription has no activity_types', () => { const subscriptionsWithoutActivityTypes = [ { entity_name: 'default.some_node', entity_type: 'node', node_type: 'metric', activity_types: null, status: 'valid', }, ]; render( <NotificationSubscriptionsSection subscriptions={subscriptionsWithoutActivityTypes} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('All')).toBeInTheDocument(); }); it('shows "All" when subscription has undefined activity_types', () => { const subscriptionsWithUndefinedActivityTypes = [ { entity_name: 'default.other_node', entity_type: 'node', node_type: 'dimension', status: 'valid', // activity_types not defined }, ]; render( <NotificationSubscriptionsSection subscriptions={subscriptionsWithUndefinedActivityTypes} onUpdate={mockOnUpdate} onUnsubscribe={mockOnUnsubscribe} />, ); expect(screen.getByText('All')).toBeInTheDocument(); }); });