Skip to content

Commit

Permalink
test: create type tests (#584)
Browse files Browse the repository at this point in the history
* Create type tests

* moar

* test: add test for remaining composer methods

* fix: fix bugs discovered by new tests

* test: fix incorrect usage of testing lib

* test: fix composer tests for inline and game queries

* test: fix incorrect usage of testing lib

---------

Co-authored-by: KnorpelSenf <[email protected]>
  • Loading branch information
KnightNiwrem and KnorpelSenf authored Jun 16, 2024
1 parent 334f617 commit 2ba57c1
Show file tree
Hide file tree
Showing 3 changed files with 326 additions and 5 deletions.
13 changes: 8 additions & 5 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2853,7 +2853,7 @@ type GameQueryContextCore = FilterCore<"callback_query:game_short_name">;
* in separate files and still have the correct types.
*/
export type GameQueryContext<C extends Context> = Filter<
C,
NarrowMatch<C, string | RegExpMatchArray>,
"callback_query:game_short_name"
>;

Expand All @@ -2868,7 +2868,10 @@ type InlineQueryContextCore = FilterCore<"inline_query">;
* inferring the correct type automatically. That way, handlers can be defined
* in separate files and still have the correct types.
*/
export type InlineQueryContext<C extends Context> = Filter<C, "inline_query">;
export type InlineQueryContext<C extends Context> = Filter<
NarrowMatch<C, string | RegExpMatchArray>,
"inline_query"
>;

type ReactionContextCore = FilterCore<"message_reaction">;
/**
Expand All @@ -2895,13 +2898,14 @@ type ChosenInlineResultContextCore = FilterCore<"chosen_inline_result">;
* in separate files and still have the correct types.
*/
export type ChosenInlineResultContext<C extends Context> = Filter<
C,
NarrowMatch<C, string | RegExpMatchArray>,
"chosen_inline_result"
>;

type ChatTypeContextCore<T extends Chat["type"]> =
& Record<"update", ChatTypeUpdate<T>> // ctx.update
& ChatType<T> // ctx.chat
& Record<"chatId", number> // ctx.chatId
& ChatFrom<T> // ctx.from
& ChatTypeRecord<"msg", T> // ctx.msg
& AliasProps<ChatTypeUpdate<T>>; // ctx.message etc
Expand All @@ -2916,8 +2920,7 @@ type ChatTypeContextCore<T extends Chat["type"]> =
* files and still have the correct types.
*/
export type ChatTypeContext<C extends Context, T extends Chat["type"]> =
& C
& ChatTypeContextCore<T>;
T extends unknown ? C & ChatTypeContextCore<T> : never;
type ChatTypeUpdate<T extends Chat["type"]> =
& ChatTypeRecord<
| "message"
Expand Down
314 changes: 314 additions & 0 deletions test/composer.type.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
import { Composer } from "../src/composer.ts";
import { Context } from "../src/mod.ts";
import type { Chat, MaybeInaccessibleMessage, User } from "../src/types.ts";
import {
assertType,
beforeEach,
describe,
type IsExact,
it,
} from "./deps.test.ts";

// Compile-time type tests. No run-time assertion will actually run. Either compile fails or test passes.
describe("Composer types", () => {
let composer: Composer<Context>;

beforeEach(() => {
composer = new Composer();
});

describe(".hears", () => {
it("should have correct type for properties", () => {
composer.hears("test", (ctx) => {
const msgCaption = ctx.msg.caption;
const msgText = ctx.msg.text;
const messageCaption = ctx.message?.caption;
const messageText = ctx.message?.text;
const channelPostCaption = ctx.channelPost?.caption;
const channelPostText = ctx.channelPost?.text;
const match = ctx.match;
assertType<IsExact<typeof msgCaption, string | undefined>>(
true,
);
assertType<IsExact<typeof msgText, string | undefined>>(true);
assertType<IsExact<typeof messageCaption, string | undefined>>(
true,
);
assertType<IsExact<typeof messageText, string | undefined>>(
true,
);
assertType<
IsExact<typeof channelPostCaption, string | undefined>
>(
true,
);
assertType<IsExact<typeof channelPostText, string | undefined>>(
true,
);
assertType<IsExact<typeof match, string | RegExpMatchArray>>(
true,
);
});
});
});

describe(".callbackQuery", () => {
it("should have correct type for properties", () => {
composer.callbackQuery("test", (ctx) => {
const msg = ctx.msg;
const message = ctx.message;
const callbackQueryMessage = ctx.callbackQuery.message;
const callbackQueryData = ctx.callbackQuery.data;
const match = ctx.match;
assertType<
IsExact<typeof msg, MaybeInaccessibleMessage | undefined>
>(
true,
);
assertType<
IsExact<
typeof message,
undefined // This is ctx.update.message, but not ctx.update.callback_query.message
>
>(
true,
);
assertType<
IsExact<
typeof callbackQueryMessage,
MaybeInaccessibleMessage | undefined
>
>(
true,
);
assertType<
IsExact<
typeof callbackQueryData,
string
>
>(
true,
);
assertType<IsExact<typeof match, string | RegExpMatchArray>>(
true,
);
});
});
});

describe(".command", () => {
it("should have correct type for properties", () => {
composer.command("test", (ctx) => {
const msgText = ctx.msg.text;
const messageCaption = ctx.message?.caption;
const messageText = ctx.message?.text;
const channelPostCaption = ctx.channelPost?.caption;
const channelPostText = ctx.channelPost?.text;
const match = ctx.match;
assertType<IsExact<typeof msgText, string>>(true);
assertType<IsExact<typeof messageCaption, string | undefined>>(
true,
);
assertType<IsExact<typeof messageText, string | undefined>>(
true,
);
assertType<
IsExact<typeof channelPostCaption, string | undefined>
>(true);
assertType<IsExact<typeof channelPostText, string | undefined>>(
true,
);
assertType<IsExact<typeof match, string>>(true);
});
});
});

describe(".chatType", () => {
it("should have correct type for properties in private chats", () => {
composer.chatType("private", (ctx) => {
const chat = ctx.chat;
const chatId = ctx.chatId;
const from = ctx.from;
const channelPost = ctx.channelPost;

assertType<IsExact<typeof chat, Chat.PrivateChat>>(true);
assertType<IsExact<typeof chatId, number>>(true);
assertType<IsExact<typeof from, User>>(true);
assertType<IsExact<typeof channelPost, undefined>>(true);
if (ctx.message) {
assertType<
IsExact<typeof ctx.message.chat, Chat.PrivateChat>
>(true);
}
if (ctx.callbackQuery?.message) {
assertType<
IsExact<
typeof ctx.callbackQuery.message.chat,
Chat.PrivateChat
>
>(true);
}
});
});
it("should have correct type for properties in group chats", () => {
composer.chatType("group", (ctx) => {
const chat = ctx.chat;
const chatId = ctx.chatId;
const channelPost = ctx.channelPost;

assertType<IsExact<typeof chat, Chat.GroupChat>>(true);
assertType<IsExact<typeof chatId, number>>(true);
assertType<IsExact<typeof channelPost, undefined>>(true);
if (ctx.message) {
assertType<
IsExact<typeof ctx.message.chat, Chat.GroupChat>
>(true);
}
if (ctx.callbackQuery?.message) {
assertType<
IsExact<
typeof ctx.callbackQuery.message.chat,
Chat.GroupChat
>
>(true);
}
});
});
it("should have correct type for properties in supergroup chats", () => {
composer.chatType("supergroup", (ctx) => {
const chat = ctx.chat;
const chatId = ctx.chatId;
const channelPost = ctx.channelPost;

assertType<IsExact<typeof chat, Chat.SupergroupChat>>(true);
assertType<IsExact<typeof chatId, number>>(true);
assertType<IsExact<typeof channelPost, undefined>>(true);
if (ctx.message) {
assertType<
IsExact<typeof ctx.message.chat, Chat.SupergroupChat>
>(true);
}
if (ctx.callbackQuery?.message) {
assertType<
IsExact<
typeof ctx.callbackQuery.message.chat,
Chat.SupergroupChat
>
>(true);
}
});
});
it("should have correct type for properties in channel chats", () => {
composer.chatType("channel", (ctx) => {
const chat = ctx.chat;
const chatId = ctx.chatId;
const message = ctx.message;

assertType<IsExact<typeof chat, Chat.ChannelChat>>(true);
assertType<IsExact<typeof chatId, number>>(true);
assertType<IsExact<typeof message, undefined>>(true);
if (ctx.channelPost) {
assertType<
IsExact<typeof ctx.channelPost.chat, Chat.ChannelChat>
>(true);
}
if (ctx.callbackQuery?.message) {
assertType<
IsExact<
typeof ctx.callbackQuery.message.chat,
Chat.ChannelChat
>
>(true);
}
});
});
it("should combine different chat types correctly", () => {
composer.chatType(["private", "channel"], (ctx) => {
const chat = ctx.chat;
const chatId = ctx.chatId;

assertType<
IsExact<typeof chat, Chat.PrivateChat | Chat.ChannelChat>
>(true);
assertType<IsExact<typeof chatId, number>>(true);
if (ctx.message) {
assertType<
IsExact<typeof ctx.message.chat, Chat.PrivateChat>
>(true);
}
if (ctx.channelPost) {
assertType<
IsExact<typeof ctx.channelPost.chat, Chat.ChannelChat>
>(true);
}
});
});
});

describe(".gameQuery", () => {
it("should have correct type for properties", () => {
composer.gameQuery("test", (ctx) => {
const msg = ctx.msg;
const message = ctx.message;
const callbackQueryMessage = ctx.callbackQuery.message;
const gameShortName = ctx.callbackQuery.game_short_name;
const match = ctx.match;
assertType<
IsExact<typeof msg, MaybeInaccessibleMessage | undefined>
>(
true,
);
assertType<
IsExact<
typeof message,
undefined // This is ctx.update.message, but not ctx.update.callback_query.message
>
>(
true,
);
assertType<
IsExact<
typeof callbackQueryMessage,
MaybeInaccessibleMessage | undefined
>
>(
true,
);
assertType<
IsExact<
typeof gameShortName,
string
>
>(
true,
);
assertType<IsExact<typeof match, string | RegExpMatchArray>>(
true,
);
});
});
});

describe(".inlineQuery", () => {
it("should have correct type for properties", () => {
composer.inlineQuery("test", (ctx) => {
const query = ctx.inlineQuery.query;
const match = ctx.match;
assertType<IsExact<typeof query, string>>(true);
assertType<IsExact<typeof match, RegExpMatchArray | string>>(
true,
);
});
});
});

describe(".filter", () => {
it("should have correct type for properties", () => {
type TmpCtx = Context & { prop: number };
composer.filter((_ctx): _ctx is TmpCtx => true, (ctx) => {
assertType<IsExact<typeof ctx, TmpCtx>>(true);
assertType<IsExact<typeof ctx.prop, number>>(true);
});
});
});
});
4 changes: 4 additions & 0 deletions test/deps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export {
type Stub,
stub,
} from "https://deno.land/[email protected]/testing/mock.ts";
export {
assertType,
type IsExact,
} from "https://deno.land/[email protected]/testing/types.ts";

export async function convertToUint8Array(
data: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
Expand Down

0 comments on commit 2ba57c1

Please sign in to comment.