@yaebal/test 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +334 -160
  2. package/lib/api.d.ts +82 -0
  3. package/lib/api.d.ts.map +1 -0
  4. package/lib/api.js +183 -0
  5. package/lib/api.js.map +1 -0
  6. package/lib/api.test.d.ts +2 -0
  7. package/lib/api.test.d.ts.map +1 -0
  8. package/lib/api.test.js +131 -0
  9. package/lib/api.test.js.map +1 -0
  10. package/lib/bot-messages.d.ts +42 -0
  11. package/lib/bot-messages.d.ts.map +1 -0
  12. package/lib/bot-messages.js +72 -0
  13. package/lib/bot-messages.js.map +1 -0
  14. package/lib/chat-actor.d.ts +45 -0
  15. package/lib/chat-actor.d.ts.map +1 -0
  16. package/lib/chat-actor.js +72 -0
  17. package/lib/chat-actor.js.map +1 -0
  18. package/lib/clock.d.ts +22 -0
  19. package/lib/clock.d.ts.map +1 -0
  20. package/lib/clock.js +72 -0
  21. package/lib/clock.js.map +1 -0
  22. package/lib/clock.test.d.ts +2 -0
  23. package/lib/clock.test.d.ts.map +1 -0
  24. package/lib/clock.test.js +69 -0
  25. package/lib/clock.test.js.map +1 -0
  26. package/lib/env.d.ts +100 -0
  27. package/lib/env.d.ts.map +1 -0
  28. package/lib/env.js +164 -0
  29. package/lib/env.js.map +1 -0
  30. package/lib/env.test.d.ts +2 -0
  31. package/lib/env.test.d.ts.map +1 -0
  32. package/lib/env.test.js +302 -0
  33. package/lib/env.test.js.map +1 -0
  34. package/lib/fetch.d.ts +3 -0
  35. package/lib/fetch.d.ts.map +1 -0
  36. package/lib/fetch.js +12 -0
  37. package/lib/fetch.js.map +1 -0
  38. package/lib/index.d.ts +19 -205
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +19 -391
  41. package/lib/index.js.map +1 -1
  42. package/lib/internal.d.ts +25 -0
  43. package/lib/internal.d.ts.map +1 -0
  44. package/lib/internal.js +19 -0
  45. package/lib/internal.js.map +1 -0
  46. package/lib/keyboard.d.ts +15 -0
  47. package/lib/keyboard.d.ts.map +1 -0
  48. package/lib/keyboard.js +25 -0
  49. package/lib/keyboard.js.map +1 -0
  50. package/lib/keyboard.test.d.ts +2 -0
  51. package/lib/keyboard.test.d.ts.map +1 -0
  52. package/lib/keyboard.test.js +31 -0
  53. package/lib/keyboard.test.js.map +1 -0
  54. package/lib/normalize.d.ts +10 -0
  55. package/lib/normalize.d.ts.map +1 -0
  56. package/lib/normalize.js +27 -0
  57. package/lib/normalize.js.map +1 -0
  58. package/lib/reactions.d.ts +3 -0
  59. package/lib/reactions.d.ts.map +1 -0
  60. package/lib/reactions.js +17 -0
  61. package/lib/reactions.js.map +1 -0
  62. package/lib/updates.d.ts +126 -0
  63. package/lib/updates.d.ts.map +1 -0
  64. package/lib/updates.js +200 -0
  65. package/lib/updates.js.map +1 -0
  66. package/lib/updates.test.d.ts +2 -0
  67. package/lib/updates.test.d.ts.map +1 -0
  68. package/lib/updates.test.js +72 -0
  69. package/lib/updates.test.js.map +1 -0
  70. package/lib/user-actor.d.ts +188 -0
  71. package/lib/user-actor.d.ts.map +1 -0
  72. package/lib/user-actor.js +465 -0
  73. package/lib/user-actor.js.map +1 -0
  74. package/lib/webhook.d.ts +18 -0
  75. package/lib/webhook.d.ts.map +1 -0
  76. package/lib/webhook.js +25 -0
  77. package/lib/webhook.js.map +1 -0
  78. package/lib/webhook.test.d.ts +2 -0
  79. package/lib/webhook.test.d.ts.map +1 -0
  80. package/lib/webhook.test.js +36 -0
  81. package/lib/webhook.test.js.map +1 -0
  82. package/package.json +7 -5
  83. package/src/api.test.ts +180 -0
  84. package/src/api.ts +281 -0
  85. package/src/bot-messages.ts +117 -0
  86. package/src/chat-actor.ts +101 -0
  87. package/src/clock.test.ts +80 -0
  88. package/src/clock.ts +118 -0
  89. package/src/env.test.ts +370 -0
  90. package/src/env.ts +235 -0
  91. package/src/fetch.ts +11 -0
  92. package/src/index.ts +79 -630
  93. package/src/internal.ts +43 -0
  94. package/src/keyboard.test.ts +35 -0
  95. package/src/keyboard.ts +38 -0
  96. package/src/normalize.ts +34 -0
  97. package/src/reactions.ts +18 -0
  98. package/src/updates.test.ts +107 -0
  99. package/src/updates.ts +354 -0
  100. package/src/user-actor.ts +702 -0
  101. package/src/webhook.test.ts +54 -0
  102. package/src/webhook.ts +48 -0
  103. package/lib/index.test.d.ts +0 -2
  104. package/lib/index.test.d.ts.map +0 -1
  105. package/lib/index.test.js +0 -213
  106. package/lib/index.test.js.map +0 -1
  107. package/src/index.test.ts +0 -320
