// Licensed to Cloudera, Inc. under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. Cloudera, Inc. licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import StorageFilePage from './StorageFilePage'; import { BrowserViewType, FileStats } from '../types'; import '@testing-library/jest-dom'; import userEvent from '@testing-library/user-event'; import { DOWNLOAD_API_URL } from '../api'; import huePubSub from '../../../utils/huePubSub'; jest.mock('../../../utils/dateTimeUtils', () => ({ ...jest.requireActual('../../../utils/dateTimeUtils'), formatTimestamp: () => 'April 8, 2021 at 00:00 AM' })); jest.mock('../../../utils/huePubSub', () => ({ publish: jest.fn() })); const mockSendApiRequest = jest.fn(); jest.mock('../../../api/utils', () => { const original = jest.requireActual('../../../api/utils'); return { ...original, sendApiRequest: () => mockSendApiRequest() }; }); const mockLoadData = jest.fn().mockReturnValue({ contents: 'Initial file content' }); const mockError = jest.fn().mockImplementationOnce(() => null); jest.mock('../../../utils/hooks/useLoadData/useLoadData', () => ({ __esModule: true, default: jest.fn(() => ({ data: mockLoadData(), loading: false, error: mockError() })) })); const mockConfig = { storage_browser: { enable_file_download_button: true, max_file_editor_size: 1000000000 } }; jest.mock('../../../config/hueConfig', () => ({ getLastKnownConfig: () => mockConfig })); const mockFileStats: FileStats = { path: '/path/to/file.txt', size: 123456, user: 'testuser', group: 'testgroup', mtime: 1617877200, atime: 1617877200, mode: 33188, rwx: 'rwxr-xr-x', blockSize: 1, replication: 1, type: BrowserViewType.file }; const mockReload = jest.fn(); describe('StorageFilePage', () => { it('should render file metadata and content', async () => { render(); expect(screen.getByText('Size')).toBeInTheDocument(); expect(screen.getByText('120.56 KB')).toBeInTheDocument(); expect(screen.getByText('Created By')).toBeInTheDocument(); expect(screen.getByText('testuser')).toBeInTheDocument(); expect(screen.getByText('Group')).toBeInTheDocument(); expect(screen.getByText('testgroup')).toBeInTheDocument(); expect(screen.getByText('Permissions')).toBeInTheDocument(); expect(screen.getByText('rwxr-xr-x')).toBeInTheDocument(); expect(screen.getByText('Last Modified')).toBeInTheDocument(); expect(screen.getByText('April 8, 2021 at 00:00 AM')).toBeInTheDocument(); expect(screen.getByText('Content')).toBeInTheDocument(); // TODO: fix this test when mocking of useLoadData onSuccess callback is mproperly mocked // expect(screen.getByText('Initial file content')).toBeInTheDocument(); }); // TODO: fix this test when mocking of useLoadData onSuccess callback is mproperly mocked it.skip('should show edit button and hides save/cancel buttons initially', () => { render(); expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible(); expect(screen.queryByRole('button', { name: 'Save' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull(); }); it('should hide edit button when file type is not text', async () => { const fileStats = { ...mockFileStats, path: '/path/to/file.zip' }; render(); expect(screen.queryByRole('button', { name: 'Edit' })).toBeNull(); }); it('should show save and cancel buttons when editing', async () => { const user = userEvent.setup(); render(); expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible(); expect(screen.queryByRole('button', { name: 'Save' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull(); await user.click(screen.getByRole('button', { name: 'Edit' })); await waitFor(() => { expect(screen.getByRole('button', { name: 'Save' })).toBeVisible(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeVisible(); }); expect(screen.queryByRole('button', { name: 'Edit' })).toBeNull(); }); it('should update textarea value and calls handleSave', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Edit' })); const textarea = screen.getByRole('textbox'); expect(textarea).toBeVisible(); await user.clear(textarea); await user.type(textarea, 'Updated file content'); expect(textarea).toHaveValue('Updated file content'); await user.click(screen.getByRole('button', { name: 'Save' })); expect(screen.getByText('Updated file content')).toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Save' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull(); expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible(); }); it('should cancel editing and reverts textarea value', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Edit' })); const textarea = screen.getByRole('textbox'); expect(textarea).toBeVisible(); await user.clear(textarea); await user.type(textarea, 'Updated file content'); expect(textarea).toHaveValue('Updated file content'); await user.click(screen.getByRole('button', { name: 'Cancel' })); expect(textarea).toHaveValue('Initial file content'); expect(screen.queryByRole('button', { name: 'Save' })).toBeNull(); expect(screen.queryByRole('button', { name: 'Cancel' })).toBeNull(); expect(screen.getByRole('button', { name: 'Edit' })).toBeVisible(); }); it('should download a file when download button is clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Download' })); expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', { message: 'Downloading your file, Please wait...' }); const downloadLink = screen.getByRole('link', { name: 'Download' }); expect(downloadLink).toHaveAttribute('href', `${DOWNLOAD_API_URL}?path=${mockFileStats.path}`); }); it('should download a file when download button is clicked', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Download' })); expect(huePubSub.publish).toHaveBeenCalledWith('hue.global.info', { message: 'Downloading your file, Please wait...' }); const downloadLink = screen.getByRole('link', { name: 'Download' }); expect(downloadLink).toHaveAttribute('href', `${DOWNLOAD_API_URL}?path=${mockFileStats.path}`); }); it('should not render the download button when show_download_button is false', () => { mockConfig.storage_browser.enable_file_download_button = false; render(); expect(screen.queryByRole('button', { name: 'Download' })).toBeNull(); expect(screen.queryByRole('link', { name: 'Download' })).toBeNull(); }); // TODO: fix this test when mocking of useLoadData onSuccess callback is properly mocked it('should render a textarea for text files', () => { render(); const textarea = screen.getByRole('textbox'); expect(textarea).toBeInTheDocument(); // expect(textarea).toHaveValue('Initial file content'); }); it('should render a textarea for other files', () => { render(); const textarea = screen.getByRole('textbox'); expect(textarea).toBeInTheDocument(); // expect(textarea).toHaveValue('Initial file content'); }); it('should render an image for image files', () => { render( ); const img = screen.getByRole('img'); expect(img).toBeInTheDocument(); expect(img).toHaveAttribute('src', expect.stringContaining('imagefile.png')); }); it('should render a preview button for document files', () => { render( ); expect(screen.getByRole('button', { name: /preview document/i })).toBeInTheDocument(); }); it('should render an audio player for audio files', () => { render( ); const audio = screen.getByTestId('preview__content__audio'); // audio tag can't be access using getByRole expect(audio).toBeInTheDocument(); expect(audio.children[0]).toHaveAttribute('src', expect.stringContaining('audiofile.mp3')); }); it('should render a video player for video files', () => { render( ); const video = screen.getByTestId('preview__content__video'); // video tag can't be access using getByRole expect(video).toBeInTheDocument(); expect(video.children[0]).toHaveAttribute('src', expect.stringContaining('videofile.mp4')); }); it('should display a message for unsupported file types', () => { mockError.mockImplementationOnce(() => ({ response: { status: 422 } })); render( ); expect(screen.getByText(/Preview is not available for this file/i)).toBeInTheDocument(); }); });