Skip to content
Closed
12 changes: 12 additions & 0 deletions packages/chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"./jsx-dev-runtime": {
"types": "./dist/jsx-runtime.d.ts",
"import": "./dist/jsx-runtime.js"
},
"./test": {
"types": "./dist/test.d.ts",
"import": "./dist/test.js"
}
},
"files": [
Expand All @@ -41,6 +45,14 @@
"remend": "^1.2.1",
"unified": "^11.0.5"
},
"peerDependencies": {
"vitest": ">=2.0.0"
},
"peerDependenciesMeta": {
"vitest": {
"optional": true
}
},
"devDependencies": {
"@types/mdast": "^4.0.4",
"@types/node": "^25.3.2",
Expand Down
44 changes: 43 additions & 1 deletion packages/chat/src/mock-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { vi } from "vitest";
import { parseMarkdown } from "./markdown";
import { Message, type MessageData } from "./message";
import { ThreadImpl, type ThreadImplConfigWithAdapter } from "./thread";
import type {
Adapter,
FormattedContent,
Expand Down Expand Up @@ -220,7 +221,7 @@ export function createMockState(): MockStateAdapter {
}

/**
* Create a test message for testing.
* Create a test message.
* @param id - Message ID
* @param text - Message text content
* @param overrides - Optional overrides for message fields
Expand Down Expand Up @@ -252,3 +253,44 @@ export function createTestMessage(
...overrides,
});
}

let threadCounter = 0;

type TestThreadOpts = {
adapter: string | (Partial<Adapter> & { name: string });
} & Partial<Omit<ThreadImplConfigWithAdapter, "adapter" | "stateAdapter">>;

/**
* Create a test thread backed by a mock adapter with spyable methods.
* All adapter methods (post, postEphemeral, fetchMessages, etc.) are vi.fn() spies.
*/
export function createTestThread(
opts: TestThreadOpts
): ThreadImpl & { mockAdapter: Adapter; mockState: MockStateAdapter } {
const {
adapter: adapterOpt,
id: idOpt,
channelId: channelIdOpt,
...rest
} = opts;
const adapterName =
typeof adapterOpt === "string" ? adapterOpt : adapterOpt.name;
const adapterOverrides = typeof adapterOpt === "string" ? {} : adapterOpt;
const mockAdapter = {
...createMockAdapter(adapterName),
...adapterOverrides,
};
const mockState = createMockState();
const id = idOpt ?? `${adapterName}:C${++threadCounter}:thread`;
const channelId = channelIdOpt ?? `C${threadCounter}`;

const thread = new ThreadImpl({
id,
channelId,
adapter: mockAdapter,
stateAdapter: mockState,
...rest,
});

return Object.assign(thread, { mockAdapter, mockState });
}
43 changes: 43 additions & 0 deletions packages/chat/src/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { MessageData } from "./message";
import { createTestMessage as _createTestMessage } from "./mock-adapter";
import type { Author } from "./types";

export {
createMockAdapter,
createMockState,
createTestThread,
type MockStateAdapter,
mockLogger,
} from "./mock-adapter";

let messageCounter = 0;

/** Create a test message with sensible defaults. Only `text` is required. Extra properties are spread as MessageData overrides. */
export function createTestMessage(
opts: {
text: string;
id?: string;
author?: Partial<Author>;
raw?: unknown;
edited?: boolean;
} & Omit<
Partial<MessageData>,
"text" | "id" | "author" | "raw" | "formatted"
>
) {
const { text, id, author, raw, edited, ...rest } = opts;
return _createTestMessage(id ?? `msg-${++messageCounter}`, text, {
raw: raw ?? {},
metadata: edited != null ? { dateSent: new Date(), edited } : undefined,
author: author
? {
userId: author.userId ?? "U000",
userName: author.userName ?? "testuser",
fullName: author.fullName ?? "Test User",
isBot: author.isBot ?? false,
isMe: author.isMe ?? false,
}
: undefined,
Comment thread
vercel[bot] marked this conversation as resolved.
Outdated
...rest,
});
}
2 changes: 1 addition & 1 deletion packages/chat/src/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface SerializedThread {
/**
* Config for creating a ThreadImpl with explicit adapter/state instances.
*/
interface ThreadImplConfigWithAdapter {
export interface ThreadImplConfigWithAdapter {
adapter: Adapter;
channelId: string;
channelVisibility?: ChannelVisibility;
Expand Down
3 changes: 2 additions & 1 deletion packages/chat/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { defineConfig } from "tsup";

export default defineConfig({
entry: ["src/index.ts", "src/jsx-runtime.ts"],
entry: ["src/index.ts", "src/jsx-runtime.ts", "src/test.ts"],
format: ["esm"],
dts: true,
clean: true,
sourcemap: false,
external: ["vitest"],
});
Loading