import React from "react"; import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom"; import { ResponseDetailView } from "./responses-detail"; import { OpenAIResponse, InputItemListResponse } from "@/lib/types"; describe("ResponseDetailView", () => { const defaultProps = { response: null, inputItems: null, isLoading: false, isLoadingInputItems: false, error: null, inputItemsError: null, id: "test_id", }; describe("Loading State", () => { test("renders loading skeleton when isLoading is true", () => { const { container } = render( , ); // Check for skeleton elements const skeletons = container.querySelectorAll('[data-slot="skeleton"]'); expect(skeletons.length).toBeGreaterThan(0); // The title is replaced by a skeleton when loading, so we shouldn't expect the text }); }); describe("Error State", () => { test("renders error message when error prop is provided", () => { const errorMessage = "Network Error"; render( , ); expect(screen.getByText("Responses Details")).toBeInTheDocument(); // The error message is split across elements, so we check for parts expect( screen.getByText(/Error loading details for ID/), ).toBeInTheDocument(); expect(screen.getByText(/test_id/)).toBeInTheDocument(); expect(screen.getByText(/Network Error/)).toBeInTheDocument(); }); test("renders default error message when error.message is not available", () => { render( , ); expect( screen.getByText(/Error loading details for ID/), ).toBeInTheDocument(); expect(screen.getByText(/test_id/)).toBeInTheDocument(); }); }); describe("Not Found State", () => { test("renders not found message when response is null and not loading/error", () => { render(); expect(screen.getByText("Responses Details")).toBeInTheDocument(); // The message is split across elements expect(screen.getByText(/No details found for ID:/)).toBeInTheDocument(); expect(screen.getByText(/test_id/)).toBeInTheDocument(); }); }); describe("Response Data Rendering", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "llama-test-model", status: "completed", output: [ { type: "message", role: "assistant", content: "Test response output", }, ], input: [ { type: "message", role: "user", content: "Test input message", }, ], temperature: 0.7, top_p: 0.9, parallel_tool_calls: true, previous_response_id: "prev_resp_456", }; test("renders response data with input and output sections", () => { render(); // Check main sections expect(screen.getByText("Responses Details")).toBeInTheDocument(); expect(screen.getByText("Input")).toBeInTheDocument(); expect(screen.getByText("Output")).toBeInTheDocument(); // Check input content expect(screen.getByText("Test input message")).toBeInTheDocument(); expect(screen.getByText("User")).toBeInTheDocument(); // Check output content expect(screen.getByText("Test response output")).toBeInTheDocument(); expect(screen.getByText("Assistant")).toBeInTheDocument(); }); test("renders properties sidebar with all response metadata", () => { render(); // Check properties - use regex to handle text split across elements expect(screen.getByText(/Created/)).toBeInTheDocument(); expect( screen.getByText(new Date(1710000000 * 1000).toLocaleString()), ).toBeInTheDocument(); // Check for the specific ID label (not Previous Response ID) expect( screen.getByText((content, element) => { return element?.tagName === "STRONG" && content === "ID:"; }), ).toBeInTheDocument(); expect(screen.getByText("resp_123")).toBeInTheDocument(); expect(screen.getByText(/Model/)).toBeInTheDocument(); expect(screen.getByText("llama-test-model")).toBeInTheDocument(); expect(screen.getByText(/Status/)).toBeInTheDocument(); expect(screen.getByText("completed")).toBeInTheDocument(); expect(screen.getByText(/Temperature/)).toBeInTheDocument(); expect(screen.getByText("0.7")).toBeInTheDocument(); expect(screen.getByText(/Top P/)).toBeInTheDocument(); expect(screen.getByText("0.9")).toBeInTheDocument(); expect(screen.getByText(/Parallel Tool Calls/)).toBeInTheDocument(); expect(screen.getByText("Yes")).toBeInTheDocument(); expect(screen.getByText(/Previous Response ID/)).toBeInTheDocument(); expect(screen.getByText("prev_resp_456")).toBeInTheDocument(); }); test("handles optional properties correctly", () => { const minimalResponse: OpenAIResponse = { id: "resp_minimal", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [], }; render( , ); // Should show required properties expect(screen.getByText("resp_minimal")).toBeInTheDocument(); expect(screen.getByText("test-model")).toBeInTheDocument(); expect(screen.getByText("completed")).toBeInTheDocument(); // Should not show optional properties expect(screen.queryByText("Temperature")).not.toBeInTheDocument(); expect(screen.queryByText("Top P")).not.toBeInTheDocument(); expect(screen.queryByText("Parallel Tool Calls")).not.toBeInTheDocument(); expect( screen.queryByText("Previous Response ID"), ).not.toBeInTheDocument(); }); test("renders error information when response has error", () => { const errorResponse: OpenAIResponse = { ...mockResponse, error: { code: "invalid_request", message: "The request was invalid", }, }; render(); // The error is shown in the properties sidebar, not as a separate "Error" label expect( screen.getByText("invalid_request: The request was invalid"), ).toBeInTheDocument(); }); }); describe("Input Items Handling", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [{ type: "message", role: "assistant", content: "output" }], input: [{ type: "message", role: "user", content: "fallback input" }], }; test("shows loading state for input items", () => { render( , ); // Check for skeleton loading in input items section const { container } = render( , ); const skeletons = container.querySelectorAll('[data-slot="skeleton"]'); expect(skeletons.length).toBeGreaterThan(0); }); test("shows error message for input items with fallback", () => { render( , ); expect( screen.getByText( "Error loading input items: Failed to load input items", ), ).toBeInTheDocument(); expect( screen.getByText("Falling back to response input data."), ).toBeInTheDocument(); // Should still show fallback input data expect(screen.getByText("fallback input")).toBeInTheDocument(); }); test("uses input items data when available", () => { const mockInputItems: InputItemListResponse = { object: "list", data: [ { type: "message", role: "user", content: "input from items API", }, ], }; render( , ); // Should show input items data, not response.input expect(screen.getByText("input from items API")).toBeInTheDocument(); expect(screen.queryByText("fallback input")).not.toBeInTheDocument(); }); test("falls back to response.input when input items is empty", () => { const emptyInputItems: InputItemListResponse = { object: "list", data: [], }; render( , ); // Should show fallback input data expect(screen.getByText("fallback input")).toBeInTheDocument(); }); test("shows no input message when no data available", () => { const responseWithoutInput: OpenAIResponse = { ...mockResponse, input: [], }; render( , ); expect(screen.getByText("No input data available.")).toBeInTheDocument(); }); }); describe("Input Display Components", () => { test("renders string content input correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "message", role: "user", content: "Simple string input", }, ], }; render(); expect(screen.getByText("Simple string input")).toBeInTheDocument(); expect(screen.getByText("User")).toBeInTheDocument(); }); test("renders array content input correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "message", role: "user", content: [ { type: "input_text", text: "First part" }, { type: "output_text", text: "Second part" }, ], }, ], }; render(); expect(screen.getByText("First part Second part")).toBeInTheDocument(); expect(screen.getByText("User")).toBeInTheDocument(); }); test("renders non-message input types correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "function_call", content: "function call content", }, ], }; render(); expect(screen.getByText("function call content")).toBeInTheDocument(); // Use getAllByText to find the specific "Input" with the type detail const inputElements = screen.getAllByText("Input"); expect(inputElements.length).toBeGreaterThan(0); expect(screen.getByText("(function_call)")).toBeInTheDocument(); }); test("handles input with object content", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "custom_type", content: JSON.stringify({ key: "value", nested: { data: "test" } }), }, ], }; render(); // Should show JSON stringified content (without quotes around keys in the rendered output) expect(screen.getByText(/key.*value/)).toBeInTheDocument(); // Use getAllByText to find the specific "Input" with the type detail const inputElements = screen.getAllByText("Input"); expect(inputElements.length).toBeGreaterThan(0); expect(screen.getByText("(custom_type)")).toBeInTheDocument(); }); test("renders function call input correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "function_call", id: "call_456", status: "completed", name: "input_function", arguments: '{"param": "value"}', }, ], }; render(); expect( screen.getByText('input_function({"param": "value"})'), ).toBeInTheDocument(); expect(screen.getByText("Function Call")).toBeInTheDocument(); }); test("renders web search call input correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "web_search_call", id: "search_789", status: "completed", }, ], }; render(); expect( screen.getByText("web_search_call(status: completed)"), ).toBeInTheDocument(); expect(screen.getByText("Function Call")).toBeInTheDocument(); expect(screen.getByText("(Web Search)")).toBeInTheDocument(); }); }); describe("Output Display Components", () => { test("renders message output with string content", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "message", role: "assistant", content: "Simple string output", }, ], input: [], }; render(); expect(screen.getByText("Simple string output")).toBeInTheDocument(); expect(screen.getByText("Assistant")).toBeInTheDocument(); }); test("renders message output with array content", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "message", role: "assistant", content: [ { type: "output_text", text: "First output" }, { type: "input_text", text: "Second output" }, ], }, ], input: [], }; render(); expect( screen.getByText("First output Second output"), ).toBeInTheDocument(); expect(screen.getByText("Assistant")).toBeInTheDocument(); }); test("renders function call output correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "function_call", id: "call_123", status: "completed", name: "search_function", arguments: '{"query": "test"}', }, ], input: [], }; render(); expect( screen.getByText('search_function({"query": "test"})'), ).toBeInTheDocument(); expect(screen.getByText("Function Call")).toBeInTheDocument(); }); test("renders function call output without arguments", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "function_call", id: "call_123", status: "completed", name: "simple_function", }, ], input: [], }; render(); expect(screen.getByText("simple_function({})")).toBeInTheDocument(); expect(screen.getByText(/Function Call/)).toBeInTheDocument(); }); test("renders web search call output correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "web_search_call", id: "search_123", status: "completed", }, ], input: [], }; render(); expect( screen.getByText("web_search_call(status: completed)"), ).toBeInTheDocument(); expect(screen.getByText(/Function Call/)).toBeInTheDocument(); expect(screen.getByText("(Web Search)")).toBeInTheDocument(); }); test("renders unknown output types with JSON fallback", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "unknown_type", custom_field: "custom_value", data: { nested: "object" }, } as any, ], input: [], }; render(); // Should show JSON stringified content expect( screen.getByText(/custom_field.*custom_value/), ).toBeInTheDocument(); expect(screen.getByText("(unknown_type)")).toBeInTheDocument(); }); test("shows no output message when output array is empty", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [], }; render(); expect(screen.getByText("No output data available.")).toBeInTheDocument(); }); test("groups function call with its output correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "function_call", id: "call_123", status: "completed", name: "get_weather", arguments: '{"city": "Tokyo"}', }, { type: "message", role: "assistant", call_id: "call_123", content: "sunny and warm", } as any, // Using any to bypass the type restriction for this test ], input: [], }; render(); // Should show the function call and message as separate items (not grouped) expect(screen.getByText("Function Call")).toBeInTheDocument(); expect( screen.getByText('get_weather({"city": "Tokyo"})'), ).toBeInTheDocument(); expect(screen.getByText("Assistant")).toBeInTheDocument(); expect(screen.getByText("sunny and warm")).toBeInTheDocument(); // Should NOT have the grouped "Arguments" and "Output" labels expect(screen.queryByText("Arguments")).not.toBeInTheDocument(); }); test("groups function call with function_call_output correctly", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "function_call", call_id: "call_123", status: "completed", name: "get_weather", arguments: '{"city": "Tokyo"}', }, { type: "function_call_output", id: "fc_68364957013081...", status: "completed", call_id: "call_123", output: "sunny and warm", } as any, // Using any to bypass the type restriction for this test ], input: [], }; render(); // Should show the function call grouped with its clean output expect(screen.getByText("Function Call")).toBeInTheDocument(); expect(screen.getByText("Arguments")).toBeInTheDocument(); expect( screen.getByText('get_weather({"city": "Tokyo"})'), ).toBeInTheDocument(); // Use getAllByText since there are multiple "Output" elements (card title and output label) const outputElements = screen.getAllByText("Output"); expect(outputElements.length).toBeGreaterThan(0); expect(screen.getByText("sunny and warm")).toBeInTheDocument(); }); }); describe("Edge Cases and Error Handling", () => { test("handles missing role in message input", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [], input: [ { type: "message", content: "Message without role", }, ], }; render(); expect(screen.getByText("Message without role")).toBeInTheDocument(); expect(screen.getByText("Unknown")).toBeInTheDocument(); // Default role }); test("handles missing name in function call output", () => { const mockResponse: OpenAIResponse = { id: "resp_123", object: "response", created_at: 1710000000, model: "test-model", status: "completed", output: [ { type: "function_call", id: "call_123", status: "completed", }, ], input: [], }; render(); // When name is missing, it falls back to JSON.stringify of the entire output const functionCallElements = screen.getAllByText(/function_call/); expect(functionCallElements.length).toBeGreaterThan(0); expect(screen.getByText(/call_123/)).toBeInTheDocument(); }); }); });