一键导入
plugin-test
为 Zhin.js 插件编写和运行测试。Use when asked to write tests, add test coverage, generate test cases, or verify plugin behavior. 引导编写符合 Zhin 插件测试规范的 Vitest 测试用例。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
为 Zhin.js 插件编写和运行测试。Use when asked to write tests, add test coverage, generate test cases, or verify plugin behavior. 引导编写符合 Zhin 插件测试规范的 Vitest 测试用例。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
| name | plugin-test |
| description | 为 Zhin.js 插件编写和运行测试。Use when asked to write tests, add test coverage, generate test cases, or verify plugin behavior. 引导编写符合 Zhin 插件测试规范的 Vitest 测试用例。 |
| keywords | ["测试","test","vitest","测试用例","test case","覆盖率","coverage","单元测试","unit test"] |
| tags | ["development","testing","quality"] |
引导为 Zhin.js 插件编写高质量测试用例,确保功能正确、回归安全,并达到发布所需的覆盖率。
Zhin 项目使用 Vitest,全局 API 已注入(describe、it、expect、vi 等)。
# 运行测试
pnpm test # 单次运行
pnpm test:watch # 监听模式
pnpm test:coverage # 覆盖率报告
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Plugin } from '@zhin.js/core'
describe('MyPlugin Lifecycle', () => {
let plugin: Plugin
let root: Plugin
beforeEach(() => {
root = new Plugin('/test/root.ts')
plugin = new Plugin('/plugins/my-plugin/src/index.ts', root)
})
afterEach(async () => {
if (plugin?.started) await plugin.stop()
})
it('should create instance', () => {
expect(plugin).toBeInstanceOf(Plugin)
expect(plugin.name).toBe('my-plugin')
})
it('should start and stop', async () => {
await plugin.start()
expect(plugin.started).toBe(true)
await plugin.stop()
expect(plugin.started).toBe(false)
})
it('should emit lifecycle events', async () => {
const mounted = vi.fn()
const disposed = vi.fn()
plugin.on('mounted', mounted)
plugin.on('dispose', disposed)
await plugin.start()
expect(mounted).toHaveBeenCalled()
await plugin.stop()
expect(disposed).toHaveBeenCalled()
})
})
优先用 command.handle() 走完整匹配链(与仓库 teach-command.test.ts 一致),不要访问私有 _action:
import { describe, it, expect, vi } from 'vitest'
import { MessageCommand, type Message } from 'zhin.js'
function msg(text: string): Message<any> {
return {
$content: [{ type: 'text', data: { text } }],
$raw: text,
$sender: { id: 'u1', name: 'User' },
$reply: vi.fn(),
$channel: { id: 'g1', type: 'group' },
$adapter: 'sandbox',
$endpoint: 'sandbox-bot',
} as Message<any>
}
const mockPlugin = { contextIsReady: () => false, inject: () => null } as any
describe('greet command', () => {
const command = new MessageCommand('greet <name:text>').action(
async (_message, result) => `Hello, ${result.params.name}!`,
)
it('executes greet with parsed name', async () => {
const output = await command.handle(msg('greet World'), mockPlugin)
expect(output).toBe('Hello, World!')
})
})
import { describe, it, expect, vi } from 'vitest'
describe('Middleware', () => {
it('should call next in order', async () => {
const order: number[] = []
const middleware1 = async (msg: any, next: () => Promise<void>) => {
order.push(1)
await next()
order.push(4)
}
const middleware2 = async (msg: any, next: () => Promise<void>) => {
order.push(2)
await next()
order.push(3)
}
// 模拟中间件链
const mockMsg = { $raw: 'test' } as any
await middleware1(mockMsg, () => middleware2(mockMsg, async () => {}))
expect(order).toEqual([1, 2, 3, 4])
})
it('should intercept message when not calling next', async () => {
const afterSpy = vi.fn()
const filter = async (msg: any, next: () => Promise<void>) => {
if (msg.$raw.includes('spam')) return // 拦截
await next()
}
const mockMsg = { $raw: 'this is spam' } as any
await filter(mockMsg, afterSpy)
expect(afterSpy).not.toHaveBeenCalled()
})
})
import { describe, it, expect, vi } from 'vitest'
describe('MyService', () => {
it('should initialize correctly', async () => {
// 测试服务创建逻辑(不依赖框架注入)
const service = new MyService({ port: 3000 })
expect(service.port).toBe(3000)
})
it('should cleanup on dispose', async () => {
const service = new MyService({})
const cleanupSpy = vi.spyOn(service, 'cleanup')
await service.cleanup()
expect(cleanupSpy).toHaveBeenCalled()
})
})
import { describe, it, expect } from 'vitest'
import { ZhinTool } from '@zhin.js/core'
describe('AI Tools', () => {
it('should build tool correctly', () => {
const tool = new ZhinTool('calculator')
.desc('计算表达式')
.param('expression', { type: 'string', description: '表达式' }, true)
.execute(async (args) => String(eval(args.expression)))
const built = tool.toTool()
expect(built.name).toBe('calculator')
expect(built.description).toBe('计算表达式')
expect(built.parameters.required).toContain('expression')
})
it('should execute tool', async () => {
const tool = new ZhinTool('add')
.desc('加法')
.param('a', { type: 'number', description: '数字a' }, true)
.param('b', { type: 'number', description: '数字b' }, true)
.execute(async ({ a, b }) => a + b)
const built = tool.toTool()
const result = await built.execute({ a: 1, b: 2 })
expect(result).toBe(3)
})
})
import { describe, it, expect, vi } from 'vitest'
describe('Cron Jobs', () => {
it('should define valid cron expression', () => {
// 验证 cron 表达式格式
const expression = '0 9 * * 1-5'
const parts = expression.split(' ')
expect(parts).toHaveLength(5)
})
it('should execute cron callback', async () => {
const callback = vi.fn()
// 直接测试回调逻辑
await callback()
expect(callback).toHaveBeenCalled()
})
})
发布到插件市场前建议的最低覆盖率:
| 指标 | 最低 | 推荐 |
|---|---|---|
| 语句覆盖 (Statements) | 60% | 80%+ |
| 分支覆盖 (Branches) | 50% | 70%+ |
| 函数覆盖 (Functions) | 70% | 90%+ |
| 行覆盖 (Lines) | 60% | 80%+ |
tests/
├── index.test.ts # 插件生命周期
├── commands.test.ts # 命令逻辑
├── middlewares.test.ts # 中间件逻辑
├── services.test.ts # 服务/Context 逻辑
├── tools.test.ts # AI 工具逻辑
└── integration.test.ts # 集成测试(可选)
| 触发条件 | 一线处理 | 仍失败 |
|---|---|---|
Plugin 启动失败 | 检查路径、peerDependencies、mock 的 root 插件 | 先跑 plugin-init 骨架是否可 start/stop |
| 命令 action 难测 | 直接调 action 回调或抽纯函数测逻辑 | 避免强依赖完整 IM 消息链 |
| 覆盖率不达标 | 补命令/中间件/工具单测,不堆无意义断言 | 用 plugin-quality 审查缺口 |
| CI 与本地结果不一致 | 对齐 Node 版本;本地 pnpm test 复现 | 查 vitest 配置 isolate |
先列出待测能力(命令/中间件/工具/服务),再写测试文件,避免只测 start/stop 空壳。
.skip / .only 进 PReval 测 AI 工具(用固定输入测 execute)expect(true).toBe(true)pnpm test.skip)或仅运行(.only)的测试Implement Zhin.js plugins with the standard development workflow. Use when asked to create a plugin, add commands, middleware, event hooks, components, cron jobs, Contexts, AI tools, AI skills, AI agent presets, config schema, database, router, or web console integration, or follow the recommended Zhin plugin development pattern. 适用于 Zhin 插件标准开发姿势实现、插件功能落地、命令与 Context 接入。
初始化 Zhin.js 插件项目。Use when asked to create a new plugin, scaffold plugin structure, or set up a plugin project. 引导生成符合 Zhin 规范的目录结构、package.json、tsconfig、入口文件和 README。
Audit Zhin.js monorepo for security vulnerabilities, performance bottlenecks, and architecture issues. Use when asked to "audit code", "check security", "find performance issues", "review architecture", "scan for vulnerabilities", "check memory leaks", or "review code quality".
Implement Zhin.js plugin features: commands, AI tools, cron jobs, middleware, and console pages. Use when asked to add a command, register a tool, wire cron, extend middleware, or build a console page entry. Triggers: 插件开发, add command, addTool, 加命令, 写插件功能.
Refactor existing Zhin.js plugins into a cleaner standard structure. Use when asked to reorganize plugin files, split commands or services, migrate a messy plugin to standard layout, reduce coupling, clean lifecycle logic, or standardize plugin structure without changing behavior. 适用于已有 Zhin 插件重构、结构整理、职责拆分与标准化迁移。
Prepare GitHub issues, pull requests, reviews, and release notes for Zhin projects. Use when asked to write an issue, PR description, changelog entry, or review comment. Triggers: 写 issue, PR 描述, 提 PR, release notes, gh pr.