Repository: Yoctol/bottender
Pull request #472 (2019-10-11 08:57:03)

feat(withProps): implement withProps
This adds the ability to pass properties to the action:

```js
async function SendSomeText(context: Context, { text }: Props) {
  await context.sendText(text);
}
```

```js
const { withProps } = require('bottender');

module.exports = function App() {
  return withProps(SendSomeText, { text: 'Hello, World!' });
};
```


Patch:
diff --git a/packages/bottender/src/bot/Bot.ts b/packages/bottender/src/bot/Bot.ts
index 36cacbe4..cc062a7f 100644
--- a/packages/bottender/src/bot/Bot.ts
+++ b/packages/bottender/src/bot/Bot.ts
@@ -34,7 +34,7 @@ function createMemorySessionStore(): SessionStore {
   return new CacheBasedSessionStore(cache, MINUTES_IN_ONE_YEAR);
 }
 
-function run(fn: FunctionalHandler): FunctionalHandler {
+export function run(fn: FunctionalHandler): FunctionalHandler {
   return async function Run(context): Promise<void> {
     let nextDialog: FunctionalHandler | void = fn;
 
@@ -42,7 +42,7 @@ function run(fn: FunctionalHandler): FunctionalHandler {
       // TODO: improve this debug helper
       debugDialog(`Current Dialog: ${nextDialog.name || 'Anonymous'}`);
       // eslint-disable-next-line no-await-in-loop
-      nextDialog = await nextDialog(context);
+      nextDialog = await nextDialog(context, {});
     } while (typeof nextDialog === 'function');
 
     return nextDialog;
diff --git a/packages/bottender/src/browser.ts b/packages/bottender/src/browser.ts
index d1801d63..016f17d0 100644
--- a/packages/bottender/src/browser.ts
+++ b/packages/bottender/src/browser.ts
@@ -1,4 +1,5 @@
 export { default as chain } from './chain';
+export { default as withProps } from './withProps';
 
 /* Bot */
 export { default as Bot } from './bot/Bot';
diff --git a/packages/bottender/src/index.ts b/packages/bottender/src/index.ts
index 15d10273..cc67ebf6 100644
--- a/packages/bottender/src/index.ts
+++ b/packages/bottender/src/index.ts
@@ -3,6 +3,7 @@ import * as utils from './utils';
 export { createServer } from '@bottender/express';
 
 export { default as chain } from './chain';
+export { default as withProps } from './withProps';
 
 /* Bot */
 export { default as Bot } from './bot/Bot';
diff --git a/packages/bottender/src/withProps.ts b/packages/bottender/src/withProps.ts
new file mode 100644
index 00000000..ed2380fa
--- /dev/null
+++ b/packages/bottender/src/withProps.ts
@@ -0,0 +1,19 @@
+import Context from './context/Context';
+import { Action, Props } from './types';
+
+function withProps(action: Action, props: Props): Action {
+  // TODO: we may only apply this on dev env
+  Object.freeze(props);
+
+  const actionWithProps = (context: Context): Action => {
+    return action.bind(null, context, props);
+  };
+
+  Object.defineProperty(actionWithProps, 'name', {
+    value: `withProps(${action.name || 'Anonymous'})`,
+  });
+
+  return actionWithProps;
+}
+
+export default withProps;


Test patch:
diff --git a/packages/bottender/src/__tests__/browser.spec.ts b/packages/bottender/src/__tests__/browser.spec.ts
index 03fd7ca8..e264830c 100644
--- a/packages/bottender/src/__tests__/browser.spec.ts
+++ b/packages/bottender/src/__tests__/browser.spec.ts
@@ -25,4 +25,8 @@ describe('browser', () => {
   it('export chain', () => {
     expect(core.chain).toBeDefined();
   });
+
+  it('export withProps', () => {
+    expect(core.withProps).toBeDefined();
+  });
 });
diff --git a/packages/bottender/src/__tests__/index.spec.ts b/packages/bottender/src/__tests__/index.spec.ts
index 60fc7209..3128a1a1 100644
--- a/packages/bottender/src/__tests__/index.spec.ts
+++ b/packages/bottender/src/__tests__/index.spec.ts
@@ -54,4 +54,8 @@ describe('core', () => {
   it('export chain', () => {
     expect(core.chain).toBeDefined();
   });
+
+  it('export withProps', () => {
+    expect(core.withProps).toBeDefined();
+  });
 });
diff --git a/packages/bottender/src/__tests__/withProps.spec.ts b/packages/bottender/src/__tests__/withProps.spec.ts
new file mode 100644
index 00000000..1e971c5f
--- /dev/null
+++ b/packages/bottender/src/__tests__/withProps.spec.ts
@@ -0,0 +1,28 @@
+import Context from '../context/Context';
+import withProps from '../withProps';
+import { Props } from '../types';
+import { run } from '../bot/Bot';
+
+function setup() {
+  const context = {
+    sendText: jest.fn(),
+  };
+
+  return {
+    context,
+  };
+}
+
+async function SendSomeText(context: Context, { text }: Props) {
+  await context.sendText(text);
+}
+
+it('should support', async () => {
+  const { context } = setup();
+
+  const SendHello = withProps(SendSomeText, { text: 'hello' });
+
+  await run(SendHello)(context);
+
+  expect(context.sendText).toBeCalledWith('hello');
+});
diff --git a/packages/bottender/src/bot/__tests__/Bot.spec.ts b/packages/bottender/src/bot/__tests__/Bot.spec.ts
index 60e8e011..350b7303 100644
--- a/packages/bottender/src/bot/__tests__/Bot.spec.ts
+++ b/packages/bottender/src/bot/__tests__/Bot.spec.ts
@@ -124,14 +124,17 @@ describe('#createRequestHandler', () => {
     const body = {};
     await requestHandler(body);
 
-    expect(handler).toBeCalledWith({
-      event: {},
-      session: expect.objectContaining({
-        id: 'any:__id__',
-        platform: 'any',
-        user: {},
-      }),
-    });
+    expect(handler).toBeCalledWith(
+      {
+        event: {},
+        session: expect.objectContaining({
+          id: 'any:__id__',
+          platform: 'any',
+          user: {},
+        }),
+      },
+      {}
+    );
   });
 
   it('should return response in sync mode', async () => {
@@ -241,7 +244,8 @@ describe('#createRequestHandler', () => {
     expect(handler).toBeCalledWith(
       expect.objectContaining({
         session: undefined,
-      })
+      }),
+      {}
     );
   });
 
diff --git a/packages/bottender/src/bot/__tests__/TelegramBot.spec.ts b/packages/bottender/src/bot/__tests__/TelegramBot.spec.ts
index e85a2586..6bd2e002 100644
--- a/packages/bottender/src/bot/__tests__/TelegramBot.spec.ts
+++ b/packages/bottender/src/bot/__tests__/TelegramBot.spec.ts
@@ -72,7 +72,7 @@ describe('#createLongPollingRuntime', () => {
       .mockImplementationOnce(() => {
         bot.stop();
         expect(bot.connector.client.getUpdates).toBeCalledWith({});
-        expect(handler).toBeCalledWith(expect.any(Object));
+        expect(handler).toBeCalledWith(expect.any(Object), {});
         done();
       });
 
@@ -122,7 +122,7 @@ describe('#createLongPollingRuntime', () => {
           allowed_updates: ['message', 'edited_channel_post', 'callback_query'],
           offset: 1,
         });
-        expect(handler).toBeCalledWith(expect.any(Object));
+        expect(handler).toBeCalledWith(expect.any(Object), {});
         done();
       });