mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
280 lines
8.2 KiB
TypeScript
280 lines
8.2 KiB
TypeScript
import { describe, expect, it, beforeEach } from "vitest";
|
|
import { renderHook, act } from "@testing-library/react";
|
|
import { useTaskList } from "#/hooks/use-task-list";
|
|
import { useEventStore } from "#/stores/use-event-store";
|
|
import type { OHEvent } from "#/stores/use-event-store";
|
|
import type { TaskTrackingObservation } from "#/types/core/observations";
|
|
|
|
function createV0TaskTrackingObservation(
|
|
id: number,
|
|
command: string,
|
|
taskList: TaskTrackingObservation["extras"]["task_list"],
|
|
): TaskTrackingObservation {
|
|
return {
|
|
id,
|
|
source: "agent",
|
|
observation: "task_tracking",
|
|
message: "Task tracking update",
|
|
timestamp: `2025-07-01T00:00:0${id}Z`,
|
|
cause: 0,
|
|
content: "",
|
|
extras: {
|
|
command,
|
|
task_list: taskList,
|
|
},
|
|
};
|
|
}
|
|
|
|
function createV1TaskTrackerObservation(
|
|
id: string,
|
|
command: string,
|
|
taskList: Array<{
|
|
title: string;
|
|
notes: string;
|
|
status: "todo" | "in_progress" | "done";
|
|
}>,
|
|
): OHEvent {
|
|
return {
|
|
id,
|
|
timestamp: `2025-07-01T00:00:0${id}Z`,
|
|
source: "environment",
|
|
tool_name: "task_tracker",
|
|
tool_call_id: `call_${id}`,
|
|
action_id: `action_${id}`,
|
|
observation: {
|
|
kind: "TaskTrackerObservation",
|
|
content: "Task list updated",
|
|
command,
|
|
task_list: taskList,
|
|
},
|
|
} as unknown as OHEvent;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
useEventStore.setState({
|
|
events: [],
|
|
eventIds: new Set(),
|
|
uiEvents: [],
|
|
});
|
|
});
|
|
|
|
describe("useTaskList", () => {
|
|
it("returns empty taskList and hasTaskList=false when no events exist", () => {
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
|
|
it("returns empty taskList when no task tracking observations exist", () => {
|
|
useEventStore.setState({
|
|
events: [
|
|
{
|
|
id: 1,
|
|
source: "user",
|
|
action: "message",
|
|
args: { content: "Hello", image_urls: [], file_urls: [] },
|
|
message: "Hello",
|
|
timestamp: "2025-07-01T00:00:01Z",
|
|
},
|
|
],
|
|
eventIds: new Set([1]),
|
|
uiEvents: [],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
|
|
describe("v0 events", () => {
|
|
it('returns the task list from a TaskTrackingObservation with command="plan"', () => {
|
|
const tasks = [
|
|
{ id: "1", title: "First task", status: "todo" as const },
|
|
{ id: "2", title: "Second task", status: "in_progress" as const },
|
|
];
|
|
const event = createV0TaskTrackingObservation(1, "plan", tasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set([1]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual(tasks);
|
|
expect(result.current.hasTaskList).toBe(true);
|
|
});
|
|
|
|
it('ignores TaskTrackingObservation events with command !== "plan"', () => {
|
|
const tasks = [{ id: "1", title: "First task", status: "todo" as const }];
|
|
const event = createV0TaskTrackingObservation(1, "update", tasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set([1]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
|
|
it("returns the latest task list when multiple plan events exist", () => {
|
|
const earlyTasks = [
|
|
{ id: "1", title: "First task", status: "todo" as const },
|
|
];
|
|
const lateTasks = [
|
|
{ id: "1", title: "First task", status: "done" as const },
|
|
{ id: "2", title: "New task", status: "in_progress" as const },
|
|
];
|
|
|
|
const event1 = createV0TaskTrackingObservation(1, "plan", earlyTasks);
|
|
const event2 = createV0TaskTrackingObservation(2, "plan", lateTasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event1, event2],
|
|
eventIds: new Set([1, 2]),
|
|
uiEvents: [event1, event2],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual(lateTasks);
|
|
expect(result.current.hasTaskList).toBe(true);
|
|
});
|
|
|
|
it("updates when new events are added to the store", () => {
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
|
|
const tasks = [{ id: "1", title: "New task", status: "todo" as const }];
|
|
const event = createV0TaskTrackingObservation(1, "plan", tasks);
|
|
|
|
act(() => {
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set([1]),
|
|
uiEvents: [event],
|
|
});
|
|
});
|
|
|
|
expect(result.current.taskList).toEqual(tasks);
|
|
expect(result.current.hasTaskList).toBe(true);
|
|
});
|
|
|
|
it("returns hasTaskList=false when the latest plan has an empty task list", () => {
|
|
const event = createV0TaskTrackingObservation(1, "plan", []);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set([1]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("v1 events", () => {
|
|
it('returns the task list from a v1 TaskTrackerObservation with command="plan"', () => {
|
|
const tasks = [
|
|
{ title: "First task", notes: "", status: "todo" as const },
|
|
{
|
|
title: "Second task",
|
|
notes: "some note",
|
|
status: "in_progress" as const,
|
|
},
|
|
];
|
|
const event = createV1TaskTrackerObservation("1", "plan", tasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set(["1"]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([
|
|
{ id: "1", title: "First task", notes: undefined, status: "todo" },
|
|
{
|
|
id: "2",
|
|
title: "Second task",
|
|
notes: "some note",
|
|
status: "in_progress",
|
|
},
|
|
]);
|
|
expect(result.current.hasTaskList).toBe(true);
|
|
});
|
|
|
|
it('ignores v1 TaskTrackerObservation with command !== "plan"', () => {
|
|
const tasks = [
|
|
{ title: "First task", notes: "", status: "todo" as const },
|
|
];
|
|
const event = createV1TaskTrackerObservation("1", "view", tasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set(["1"]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
|
|
it("returns the latest v1 task list when multiple plan events exist", () => {
|
|
const earlyTasks = [
|
|
{ title: "First task", notes: "", status: "todo" as const },
|
|
];
|
|
const lateTasks = [
|
|
{ title: "First task", notes: "", status: "done" as const },
|
|
{ title: "New task", notes: "wip", status: "in_progress" as const },
|
|
];
|
|
|
|
const event1 = createV1TaskTrackerObservation("1", "plan", earlyTasks);
|
|
const event2 = createV1TaskTrackerObservation("2", "plan", lateTasks);
|
|
|
|
useEventStore.setState({
|
|
events: [event1, event2],
|
|
eventIds: new Set(["1", "2"]),
|
|
uiEvents: [event1, event2],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([
|
|
{ id: "1", title: "First task", notes: undefined, status: "done" },
|
|
{ id: "2", title: "New task", notes: "wip", status: "in_progress" },
|
|
]);
|
|
expect(result.current.hasTaskList).toBe(true);
|
|
});
|
|
|
|
it("returns hasTaskList=false when the latest v1 plan has an empty task list", () => {
|
|
const event = createV1TaskTrackerObservation("1", "plan", []);
|
|
|
|
useEventStore.setState({
|
|
events: [event],
|
|
eventIds: new Set(["1"]),
|
|
uiEvents: [event],
|
|
});
|
|
|
|
const { result } = renderHook(() => useTaskList());
|
|
|
|
expect(result.current.taskList).toEqual([]);
|
|
expect(result.current.hasTaskList).toBe(false);
|
|
});
|
|
});
|
|
});
|