Back to Repositories

Testing FileExplorer Component Operations in OpenHands

This test suite validates the FileExplorer component functionality in the OpenHands application, focusing on file management operations and UI interactions. The tests cover core file explorer features including directory navigation, file uploads, and error handling.

Test Coverage Overview

The test suite provides comprehensive coverage of the FileExplorer component’s core functionality.

Key areas tested include:
  • Workspace directory retrieval and display
  • File upload capabilities (single and multiple files)
  • Explorer visibility toggling
  • Refresh functionality
  • Error handling for failed uploads
Edge cases include empty workspace states and failed upload scenarios. Integration points cover interactions with the OpenHands API and toast notification system.

Implementation Analysis

The testing approach utilizes Jest and React Testing Library with a focus on user interaction simulation and API integration verification.

Key patterns include:
  • Custom render utilities with provider wrapping
  • Mock implementations for file operations
  • Spy functions for API calls and notifications
  • User event simulation for interaction testing
The implementation leverages vitest features for mocking and spy functionality.

Technical Details

Testing tools and setup:
  • @testing-library/react for component rendering
  • @testing-library/user-event for user interactions
  • Vitest for test running and mocking
  • Custom renderWithProviders utility
  • Mock implementations for fileService
  • AgentState configuration for test context

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through isolation of test cases and comprehensive coverage.

Notable practices include:
  • Proper test setup and teardown with afterEach hooks
  • Isolated test cases with clear assertions
  • Meaningful test descriptions
  • Proper handling of asynchronous operations
  • Effective use of mock implementations
  • Clear TODO markers for future test implementations

all-hands-ai/openhands

frontend/__tests__/components/file-explorer/file-explorer.test.tsx

            
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "test-utils";
import { describe, it, expect, vi, Mock, afterEach } from "vitest";
import toast from "#/utils/toast";
import AgentState from "#/types/agent-state";
import OpenHands from "#/api/open-hands";
import { FileExplorer } from "#/components/features/file-explorer/file-explorer";

const toastSpy = vi.spyOn(toast, "error");
const uploadFilesSpy = vi.spyOn(OpenHands, "uploadFiles");
const getFilesSpy = vi.spyOn(OpenHands, "getFiles");

vi.mock("../../services/fileService", async () => ({
  uploadFiles: vi.fn(),
}));

const renderFileExplorerWithRunningAgentState = () =>
  renderWithProviders(<FileExplorer isOpen onToggle={() => {}} />, {
    preloadedState: {
      agent: {
        curAgentState: AgentState.RUNNING,
      },
    },
  });

describe.skip("FileExplorer", () => {
  afterEach(() => {
    vi.clearAllMocks();
  });

  it("should get the workspace directory", async () => {
    renderFileExplorerWithRunningAgentState();

    expect(await screen.findByText("folder1")).toBeInTheDocument();
    expect(await screen.findByText("file1.ts")).toBeInTheDocument();
    expect(getFilesSpy).toHaveBeenCalledTimes(1); // once for root
  });

  it.todo("should render an empty workspace");

  it("should refetch the workspace when clicking the refresh button", async () => {
    const user = userEvent.setup();
    renderFileExplorerWithRunningAgentState();

    expect(await screen.findByText("folder1")).toBeInTheDocument();
    expect(await screen.findByText("file1.ts")).toBeInTheDocument();
    expect(getFilesSpy).toHaveBeenCalledTimes(1); // once for root

    const refreshButton = screen.getByTestId("refresh");
    await user.click(refreshButton);

    expect(getFilesSpy).toHaveBeenCalledTimes(2); // once for root, once for refresh button
  });

  it("should toggle the explorer visibility when clicking the toggle button", async () => {
    const user = userEvent.setup();
    renderFileExplorerWithRunningAgentState();

    const folder1 = await screen.findByText("folder1");
    expect(folder1).toBeInTheDocument();

    const toggleButton = screen.getByTestId("toggle");
    await user.click(toggleButton);

    expect(folder1).toBeInTheDocument();
    expect(folder1).not.toBeVisible();
  });

  it("should upload files", async () => {
    const user = userEvent.setup();
    renderFileExplorerWithRunningAgentState();

    const file = new File([""], "file-name");
    const uploadFileInput = await screen.findByTestId("file-input");
    await user.upload(uploadFileInput, file);

    // TODO: Improve this test by passing expected argument to `uploadFiles`
    expect(uploadFilesSpy).toHaveBeenCalledOnce();
    expect(getFilesSpy).toHaveBeenCalled();

    const file2 = new File([""], "file-name-2");
    const uploadDirInput = await screen.findByTestId("file-input");
    await user.upload(uploadDirInput, [file, file2]);

    expect(uploadFilesSpy).toHaveBeenCalledTimes(2);
    expect(getFilesSpy).toHaveBeenCalled();
  });

  it.todo("should upload files when dragging them to the explorer", () => {
    // It will require too much work to mock drag logic, especially for our case
    // https://github.com/testing-library/user-event/issues/440#issuecomment-685010755
    // TODO: should be tested in an e2e environment such as Cypress/Playwright
  });

  it.todo("should download a file");

  it("should display an error toast if file upload fails", async () => {
    (uploadFilesSpy as Mock).mockRejectedValue(new Error());
    const user = userEvent.setup();
    renderFileExplorerWithRunningAgentState();

    const uploadFileInput = await screen.findByTestId("file-input");
    const file = new File([""], "test");

    await user.upload(uploadFileInput, file);

    expect(uploadFilesSpy).rejects.toThrow();
    expect(toastSpy).toHaveBeenCalledWith(
      expect.stringContaining("upload-error"),
      expect.any(String),
    );
  });
});