Skip to content

Commit 4d0a0a2

Browse files
authoredSep 1, 2021
feat: interactWith sends the association action to your base API when triggered (#6)
* feat: interactWith now sends any associated action back to your app * interactWith tests
1 parent 1765817 commit 4d0a0a2

File tree

2 files changed

+235
-25
lines changed

2 files changed

+235
-25
lines changed
 

‎src/__tests__/slack-testing-library.test.ts

+177
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { View } from "@slack/types";
22
import { Message } from "@slack/web-api/dist/response/ChatScheduleMessageResponse";
33
import { Server } from "http";
4+
import fetch from "cross-fetch";
45
import { SlackTestingLibrary } from "../slack-testing-library";
56
import { startServer } from "../util/server";
67

78
jest.mock("../util/server");
89
jest.mock("http");
10+
jest.mock("cross-fetch");
911

1012
export const createMockServer = ({
1113
listen,
@@ -260,4 +262,179 @@ describe("SlackTestingLibrary", () => {
260262
await sl.getByText("Match: 1234");
261263
});
262264
});
265+
266+
describe("#interactWith()", () => {
267+
it("should throw an error if the server hasn't been initialised", async () => {
268+
const sl = new SlackTestingLibrary({
269+
baseUrl: "https://www.github.com/chrishutchinson/slack-testing-library",
270+
});
271+
272+
await expect(sl.interactWith("button", "Label")).rejects.toThrow(
273+
"Start the Slack listening server first by awaiting `sl.init()`"
274+
);
275+
});
276+
277+
it("should throw an error if an active screen hasn't been set", async () => {
278+
const sl = new SlackTestingLibrary({
279+
baseUrl: "https://www.github.com/chrishutchinson/slack-testing-library",
280+
});
281+
282+
(startServer as jest.Mock<Promise<Server>>).mockImplementation(async () =>
283+
createMockServer()
284+
);
285+
286+
await sl.init();
287+
288+
await expect(sl.interactWith("button", "Label")).rejects.toThrow(
289+
"No active screen"
290+
);
291+
});
292+
293+
describe("active screen: view", () => {
294+
it("should throw if an element with the type and label can't be found in the view", async () => {
295+
const sl = new SlackTestingLibrary({
296+
baseUrl:
297+
"https://www.github.com/chrishutchinson/slack-testing-library",
298+
});
299+
300+
(startServer as jest.Mock<Promise<Server>>).mockImplementation(
301+
async ({ onViewChange }) => {
302+
// Set the active screen
303+
onViewChange({
304+
blocks: [
305+
{
306+
type: "section",
307+
text: {
308+
text: "Match: 1234",
309+
type: "plain_text",
310+
},
311+
accessory: {
312+
type: "button",
313+
action_id: "sample_button_action_id",
314+
text: {
315+
text: "Match: 5678",
316+
type: "plain_text",
317+
},
318+
},
319+
},
320+
],
321+
} as View);
322+
323+
return createMockServer();
324+
}
325+
);
326+
327+
await sl.init();
328+
329+
await expect(sl.interactWith("button", "Match: 1234")).rejects.toThrow(
330+
"Unable to find button with the label 'Match: 1234'."
331+
);
332+
});
333+
334+
it("should throw if a matching element is found in the view but it doesn't have an action ID", async () => {
335+
const sl = new SlackTestingLibrary({
336+
baseUrl:
337+
"https://www.github.com/chrishutchinson/slack-testing-library",
338+
});
339+
340+
(startServer as jest.Mock<Promise<Server>>).mockImplementation(
341+
async ({ onViewChange }) => {
342+
// Set the active screen
343+
onViewChange({
344+
blocks: [
345+
{
346+
type: "section",
347+
text: {
348+
text: "Match: 1234",
349+
type: "plain_text",
350+
},
351+
accessory: {
352+
type: "button",
353+
text: {
354+
text: "Match: 1234",
355+
type: "plain_text",
356+
},
357+
},
358+
},
359+
],
360+
} as View);
361+
362+
return createMockServer();
363+
}
364+
);
365+
366+
await sl.init();
367+
368+
await expect(sl.interactWith("button", "Match: 1234")).rejects.toThrow(
369+
"Unable to interact with the matching button element. It does not have an associated action ID."
370+
);
371+
});
372+
373+
it("should call the URL provided in the constructor with a block action payload", async () => {
374+
const sl = new SlackTestingLibrary({
375+
baseUrl:
376+
"https://www.github.com/chrishutchinson/slack-testing-library",
377+
actor: {
378+
teamId: "T1234567",
379+
userId: "U1234567",
380+
},
381+
});
382+
383+
(startServer as jest.Mock<Promise<Server>>).mockImplementation(
384+
async ({ onViewChange }) => {
385+
// Set the active screen
386+
onViewChange({
387+
blocks: [
388+
{
389+
type: "section",
390+
text: {
391+
text: "Match: 1234",
392+
type: "plain_text",
393+
},
394+
accessory: {
395+
type: "button",
396+
action_id: "sample_button_action_id",
397+
text: {
398+
text: "Match: 1234",
399+
type: "plain_text",
400+
},
401+
},
402+
},
403+
],
404+
} as View);
405+
406+
return createMockServer();
407+
}
408+
);
409+
410+
await sl.init();
411+
412+
await sl.interactWith("button", "Match: 1234");
413+
414+
expect(fetch).toHaveBeenCalledWith(
415+
"https://www.github.com/chrishutchinson/slack-testing-library",
416+
{
417+
method: "post",
418+
headers: {
419+
"Content-Type": "application/json",
420+
},
421+
body: JSON.stringify({
422+
payload: JSON.stringify({
423+
user: {
424+
id: "U1234567",
425+
team_id: "T1234567",
426+
},
427+
type: "block_actions",
428+
actions: [
429+
{
430+
action_id: "sample_button_action_id",
431+
},
432+
],
433+
}),
434+
}),
435+
}
436+
);
437+
});
438+
});
439+
});
263440
});

