Repository: Yoctol/bottender
Pull request #604 (2019-12-26 09:52:19)

feat(router): add telegram routes
Add telegram routes:

```js
const { router, telegram } = require('bottender/router');

function Action() {
  // ...
}

function App() {
  return router([
    telegram.message(Action),
    telegram.editedMessage(Action),
    telegram.channelPost(Action),
    telegram.editedChannelPost(Action),
    telegram.inlineQuery(Action),
    telegram.chosenInlineResult(Action),
    telegram.callbackQuery(Action),
    telegram.shippingQuery(Action),
    telegram.preCheckoutQuery(Action),
    telegram.poll(Action),    
    telegram(Action),
  ]);
}
```




Patch:
diff --git a/packages/bottender/src/router/index.ts b/packages/bottender/src/router/index.ts
index df5201b4..391484d3 100644
--- a/packages/bottender/src/router/index.ts
+++ b/packages/bottender/src/router/index.ts
@@ -1,6 +1,7 @@
 import Context from '../context/Context';
 import line from '../line/routes';
 import messenger from '../messenger/routes';
+import telegram from '../telegram/routes';
 import { Action, Client, Event, Props } from '../types';
 
 type MatchPattern = string | Array<string> | RegExp;
@@ -193,4 +194,4 @@ function platform<C extends Client = any, E extends Event = any>(
 
 export default router;
 
-export { router, route, text, payload, platform, line, messenger };
+export { router, route, text, payload, platform, line, messenger, telegram };
diff --git a/packages/bottender/src/telegram/TelegramEvent.ts b/packages/bottender/src/telegram/TelegramEvent.ts
index dad0a7b0..fd0d3ba3 100644
--- a/packages/bottender/src/telegram/TelegramEvent.ts
+++ b/packages/bottender/src/telegram/TelegramEvent.ts
@@ -165,6 +165,18 @@ type PreCheckoutQuery = {
   orderInfo?: OrderInfo;
 };
 
+type PollOption = {
+  text: string;
+  voterCount: number;
+};
+
+type Poll = {
+  id: string;
+  question: string;
+  options: PollOption[];
+  isClosed: boolean;
+};
+
 export type TelegramRawEvent = {
   updateId: number;
   message?: Message;
@@ -176,6 +188,7 @@ export type TelegramRawEvent = {
   callbackQuery?: CallbackQuery;
   shippingQuery?: ShippingQuery;
   preCheckoutQuery?: PreCheckoutQuery;
+  poll?: Poll;
 };
 
 export default class TelegramEvent implements Event<TelegramRawEvent> {
@@ -655,4 +668,20 @@ export default class TelegramEvent implements Event<TelegramRawEvent> {
   get preCheckoutQuery(): PreCheckoutQuery | null {
     return this._rawEvent.preCheckoutQuery || null;
   }
+
+  /**
+   * Determine if the event is a poll event.
+   *
+   */
+  get isPoll(): boolean {
+    return !!this.poll && typeof this.poll === 'object';
+  }
+
+  /**
+   * The poll from Telegram raw event.
+   *
+   */
+  get poll(): Poll | null {
+    return this._rawEvent.poll || null;
+  }
 }
diff --git a/packages/bottender/src/telegram/routes.ts b/packages/bottender/src/telegram/routes.ts
new file mode 100644
index 00000000..553eeecd
--- /dev/null
+++ b/packages/bottender/src/telegram/routes.ts
@@ -0,0 +1,151 @@
+import Context from '../context/Context';
+import { Action, Client, Event } from '../types';
+import { RoutePredicate, route } from '../router';
+
+type Route = <C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) => {
+  predicate: RoutePredicate<C, E>;
+  action: Action<C, E>;
+};
+
+type Telegram = Route & {
+  message: Route;
+  editedMessage: Route;
+  channelPost: Route;
+  editedChannelPost: Route;
+  inlineQuery: Route;
+  chosenInlineResult: Route;
+  callbackQuery: Route;
+  shippingQuery: Route;
+  preCheckoutQuery: Route;
+  poll: Route;
+};
+
+const telegram: Telegram = <C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) => {
+  return route(context => context.platform === 'telegram', action);
+};
+
+function message<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isMessage,
+    action
+  );
+}
+
+telegram.message = message;
+
+function editedMessage<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isEditedMessage,
+    action
+  );
+}
+
+telegram.editedMessage = editedMessage;
+
+function channelPost<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isChannelPost,
+    action
+  );
+}
+
+telegram.channelPost = channelPost;
+
+function editedChannelPost<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isEditedChannelPost,
+    action
+  );
+}
+
+telegram.editedChannelPost = editedChannelPost;
+
+function inlineQuery<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isInlineQuery,
+    action
+  );
+}
+
+telegram.inlineQuery = inlineQuery;
+
+function chosenInlineResult<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isChosenInlineResult,
+    action
+  );
+}
+
+telegram.chosenInlineResult = chosenInlineResult;
+
+function callbackQuery<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isCallbackQuery,
+    action
+  );
+}
+
+telegram.callbackQuery = callbackQuery;
+
+function shippingQuery<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isShippingQuery,
+    action
+  );
+}
+
+telegram.shippingQuery = shippingQuery;
+
+function preCheckoutQuery<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isPreCheckoutQuery,
+    action
+  );
+}
+
+telegram.preCheckoutQuery = preCheckoutQuery;
+
+function poll<C extends Client = any, E extends Event = any>(
+  action: Action<C, E>
+) {
+  return route(
+    (context: Context<C, E>) =>
+      context.platform === 'telegram' && context.event.isPoll,
+    action
+  );
+}
+
+telegram.poll = poll;
+
+export default telegram;


