import React from 'react'; import { render, fireEvent, act } from '@testing-library/react'; import { useBulkActions } from '../../../src/static/js/utils/hooks/useBulkActions'; // Mock translateString to return the input for easier assertions jest.mock('../../../src/static/js/utils/helpers', () => ({ translateString: (s: string) => s, })); // Component that exposes hook state/handlers to DOM for testing function HookConsumer() { const hook = useBulkActions(); return (
{Array.from(hook.selectedMedia).length}
{hook.availableMediaIds.length}
{String(hook.showConfirmModal)}
{hook.confirmMessage}
{hook.listKey}
{hook.notificationMessage}
{String(hook.showNotification)}
{/* @todo: It doesn't used */} {/*
{hook.notificationType}
*/}
{String(hook.showPermissionModal)}
{hook.permissionType || ''}
{String(hook.showPlaylistModal)}
{String(hook.showChangeOwnerModal)}
{String(hook.showPublishStateModal)}
{String(hook.showCategoryModal)}
{String(hook.showTagModal)}
); } describe('useBulkActions', () => { beforeEach(() => { jest.clearAllMocks(); document.cookie.split(';').forEach((c) => { document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); }); global.fetch = jest.fn(); jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); }); describe('Utility Functions', () => { test('getCsrfToken reads csrftoken from cookies', () => { document.cookie = 'csrftoken=abc123'; const { getByTestId } = render(); expect(getByTestId('csrf').textContent).toBe('abc123'); }); test('getCsrfToken returns null when csrftoken is not present', () => { // No cookie set, should return null const { getByTestId } = render(); expect(getByTestId('csrf').textContent).toBe('null'); }); test('getCsrfToken returns null when document.cookie is empty', () => { // Even if we try to set empty cookie, it should return null if no csrftoken const { getByTestId } = render(); expect(getByTestId('csrf').textContent).toBe('null'); }); }); describe('Selection Management', () => { test('handleMediaSelection toggles selected media', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); expect(getByTestId('selected-count').textContent).toBe('1'); fireEvent.click(getByTestId('btn-handle-media-deselect')); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('handleItemsUpdate extracts ids correctly from items with different id types', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-items-update')); expect(getByTestId('available-count').textContent).toBe('3'); }); test('handleSelectAll selects all available items', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-items-update')); fireEvent.click(getByTestId('btn-select-all')); expect(getByTestId('selected-count').textContent).toBe('3'); }); test('handleDeselectAll deselects all items', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-items-update')); fireEvent.click(getByTestId('btn-select-all')); fireEvent.click(getByTestId('btn-deselect-all')); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('clearSelection clears all selected media', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); expect(getByTestId('selected-count').textContent).toBe('1'); fireEvent.click(getByTestId('btn-clear-selection')); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('clearSelectionAndRefresh clears selection and increments listKey', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-items-update')); fireEvent.click(getByTestId('btn-select-all')); expect(getByTestId('list-key').textContent).toBe('0'); fireEvent.click(getByTestId('btn-clear-refresh')); expect(getByTestId('selected-count').textContent).toBe('0'); expect(getByTestId('list-key').textContent).toBe('1'); }); }); describe('Bulk Actions - Modal Opening', () => { test('handleBulkAction does nothing when no selection', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-bulk-delete')); expect(getByTestId('show-confirm').textContent).toBe('false'); }); test('handleBulkAction opens confirm modal for delete, enable/disable comments and download, copy', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-delete')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-enable-comments')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-disable-comments')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-enable-download')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-disable-download')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-copy')); expect(getByTestId('show-confirm').textContent).toBe('true'); }); test('handleBulkAction opens permission modals with correct types', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-perm-viewer')); expect(getByTestId('show-permission').textContent).toBe('true'); expect(getByTestId('permission-type').textContent).toBe('viewer'); fireEvent.click(getByTestId('btn-bulk-perm-editor')); expect(getByTestId('permission-type').textContent).toBe('editor'); fireEvent.click(getByTestId('btn-bulk-perm-owner')); expect(getByTestId('permission-type').textContent).toBe('owner'); }); test('handleBulkAction opens other modals', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-playlist')); expect(getByTestId('show-playlist').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-change-owner')); expect(getByTestId('show-change-owner').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-publish')); expect(getByTestId('show-publish-state').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-category')); expect(getByTestId('show-category').textContent).toBe('true'); fireEvent.click(getByTestId('btn-bulk-tag')); expect(getByTestId('show-tag').textContent).toBe('true'); }); test('handleBulkAction with unknown action does nothing', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-unknown')); expect(getByTestId('show-confirm').textContent).toBe('false'); expect(getByTestId('show-permission').textContent).toBe('false'); }); }); describe('Confirm Modal Handlers', () => { test('handleConfirmCancel closes confirm modal and resets state', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-delete')); expect(getByTestId('show-confirm').textContent).toBe('true'); fireEvent.click(getByTestId('btn-confirm-cancel')); expect(getByTestId('show-confirm').textContent).toBe('false'); expect(getByTestId('confirm-message').textContent).toBe(''); }); }); describe('Delete Media Execution', () => { test('executeDeleteMedia success with notification', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-delete')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('The media was deleted successfully'); expect(getByTestId('show-notification').textContent).toBe('true'); act(() => { jest.advanceTimersByTime(5000); }); expect(getByTestId('show-notification').textContent).toBe('false'); }); test('executeDeleteMedia handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-delete')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to delete media'); }); test('executeDeleteMedia handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-delete')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to delete media'); expect(getByTestId('selected-count').textContent).toBe('0'); }); }); describe('Comments Management Execution', () => { test('executeEnableComments success', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Successfully Enabled comments'); }); test('executeEnableComments handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to enable comments'); }); test('executeEnableComments handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to enable comments'); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('executeDisableComments success', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Successfully Disabled comments'); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('executeDisableComments handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to disable comments'); }); test('executeDisableComments handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-comments')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to disable comments'); expect(getByTestId('selected-count').textContent).toBe('0'); }); }); describe('Download Management Execution', () => { test('executeEnableDownload success', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Successfully Enabled Download'); }); test('executeEnableDownload handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to enable download'); }); test('executeEnableDownload handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-enable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to enable download'); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('executeDisableDownload success', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Successfully Disabled Download'); expect(getByTestId('selected-count').textContent).toBe('0'); }); test('executeDisableDownload handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to disable download'); }); test('executeDisableDownload handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-disable-download')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to disable download'); expect(getByTestId('selected-count').textContent).toBe('0'); }); }); describe('Copy Media Execution', () => { test('executeCopyMedia success', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: true, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-copy')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Successfully Copied'); }); test('executeCopyMedia handles response.ok = false', async () => { (global.fetch as jest.Mock).mockResolvedValue({ ok: false, json: () => Promise.resolve({}) }); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-copy')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to copy media'); }); test('executeCopyMedia handles fetch rejection exception', async () => { (global.fetch as jest.Mock).mockRejectedValue(new Error('Network error')); const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-copy')); await act(async () => { fireEvent.click(getByTestId('btn-confirm-proceed')); await Promise.resolve(); }); expect(getByTestId('notification-message').textContent).toContain('Failed to copy media'); expect(getByTestId('selected-count').textContent).toBe('0'); }); }); describe('Permission Modal Handlers', () => { test('handlePermissionModalCancel closes permission modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-perm-viewer')); expect(getByTestId('show-permission').textContent).toBe('true'); fireEvent.click(getByTestId('btn-perm-cancel')); expect(getByTestId('show-permission').textContent).toBe('false'); expect(getByTestId('permission-type').textContent).toBe(''); }); test('handlePermissionModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-perm-success')); expect(getByTestId('notification-message').textContent).toBe('perm ok'); expect(getByTestId('show-permission').textContent).toBe('false'); }); test('handlePermissionModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-perm-error')); expect(getByTestId('notification-message').textContent).toBe('perm err'); expect(getByTestId('show-permission').textContent).toBe('false'); }); }); describe('Playlist Modal Handlers', () => { test('handlePlaylistModalCancel closes playlist modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-playlist')); expect(getByTestId('show-playlist').textContent).toBe('true'); fireEvent.click(getByTestId('btn-playlist-cancel')); expect(getByTestId('show-playlist').textContent).toBe('false'); }); test('handlePlaylistModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-playlist-success')); expect(getByTestId('notification-message').textContent).toBe('pl ok'); expect(getByTestId('show-playlist').textContent).toBe('false'); }); test('handlePlaylistModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-playlist-error')); expect(getByTestId('notification-message').textContent).toBe('pl err'); expect(getByTestId('show-playlist').textContent).toBe('false'); }); }); describe('Change Owner Modal Handlers', () => { test('handleChangeOwnerModalCancel closes change owner modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-change-owner')); expect(getByTestId('show-change-owner').textContent).toBe('true'); fireEvent.click(getByTestId('btn-change-owner-cancel')); expect(getByTestId('show-change-owner').textContent).toBe('false'); }); test('handleChangeOwnerModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-change-owner-success')); expect(getByTestId('notification-message').textContent).toBe('owner ok'); expect(getByTestId('show-change-owner').textContent).toBe('false'); }); test('handleChangeOwnerModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-change-owner-error')); expect(getByTestId('notification-message').textContent).toBe('owner err'); expect(getByTestId('show-change-owner').textContent).toBe('false'); }); }); describe('Publish State Modal Handlers', () => { test('handlePublishStateModalCancel closes publish state modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-publish')); expect(getByTestId('show-publish-state').textContent).toBe('true'); fireEvent.click(getByTestId('btn-publish-cancel')); expect(getByTestId('show-publish-state').textContent).toBe('false'); }); test('handlePublishStateModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-publish-success')); expect(getByTestId('notification-message').textContent).toBe('pub ok'); expect(getByTestId('show-publish-state').textContent).toBe('false'); }); test('handlePublishStateModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-publish-error')); expect(getByTestId('notification-message').textContent).toBe('pub err'); expect(getByTestId('show-publish-state').textContent).toBe('false'); }); }); describe('Category Modal Handlers', () => { test('handleCategoryModalCancel closes category modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-category')); expect(getByTestId('show-category').textContent).toBe('true'); fireEvent.click(getByTestId('btn-category-cancel')); expect(getByTestId('show-category').textContent).toBe('false'); }); test('handleCategoryModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-category-success')); expect(getByTestId('notification-message').textContent).toBe('cat ok'); expect(getByTestId('show-category').textContent).toBe('false'); }); test('handleCategoryModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-category-error')); expect(getByTestId('notification-message').textContent).toBe('cat err'); expect(getByTestId('show-category').textContent).toBe('false'); }); }); describe('Tag Modal Handlers', () => { test('handleTagModalCancel closes tag modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-handle-media-select')); fireEvent.click(getByTestId('btn-bulk-tag')); expect(getByTestId('show-tag').textContent).toBe('true'); fireEvent.click(getByTestId('btn-tag-cancel')); expect(getByTestId('show-tag').textContent).toBe('false'); }); test('handleTagModalSuccess shows notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-tag-success')); expect(getByTestId('notification-message').textContent).toBe('tag ok'); expect(getByTestId('show-tag').textContent).toBe('false'); }); test('handleTagModalError shows error notification and closes modal', () => { const { getByTestId } = render(); fireEvent.click(getByTestId('btn-tag-error')); expect(getByTestId('notification-message').textContent).toBe('tag err'); expect(getByTestId('show-tag').textContent).toBe('false'); }); }); });