‎src/slack-testing-library.ts

+58-25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Server } from "http";
22
import fetch from "cross-fetch";
3-
import { Button, KnownBlock, View } from "@slack/types";
3+
import { Button, KnownBlock, SectionBlock, View } from "@slack/types";
44
import { Message } from "@slack/web-api/dist/response/ChatPostMessageResponse";
55
import { WebAPICallResult } from "@slack/web-api";
66

@@ -145,6 +145,31 @@ export class SlackTestingLibrary {
145145
});
146146
}
147147

148+
private async fireInteraction(actionId: string) {
149+
this.checkActorStatus();
150+
151+
await fetch(this.options.baseUrl, {
152+
method: "post",
153+
headers: {
154+
"Content-Type": "application/json",
155+
},
156+
body: JSON.stringify({
157+
payload: JSON.stringify({
158+
user: {
159+
id: this.options.actor!.userId,
160+
team_id: this.options.actor!.teamId,
161+
},
162+
type: "block_actions",
163+
actions: [
164+
{
165+
action_id: actionId,
166+
},
167+
],
168+
}),
169+
}),
170+
});
171+
}
172+
148173
private async checkRequestLog(
149174
matcher: Partial<{
150175
url: string;
@@ -242,25 +267,28 @@ export class SlackTestingLibrary {
242267
}
243268
}
244269

245-
/**
246-
* Opens the "App Home" view
247-
*/
248-
async openHome() {
249-
this.checkServerStatus();
250-
270+
private checkActorStatus() {
251271
if (!this.options.actor) {
252272
throw new Error(
253273
"Please provide an actor team ID and user ID when you initialise SlackTester"
254274
);
255275
}
276+
}
277+
278+
/**
279+
* Opens the "App Home" view
280+
*/
281+
async openHome() {
282+
this.checkServerStatus();
283+
this.checkActorStatus();
256284

257285
await this.fireEvent({
258286
type: "event",
259-
team_id: this.options.actor.teamId,
287+
team_id: this.options.actor!.teamId,
260288
event: {
261289
type: "app_home_opened",
262290
},
263-
user: this.options.actor.userId,
291+
user: this.options.actor!.userId,
264292
} as SlackEvent<any>);
265293
}
266294

@@ -275,22 +303,17 @@ export class SlackTestingLibrary {
275303

276304
async mentionApp({ channelId }: { channelId: string }) {
277305
this.checkServerStatus();
278-
279-
if (!this.options.actor) {
280-
throw new Error(
281-
"Please provide an actor team ID and user ID when you initialise SlackTester"
282-
);
283-
}
306+
this.checkActorStatus();
284307

285308
const timestamp = Date.now();
286309

287310
await this.fireEvent({
288311
type: "event",
289-
team_id: this.options.actor.teamId,
312+
team_id: this.options.actor!.teamId,
290313
event: {
291314
type: "app_mention",
292-
user: this.options.actor.userId,
293-
team: this.options.actor.teamId,
315+
user: this.options.actor!.userId,
316+
team: this.options.actor!.teamId,
294317
text: `<@${this.options.app.botId}>`,
295318
ts: (timestamp / 1000).toFixed(6),
296319
channel: channelId,
@@ -333,9 +356,21 @@ export class SlackTestingLibrary {
333356

334357
if (!matchingElement) {
335358
throw new Error(
336-
`Unable to find ${elementType} with the label ${label}`
359+
`Unable to find ${elementType} with the label '${label}'.`
337360
);
338361
}
362+
363+
const { action_id } = (matchingElement as SectionBlock)
364+
.accessory as Button;
365+
366+
if (!action_id) {
367+
throw new Error(
368+
`Unable to interact with the matching ${elementType} element. It does not have an associated action ID.`
369+
);
370+
}
371+
372+
await this.fireInteraction(action_id);
373+
339374
return;
340375
}
341376

@@ -391,24 +426,22 @@ export class SlackTestingLibrary {
391426
*
392427
* @returns boolean Whether or not a view has been published
393428
*/
394-
async hasViewPublish() {
429+
async hasViewPublish(count = 1) {
395430
this.checkServerStatus();
396431

397432
const requestLog = await this.checkRequestLog({
398433
url: "/slack/api/views.publish",
399434
});
400435

401-
if (requestLog.length === 0) {
436+
if (requestLog.length === 0 && count !== 0) {
402437
throw new Error("Did not find any matching view publishes");
403438
}
404439

405-
if (requestLog.length > 1) {
440+
if (requestLog.length !== count) {
406441
throw new Error(
407-
"Found more than one matching view publishes. Use `hasManyViewPublish` or `hasViewPublish(count)`."
442+
`Did not find ${count} matching view publishes (got ${requestLog.length}).`
408443
);
409444
}
410-
411-
return true;
412445
}
413446

414447
/**

0 commit comments

Comments
 (0)
Please sign in to comment.