Back to Repositories

Testing Image Upload Component Integration in OpenHands

This test suite validates the UploadImageInput component functionality in OpenHands, focusing on file upload handling and custom label rendering. The tests verify both single and multiple image upload scenarios while ensuring proper validation and customization options.

Test Coverage Overview

The test suite provides comprehensive coverage of the UploadImageInput component’s core functionalities:

  • Input rendering and presence verification
  • Single image file upload handling
  • Multiple image files upload processing
  • File type validation (image-only)
  • Custom label rendering and replacement

Implementation Analysis

The testing approach utilizes React Testing Library and userEvent for simulating user interactions. The implementation leverages vitest’s mocking capabilities for upload handlers and employs component rerendering to validate dynamic label updates.

Key patterns include mock function verification, file object creation, and DOM element querying using test IDs.

Technical Details

  • Testing Framework: Vitest
  • UI Testing: @testing-library/react
  • User Interaction: @testing-library/user-event
  • Component Testing: Jest DOM assertions
  • Mock Implementation: vi.fn() for upload callbacks

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of test cases, proper cleanup with afterEach hooks, and comprehensive edge case handling.

  • Clear test case organization and naming
  • Mock cleanup between tests
  • Data-testid usage for reliable element selection
  • Async/await pattern for upload operations
  • Validation of both success and failure scenarios

all-hands-ai/openhands

frontend/__tests__/components/upload-image-input.test.tsx

            
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { UploadImageInput } from "#/components/features/images/upload-image-input";

describe("UploadImageInput", () => {
  const user = userEvent.setup();
  const onUploadMock = vi.fn();

  afterEach(() => {
    vi.clearAllMocks();
  });

  it("should render an input", () => {
    render(<UploadImageInput onUpload={onUploadMock} />);
    expect(screen.getByTestId("upload-image-input")).toBeInTheDocument();
  });

  it("should call onUpload when a file is selected", async () => {
    render(<UploadImageInput onUpload={onUploadMock} />);

    const file = new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" });
    const input = screen.getByTestId("upload-image-input");

    await user.upload(input, file);

    expect(onUploadMock).toHaveBeenNthCalledWith(1, [file]);
  });

  it("should call onUpload when multiple files are selected", async () => {
    render(<UploadImageInput onUpload={onUploadMock} />);

    const files = [
      new File(["(⌐□_□)"], "chucknorris.png", { type: "image/png" }),
      new File(["(⌐□_□)"], "chucknorris2.png", { type: "image/png" }),
    ];
    const input = screen.getByTestId("upload-image-input");

    await user.upload(input, files);

    expect(onUploadMock).toHaveBeenNthCalledWith(1, files);
  });

  it("should not upload any file that is not an image", async () => {
    render(<UploadImageInput onUpload={onUploadMock} />);

    const file = new File(["(⌐□_□)"], "chucknorris.txt", {
      type: "text/plain",
    });
    const input = screen.getByTestId("upload-image-input");

    await user.upload(input, file);

    expect(onUploadMock).not.toHaveBeenCalled();
  });

  it("should render custom labels", () => {
    const { rerender } = render(<UploadImageInput onUpload={onUploadMock} />);
    expect(screen.getByTestId("default-label")).toBeInTheDocument();

    function CustomLabel() {
      return <span>Custom label</span>;
    }
    rerender(
      <UploadImageInput onUpload={onUploadMock} label={<CustomLabel />} />,
    );

    expect(screen.getByText("Custom label")).toBeInTheDocument();
    expect(screen.queryByTestId("default-label")).not.toBeInTheDocument();
  });
});