From 7df0382a8faf7e8e541add8fd8297b399e55a8cb Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 11 Apr 2023 17:49:28 +0900 Subject: [PATCH 1/4] Fix #904 by improving app.message listener's TS compatibility while bringing breaking changes --- src/App-routes.spec.ts | 94 +++++++++++++++++++++++ src/App.ts | 117 ++++++++++++++++++++++++----- src/middleware/builtin.spec.ts | 13 ++-- src/middleware/builtin.ts | 15 +++- src/types/events/base-events.ts | 6 +- src/types/events/index.ts | 23 ++++-- src/types/events/message-events.ts | 6 ++ 7 files changed, 244 insertions(+), 30 deletions(-) diff --git a/src/App-routes.spec.ts b/src/App-routes.spec.ts index 75c6b04aa..be3d1dd51 100644 --- a/src/App-routes.spec.ts +++ b/src/App-routes.spec.ts @@ -828,6 +828,22 @@ describe('App event routing', () => { ack: noop, }); + const fakeSubtypedMessageEvent = ( + receiver: FakeReceiver, + subtype: string, + message: string, + ): Promise => receiver.sendEvent({ + body: { + type: 'event_callback', + event: { + type: 'message', + subtype, + text: message, + }, + }, + ack: noop, + }); + const controlledMiddleware = (shouldCallNext: boolean) => async ({ next }: { next?: NextFn }) => { if (next && shouldCallNext) { await next(); @@ -1012,6 +1028,84 @@ describe('App event routing', () => { // Assert assertMiddlewaresNotCalled(); }); + + it('should handle bot_message events', async () => { + // Act + app.message(PASS_STRING, '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'bot_message', message); + // Assert + assertMiddlewaresCalledOnce(); + assertMiddlewaresCalledOrder(); + }); + it('should not handle bot_message events when the constraints do not match', async () => { + // Act + app.message('foo-bar', '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'bot_message', message); + // Assert + assert.isFalse(fakeMiddleware1.calledOnce); + }); + it('should handle file_share events', async () => { + // Act + app.message(PASS_STRING, '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'file_share', message); + // Assert + assertMiddlewaresCalledOnce(); + assertMiddlewaresCalledOrder(); + }); + it('should not handle file_share events when the constraints do not match', async () => { + // Act + app.message('foo-bar', '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'file_share', message); + // Assert + assert.isFalse(fakeMiddleware1.calledOnce); + }); + it('should handle thread_broadcast events', async () => { + // Act + app.message(PASS_STRING, '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'thread_broadcast', message); + // Assert + assertMiddlewaresCalledOnce(); + assertMiddlewaresCalledOrder(); + }); + it('should not handle message_changed events', async () => { + // Act + app.message(PASS_STRING, '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'message_changed', message); + // Assert + assert.isFalse(fakeMiddleware1.calledOnce); + }); + it('should handle message_changed events when using allMessageSubtypes', async () => { + // Act + app.allMessageSubtypes(PASS_STRING, '- val', ...fakeMiddlewares); + await fakeSubtypedMessageEvent(fakeReceiver, 'message_changed', message); + // Assert + assertMiddlewaresCalledOnce(); + assertMiddlewaresCalledOrder(); + }); + + it('should provide better typed payloads', async () => { + app.message(async ({ payload }) => { + // verify it compiles + assert.isNotNull(payload.channel); + assert.isNotNull(payload.ts); + assert.isNotNull(payload.text); + assert.isNotNull(payload.blocks); + assert.isNotNull(payload.attachments); + }); + app.allMessageSubtypes(async ({ payload }) => { + // verify it compiles + if ((!payload.subtype || + payload.subtype === 'bot_message' || + payload.subtype === 'file_share' || + payload.subtype === 'thread_broadcast')) { + assert.isNotNull(payload.channel); + assert.isNotNull(payload.ts); + assert.isNotNull(payload.text); + assert.isNotNull(payload.blocks); + assert.isNotNull(payload.attachments); + } + }); + }); }); describe('Quick type compatibility checks', () => { diff --git a/src/App.ts b/src/App.ts index 573ade950..eb4a4ef56 100644 --- a/src/App.ts +++ b/src/App.ts @@ -50,9 +50,9 @@ import { InteractiveAction, ViewOutput, KnownOptionsPayloadFromType, - KnownEventFromType, SlashCommand, WorkflowStepEdit, + KnownEventFromType, } from './types'; import { IncomingEventType, getTypeAndConversation, assertNever } from './helpers'; import { CodedError, asCodedError, AppInitializationError, MultipleListenerError, ErrorCode, InvalidCustomPropertyError } from './errors'; @@ -182,9 +182,17 @@ export interface AnyErrorHandler extends ErrorHandler, ExtendedErrorHandler { } // Used only in this file -type MessageEventMiddleware< +type AllMessageEventMiddleware< + CustomContext extends StringIndexed = StringIndexed, +> = Middleware, CustomContext>; + +// Used only in this file +type FilteredMessageEventMiddleware< CustomContext extends StringIndexed = StringIndexed, -> = Middleware, CustomContext>; +> = Middleware, CustomContext>; class WebClientPool { private pool: { [token: string]: WebClient } = {}; @@ -535,21 +543,27 @@ export default class App MiddlewareCustomContext extends StringIndexed = StringIndexed, >( eventName: EventType, - ...listeners: Middleware, AppCustomContext & MiddlewareCustomContext>[] + ...listeners: Middleware< + SlackEventMiddlewareArgs, + AppCustomContext & MiddlewareCustomContext + >[] ): void; public event< EventType extends RegExp = RegExp, MiddlewareCustomContext extends StringIndexed = StringIndexed, >( eventName: EventType, - ...listeners: Middleware, AppCustomContext & MiddlewareCustomContext>[] + ...listeners: Middleware[] ): void; public event< EventType extends EventTypePattern = EventTypePattern, MiddlewareCustomContext extends StringIndexed = StringIndexed, >( eventNameOrPattern: EventType, - ...listeners: Middleware, AppCustomContext & MiddlewareCustomContext>[] + ...listeners: Middleware< + SlackEventMiddlewareArgs, + AppCustomContext & MiddlewareCustomContext + >[] ): void { let invalidEventName = false; if (typeof eventNameOrPattern === 'string') { @@ -581,7 +595,7 @@ export default class App */ public message< MiddlewareCustomContext extends StringIndexed = StringIndexed, - >(...listeners: MessageEventMiddleware[]): void; + >(...listeners: FilteredMessageEventMiddleware[]): void; /** * * @param pattern Used for filtering out messages that don't match. @@ -592,7 +606,7 @@ export default class App MiddlewareCustomContext extends StringIndexed = StringIndexed, >( pattern: string | RegExp, - ...listeners: MessageEventMiddleware[] + ...listeners: FilteredMessageEventMiddleware[] ): void; /** * @@ -605,9 +619,9 @@ export default class App public message< MiddlewareCustomContext extends StringIndexed = StringIndexed, >( - filter: MessageEventMiddleware, + filter: FilteredMessageEventMiddleware, pattern: string | RegExp, - ...listeners: MessageEventMiddleware[] + ...listeners: FilteredMessageEventMiddleware[] ): void; /** * @@ -618,8 +632,8 @@ export default class App public message< MiddlewareCustomContext extends StringIndexed = StringIndexed, >( - filter: MessageEventMiddleware, - ...listeners: MessageEventMiddleware[] + filter: FilteredMessageEventMiddleware, + ...listeners: FilteredMessageEventMiddleware[] ): void; /** * This allows for further control of the filtering and response logic. Patterns and middlewares are processed in @@ -630,16 +644,78 @@ export default class App public message< MiddlewareCustomContext extends StringIndexed = StringIndexed, >( - ...patternsOrMiddleware: (string | RegExp | MessageEventMiddleware)[] + ...patternsOrMiddleware: ( + | string + | RegExp + | FilteredMessageEventMiddleware)[] ): void; public message< MiddlewareCustomContext extends StringIndexed = StringIndexed, >( - ...patternsOrMiddleware: (string | RegExp | MessageEventMiddleware)[] + ...patternsOrMiddleware: ( + | string + | RegExp + | FilteredMessageEventMiddleware)[] + ): void { + const messageMiddleware = patternsOrMiddleware.map((patternOrMiddleware) => { + if (typeof patternOrMiddleware === 'string' || util.types.isRegExp(patternOrMiddleware)) { + return matchMessage(patternOrMiddleware, true); + } + return patternOrMiddleware; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }) as any; // FIXME: workaround for TypeScript 4.7 breaking changes + + this.listeners.push([ + onlyEvents, + matchEventType('message'), + ...messageMiddleware, + ] as Middleware[]); + } + + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >(...listeners: AllMessageEventMiddleware[]): void; + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >( + pattern: string | RegExp, + ...listeners: AllMessageEventMiddleware[] + ): void; + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >( + filter: AllMessageEventMiddleware, + pattern: string | RegExp, + ...listeners: AllMessageEventMiddleware[] + ): void; + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >( + filter: AllMessageEventMiddleware, + ...listeners: AllMessageEventMiddleware[] + ): void; + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >( + ...patternsOrMiddleware: ( + | string + | RegExp + | AllMessageEventMiddleware)[] + ): void; + /** + * Accepts all subtype events of message ones. + */ + public allMessageSubtypes< + MiddlewareCustomContext extends StringIndexed = StringIndexed, + >( + ...patternsOrMiddleware: ( + | string + | RegExp + | AllMessageEventMiddleware)[] ): void { const messageMiddleware = patternsOrMiddleware.map((patternOrMiddleware) => { if (typeof patternOrMiddleware === 'string' || util.types.isRegExp(patternOrMiddleware)) { - return matchMessage(patternOrMiddleware); + return matchMessage(patternOrMiddleware, false); } return patternOrMiddleware; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -943,8 +1019,15 @@ export default class App // Set body and payload // TODO: this value should eventually conform to AnyMiddlewareArgs - let payload: DialogSubmitAction | WorkflowStepEdit | SlackShortcut | KnownEventFromType | SlashCommand - | KnownOptionsPayloadFromType | BlockElementAction | ViewOutput | InteractiveAction; + let payload: DialogSubmitAction + | WorkflowStepEdit + | SlackShortcut + | KnownEventFromType + | SlashCommand + | KnownOptionsPayloadFromType + | BlockElementAction + | ViewOutput + | InteractiveAction; switch (type) { case IncomingEventType.Event: payload = (bodyArg as SlackEventMiddlewareArgs['body']).event; diff --git a/src/middleware/builtin.spec.ts b/src/middleware/builtin.spec.ts index d1f76b763..9a63bd05c 100644 --- a/src/middleware/builtin.spec.ts +++ b/src/middleware/builtin.spec.ts @@ -17,7 +17,7 @@ import { import { onlyCommands, onlyEvents, matchCommandName, matchEventType, subtype } from './builtin'; import { SlashCommand } from '../types/command'; import { AppMentionEvent, AppHomeOpenedEvent } from '../types/events'; -import { GenericMessageEvent } from '../types/events/message-events'; +import { GenericMessageEvent, MessagePostedEvent } from '../types/events/message-events'; // Test fixtures const validCommandPayload: SlashCommand = { @@ -92,7 +92,7 @@ describe('Built-in global middleware', () => { function matchesPatternTestCase( pattern: string | RegExp, matchingText: string, - buildFakeEvent: (content: string) => SlackEvent, + buildFakeEvent: (content: string) => MessagePostedEvent | AppMentionEvent, ): Mocha.AsyncFunc { return async () => { // Arrange @@ -859,7 +859,10 @@ interface MiddlewareCommonArgs { logger: Logger; client: WebClient; } -type MessageMiddlewareArgs = SlackEventMiddlewareArgs<'message'> & MiddlewareCommonArgs; +type MessageMiddlewareArgs = SlackEventMiddlewareArgs< +'message' | 'app_mention', +undefined | 'bot_message' | 'file_share' | 'thread_broadcast' | never +> & MiddlewareCommonArgs; type TokensRevokedMiddlewareArgs = SlackEventMiddlewareArgs<'tokens_revoked'> & MiddlewareCommonArgs; type MemberJoinedOrLeftChannelMiddlewareArgs = SlackEventMiddlewareArgs<'member_joined_channel' | 'member_left_channel'> & MiddlewareCommonArgs; @@ -870,7 +873,7 @@ async function importBuiltin(overrides: Override = {}): Promise import('./builtin'), overrides); } -function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] = ''): MessageEvent { +function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] = ''): MessagePostedEvent { const event: Partial = { type: 'message', channel: 'CHANNEL_ID', @@ -882,7 +885,7 @@ function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] } else { event.blocks = content; } - return event as MessageEvent; + return event as MessagePostedEvent; } function createFakeAppMentionEvent(text: string = ''): AppMentionEvent { diff --git a/src/middleware/builtin.ts b/src/middleware/builtin.ts index aa43e2445..1481ed706 100644 --- a/src/middleware/builtin.ts +++ b/src/middleware/builtin.ts @@ -206,18 +206,29 @@ export function matchConstraints( }; } +const messagePostedEventSubtypesAsArray = [undefined, 'bot_message', 'file_share', 'thread_broadcast']; + /* * Middleware that filters out messages that don't match pattern */ -export function matchMessage( +export function matchMessage< + Subtypes extends string | undefined = string | undefined, +>( pattern: string | RegExp, -): Middleware> { + onlyMessagePosted: boolean = false, // false for backward compatibility +): Middleware> { return async ({ event, context, next }) => { let tempMatches: RegExpMatchArray | null; if (!('text' in event) || event.text === undefined) { return; } + // Since version 3.14, handling only message posted events are allowed + if (onlyMessagePosted && + event.type === 'message' && + !messagePostedEventSubtypesAsArray.includes(event.subtype)) { + return; + } // Filter out messages or app mentions that don't contain the pattern if (typeof pattern === 'string') { diff --git a/src/types/events/base-events.ts b/src/types/events/base-events.ts index 378ae67e2..dab0a7830 100644 --- a/src/types/events/base-events.ts +++ b/src/types/events/base-events.ts @@ -95,8 +95,12 @@ export type EventTypePattern = string | RegExp; * this interface. That condition isn't enforced, since we're not interested in factoring out common properties from the * known event types. */ -export interface BasicSlackEvent { +export interface BasicSlackEvent< + Type extends string = string, + Subtype extends string | undefined = string | undefined, +> { type: Type; + subtype: Type extends 'message' | 'emoji_changed' ? Subtype : never; } interface BotProfile { diff --git a/src/types/events/index.ts b/src/types/events/index.ts index b888b4a00..cf873c031 100644 --- a/src/types/events/index.ts +++ b/src/types/events/index.ts @@ -26,8 +26,11 @@ export { /** * Arguments which listeners and middleware receive to process an event from Slack's Events API. */ -export interface SlackEventMiddlewareArgs { - payload: EventFromType; +export interface SlackEventMiddlewareArgs< + EventType extends string = string, + EventSubtype extends string | undefined | never = string | undefined | never, +> { + payload: EventFromType; event: this['payload']; message: EventType extends 'message' ? this['payload'] : never; body: EnvelopedEvent; @@ -71,10 +74,20 @@ interface Authorization { * When the string matches known event(s) from the `SlackEvent` union, only those types are returned (also as a union). * Otherwise, the `BasicSlackEvent` type is returned. */ -export type EventFromType = KnownEventFromType extends never ? +export type EventFromType< + T extends string, + ST extends string | undefined | never, +> = KnownEventFromType extends never ? BasicSlackEvent : - KnownEventFromType; -export type KnownEventFromType = Extract; + KnownEventFromType; + +export type KnownEventFromType< + T extends string, + ST extends string | undefined | never = string | undefined | never, +> = Extract; /** * Type function which tests whether or not the given `Event` contains a channel ID context for where the event diff --git a/src/types/events/message-events.ts b/src/types/events/message-events.ts index c64fe144a..e565d6260 100644 --- a/src/types/events/message-events.ts +++ b/src/types/events/message-events.ts @@ -1,5 +1,11 @@ import { MessageAttachment, KnownBlock, Block, MessageMetadata } from '@slack/types'; +export type MessagePostedEvent = + | GenericMessageEvent + | BotMessageEvent + | FileShareMessageEvent + | ThreadBroadcastMessageEvent; + export type MessageEvent = | GenericMessageEvent | BotMessageEvent From 16c2c3eb240e65ed95040180370778bd2a1745df Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 11 Apr 2023 18:12:08 +0900 Subject: [PATCH 2/4] Fix tests --- src/middleware/builtin.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/middleware/builtin.spec.ts b/src/middleware/builtin.spec.ts index 9a63bd05c..cd4649512 100644 --- a/src/middleware/builtin.spec.ts +++ b/src/middleware/builtin.spec.ts @@ -17,7 +17,7 @@ import { import { onlyCommands, onlyEvents, matchCommandName, matchEventType, subtype } from './builtin'; import { SlashCommand } from '../types/command'; import { AppMentionEvent, AppHomeOpenedEvent } from '../types/events'; -import { GenericMessageEvent, MessagePostedEvent } from '../types/events/message-events'; +import { GenericMessageEvent } from '../types/events/message-events'; // Test fixtures const validCommandPayload: SlashCommand = { @@ -92,7 +92,7 @@ describe('Built-in global middleware', () => { function matchesPatternTestCase( pattern: string | RegExp, matchingText: string, - buildFakeEvent: (content: string) => MessagePostedEvent | AppMentionEvent, + buildFakeEvent: (content: string) => AppMentionEvent | MessageEvent, ): Mocha.AsyncFunc { return async () => { // Arrange @@ -860,8 +860,8 @@ interface MiddlewareCommonArgs { client: WebClient; } type MessageMiddlewareArgs = SlackEventMiddlewareArgs< -'message' | 'app_mention', -undefined | 'bot_message' | 'file_share' | 'thread_broadcast' | never +'message', +undefined | 'bot_message' | 'file_share' | 'thread_broadcast' > & MiddlewareCommonArgs; type TokensRevokedMiddlewareArgs = SlackEventMiddlewareArgs<'tokens_revoked'> & MiddlewareCommonArgs; @@ -873,7 +873,7 @@ async function importBuiltin(overrides: Override = {}): Promise import('./builtin'), overrides); } -function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] = ''): MessagePostedEvent { +function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] = ''): MessageEvent { const event: Partial = { type: 'message', channel: 'CHANNEL_ID', @@ -885,7 +885,7 @@ function createFakeMessageEvent(content: string | GenericMessageEvent['blocks'] } else { event.blocks = content; } - return event as MessagePostedEvent; + return event as MessageEvent; } function createFakeAppMentionEvent(text: string = ''): AppMentionEvent { From bc7af32d5e1764a3293a08e99d4d20f0acfb101f Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Tue, 11 Apr 2023 18:42:31 +0900 Subject: [PATCH 3/4] Fix issues with app.event --- src/App.ts | 2 +- src/types/events/index.ts | 8 +++---- types-tests/message.test-d.ts | 40 ++++++++++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/App.ts b/src/App.ts index eb4a4ef56..29bfb2c75 100644 --- a/src/App.ts +++ b/src/App.ts @@ -544,7 +544,7 @@ export default class App >( eventName: EventType, ...listeners: Middleware< - SlackEventMiddlewareArgs, + SlackEventMiddlewareArgs, AppCustomContext & MiddlewareCustomContext >[] ): void; diff --git a/src/types/events/index.ts b/src/types/events/index.ts index cf873c031..4a6f6c1a4 100644 --- a/src/types/events/index.ts +++ b/src/types/events/index.ts @@ -4,6 +4,7 @@ import { SayFn } from '../utilities'; export * from './base-events'; export { + MessagePostedEvent, GenericMessageEvent, BotMessageEvent, ChannelArchiveMessageEvent, @@ -84,10 +85,9 @@ export type EventFromType< export type KnownEventFromType< T extends string, ST extends string | undefined | never = string | undefined | never, -> = Extract; +> = T extends 'message' | 'emoji_changed' + ? Extract + : Extract; /** * Type function which tests whether or not the given `Event` contains a channel ID context for where the event diff --git a/types-tests/message.test-d.ts b/types-tests/message.test-d.ts index 65b24e9c1..c2517f181 100644 --- a/types-tests/message.test-d.ts +++ b/types-tests/message.test-d.ts @@ -1,5 +1,5 @@ import { expectNotType, expectType, expectError } from 'tsd'; -import { App, MessageEvent, GenericMessageEvent, BotMessageEvent, MessageRepliedEvent, MeMessageEvent, MessageDeletedEvent, ThreadBroadcastMessageEvent, MessageChangedEvent, EKMAccessDeniedMessageEvent } from '..'; +import { App, MessageEvent, GenericMessageEvent, BotMessageEvent, MessageRepliedEvent, MeMessageEvent, MessageDeletedEvent, ThreadBroadcastMessageEvent, MessageChangedEvent, EKMAccessDeniedMessageEvent, MessagePostedEvent } from '..'; const app = new App({ token: 'TOKEN', signingSecret: 'Signing Secret' }); @@ -7,6 +7,40 @@ expectType( // TODO: Resolve the event type when having subtype in a listener constraint // app.message({pattern: 'foo', subtype: 'message_replied'}, async ({ message }) => {}); app.message(async ({ message }) => { + expectType(message); + + message.channel; // the property access should compile + message.user; // the property access should compile + + if (message.subtype === undefined) { + expectType(message); + expectNotType(message); + message.user; // the property access should compile + message.channel; // the property access should compile + message.team; // the property access should compile + } + if (message.subtype === 'bot_message') { + expectType(message); + expectNotType(message); + message.user; // the property access should compile + message.channel; // the property access should compile + } + if (message.subtype === 'thread_broadcast') { + expectType(message); + expectNotType(message); + message.channel; // the property access should compile + message.thread_ts; // the property access should compile + message.ts; // the property access should compile + message.root; // the property access should compile + } + + await Promise.resolve(message); + }) +); + + +expectType( + app.allMessageSubtypes(async ({ message }) => { expectType(message); message.channel; // the property access should compile @@ -68,5 +102,5 @@ expectType( } await Promise.resolve(message); - }), -); + }) +); \ No newline at end of file From 5dce16849af2123ea6accb65db6fcd6611fc4aa5 Mon Sep 17 00:00:00 2001 From: Kazuhiro Sera Date: Wed, 12 Apr 2023 10:03:45 +0900 Subject: [PATCH 4/4] Update --- src/middleware/builtin.ts | 2 +- types-tests/event.test-d.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/middleware/builtin.ts b/src/middleware/builtin.ts index 1481ed706..e192bb38e 100644 --- a/src/middleware/builtin.ts +++ b/src/middleware/builtin.ts @@ -223,10 +223,10 @@ export function matchMessage< if (!('text' in event) || event.text === undefined) { return; } - // Since version 3.14, handling only message posted events are allowed if (onlyMessagePosted && event.type === 'message' && !messagePostedEventSubtypesAsArray.includes(event.subtype)) { + // Handle only message posted events return; } diff --git a/types-tests/event.test-d.ts b/types-tests/event.test-d.ts index b24a6687a..9e73a5d21 100644 --- a/types-tests/event.test-d.ts +++ b/types-tests/event.test-d.ts @@ -1,5 +1,5 @@ import { expectNotType, expectType } from 'tsd'; -import { App, SlackEvent, AppMentionEvent, ReactionAddedEvent, ReactionRemovedEvent, UserHuddleChangedEvent, UserProfileChangedEvent, UserStatusChangedEvent, PinAddedEvent, SayFn, PinRemovedEvent } from '..'; +import { App, SlackEvent, AppMentionEvent, ReactionAddedEvent, ReactionRemovedEvent, UserHuddleChangedEvent, UserProfileChangedEvent, UserStatusChangedEvent, PinAddedEvent, SayFn, PinRemovedEvent, EmojiChangedEvent } from '..'; const app = new App({ token: 'TOKEN', signingSecret: 'Signing Secret' }); @@ -80,3 +80,11 @@ expectType( await Promise.resolve(event); }) ); + +expectType( + app.event('emoji_changed', async ({ event }) => { + expectType(event); + expectNotType(event); + await Promise.resolve(event); + }) +);