Skip to content

Commit

Permalink
todos, add view routing unit tests, expose a createDummyViewClosed ar…
Browse files Browse the repository at this point in the history
…gs test helper.
  • Loading branch information
Filip Maj committed Sep 19, 2024
1 parent a736242 commit 2a575b2
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export interface ShortcutConstraints<S extends SlackShortcut = SlackShortcut> {
type?: S['type'];
callback_id?: string | RegExp;
}

// TODO: add a type parameter here, just like the above constraint interfaces have.
export interface ViewConstraints {
callback_id?: string | RegExp;
type?: 'view_closed' | 'view_submission';
Expand Down
19 changes: 0 additions & 19 deletions test/unit/App-routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,6 @@ describe('App event routing', () => {

it('should acknowledge any of possible events', async () => {
// Arrange
const viewFn = sinon.fake.resolves({});
const optionsFn = sinon.fake.resolves({});
overrides = buildOverrides([withNoopWebClient()]);
const MockApp = await importApp(overrides);
Expand All @@ -484,12 +483,6 @@ describe('App event routing', () => {
authorize: sinon.fake.resolves(dummyAuthorizationResult),
});

app.view('view_callback_id', async () => {
await viewFn();
});
app.view({ callback_id: 'view_callback_id', type: 'view_closed' }, async () => {
await viewFn();
});
app.options('external_select_action_id', async () => {
await optionsFn();
});
Expand Down Expand Up @@ -518,22 +511,10 @@ describe('App event routing', () => {
app.message('hello', noop);
app.command('/echo', noop);
app.command(/\/e.*/, noop);

assert.isTrue(fakeLogger.error.called);
fakeLogger.error.reset();

assert.isTrue(fakeLogger.error.called);

app.error(fakeErrorHandler);
await Promise.all(dummyReceiverEvents.map((event) => fakeReceiver.sendEvent(event)));

// Assert
assert.equal(actionFn.callCount, 3);
assert.equal(shortcutFn.callCount, 4);
assert.equal(viewFn.callCount, 5);
assert.equal(optionsFn.callCount, 4);
assert.equal(ackFn.callCount, dummyReceiverEvents.length);
assert(fakeErrorHandler.notCalled);
});

// This test confirms authorize is being used for org events
Expand Down
21 changes: 9 additions & 12 deletions test/unit/App/middleware.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,11 @@ describe('App middleware processing', () => {
app.error(fakeErrorHandler);
await fakeReceiver.sendEvent(
createDummyViewSubmissionMiddlewareArgs(
{
id: 'V111',
type: 'modal',
callback_id: 'view-id',
},
{
response_urls: [
{
Expand All @@ -577,11 +582,6 @@ describe('App middleware processing', () => {
},
],
},
{
id: 'V111',
type: 'modal',
callback_id: 'view-id',
},
),
);

Expand Down Expand Up @@ -1069,13 +1069,10 @@ describe('App middleware processing', () => {
app.error(fakeErrorHandler);

await fakeReceiver.sendEvent(
createDummyViewSubmissionMiddlewareArgs(
{},
{
callback_id,
app_installed_team_id,
},
),
createDummyViewSubmissionMiddlewareArgs({
callback_id,
app_installed_team_id,
}),
);

assert.isTrue(ackCalled);
Expand Down
101 changes: 101 additions & 0 deletions test/unit/App/routing-view.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import sinon, { type SinonSpy } from 'sinon';
import {
FakeReceiver,
type Override,
createFakeLogger,
createDummyViewSubmissionMiddlewareArgs,
createDummyViewClosedMiddlewareArgs,
importApp,
mergeOverrides,
noopMiddleware,
withConversationContext,
withMemoryStore,
withNoopAppMetadata,
withNoopWebClient,
} from '../helpers';
import type App from '../../../src/App';

function buildOverrides(secondOverrides: Override[]): Override {
return mergeOverrides(
withNoopAppMetadata(),
withNoopWebClient(),
...secondOverrides,
withMemoryStore(sinon.fake()),
withConversationContext(sinon.fake.returns(noopMiddleware)),
);
}

describe('App view() routing', () => {
let fakeReceiver: FakeReceiver;
let fakeHandler: SinonSpy;
const fakeLogger = createFakeLogger();
let dummyAuthorizationResult: { botToken: string; botId: string };
let MockApp: Awaited<ReturnType<typeof importApp>>;
let app: App;

beforeEach(async () => {
fakeLogger.error.reset();
fakeReceiver = new FakeReceiver();
fakeHandler = sinon.fake();
dummyAuthorizationResult = { botToken: '', botId: '' };
MockApp = await importApp(buildOverrides([]));
app = new MockApp({
logger: fakeLogger,
receiver: fakeReceiver,
authorize: sinon.fake.resolves(dummyAuthorizationResult),
});
});

it('should throw if provided a constraint with unknown view constraint keys', async () => {
// @ts-ignore providing known invalid view constraint parameter
app.view({ id: 'boom' }, fakeHandler);
sinon.assert.calledWithMatch(fakeLogger.error, 'unknown constraint keys');
});
describe('for view submission events', () => {
it('should route a view submission event to a handler registered with `view(string)` that matches the callback ID', async () => {
app.view('my_id', fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewSubmissionMiddlewareArgs({ callback_id: 'my_id' }),
});
sinon.assert.called(fakeHandler);
});
it('should route a view submission event to a handler registered with `view(RegExp)` that matches the callback ID', async () => {
app.view(/my_action/, fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewSubmissionMiddlewareArgs({ callback_id: 'my_action' }),
});
sinon.assert.called(fakeHandler);
});
it('should route a view submission event to a handler registered with `view({callback_id})` that matches callback ID', async () => {
app.view({ callback_id: 'my_id' }, fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewSubmissionMiddlewareArgs({ callback_id: 'my_id' }),
});
sinon.assert.called(fakeHandler);
});
it('should route a view submission event to a handler registered with `view({type:view_submission})`', async () => {
app.view({ type: 'view_submission' }, fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewSubmissionMiddlewareArgs(),
});
sinon.assert.called(fakeHandler);
});
});

describe('for view closed events', () => {
it('should route a view closed event to a handler registered with `view({callback_id, type:view_closed})` that matches callback ID', async () => {
app.view({ callback_id: 'my_id', type: 'view_closed' }, fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewClosedMiddlewareArgs({ callback_id: 'my_id', type: 'view_closed' }),
});
sinon.assert.called(fakeHandler);
});
it('should route a view closed event to a handler registered with `view({type:view_closed})`', async () => {
app.view({ type: 'view_closed' }, fakeHandler);
await fakeReceiver.sendEvent({
...createDummyViewClosedMiddlewareArgs(),
});
sinon.assert.called(fakeHandler);
});
});
});
51 changes: 39 additions & 12 deletions test/unit/helpers/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {
SlackEventMiddlewareArgs,
SlackShortcutMiddlewareArgs,
SlackViewMiddlewareArgs,
ViewClosedAction,
ViewSubmitAction,
ViewOutput,
} from '../../../src/types';
Expand Down Expand Up @@ -110,13 +111,8 @@ export function createDummyBlockActionEventMiddlewareArgs(
};
}

export function createDummyViewSubmissionMiddlewareArgs(
// biome-ignore lint/suspicious/noExplicitAny: allow mocking tools to provide any override
bodyOverrides?: Record<string, any>,
viewOverrides?: Partial<ViewOutput>,
viewEvent?: ViewSubmitAction,
): SlackViewMiddlewareArgs<ViewSubmitAction> {
const payload: ViewOutput = {
function createDummyViewOutput(viewOverrides?: Partial<ViewOutput>): ViewOutput {
return {
type: 'view',
id: 'V1234',
callback_id: 'Cb1234',
Expand All @@ -136,12 +132,18 @@ export function createDummyViewSubmissionMiddlewareArgs(
notify_on_close: false,
...viewOverrides,
};
}

export function createDummyViewSubmissionMiddlewareArgs(
viewOverrides?: Partial<ViewOutput>,
// biome-ignore lint/suspicious/noExplicitAny: allow mocking tools to provide any override
bodyOverrides?: Record<string, any>,
): SlackViewMiddlewareArgs<ViewSubmitAction> {
const payload = createDummyViewOutput(viewOverrides);
const event: ViewSubmitAction = {
...(viewEvent || {
type: 'view_submission',
team: { id: team, domain: 'slack.com' },
user: { id: user, name: 'filmaj' },
}),
type: 'view_submission',
team: { id: team, domain: 'slack.com' },
user: { id: user, name: 'filmaj' },
view: payload,
api_app_id: app_id,
token,
Expand All @@ -157,6 +159,31 @@ export function createDummyViewSubmissionMiddlewareArgs(
};
}

export function createDummyViewClosedMiddlewareArgs(
viewOverrides?: Partial<ViewOutput>,
// biome-ignore lint/suspicious/noExplicitAny: allow mocking tools to provide any override
bodyOverrides?: Record<string, any>,
): SlackViewMiddlewareArgs<ViewClosedAction> {
const payload = createDummyViewOutput(viewOverrides);
const event: ViewClosedAction = {
type: 'view_closed',
team: { id: team, domain: 'slack.com' },
user: { id: user, name: 'filmaj' },
view: payload,
api_app_id: app_id,
token,
is_cleared: false,
...bodyOverrides,
};
return {
payload,
view: payload,
body: event,
respond,
ack: () => Promise.resolve(),
};
}

export function createDummyMessageShortcutMiddlewareArgs(
callback_id = 'Cb1234',
shortcut?: MessageShortcut,
Expand Down

0 comments on commit 2a575b2

Please sign in to comment.