Back to Repositories

Testing useRate Hook Implementation in OpenHands

This test suite validates the useRate custom React hook implementation, focusing on rate calculation and threshold monitoring functionality. The tests verify initialization, rate calculations, and threshold state management using Jest and React Testing Library.

Test Coverage Overview

The test suite provides comprehensive coverage of the useRate hook functionality.

Key areas tested include:
  • Initial state verification
  • Single element handling
  • Rate calculation between elements
  • Threshold monitoring
  • State updates based on time intervals
Edge cases covered include empty state, single element state, and threshold timing boundaries.

Implementation Analysis

The testing approach utilizes React Testing Library’s renderHook utility for hook testing, combined with Vitest’s timer mocking capabilities.

Implementation patterns include:
  • Fake timer manipulation for time-dependent testing
  • Act wrapper for state updates
  • Isolated test cases for specific functionality
  • Mock time advancement for threshold testing

Technical Details

Testing tools and configuration:
  • Vitest for test runner and assertions
  • React Testing Library for hook testing
  • Timer mocking via vi.useFakeTimers()
  • beforeEach/afterEach hooks for test isolation
  • Act utility for state updates

Best Practices Demonstrated

The test suite exemplifies several testing best practices for React hooks.

Notable practices include:
  • Proper test isolation and cleanup
  • Comprehensive state verification
  • Controlled time manipulation
  • Clear test case organization
  • Explicit assertion messages

all-hands-ai/openhands

frontend/__tests__/hooks/use-rate.test.ts

            
import { act, renderHook } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useRate } from "#/hooks/use-rate";

describe("useRate", () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

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

  it("should initialize", () => {
    const { result } = renderHook(() => useRate());

    expect(result.current.items).toHaveLength(0);
    expect(result.current.rate).toBeNull();
    expect(result.current.lastUpdated).toBeNull();
    expect(result.current.isUnderThreshold).toBe(true);
  });

  it("should handle the case of a single element", () => {
    const { result } = renderHook(() => useRate());

    act(() => {
      result.current.record(123);
    });

    expect(result.current.items).toHaveLength(1);
    expect(result.current.lastUpdated).not.toBeNull();
  });

  it("should return the difference between the last two elements", () => {
    const { result } = renderHook(() => useRate());

    vi.setSystemTime(500);
    act(() => {
      result.current.record(4);
    });

    vi.advanceTimersByTime(500);
    act(() => {
      result.current.record(9);
    });

    expect(result.current.items).toHaveLength(2);
    expect(result.current.rate).toBe(5);
    expect(result.current.lastUpdated).toBe(1000);
  });

  it("should update isUnderThreshold after [threshold]ms of no activity", () => {
    const { result } = renderHook(() => useRate({ threshold: 500 }));

    expect(result.current.isUnderThreshold).toBe(true);

    act(() => {
      // not sure if fake timers is buggy with intervals,
      // but I need to call it twice to register
      vi.advanceTimersToNextTimer();
      vi.advanceTimersToNextTimer();
    });

    expect(result.current.isUnderThreshold).toBe(false);
  });

  it("should return an isUnderThreshold boolean", () => {
    const { result } = renderHook(() => useRate({ threshold: 500 }));

    vi.setSystemTime(500);
    act(() => {
      result.current.record(400);
    });
    act(() => {
      result.current.record(1000);
    });

    expect(result.current.isUnderThreshold).toBe(false);

    act(() => {
      result.current.record(1500);
    });

    expect(result.current.isUnderThreshold).toBe(true);

    act(() => {
      vi.advanceTimersToNextTimer();
      vi.advanceTimersToNextTimer();
    });

    expect(result.current.isUnderThreshold).toBe(false);
  });
});