agentsclimarketplace

Test package

Skill neverlane/yaebal/.claude/skills/test-package

Use when writing or updating tests for @yaebal/* packages or bots — the actor-driven @yaebal/test 0.2 api (createTestEnv, UserActor, ChatActor, onApi, virtual clock), node:test conventions, and how tests run over compiled lib/.From its SKILL.md

Install
npx -y skills add neverlane/yaebal --skill test-package

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.4 KB, as published. Nobody here has run it

test a yaebal package or bot

use this skill when writing tests anywhere in the monorepo.

conventions

  • tests live next to source: packages/<name>/src/index.test.ts, importing the unit under test from ./index.js (source-relative, .js specifier).
  • runner is node:test + node:assert/strict. no vitest/jest in the monorepo.
  • tests compile with the package and run over lib/: pnpm build first, then node --test lib/*.test.js (unquoted glob — node --test lib silently runs zero tests on node 25, quoted ** breaks node 20). root pnpm test does build + all package tests.
  • to use @yaebal/test, add it to devDependencies as workspace:* and add { "path": "../test" } to the package's tsconfig references.

the actor api (0.2 — the only api)

the old flat api (createContext / runMiddleware / messageContext / setResult) is gone. full reference: apps/docs/src/routes/docs/plugins/test/+page.svelte.

import assert from "node:assert/strict";
import test from "node:test";
import { Composer, type Context } from "@yaebal/core";
import { createTestEnv } from "@yaebal/test";
import { myPlugin } from "./index.js";

test("replies to /start", async () => {
	const bot = new Composer<Context>()
		.install(myPlugin())
		.command("start", (ctx) => ctx.reply("welcome"));

	const env = createTestEnv(bot);
	const user = env.createUser({ firstName: "Linia" });

	await user.sendCommand("start");
	assert.equal(env.lastApiCall("sendMessage")?.params?.text, "welcome");
});

createTestEnv(bot) accepts any Composer or Bot, intercepts every api call (no http), and auto-stubs results (auto-incrementing message_id, stub getMe, true for answerCallbackQuery).

driving the bot

  • env.createUser(opts?) → UserActor: sendMessage, sendCommand("start", "payload"), sendReply, all media (sendPhoto/sendVideo/sendSticker/…, auto-generated file_ids), sendMediaGroup, react, join/leave, sendInlineQuery, chooseInlineResult, sendSuccessfulPayment (runs the real pre-checkout flow and throws if the bot didn't answer ok).
  • env.createChat({ type, title? }) → ChatActor; scopes: user.in(group).sendCommand("help"), user.on(msg).clickByText("Next »"), user.on(msg).click("raw:data").
  • escape hatch for shapes actors don't cover: fixture builders (messageUpdate, callbackUpdate, pollUpdate, …, createUpdate) + env.dispatch(update).

asserting

  • env.lastApiCall(method?), env.callsTo(method), env.apiCalls, env.clearApiCalls().
  • env.lastBotMessage({ withReplyMarkup?, chat?, where? }) — live mirror of the bot's last sent message, mutated in place by later editMessage* calls; feed it back to user.on(bubble).clickByText(...).
  • prefer asserting on observable behavior (api calls, storage contents) over plugin internals.

failures, strictness, time

  • env.onApi("sendMessage", apiError(429, "Too Many Requests", { retry_after: 1 }), { times: 1 }) — one-shot error then fall through; the bot sees a real TelegramError subclass.
  • createTestEnv(bot, { strictApi: true }) throws on unstubbed methods; { strictDispatch: true } throws when no handler consumed an update.
  • virtual clock for ttl/retry/debounce — never sleep real time: env.useFakeTimers() (arm before the timer is scheduled) → await env.advanceTime(ms) → env.shutdown() in teardown (restores real timers).
  • satellite plugins ship fixtures as a TestPack (createTestEnv(bot, { packs: [againTestPack()] })) — explicit, never a global registry.

webhooks / runners

webhookRequest(update, { secretToken }) builds the POST for webhookCallback(bot, ...); collectUpdates() gives a recording UpdateSink; withFetch(handler, fn) stubs global fetch.

when NOT to use @yaebal/test

@yaebal/core and @yaebal/session test with a hand-built Context + Composer.toMiddleware() (see packages/session/src/index.test.ts) because depending on @yaebal/test there would be circular. everything downstream of them should use actors.

verify

pnpm typecheck
pnpm build && cd packages/<name> && node --test lib/*.test.js

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.