Test patch:
diff --git a/packages/bottender/src/messenger/__tests__/routes.spec.ts b/packages/bottender/src/messenger/__tests__/routes.spec.ts
index dc2be924..bc9bdf90 100644
--- a/packages/bottender/src/messenger/__tests__/routes.spec.ts
+++ b/packages/bottender/src/messenger/__tests__/routes.spec.ts
@@ -338,7 +338,7 @@ async function expectRouteMatchContext({ route, context }) {
   expect(context.sendText).toBeCalledWith('hello');
 }
 
-async function expectRouteMatchLineEvent({ route, event }) {
+async function expectRouteMatchMessengerEvent({ route, event }) {
   const context = new MessengerContext({
     client: {} as any,
     event,
@@ -362,7 +362,7 @@ async function expectRouteNotMatchContext({ route, context }) {
   expect(context.sendText).not.toBeCalledWith('hello');
 }
 
-async function expectRouteNotMatchLineEvent({ route, event }) {
+async function expectRouteNotMatchMessengerEvent({ route, event }) {
   const context = new MessengerContext({
     client: {} as any,
     event,
@@ -385,7 +385,7 @@ class TestContext extends Context<any, any> {
 
 describe('#messenger', () => {
   it('should call action when it receives a messenger event', async () => {
-    await expectRouteMatchLineEvent({
+    await expectRouteMatchMessengerEvent({
       route: messenger(Action),
       event: messengerEventTextMessage,
     });
@@ -403,14 +403,14 @@ describe('#messenger', () => {
 
   describe('#messenger.message', () => {
     it('should call action when it receives a messenger message event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.message(Action),
         event: messengerEventTextMessage,
       });
     });
 
     it('should not call action when it receives a non-message event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.message(Action),
         event: messengerEventPostback,
       });
@@ -419,14 +419,14 @@ describe('#messenger', () => {
 
   describe('#messenger.accountLinking', () => {
     it('should call action when it receives a messenger accountLinking event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.accountLinking(Action),
         event: messengerEventAccountLinkingLinked,
       });
     });
 
     it('should not call action when it receives a non-accountLinking event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.accountLinking(Action),
         event: messengerEventTextMessage,
       });
@@ -434,14 +434,14 @@ describe('#messenger', () => {
 
     describe('#messenger.accountLinking.linked', () => {
       it('should call action when it receives a messenger accountLinking.linked event', async () => {
-        await expectRouteMatchLineEvent({
+        await expectRouteMatchMessengerEvent({
           route: messenger.accountLinking.linked(Action),
           event: messengerEventAccountLinkingLinked,
         });
       });
 
       it('should not call action when it receives a non-accountLinking.linked event', async () => {
-        await expectRouteNotMatchLineEvent({
+        await expectRouteNotMatchMessengerEvent({
           route: messenger.accountLinking.linked(Action),
           event: messengerEventAccountLinkingUnlinked,
         });
@@ -450,14 +450,14 @@ describe('#messenger', () => {
 
     describe('#messenger.accountLinking.unlinked', () => {
       it('should call action when it receives a messenger accountLinking.unlinked event', async () => {
-        await expectRouteMatchLineEvent({
+        await expectRouteMatchMessengerEvent({
           route: messenger.accountLinking.unlinked(Action),
           event: messengerEventAccountLinkingUnlinked,
         });
       });
 
       it('should not call action when it receives a non-accountLinking.unlinked event', async () => {
-        await expectRouteNotMatchLineEvent({
+        await expectRouteNotMatchMessengerEvent({
           route: messenger.accountLinking.unlinked(Action),
           event: messengerEventAccountLinkingLinked,
         });
@@ -467,14 +467,14 @@ describe('#messenger', () => {
 
   describe('#messenger.checkoutUpdate', () => {
     it('should call action when it receives a messenger checkoutUpdate event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.checkoutUpdate(Action),
         event: messengerEventCheckoutUpdate,
       });
     });
 
     it('should not call action when it receives a non-checkoutUpdate event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.checkoutUpdate(Action),
         event: messengerEventTextMessage,
       });
@@ -483,14 +483,14 @@ describe('#messenger', () => {
 
   describe('#messenger.delivery', () => {
     it('should call action when it receives a messenger delivery event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.delivery(Action),
         event: messengerEventDelivery,
       });
     });
 
     it('should not call action when it receives a non-delivery event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.delivery(Action),
         event: messengerEventTextMessage,
       });
@@ -499,14 +499,14 @@ describe('#messenger', () => {
 
   describe('#messenger.echo', () => {
     it('should call action when it receives a messenger echo event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.echo(Action),
         event: messengerEventEcho,
       });
     });
 
     it('should not call action when it receives a non-echo event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.echo(Action),
         event: messengerEventTextMessage,
       });
@@ -515,14 +515,14 @@ describe('#messenger', () => {
 
   describe('#messenger.gamePlay', () => {
     it('should call action when it receives a messenger gamePlay event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.gamePlay(Action),
         event: messengerEventGamePlay,
       });
     });
 
     it('should not call action when it receives a non-gamePlay event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.gamePlay(Action),
         event: messengerEventTextMessage,
       });
@@ -531,14 +531,14 @@ describe('#messenger', () => {
 
   describe('#messenger.passThreadControl', () => {
     it('should call action when it receives a messenger passThreadControl event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.passThreadControl(Action),
         event: messengerEventPassThreadControl,
       });
     });
 
     it('should not call action when it receives a non-passThreadControl event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.passThreadControl(Action),
         event: messengerEventTextMessage,
       });
@@ -547,14 +547,14 @@ describe('#messenger', () => {
 
   describe('#messenger.takeThreadControl', () => {
     it('should call action when it receives a messenger takeThreadControl event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.takeThreadControl(Action),
         event: messengerEventTakeThreadControl,
       });
     });
 
     it('should not call action when it receives a non-takeThreadControl event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.takeThreadControl(Action),
         event: messengerEventTextMessage,
       });
@@ -563,14 +563,14 @@ describe('#messenger', () => {
 
   describe('#messenger.requestThreadControl', () => {
     it('should call action when it receives a messenger requestThreadControl event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.requestThreadControl(Action),
         event: messengerEventRequestThreadControl,
       });
     });
 
     it('should not call action when it receives a non-requestThreadControl event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.requestThreadControl(Action),
         event: messengerEventTextMessage,
       });
@@ -579,14 +579,14 @@ describe('#messenger', () => {
 
   describe('#messenger.appRoles', () => {
     it('should call action when it receives a messenger appRoles event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.appRoles(Action),
         event: messengerEventAppRoles,
       });
     });
 
     it('should not call action when it receives a non-appRoles event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.appRoles(Action),
         event: messengerEventTextMessage,
       });
@@ -595,14 +595,14 @@ describe('#messenger', () => {
 
   describe('#messenger.optin', () => {
     it('should call action when it receives a messenger optin event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.optin(Action),
         event: messengerEventOptin,
       });
     });
 
     it('should not call action when it receives a non-optin event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.optin(Action),
         event: messengerEventTextMessage,
       });
@@ -611,14 +611,14 @@ describe('#messenger', () => {
 
   describe('#messenger.payment', () => {
     it('should call action when it receives a messenger payment event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.payment(Action),
         event: messengerEventPayment,
       });
     });
 
     it('should not call action when it receives a non-payment event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.payment(Action),
         event: messengerEventTextMessage,
       });
@@ -627,14 +627,14 @@ describe('#messenger', () => {
 
   describe('#messenger.policyEnforcement', () => {
     it('should call action when it receives a messenger policyEnforcement event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.policyEnforcement(Action),
         event: messengerEventPolicyEnforcement,
       });
     });
 
     it('should not call action when it receives a non-policyEnforcement event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.policyEnforcement(Action),
         event: messengerEventTextMessage,
       });
@@ -643,14 +643,14 @@ describe('#messenger', () => {
 
   describe('#messenger.postback', () => {
     it('should call action when it receives a messenger postback event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.postback(Action),
         event: messengerEventPostback,
       });
     });
 
     it('should not call action when it receives a non-postback event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.postback(Action),
         event: messengerEventTextMessage,
       });
@@ -659,14 +659,14 @@ describe('#messenger', () => {
 
   describe('#messenger.preCheckout', () => {
     it('should call action when it receives a messenger preCheckout event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.preCheckout(Action),
         event: messengerEventPreCheckout,
       });
     });
 
     it('should not call action when it receives a non-preCheckout event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.preCheckout(Action),
         event: messengerEventTextMessage,
       });
@@ -675,14 +675,14 @@ describe('#messenger', () => {
 
   describe('#messenger.read', () => {
     it('should call action when it receives a messenger read event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.read(Action),
         event: messengerEventRead,
       });
     });
 
     it('should not call action when it receives a non-read event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.read(Action),
         event: messengerEventTextMessage,
       });
@@ -691,14 +691,14 @@ describe('#messenger', () => {
 
   describe('#messenger.referral', () => {
     it('should call action when it receives a messenger referral event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.referral(Action),
         event: messengerEventReferral,
       });
     });
 
     it('should not call action when it receives a non-referral event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.referral(Action),
         event: messengerEventTextMessage,
       });
@@ -707,14 +707,14 @@ describe('#messenger', () => {
 
   describe('#messenger.standby', () => {
     it('should call action when it receives a messenger standby event', async () => {
-      await expectRouteMatchLineEvent({
+      await expectRouteMatchMessengerEvent({
         route: messenger.standby(Action),
         event: messengerEventStandby,
       });
     });
 
     it('should not call action when it receives a non-standby event', async () => {
-      await expectRouteNotMatchLineEvent({
+      await expectRouteNotMatchMessengerEvent({
         route: messenger.standby(Action),
         event: messengerEventTextMessage,
       });
diff --git a/packages/bottender/src/router/__tests__/router.spec.ts b/packages/bottender/src/router/__tests__/router.spec.ts
index 3b4525cc..d0c50a75 100644
--- a/packages/bottender/src/router/__tests__/router.spec.ts
+++ b/packages/bottender/src/router/__tests__/router.spec.ts
@@ -1,6 +1,14 @@
 import { run } from '../../bot/Bot';
 
-import router, { line, messenger, payload, platform, route, text } from '..';
+import router, {
+  line,
+  messenger,
+  payload,
+  platform,
+  route,
+  telegram,
+  text,
+} from '..';
 
 function textContext(message = '') {
   return {
@@ -349,3 +357,7 @@ it('#line should be exported', () => {
 it('#messenger should be exported', () => {
   expect(messenger).toBeDefined();
 });
+
+it('#telegram should be exported', () => {
+  expect(telegram).toBeDefined();
+});
diff --git a/packages/bottender/src/telegram/__tests__/TelegramEvent.spec.ts b/packages/bottender/src/telegram/__tests__/TelegramEvent.spec.ts
index e21d8386..d487c101 100644
--- a/packages/bottender/src/telegram/__tests__/TelegramEvent.spec.ts
+++ b/packages/bottender/src/telegram/__tests__/TelegramEvent.spec.ts
@@ -547,6 +547,19 @@ const preCheckoutQuery = {
   },
 };
 
+const poll = {
+  updateId: 141921690,
+  poll: {
+    id: '123',
+    question: 'What is it?',
+    options: [
+      { text: 'A', voterCount: 0 },
+      { text: 'B', voterCount: 0 },
+    ],
+    isClosed: false,
+  },
+};
+
 const replyToTextMessage = {
   message: {
     messageId: 666,
@@ -602,6 +615,7 @@ it('#rawEvent', () => {
   expect(new TelegramEvent(preCheckoutQuery).rawEvent).toEqual(
     preCheckoutQuery
   );
+  expect(new TelegramEvent(poll).rawEvent).toEqual(poll);
   expect(new TelegramEvent(replyToTextMessage).rawEvent).toEqual(
     replyToTextMessage
   );
@@ -618,6 +632,7 @@ it('#isMessage', () => {
   expect(new TelegramEvent(callbackQuery).isMessage).toEqual(false);
   expect(new TelegramEvent(shippingQuery).isMessage).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isMessage).toEqual(false);
+  expect(new TelegramEvent(poll).isMessage).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isMessage).toEqual(true);
 });
 
@@ -666,6 +681,7 @@ it('#message', () => {
   expect(new TelegramEvent(callbackQuery).message).toEqual(null);
   expect(new TelegramEvent(shippingQuery).message).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).message).toEqual(null);
+  expect(new TelegramEvent(poll).message).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).message).toEqual({
     messageId: 666,
     from: {
@@ -729,6 +745,7 @@ it('#isReplyToMessage', () => {
   expect(new TelegramEvent(callbackQuery).isReplyToMessage).toEqual(false);
   expect(new TelegramEvent(shippingQuery).isReplyToMessage).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isReplyToMessage).toEqual(false);
+  expect(new TelegramEvent(poll).isReplyToMessage).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isReplyToMessage).toEqual(true);
 });
 
@@ -742,6 +759,7 @@ it('#replyToMessage', () => {
   expect(new TelegramEvent(callbackQuery).replyToMessage).toEqual(null);
   expect(new TelegramEvent(shippingQuery).replyToMessage).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).replyToMessage).toEqual(null);
+  expect(new TelegramEvent(poll).replyToMessage).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).replyToMessage).toEqual({
     messageId: 777,
     from: {
@@ -989,6 +1007,7 @@ it('#isEditedMessage', () => {
   expect(new TelegramEvent(callbackQuery).isEditedMessage).toEqual(false);
   expect(new TelegramEvent(shippingQuery).isEditedMessage).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isEditedMessage).toEqual(false);
+  expect(new TelegramEvent(poll).isEditedMessage).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isEditedMessage).toEqual(false);
 });
 
@@ -1039,6 +1058,7 @@ it('#editedMessage', () => {
   expect(new TelegramEvent(callbackQuery).editedMessage).toEqual(null);
   expect(new TelegramEvent(shippingQuery).editedMessage).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).editedMessage).toEqual(null);
+  expect(new TelegramEvent(poll).editedMessage).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).editedMessage).toEqual(null);
 });
 
@@ -1052,6 +1072,7 @@ it('#isChannelPost', () => {
   expect(new TelegramEvent(callbackQuery).isChannelPost).toEqual(false);
   expect(new TelegramEvent(shippingQuery).isChannelPost).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isChannelPost).toEqual(false);
+  expect(new TelegramEvent(poll).isChannelPost).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isChannelPost).toEqual(false);
 });
 
@@ -1074,6 +1095,7 @@ it('#channelPost', () => {
   expect(new TelegramEvent(callbackQuery).channelPost).toEqual(null);
   expect(new TelegramEvent(shippingQuery).channelPost).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).channelPost).toEqual(null);
+  expect(new TelegramEvent(poll).channelPost).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).channelPost).toEqual(null);
 });
 
@@ -1093,6 +1115,7 @@ it('#isEditedChannelPost', () => {
   expect(new TelegramEvent(preCheckoutQuery).isEditedChannelPost).toEqual(
     false
   );
+  expect(new TelegramEvent(poll).isEditedChannelPost).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isEditedChannelPost).toEqual(
     false
   );
@@ -1118,6 +1141,7 @@ it('#editedChannelPost', () => {
   expect(new TelegramEvent(callbackQuery).editedChannelPost).toEqual(null);
   expect(new TelegramEvent(shippingQuery).editedChannelPost).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).editedChannelPost).toEqual(null);
+  expect(new TelegramEvent(poll).editedChannelPost).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).editedChannelPost).toEqual(null);
 });
 
@@ -1131,6 +1155,7 @@ it('#isInlineQuery', () => {
   expect(new TelegramEvent(callbackQuery).isInlineQuery).toEqual(false);
   expect(new TelegramEvent(shippingQuery).isInlineQuery).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isInlineQuery).toEqual(false);
+  expect(new TelegramEvent(poll).isInlineQuery).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isInlineQuery).toEqual(false);
 });
 
@@ -1155,6 +1180,7 @@ it('#inlineQuery', () => {
   expect(new TelegramEvent(callbackQuery).inlineQuery).toEqual(null);
   expect(new TelegramEvent(shippingQuery).inlineQuery).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).inlineQuery).toEqual(null);
+  expect(new TelegramEvent(poll).inlineQuery).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).inlineQuery).toEqual(null);
 });
 
@@ -1174,6 +1200,7 @@ it('#isChosenInlineResult', () => {
   expect(new TelegramEvent(preCheckoutQuery).isChosenInlineResult).toEqual(
     false
   );
+  expect(new TelegramEvent(poll).isChosenInlineResult).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isChosenInlineResult).toEqual(
     false
   );
@@ -1200,6 +1227,7 @@ it('#chosenInlineResult', () => {
   expect(new TelegramEvent(callbackQuery).chosenInlineResult).toEqual(null);
   expect(new TelegramEvent(shippingQuery).chosenInlineResult).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).chosenInlineResult).toEqual(null);
+  expect(new TelegramEvent(poll).chosenInlineResult).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).chosenInlineResult).toEqual(
     null
   );
@@ -1216,6 +1244,7 @@ it('#isCallbackQuery', () => {
   expect(new TelegramEvent(groupCallbackQuery).isCallbackQuery).toEqual(true);
   expect(new TelegramEvent(shippingQuery).isCallbackQuery).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isCallbackQuery).toEqual(false);
+  expect(new TelegramEvent(poll).isCallbackQuery).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isCallbackQuery).toEqual(false);
 });
 
@@ -1286,6 +1315,7 @@ it('#callbackQuery', () => {
   });
   expect(new TelegramEvent(shippingQuery).callbackQuery).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).callbackQuery).toEqual(null);
+  expect(new TelegramEvent(poll).callbackQuery).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).callbackQuery).toEqual(null);
 });
 
@@ -1300,6 +1330,7 @@ it('#isPayload', () => {
   expect(new TelegramEvent(groupCallbackQuery).isPayload).toEqual(true);
   expect(new TelegramEvent(shippingQuery).isPayload).toEqual(false);
   expect(new TelegramEvent(preCheckoutQuery).isPayload).toEqual(false);
+  expect(new TelegramEvent(poll).isPayload).toEqual(false);
   expect(new TelegramEvent(replyToTextMessage).isPayload).toEqual(false);
 });
 
@@ -1314,5 +1345,6 @@ it('#payload', () => {
   expect(new TelegramEvent(groupCallbackQuery).payload).toEqual('123');
   expect(new TelegramEvent(shippingQuery).payload).toEqual(null);
   expect(new TelegramEvent(preCheckoutQuery).payload).toEqual(null);
+  expect(new TelegramEvent(poll).payload).toEqual(null);
   expect(new TelegramEvent(replyToTextMessage).payload).toEqual(null);
 });
diff --git a/packages/bottender/src/telegram/__tests__/routes.spec.ts b/packages/bottender/src/telegram/__tests__/routes.spec.ts
new file mode 100644
index 00000000..112dea19
--- /dev/null
+++ b/packages/bottender/src/telegram/__tests__/routes.spec.ts
@@ -0,0 +1,435 @@
+import Context from '../../context/Context';
+import TelegramContext from '../TelegramContext';
+import TelegramEvent from '../TelegramEvent';
+import router from '../../router';
+import telegram from '../routes';
+import { run } from '../../bot/Bot';
+
+const telegramEventTextMessage = new TelegramEvent({
+  message: {
+    messageId: 666,
+    from: {
+      id: 427770117,
+      isBot: false,
+      firstName: 'first',
+      lastName: 'last',
+      languageCode: 'en',
+    },
+    chat: {
+      id: 427770117,
+      firstName: 'first',
+      lastName: 'last',
+      type: 'private',
+    },
+    date: 1499402829,
+    text: 'text',
+  },
+});
+
+const telegramEventEditedMessage = new TelegramEvent({
+  updateId: 141921687,
+  editedMessage: {
+    messageId: 229,
+    from: {
+      id: 427770117,
+      isBot: false,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    chat: {
+      id: 427770117,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    date: 1515736358,
+    editDate: 1515758017,
+    text: 'hiiiii',
+  },
+});
+
+const telegramEventChannelPost = new TelegramEvent({
+  updateId: 141921710,
+  channelPost: {
+    messageId: 2,
+    chat: {
+      id: -1001305240521,
+      title: 'channel_12345',
+      type: 'channel',
+    },
+    date: 1515760382,
+    text: 'post~~~',
+  },
+});
+
+const telegramEventEditedChannelPost = new TelegramEvent({
+  updateId: 141921711,
+  editedChannelPost: {
+    messageId: 2,
+    chat: {
+      id: -1001305240521,
+      title: 'channel_12345',
+      type: 'channel',
+    },
+    date: 1515760382,
+    editDate: 1515760478,
+    text: 'post~~~edited',
+  },
+});
+
+const telegramEventInlineQuery = new TelegramEvent({
+  updateId: 141921700,
+  inlineQuery: {
+    id: '1837258670654537434',
+    from: {
+      id: 427770117,
+      isBot: false,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    query: '123',
+    offset: '',
+  },
+});
+
+const telegramEventChosenInlineResult = new TelegramEvent({
+  updateId: 141921701,
+  chosenInlineResult: {
+    resultId: '2837258670654537434',
+    from: {
+      id: 427770117,
+      isBot: false,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    inlineMessageId: '1837258670654537434',
+    query: '123',
+  },
+});
+
+const telegramEventCallbackQuery = new TelegramEvent({
+  updateId: 141921690,
+  callbackQuery: {
+    id: '123',
+    from: {
+      id: 427770117,
+      isBot: false,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    message: {
+      messageId: 666,
+      from: {
+        id: 313534466,
+        isBot: true,
+        firstName: 'bot_first',
+        username: 'bot_name',
+      },
+      chat: {
+        id: 427770117,
+        firstName: 'first',
+        lastName: 'last',
+        type: 'private',
+      },
+      date: 1499402829,
+      text: 'text',
+    },
+    chatInstance: '-1828607021492040088',
+    data: 'data',
+  },
+});
+
+const telegramEventShippingQuery = new TelegramEvent({
+  updateId: 141921690,
+  shippingQuery: {
+    id: '123',
+    from: {
+      id: 427770117,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    invoicePayload: 'bot payload',
+    shippingAddress: {
+      countryCode: 'US',
+      state: 'New York',
+      city: 'New York',
+      streetLine1: 'xx',
+      streetLine2: 'xx',
+      postCode: '10001',
+    },
+  },
+});
+
+const telegramEventPreCheckoutQuery = new TelegramEvent({
+  updateId: 141921690,
+  preCheckoutQuery: {
+    id: '123',
+    from: {
+      id: 427770117,
+      firstName: 'user_first',
+      lastName: 'user_last',
+      languageCode: 'en',
+    },
+    currency: 'USD',
+    totalAmount: 145,
+    invoicePayload: 'bot payload',
+  },
+});
+
+const telegramEventPoll = new TelegramEvent({
+  updateId: 141921690,
+  poll: {
+    id: '123',
+    question: 'What is it?',
+    options: [
+      { text: 'A', voterCount: 0 },
+      { text: 'B', voterCount: 0 },
+    ],
+    isClosed: false,
+  },
+});
+
+async function Action(context) {
+  await context.sendText('hello');
+}
+
+async function expectRouteMatchContext({ route, context }) {
+  const Router = router([route]);
+
+  const app = run(Router);
+
+  context.sendText = jest.fn();
+
+  await app(context);
+
+  expect(context.sendText).toBeCalledWith('hello');
+}
+
+async function expectRouteMatchTelegramEvent({ route, event }) {
+  const context = new TelegramContext({
+    client: {} as any,
+    event,
+  });
+
+  await expectRouteMatchContext({
+    route,
+    context,
+  });
+}
+
+async function expectRouteNotMatchContext({ route, context }) {
+  const Router = router([route]);
+
+  const app = run(Router);
+
+  context.sendText = jest.fn();
+
+  await app(context);
+
+  expect(context.sendText).not.toBeCalledWith('hello');
+}
+
+async function expectRouteNotMatchTelegramEvent({ route, event }) {
+  const context = new TelegramContext({
+    client: {} as any,
+    event,
+  });
+
+  await expectRouteNotMatchContext({
+    route,
+    context,
+  });
+}
+
+class TestContext extends Context<any, any> {
+  get platform() {
+    return 'test';
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  sendText() {}
+}
+
+describe('#telegram', () => {
+  it('should call action when it receives a telegram event', async () => {
+    await expectRouteMatchTelegramEvent({
+      route: telegram(Action),
+      event: telegramEventTextMessage,
+    });
+  });
+
+  it('should not call action when it receives a non-telegram event', async () => {
+    await expectRouteNotMatchContext({
+      route: telegram(Action),
+      context: new TestContext({
+        client: {} as any,
+        event: {},
+      }),
+    });
+  });
+
+  describe('#telegram.message', () => {
+    it('should call action when it receives a telegram message event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.message(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+
+    it('should not call action when it receives a non-message event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.message(Action),
+        event: telegramEventEditedMessage,
+      });
+    });
+  });
+
+  describe('#telegram.editedMessage', () => {
+    it('should call action when it receives a telegram editedMessage event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.editedMessage(Action),
+        event: telegramEventEditedMessage,
+      });
+    });
+
+    it('should not call action when it receives a non-editedMessage event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.editedMessage(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.channelPost', () => {
+    it('should call action when it receives a telegram channelPost event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.channelPost(Action),
+        event: telegramEventChannelPost,
+      });
+    });
+
+    it('should not call action when it receives a non-channelPost event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.channelPost(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.editedChannelPost', () => {
+    it('should call action when it receives a telegram editedChannelPost event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.editedChannelPost(Action),
+        event: telegramEventEditedChannelPost,
+      });
+    });
+
+    it('should not call action when it receives a non-editedChannelPost event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.editedChannelPost(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.inlineQuery', () => {
+    it('should call action when it receives a telegram inlineQuery event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.inlineQuery(Action),
+        event: telegramEventInlineQuery,
+      });
+    });
+
+    it('should not call action when it receives a non-inlineQuery event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.inlineQuery(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.chosenInlineResult', () => {
+    it('should call action when it receives a telegram chosenInlineResult event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.chosenInlineResult(Action),
+        event: telegramEventChosenInlineResult,
+      });
+    });
+
+    it('should not call action when it receives a non-chosenInlineResult event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.chosenInlineResult(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.callbackQuery', () => {
+    it('should call action when it receives a telegram callbackQuery event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.callbackQuery(Action),
+        event: telegramEventCallbackQuery,
+      });
+    });
+
+    it('should not call action when it receives a non-callbackQuery event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.callbackQuery(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.shippingQuery', () => {
+    it('should call action when it receives a telegram shippingQuery event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.shippingQuery(Action),
+        event: telegramEventShippingQuery,
+      });
+    });
+
+    it('should not call action when it receives a non-shippingQuery event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.shippingQuery(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.preCheckoutQuery', () => {
+    it('should call action when it receives a telegram preCheckoutQuery event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.preCheckoutQuery(Action),
+        event: telegramEventPreCheckoutQuery,
+      });
+    });
+
+    it('should not call action when it receives a non-preCheckoutQuery event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.preCheckoutQuery(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+
+  describe('#telegram.poll', () => {
+    it('should call action when it receives a telegram poll event', async () => {
+      await expectRouteMatchTelegramEvent({
+        route: telegram.poll(Action),
+        event: telegramEventPoll,
+      });
+    });
+
+    it('should not call action when it receives a non-poll event', async () => {
+      await expectRouteNotMatchTelegramEvent({
+        route: telegram.poll(Action),
+        event: telegramEventTextMessage,
+      });
+    });
+  });
+});