@@ -0,0 +1,72 @@
1
+ import { resolveSendText } from "./internal.js";
2
+ import { createUpdate } from "./updates.js";
3
+ let chatIdCounter = -1000;
4
+ /**
5
+ * a passive container actors send into — a group/supergroup/channel/private chat. tracks who's
6
+ * currently a member (best-effort — nothing enforces it unless you opt into `strictMembership`
7
+ * via {@link createTestEnv}) and every message dispatched through it.
8
+ */
9
+ export class ChatActor {
10
+ id;
11
+ type;
12
+ title;
13
+ username;
14
+ members = new Set();
15
+ messages = [];
16
+ host;
17
+ membership = new Map();
18
+ constructor(host, options) {
19
+ this.host = host;
20
+ this.id = options.id ?? chatIdCounter--;
21
+ this.type = options.type;
22
+ this.title = options.title;
23
+ this.username = options.username;
24
+ }
25
+ /** reconstruct a `ChatActor` from a raw `Chat` payload seen on an update (e.g. to find the chat a message you didn't create arrived in). not registered with any `TestEnv`. */
26
+ static fromChat(host, chat) {
27
+ return new ChatActor(host, {
28
+ type: chat.type,
29
+ id: chat.id,
30
+ title: chat.title,
31
+ username: chat.username,
32
+ });
33
+ }
34
+ /** this chat as a plain {@link Chat} payload, ready to embed in an update. */
35
+ toChat() {
36
+ return {
37
+ id: this.id,
38
+ type: this.type,
39
+ ...(this.title !== undefined ? { title: this.title } : {}),
40
+ ...(this.username !== undefined ? { username: this.username } : {}),
41
+ };
42
+ }
43
+ /** record (or update) a member's status — used by `strictMembership` and `getChatMember`-style assertions. */
44
+ setMembership(userId, membership) {
45
+ this.membership.set(userId, membership);
46
+ }
47
+ /** the tracked membership for a user, or `undefined` if never set. */
48
+ membershipOf(user) {
49
+ return this.membership.get(user.id);
50
+ }
51
+ /**
52
+ * an anonymous channel post — `update.channel_post`, with no `from` (matching real Telegram:
53
+ * channel posts aren't attributed to a user). throws on non-channel chats.
54
+ */
55
+ async post(text) {
56
+ if (this.type !== "channel") {
57
+ throw new Error("ChatActor.post(): only channel chats can post — did you mean a user actor?");
58
+ }
59
+ const { text: resolvedText, entities } = resolveSendText(text);
60
+ const message = {
61
+ message_id: this.host.nextMessageId(),
62
+ date: this.host.now(),
63
+ chat: this.toChat(),
64
+ text: resolvedText,
65
+ ...(entities.length ? { entities } : {}),
66
+ };
67
+ this.messages.push(message);
68
+ await this.host.dispatch(createUpdate({ channel_post: message }));
69
+ return message;
70
+ }
71
+ }
72
+ //# sourceMappingURL=chat-actor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-actor.js","sourceRoot":"","sources":["../src/chat-actor.ts"],"names":[],"mappings":"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"}
package/lib/clock.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * a virtual clock: overrides `Date.now`, `setTimeout`/`clearTimeout`, and
3
+ * `setInterval`/`clearInterval` so TTL / retry-delay / debounce code under test
4
+ * can be advanced instantly instead of waited on for real. installs globally
5
+ * for the lifetime of the clock; always `.restore()` when done (or let
6
+ * {@link TestEnv.shutdown} do it for you).
7
+ */
8
+ export interface TestClock {
9
+ /** the clock's current virtual time, in ms since epoch. */
10
+ now(): number;
11
+ /** advance the clock by `ms`, firing every timer whose deadline falls inside the window (in deadline order). intervals re-arm and may fire multiple times. */
12
+ advance(ms: number): Promise<void>;
13
+ /** uninstall — restores the real `Date.now`/`setTimeout`/`setInterval`. */
14
+ restore(): void;
15
+ }
16
+ /**
17
+ * install a virtual clock, overriding the global timer functions. `startAt`
18
+ * defaults to the real current time so unrelated `Date.now()` reads (e.g. log
19
+ * timestamps) stay plausible.
20
+ */
21
+ export declare function installTestClock(startAt?: number): TestClock;
22
+ //# sourceMappingURL=clock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACzB,2DAA2D;IAC3D,GAAG,IAAI,MAAM,CAAC;IACd,8JAA8J;IAC9J,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,2EAA2E;IAC3E,OAAO,IAAI,IAAI,CAAC;CAChB;AAWD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,MAAmB,GAAG,SAAS,CAuFxE"}
package/lib/clock.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * install a virtual clock, overriding the global timer functions. `startAt`
3
+ * defaults to the real current time so unrelated `Date.now()` reads (e.g. log
4
+ * timestamps) stay plausible.
5
+ */
6
+ export function installTestClock(startAt = Date.now()) {
7
+ const realDateNow = Date.now;
8
+ const realSetTimeout = globalThis.setTimeout;
9
+ const realClearTimeout = globalThis.clearTimeout;
10
+ const realSetInterval = globalThis.setInterval;
11
+ const realClearInterval = globalThis.clearInterval;
12
+ let now = startAt;
13
+ let nextId = 1;
14
+ const timers = new Map();
15
+ const schedule = (fn, delay, interval, args) => {
16
+ const id = nextId++;
17
+ timers.set(id, {
18
+ id,
19
+ due: now + Math.max(0, delay || 0),
20
+ interval,
21
+ fn,
22
+ args,
23
+ cancelled: false,
24
+ });
25
+ return id;
26
+ };
27
+ Date.now = () => now;
28
+ globalThis.setTimeout = ((fn, delay, ...args) => schedule(fn, delay ?? 0, undefined, args));
29
+ globalThis.clearTimeout = ((id) => {
30
+ if (typeof id === "number")
31
+ timers.delete(id);
32
+ });
33
+ globalThis.setInterval = ((fn, delay, ...args) => schedule(fn, delay ?? 0, delay ?? 0, args));
34
+ globalThis.clearInterval = ((id) => {
35
+ if (typeof id === "number")
36
+ timers.delete(id);
37
+ });
38
+ async function advance(ms) {
39
+ const target = now + ms;
40
+ for (;;) {
41
+ let next;
42
+ for (const timer of timers.values()) {
43
+ if (timer.due > target)
44
+ continue;
45
+ if (!next || timer.due < next.due || (timer.due === next.due && timer.id < next.id)) {
46
+ next = timer;
47
+ }
48
+ }
49
+ if (!next)
50
+ break;
51
+ now = next.due;
52
+ if (next.interval === undefined) {
53
+ timers.delete(next.id);
54
+ }
55
+ else {
56
+ next.due = now + Math.max(1, next.interval);
57
+ }
58
+ await next.fn(...next.args);
59
+ }
60
+ now = target;
61
+ }
62
+ function restore() {
63
+ Date.now = realDateNow;
64
+ globalThis.setTimeout = realSetTimeout;
65
+ globalThis.clearTimeout = realClearTimeout;
66
+ globalThis.setInterval = realSetInterval;
67
+ globalThis.clearInterval = realClearInterval;
68
+ timers.clear();
69
+ }
70
+ return { now: () => now, advance, restore };
71
+ }
72
+ //# sourceMappingURL=clock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.js","sourceRoot":"","sources":["../src/clock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=clock.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.test.d.ts","sourceRoot":"","sources":["../src/clock.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { installTestClock } from "./clock.js";
4
+ test("installTestClock overrides Date.now and advances on demand", async () => {
5
+ const clock = installTestClock(1_700_000_000_000);
6
+ try {
7
+ assert.equal(Date.now(), 1_700_000_000_000);
8
+ await clock.advance(5000);
9
+ assert.equal(Date.now(), 1_700_000_005_000);
10
+ }
11
+ finally {
12
+ clock.restore();
13
+ }
14
+ });
15
+ test("advance() fires a setTimeout whose deadline falls inside the window", async () => {
16
+ const clock = installTestClock();
17
+ try {
18
+ let fired = false;
19
+ setTimeout(() => {
20
+ fired = true;
21
+ }, 60 * 60 * 1000);
22
+ await clock.advance(30 * 60 * 1000);
23
+ assert.equal(fired, false);
24
+ await clock.advance(30 * 60 * 1000);
25
+ assert.equal(fired, true);
26
+ }
27
+ finally {
28
+ clock.restore();
29
+ }
30
+ });
31
+ test("advance() re-arms setInterval and may fire it multiple times", async () => {
32
+ const clock = installTestClock();
33
+ try {
34
+ let count = 0;
35
+ const id = setInterval(() => count++, 1000);
36
+ await clock.advance(3500);
37
+ assert.equal(count, 3);
38
+ clearInterval(id);
39
+ await clock.advance(10_000);
40
+ assert.equal(count, 3);
41
+ }
42
+ finally {
43
+ clock.restore();
44
+ }
45
+ });
46
+ test("timers scheduled inside a firing callback are picked up by the same advance() call", async () => {
47
+ const clock = installTestClock();
48
+ try {
49
+ const seen = [];
50
+ setTimeout(() => {
51
+ seen.push(1);
52
+ setTimeout(() => seen.push(2), 100);
53
+ }, 100);
54
+ await clock.advance(500);
55
+ assert.deepEqual(seen, [1, 2]);
56
+ }
57
+ finally {
58
+ clock.restore();
59
+ }
60
+ });
61
+ test("restore() puts back the real Date.now/setTimeout", () => {
62
+ const realNow = Date.now;
63
+ const realSetTimeout = setTimeout;
64
+ const clock = installTestClock();
65
+ clock.restore();
66
+ assert.equal(Date.now, realNow);
67
+ assert.equal(setTimeout, realSetTimeout);
68
+ });
69
+ //# sourceMappingURL=clock.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clock.test.js","sourceRoot":"","sources":["../src/clock.test.ts"],"names":[],"mappings":"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"}
package/lib/env.d.ts ADDED
@@ -0,0 +1,100 @@
1
+ import type { Update } from "@yaebal/core";
2
+ import { type Api, type Composer, Context } from "@yaebal/core";
3
+ import type { RecordedCall } from "./api.js";
4
+ import { type MockApi, type MockResult } from "./api.js";
5
+ import { type BotMessage, type LastBotMessageQuery } from "./bot-messages.js";
6
+ import { ChatActor, type CreateChatOptions } from "./chat-actor.js";
7
+ import type { ActorHost } from "./internal.js";
8
+ import { type BuildUserOptions, createUpdate } from "./updates.js";
9
+ import { UserActor } from "./user-actor.js";
10
+ export type { ApiErrorSentinel, MockResult, OnApiOptions, RecordedCall } from "./api.js";
11
+ export { apiError, isApiErrorSentinel, TestApiError } from "./api.js";
12
+ export type { BotMessage, LastBotMessageQuery } from "./bot-messages.js";
13
+ export { ChatActor, type ChatMembership, type ChatType, type CreateChatOptions, } from "./chat-actor.js";
14
+ export { installTestClock, type TestClock } from "./clock.js";
15
+ export { type MediaOptions, type MessageOptions, UserActor, UserInChatScope, UserOnMessageScope, } from "./user-actor.js";
16
+ /** a satellite plugin's canned test fixtures — pass to {@link createTestEnv} via `options.packs`. explicit (you always list which packs apply), matching yaebal's "no implicit plugin wiring" rule. */
17
+ export interface TestPack<C extends Context = Context> {
18
+ readonly name: string;
19
+ setup(env: TestEnv<C>): void;
20
+ }
21
+ export interface TestEnvOptions<C extends Context = Context> {
22
+ /** seed permanent per-method replies — same as calling `env.onApi(method, result)` for each entry. */
23
+ results?: Record<string, MockResult>;
24
+ /** throw instead of returning `{}` when a method has no builtin default and no override. default `false`. */
25
+ strictApi?: boolean;
26
+ /** throw if an actor-driven update falls through the whole bot with no handler consuming it. default `false`. */
27
+ strictDispatch?: boolean;
28
+ /** satellite-plugin test packs to apply (see {@link TestPack}). */
29
+ packs?: TestPack<C>[];
30
+ }
31
+ /**
32
+ * the actor-driven test environment: wraps a `Composer`/`Bot`, intercepts every outgoing api
33
+ * call (no real HTTP), and hands out {@link UserActor}/{@link ChatActor} actors that dispatch
34
+ * real updates through it — the way real Telegram users would.
35
+ */
36
+ export declare class TestEnv<C extends Context = Context> implements ActorHost {
37
+ readonly api: Api;
38
+ readonly apiCalls: RecordedCall[];
39
+ readonly hooks: MockApi["hooks"];
40
+ readonly users: UserActor[];
41
+ readonly chats: ChatActor[];
42
+ private readonly bot;
43
+ private readonly mock;
44
+ private readonly botMessages;
45
+ private readonly strictDispatch;
46
+ private readonly postDispatchHooks;
47
+ private clock;
48
+ private messageIdCounter;
49
+ constructor(bot: Composer<C>, options?: TestEnvOptions<C>);
50
+ /** the clock's current time if {@link advanceTime} has installed one, else the real `Date.now()`. */
51
+ now(): number;
52
+ nextMessageId(): number;
53
+ private resolveChatType;
54
+ /** create a user actor. auto-allocates an id unless one is given. */
55
+ createUser(options?: BuildUserOptions): UserActor;
56
+ /** create a chat actor (group/supergroup/channel/private). */
57
+ createChat(options: CreateChatOptions): ChatActor;
58
+ /**
59
+ * dispatch a raw {@link Update} through the bot — the escape hatch beneath every actor
60
+ * method, for update shapes the actors don't cover (business connections, exotic service
61
+ * messages, whatever ships in the next Bot API release before the actors catch up).
62
+ */
63
+ dispatch(update: Update): Promise<void>;
64
+ /** alias for {@link dispatch} — ships an arbitrary update payload, puregram-style naming. */
65
+ inject(update: Update): Promise<void>;
66
+ /** run `fn` after every actor-driven (or `dispatch`ed) update finishes. hooks compose in registration order. */
67
+ onPostDispatch(fn: (update: Update) => void | Promise<void>): void;
68
+ /** override a method's reply. permanent unless `opts.times` is given. */
69
+ onApi(...args: Parameters<MockApi["onApi"]>): void;
70
+ /** drop a method's overrides (or every method's, if none given). */
71
+ offApi(method?: string): void;
72
+ /** the most recent recorded call, optionally filtered to a method. */
73
+ lastApiCall(method?: string): RecordedCall | undefined;
74
+ /** every recorded call to a given method, in call order. */
75
+ callsTo(method: string): RecordedCall[];
76
+ /** empty `apiCalls` and drop tracked bot messages — useful between logical phases of a test. */
77
+ clearApiCalls(): void;
78
+ /** the bot's most recent `send*`/`forwardMessage`/`copyMessage`, optionally filtered; kept in sync with later edits. */
79
+ lastBotMessage(query?: LastBotMessageQuery): BotMessage | undefined;
80
+ /** look up a specific bot message by `(chat_id, message_id)`. */
81
+ botMessage(chatId: number, messageId: number): BotMessage | undefined;
82
+ /**
83
+ * arm the virtual clock (a no-op if one is already installed) — call this *before* triggering
84
+ * the code that schedules the timer you plan to fast-forward with {@link advanceTime}, the
85
+ * same way you'd call `vi.useFakeTimers()` before the code under test runs. a timer scheduled
86
+ * against the real clock before this call is invisible to `advanceTime` — it was never handed
87
+ * to the virtual scheduler.
88
+ */
89
+ useFakeTimers(): void;
90
+ /** advance the (auto-armed) virtual clock by `ms`, firing due timers. see {@link installTestClock}. */
91
+ advanceTime(ms: number): Promise<void>;
92
+ /** did the bot answer `answerPreCheckoutQuery` for `preCheckoutQueryId` with `ok: true`? used by `sendSuccessfulPayment`. */
93
+ answeredPreCheckoutQuery(preCheckoutQueryId: string): boolean;
94
+ /** restore the virtual clock (if one was installed) to real timers. call in `afterEach`/teardown. */
95
+ shutdown(): void;
96
+ }
97
+ /** create a {@link TestEnv} wrapping `bot` — the main entry point of `@yaebal/test`. */
98
+ export declare function createTestEnv<C extends Context = Context>(bot: Composer<C>, options?: TestEnvOptions<C>): TestEnv<C>;
99
+ export { createUpdate };
100
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
package/lib/env.js ADDED
@@ -0,0 +1,164 @@
1
+ import { Context } from "@yaebal/core";
2
+ import { mockApi } from "./api.js";
3
+ import { attachBotMessageTracking, } from "./bot-messages.js";
4
+ import { ChatActor } from "./chat-actor.js";
5
+ import { installTestClock } from "./clock.js";
6
+ import { buildUser, createUpdate, detectUpdateType } from "./updates.js";
7
+ import { UserActor } from "./user-actor.js";
8
+ export { apiError, isApiErrorSentinel, TestApiError } from "./api.js";
9
+ export { ChatActor, } from "./chat-actor.js";
10
+ export { installTestClock } from "./clock.js";
11
+ export { UserActor, UserInChatScope, UserOnMessageScope, } from "./user-actor.js";
12
+ /**
13
+ * the actor-driven test environment: wraps a `Composer`/`Bot`, intercepts every outgoing api
14
+ * call (no real HTTP), and hands out {@link UserActor}/{@link ChatActor} actors that dispatch
15
+ * real updates through it — the way real Telegram users would.
16
+ */
17
+ export class TestEnv {
18
+ api;
19
+ apiCalls;
20
+ hooks;
21
+ users = [];
22
+ chats = [];
23
+ bot;
24
+ mock;
25
+ botMessages;
26
+ strictDispatch;
27
+ postDispatchHooks = [];
28
+ clock;
29
+ messageIdCounter = 0;
30
+ constructor(bot, options = {}) {
31
+ this.bot = bot;
32
+ this.strictDispatch = options.strictDispatch ?? false;
33
+ this.mock = mockApi({
34
+ results: options.results,
35
+ strictApi: options.strictApi,
36
+ now: () => this.now(),
37
+ });
38
+ this.api = this.mock.api;
39
+ this.apiCalls = this.mock.calls;
40
+ this.hooks = this.mock.hooks;
41
+ this.botMessages = attachBotMessageTracking(this.api, (chatId) => this.resolveChatType(chatId));
42
+ for (const pack of options.packs ?? [])
43
+ pack.setup(this);
44
+ }
45
+ /** the clock's current time if {@link advanceTime} has installed one, else the real `Date.now()`. */
46
+ now() {
47
+ return this.clock ? this.clock.now() : Date.now();
48
+ }
49
+ nextMessageId() {
50
+ return ++this.messageIdCounter;
51
+ }
52
+ resolveChatType(chatId) {
53
+ for (const chat of this.chats)
54
+ if (chat.id === chatId)
55
+ return chat.type;
56
+ for (const user of this.users)
57
+ if (user.pmChat.id === chatId)
58
+ return "private";
59
+ return "private";
60
+ }
61
+ /** create a user actor. auto-allocates an id unless one is given. */
62
+ createUser(options = {}) {
63
+ const user = new UserActor(this, buildUser(options));
64
+ this.users.push(user);
65
+ return user;
66
+ }
67
+ /** create a chat actor (group/supergroup/channel/private). */
68
+ createChat(options) {
69
+ const chat = new ChatActor(this, options);
70
+ this.chats.push(chat);
71
+ return chat;
72
+ }
73
+ /**
74
+ * dispatch a raw {@link Update} through the bot — the escape hatch beneath every actor
75
+ * method, for update shapes the actors don't cover (business connections, exotic service
76
+ * messages, whatever ships in the next Bot API release before the actors catch up).
77
+ */
78
+ async dispatch(update) {
79
+ const ctx = new Context({
80
+ api: this.api,
81
+ update,
82
+ updateType: detectUpdateType(update),
83
+ });
84
+ let reachedEnd = false;
85
+ const sentinel = async () => {
86
+ reachedEnd = true;
87
+ };
88
+ await this.bot.toMiddleware()(ctx, sentinel);
89
+ if (this.strictDispatch && reachedEnd) {
90
+ throw new Error(`TestEnv: no handler consumed a "${detectUpdateType(update)}" update (strictDispatch is on)`);
91
+ }
92
+ for (const hook of this.postDispatchHooks)
93
+ await hook(update);
94
+ }
95
+ /** alias for {@link dispatch} — ships an arbitrary update payload, puregram-style naming. */
96
+ inject(update) {
97
+ return this.dispatch(update);
98
+ }
99
+ /** run `fn` after every actor-driven (or `dispatch`ed) update finishes. hooks compose in registration order. */
100
+ onPostDispatch(fn) {
101
+ this.postDispatchHooks.push(fn);
102
+ }
103
+ /** override a method's reply. permanent unless `opts.times` is given. */
104
+ onApi(...args) {
105
+ this.mock.onApi(...args);
106
+ }
107
+ /** drop a method's overrides (or every method's, if none given). */
108
+ offApi(method) {
109
+ this.mock.offApi(method);
110
+ }
111
+ /** the most recent recorded call, optionally filtered to a method. */
112
+ lastApiCall(method) {
113
+ return this.mock.lastCall(method);
114
+ }
115
+ /** every recorded call to a given method, in call order. */
116
+ callsTo(method) {
117
+ return this.mock.callsTo(method);
118
+ }
119
+ /** empty `apiCalls` and drop tracked bot messages — useful between logical phases of a test. */
120
+ clearApiCalls() {
121
+ this.mock.reset();
122
+ this.botMessages.clear();
123
+ }
124
+ /** the bot's most recent `send*`/`forwardMessage`/`copyMessage`, optionally filtered; kept in sync with later edits. */
125
+ lastBotMessage(query) {
126
+ return this.botMessages.lastBotMessage(query);
127
+ }
128
+ /** look up a specific bot message by `(chat_id, message_id)`. */
129
+ botMessage(chatId, messageId) {
130
+ return this.botMessages.botMessage(chatId, messageId);
131
+ }
132
+ /**
133
+ * arm the virtual clock (a no-op if one is already installed) — call this *before* triggering
134
+ * the code that schedules the timer you plan to fast-forward with {@link advanceTime}, the
135
+ * same way you'd call `vi.useFakeTimers()` before the code under test runs. a timer scheduled
136
+ * against the real clock before this call is invisible to `advanceTime` — it was never handed
137
+ * to the virtual scheduler.
138
+ */
139
+ useFakeTimers() {
140
+ this.clock ??= installTestClock();
141
+ }
142
+ /** advance the (auto-armed) virtual clock by `ms`, firing due timers. see {@link installTestClock}. */
143
+ async advanceTime(ms) {
144
+ this.useFakeTimers();
145
+ await this.clock.advance(ms);
146
+ }
147
+ /** did the bot answer `answerPreCheckoutQuery` for `preCheckoutQueryId` with `ok: true`? used by `sendSuccessfulPayment`. */
148
+ answeredPreCheckoutQuery(preCheckoutQueryId) {
149
+ return this.callsTo("answerPreCheckoutQuery").some((call) => !call.error &&
150
+ call.params?.pre_checkout_query_id === preCheckoutQueryId &&
151
+ call.params?.ok === true);
152
+ }
153
+ /** restore the virtual clock (if one was installed) to real timers. call in `afterEach`/teardown. */
154
+ shutdown() {
155
+ this.clock?.restore();
156
+ this.clock = undefined;
157
+ }
158
+ }
159
+ /** create a {@link TestEnv} wrapping `bot` — the main entry point of `@yaebal/test`. */
160
+ export function createTestEnv(bot, options) {
161
+ return new TestEnv(bot, options);
162
+ }
163
+ export { createUpdate };
164
+ //# sourceMappingURL=env.js.map
package/lib/env.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=env.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.test.d.ts","sourceRoot":"","sources":["../src/env.test.ts"],"names":[],"mappings":""}