mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-22 23:52:23 +02:00
feat(sdk-js): switch from jest to vitest, add useStream FE tests (#4726)
This commit is contained in:
+14
-10
@@ -10,7 +10,7 @@
|
||||
"prepack": "yarn run build",
|
||||
"format": "prettier --write src",
|
||||
"lint": "prettier --check src && tsc --noEmit",
|
||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts",
|
||||
"test": "vitest",
|
||||
"typedoc": "typedoc && typedoc src/react/index.ts --out docs/react --options typedoc.react.json && typedoc src/auth/index.ts --out docs/auth --options typedoc.auth.json"
|
||||
},
|
||||
"main": "index.js",
|
||||
@@ -22,28 +22,32 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^29.7.0",
|
||||
"@langchain/core": "^0.3.31",
|
||||
"@langchain/scripts": "^0.1.4",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@tsconfig/recommended": "^1.0.2",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/react": "^19.0.8",
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"concat-md": "^0.5.1",
|
||||
"jest": "^29.7.0",
|
||||
"jsdom": "^26.1.0",
|
||||
"msw": "^2.8.2",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"typedoc": "^0.27.7",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"vitest": "^3.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19",
|
||||
"@langchain/core": ">=0.2.31 <0.4.0"
|
||||
"@langchain/core": ">=0.2.31 <0.4.0",
|
||||
"react": "^18 || ^19"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
|
||||
@@ -1,74 +1,76 @@
|
||||
/* eslint-disable no-process-env */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { jest } from "@jest/globals";
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { Client } from "../client.js";
|
||||
import { overrideFetchImplementation } from "../singletons/fetch.js";
|
||||
|
||||
describe.each([[""], ["mocked"]])("Client uses %s fetch", (description) => {
|
||||
let globalFetchMock: jest.Mock;
|
||||
let overriddenFetch: jest.Mock;
|
||||
let expectedFetchMock: jest.Mock;
|
||||
let unexpectedFetchMock: jest.Mock;
|
||||
describe.each([["global"], ["mocked"]])(
|
||||
"Client uses %s fetch",
|
||||
(description: string) => {
|
||||
let globalFetchMock: ReturnType<typeof vi.fn>;
|
||||
let overriddenFetch: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
globalFetchMock = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
}),
|
||||
);
|
||||
overriddenFetch = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
}),
|
||||
);
|
||||
expectedFetchMock =
|
||||
description === "mocked" ? overriddenFetch : globalFetchMock;
|
||||
unexpectedFetchMock =
|
||||
description === "mocked" ? globalFetchMock : overriddenFetch;
|
||||
let expectedFetchMock: ReturnType<typeof vi.fn>;
|
||||
let unexpectedFetchMock: ReturnType<typeof vi.fn>;
|
||||
|
||||
if (description === "mocked") {
|
||||
overrideFetchImplementation(overriddenFetch);
|
||||
} else {
|
||||
overrideFetchImplementation(globalFetchMock);
|
||||
}
|
||||
// Mock global fetch
|
||||
(globalThis as any).fetch = globalFetchMock;
|
||||
});
|
||||
beforeEach(() => {
|
||||
globalFetchMock = vi.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
}),
|
||||
);
|
||||
overriddenFetch = vi.fn(() =>
|
||||
Promise.resolve({
|
||||
ok: true,
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
batch_ingest_config: {
|
||||
use_multipart_endpoint: true,
|
||||
},
|
||||
}),
|
||||
text: () => Promise.resolve(""),
|
||||
}),
|
||||
);
|
||||
expectedFetchMock =
|
||||
description === "mocked" ? overriddenFetch : globalFetchMock;
|
||||
unexpectedFetchMock =
|
||||
description === "mocked" ? globalFetchMock : overriddenFetch;
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("createRuns", () => {
|
||||
it("should create an example with the given input and generation", async () => {
|
||||
const client = new Client({ apiKey: "test-api-key" });
|
||||
|
||||
const thread = await client.threads.create();
|
||||
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(unexpectedFetchMock).not.toHaveBeenCalled();
|
||||
|
||||
jest.clearAllMocks(); // Clear all mocks before the next operation
|
||||
|
||||
// Then clear & run the function
|
||||
await client.runs.create(thread.thread_id, "somegraph", {
|
||||
input: { foo: "bar" },
|
||||
});
|
||||
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(unexpectedFetchMock).not.toHaveBeenCalled();
|
||||
if (description === "mocked") {
|
||||
overrideFetchImplementation(overriddenFetch);
|
||||
} else {
|
||||
overrideFetchImplementation(globalFetchMock);
|
||||
}
|
||||
// Mock global fetch
|
||||
(globalThis as any).fetch = globalFetchMock;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("createRuns", () => {
|
||||
it("should create an example with the given input and generation", async () => {
|
||||
const client = new Client({ apiKey: "test-api-key" });
|
||||
|
||||
const thread = await client.threads.create();
|
||||
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(unexpectedFetchMock).not.toHaveBeenCalled();
|
||||
|
||||
vi.clearAllMocks(); // Clear all mocks before the next operation
|
||||
|
||||
// Then clear & run the function
|
||||
await client.runs.create(thread.thread_id, "somegraph", {
|
||||
input: { foo: "bar" },
|
||||
});
|
||||
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
|
||||
expect(unexpectedFetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { Readable } from "node:stream";
|
||||
import { IterableReadableStream } from "../utils/stream.js";
|
||||
import { BytesLineDecoder, SSEDecoder } from "../utils/sse.js";
|
||||
|
||||
@@ -0,0 +1,443 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { setupServer } from "msw/node";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { useStream } from "../react/stream.js";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
|
||||
function TestChatComponent() {
|
||||
const { messages, isLoading, error, submit, stop } = useStream({
|
||||
assistantId: "test-assistant",
|
||||
apiKey: "test-api-key",
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="messages">
|
||||
{messages.map((msg, i) => (
|
||||
<div key={msg.id ?? i} data-testid={`message-${i}`}>
|
||||
{typeof msg.content === "string"
|
||||
? msg.content
|
||||
: JSON.stringify(msg.content)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div data-testid="loading">
|
||||
{isLoading ? "Loading..." : "Not loading"}
|
||||
</div>
|
||||
{error ? <div data-testid="error">{String(error)}</div> : null}
|
||||
<button
|
||||
data-testid="submit"
|
||||
onClick={() =>
|
||||
submit({ messages: [{ content: "Hello", type: "human" }] })
|
||||
}
|
||||
>
|
||||
Send
|
||||
</button>
|
||||
<button data-testid="stop" onClick={stop}>
|
||||
Stop
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Mock server setup
|
||||
|
||||
const server = setupServer(
|
||||
// Mock thread creation
|
||||
http.post("*/threads", () => {
|
||||
return HttpResponse.json({ thread_id: "test-thread-id" });
|
||||
}),
|
||||
|
||||
// Mock stream endpoint
|
||||
http.post("*/threads/:threadId/runs/stream", async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const sendSSE = (event: string, data: unknown) =>
|
||||
encoder.encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("metadata", {
|
||||
run_id: "1f03278a-1734-6518-80a4-3390db59f960",
|
||||
attempt: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("values", {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "Hello",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: { model_name: "claude-3-7-sonnet-latest" },
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("messages", [
|
||||
{
|
||||
content: "",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "AIMessageChunk",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
tool_call_chunks: [],
|
||||
},
|
||||
{ run_attempt: 1 },
|
||||
]),
|
||||
);
|
||||
|
||||
controller.enqueue(
|
||||
sendSSE("values", {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
server.use(
|
||||
http.post("*/threads/:threadId/history", () => {
|
||||
return HttpResponse.json([
|
||||
{
|
||||
values: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
next: [],
|
||||
tasks: [],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
source: "loop",
|
||||
writes: {
|
||||
agent: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
step: 1,
|
||||
parents: {},
|
||||
},
|
||||
created_at: "2025-05-16T17:10:16.987537+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-38cf-6c68-8001-22b77ac43ff6",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: {
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
checkpoint_id: "1f03278a-38cf-6c68-8001-22b77ac43ff6",
|
||||
parent_checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
},
|
||||
{
|
||||
values: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hey",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {},
|
||||
type: "human",
|
||||
name: null,
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
example: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
next: ["agent"],
|
||||
tasks: [
|
||||
{
|
||||
id: "e1b7b52b-a78e-4b32-0c89-e06bf46405ed",
|
||||
name: "agent",
|
||||
path: ["__pregel_pull", "agent"],
|
||||
error: null,
|
||||
interrupts: [],
|
||||
checkpoint: null,
|
||||
state: null,
|
||||
result: {
|
||||
messages: [
|
||||
{
|
||||
content: "Hello! How can I assist you today?",
|
||||
additional_kwargs: {},
|
||||
response_metadata: {
|
||||
model_name: "claude-3-7-sonnet-latest",
|
||||
stop_reason: "end_turn",
|
||||
stop_sequence: null,
|
||||
},
|
||||
type: "ai",
|
||||
name: null,
|
||||
id: "run-3e90ba6a-71d6-49e7-94a8-6bcac2fd0f40",
|
||||
example: false,
|
||||
tool_calls: [],
|
||||
invalid_tool_calls: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
},
|
||||
created_at: "2025-05-16T17:10:14.429889+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: {
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
checkpoint_id: "1f03278a-206b-67c6-8000-ac34a0872e1a",
|
||||
parent_checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
},
|
||||
{
|
||||
values: {
|
||||
messages: [],
|
||||
},
|
||||
next: ["__start__"],
|
||||
tasks: [
|
||||
{
|
||||
id: "291af033-2ddc-3320-8bbc-28060057cae5",
|
||||
name: "__start__",
|
||||
path: ["__pregel_pull", "__start__"],
|
||||
error: null,
|
||||
interrupts: [],
|
||||
checkpoint: null,
|
||||
state: null,
|
||||
result: {
|
||||
messages: [
|
||||
{
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
type: "human",
|
||||
content: "Hey",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
run_attempt: 1,
|
||||
source: "input",
|
||||
writes: {
|
||||
__start__: {
|
||||
messages: [
|
||||
{
|
||||
id: "2d8c0d9f-a614-4e44-b474-6a56e9471cf5",
|
||||
type: "human",
|
||||
content: "Hey",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
step: -1,
|
||||
parents: {},
|
||||
},
|
||||
created_at: "2025-05-16T17:10:14.428191+00:00",
|
||||
checkpoint: {
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
thread_id: "b06fd92a-955c-446e-b233-7977716c4a9c",
|
||||
checkpoint_ns: "",
|
||||
},
|
||||
parent_checkpoint: null,
|
||||
checkpoint_id: "1f03278a-2067-6590-bfff-3fb740466fc3",
|
||||
parent_checkpoint_id: null,
|
||||
},
|
||||
]);
|
||||
}),
|
||||
);
|
||||
|
||||
return new HttpResponse(stream, {
|
||||
headers: { "Content-Type": "text/event-stream" },
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
server.use;
|
||||
|
||||
describe("useStream", () => {
|
||||
beforeEach(() => server.listen());
|
||||
|
||||
afterEach(() => {
|
||||
server.resetHandlers();
|
||||
server.close();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders initial state correctly", () => {
|
||||
render(<TestChatComponent />);
|
||||
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
expect(screen.getByTestId("messages")).toBeEmptyDOMElement();
|
||||
expect(screen.queryByTestId("error")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles message submission and streaming", async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(<TestChatComponent />);
|
||||
|
||||
// Check loading state
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Loading...");
|
||||
|
||||
// Wait for messages to appear
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("message-0")).toHaveTextContent("Hey");
|
||||
expect(screen.getByTestId("message-1")).toHaveTextContent(
|
||||
"Hello! How can I assist you today?",
|
||||
);
|
||||
});
|
||||
|
||||
// Check final state
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
});
|
||||
|
||||
it("handles stop functionality", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TestChatComponent />);
|
||||
|
||||
// Start streaming and stop immediately
|
||||
await user.click(screen.getByTestId("submit"));
|
||||
await user.click(screen.getByTestId("stop"));
|
||||
|
||||
// Check loading state is reset
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("loading")).toHaveTextContent("Not loading");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: "jsdom",
|
||||
globals: true,
|
||||
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
||||
},
|
||||
});
|
||||
+1265
-1579
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user