Skip to content
Draft
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
64 changes: 63 additions & 1 deletion packages/chat/src/mock-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import { vi } from "vitest";
import { parseMarkdown } from "./markdown";
import { Message, type MessageData } from "./message";
import { ThreadImpl } from "./thread";
import type {
Adapter,
ChannelVisibility,
FormattedContent,
Lock,
Logger,
QueueEntry,
StateAdapter,
Thread,
} from "./types";

/**
Expand Down Expand Up @@ -220,7 +223,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 +255,62 @@ export function createTestMessage(
...overrides,
});
}

let threadCounter = 0;

/** Options for creating a test thread. All fields optional — sensible defaults provided. */
export interface CreateTestThreadOptions {
/** Adapter name (default: "slack") */
adapter?: string;
/** Partial overrides merged onto the mock adapter's spy methods */
adapterOverrides?: Partial<Adapter>;
id?: string;
channelId?: string;
channelVisibility?: ChannelVisibility;
currentMessage?: Message;
fallbackStreamingPlaceholderText?: string | null;
initialMessage?: Message;
isDM?: boolean;
isSubscribedContext?: boolean;
logger?: Logger;
streamingUpdateIntervalMs?: number;
}

/** Return value of createTestThread. */
export interface TestThread {
thread: Thread;
mockAdapter: Adapter;
mockState: MockStateAdapter;
}

/**
* Create a test thread backed by a mock adapter with spyable methods.
* All adapter methods (post, edit, fetchMessages, etc.) are vi.fn() spies.
*/
export function createTestThread(opts?: CreateTestThreadOptions): TestThread {
const {
adapter: adapterName = "slack",
adapterOverrides,
id: idOpt,
channelId: channelIdOpt,
...rest
} = opts ?? {};
const mockAdapter = {
...createMockAdapter(adapterName),
...adapterOverrides,
};
const mockState = createMockState();
const counter = ++threadCounter;
const id = idOpt ?? `${adapterName}:C${counter}:thread`;
const channelId = channelIdOpt ?? `C${counter}`;

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

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

export {
createTestThread,
type CreateTestThreadOptions,
type MockStateAdapter,
type TestThread,
} 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;
const overrides: Partial<MessageData> = { raw: raw ?? {}, ...rest };
if (edited != null) {
overrides.metadata = { dateSent: new Date(), edited };
}
if (author) {
overrides.author = {
userId: author.userId ?? "U000",
userName: author.userName ?? "testuser",
fullName: author.fullName ?? "Test User",
isBot: author.isBot ?? false,
isMe: author.isMe ?? false,
};
}
return _createTestMessage(id ?? `msg-${++messageCounter}`, text, overrides);
}
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