throw together some scuffed integration testing infra

This commit is contained in:
itsMapleLeaf
2022-08-06 00:05:30 -05:00
parent e974f0073d
commit 1cbd5e9bfd
8 changed files with 293 additions and 222 deletions

View File

@@ -74,6 +74,7 @@
"@reacord/helpers": "workspace:*",
"@types/lodash-es": "^4.17.6",
"@types/prettier": "^2.6.4",
"date-fns": "^2.29.1",
"discord.js": "^14.1.2",
"dotenv": "^16.0.1",
"lodash-es": "^4.17.21",

View File

@@ -1,60 +1,17 @@
import { raise } from "@reacord/helpers/raise"
import type { TextBasedChannel } from "discord.js"
import {
CategoryChannel,
ChannelType,
ComponentType,
GatewayIntentBits,
} from "discord.js"
import { ComponentType } from "discord.js"
import React from "react"
import { beforeAll, expect, test } from "vitest"
import { createDiscordClient } from "../library/create-discord-client"
import {
ActionRow,
Button,
Option,
ReacordClient,
Select,
} from "../library/main"
import { testEnv } from "./test-env"
import { ActionRow, Button, Option, Select } from "../library/main"
import { ReacordTester } from "./tester"
let channel: TextBasedChannel
let tester: ReacordTester
beforeAll(async () => {
const client = await createDiscordClient(testEnv.TEST_BOT_TOKEN, {
intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages,
})
const category =
client.channels.cache.get(testEnv.TEST_CATEGORY_ID) ??
(await client.channels.fetch(testEnv.TEST_CATEGORY_ID))
if (!(category instanceof CategoryChannel)) {
throw new TypeError("Category channel not found")
}
const channelName = "test-channel"
let existing = category.children.cache.find((the) => the.name === channelName)
if (!existing || !existing.isTextBased()) {
existing = await category.children.create({
type: ChannelType.GuildText,
name: channelName,
})
}
channel = existing
for (const [, message] of await channel.messages.fetch()) {
await message.delete()
}
tester = await ReacordTester.create()
})
test("action row", async () => {
const reacord = new ReacordClient({
token: testEnv.TEST_BOT_TOKEN,
})
reacord.send(
channel.id,
const { message } = await tester.render(
"action row",
<>
<Button label="outside button" onClick={() => {}} />
<ActionRow>
@@ -68,10 +25,6 @@ test("action row", async () => {
</>,
)
const message = await channel
.awaitMessages({ max: 1 })
.then((result) => result.first() ?? raise("message not found"))
expect(message.components.map((c) => c.toJSON())).toEqual([
{
type: ComponentType.ActionRow,
@@ -109,4 +62,4 @@ test("action row", async () => {
],
},
])
}, 15_000)
})

View File

@@ -1,5 +1,5 @@
import React from "react"
import { test } from "vitest"
import { beforeAll, expect, test } from "vitest"
import {
Embed,
EmbedAuthor,
@@ -9,14 +9,18 @@ import {
EmbedThumbnail,
EmbedTitle,
} from "../library/main"
import { ReacordTester } from "./test-adapter"
import { ReacordTester } from "./tester"
const testing = new ReacordTester()
let tester: ReacordTester
beforeAll(async () => {
tester = await ReacordTester.create()
})
test("kitchen sink", async () => {
const now = new Date()
await testing.assertRender(
const { message } = await tester.render(
"kitchen sink",
<>
<Embed color={0xfe_ee_ef}>
<EmbedAuthor name="author" iconUrl="https://example.com/author.png" />
@@ -33,85 +37,83 @@ test("kitchen sink", async () => {
/>
</Embed>
</>,
[
{
actionRows: [],
content: "",
embeds: [
{
description: "description text",
author: {
icon_url: "https://example.com/author.png",
name: "author",
},
color: 0xfe_ee_ef,
fields: [
{
inline: true,
name: "field name",
value: "field value",
},
{
name: "block field",
value: "block field value",
},
],
footer: {
icon_url: "https://example.com/footer.png",
text: "footer text",
},
image: {
url: "https://example.com/image.png",
},
thumbnail: {
url: "https://example.com/thumbnail.png",
},
timestamp: now.toISOString(),
title: "title text",
},
],
},
],
)
expect(message.embeds.map((e) => e.toJSON())).toEqual([
expect.objectContaining({
description: "description text",
author: expect.objectContaining({
icon_url: "https://example.com/author.png",
name: "author",
}),
color: 0xfe_ee_ef,
fields: [
{
inline: true,
name: "field name",
value: "field value",
},
{
inline: false,
name: "block field",
value: "block field value",
},
],
footer: expect.objectContaining({
icon_url: "https://example.com/footer.png",
text: "footer text",
}),
image: expect.objectContaining({
url: "https://example.com/image.png",
}),
thumbnail: expect.objectContaining({
url: "https://example.com/thumbnail.png",
}),
title: "title text",
}),
])
// the timestamp format from Discord is not the same one that JS makes
expect(new Date(message.embeds[0]!.timestamp!)).toEqual(now)
})
test("author variants", async () => {
await testing.assertRender(
const { message } = await tester.render(
"author variants",
<>
<Embed>
<EmbedAuthor iconUrl="https://example.com/author.png">
author name
author name 1
</EmbedAuthor>
</Embed>
<Embed>
<EmbedAuthor iconUrl="https://example.com/author.png" />
<EmbedAuthor
name="author name 2"
iconUrl="https://example.com/author.png"
/>
</Embed>
</>,
[
{
content: "",
actionRows: [],
embeds: [
{
author: {
icon_url: "https://example.com/author.png",
name: "author name",
},
},
{
author: {
icon_url: "https://example.com/author.png",
name: "",
},
},
],
},
],
)
})
expect(message.embeds.map((e) => e.toJSON())).toEqual([
expect.objectContaining({
author: expect.objectContaining({
name: "author name 1",
icon_url: "https://example.com/author.png",
}),
}),
expect.objectContaining({
author: expect.objectContaining({
name: "author name 2",
icon_url: "https://example.com/author.png",
}),
}),
])
}, 20_000)
test("field variants", async () => {
await testing.assertRender(
const { message } = await tester.render(
"field variants",
<>
<Embed>
<EmbedField name="field name" value="field value" />
@@ -122,43 +124,41 @@ test("field variants", async () => {
<EmbedField name="field name" />
</Embed>
</>,
[
{
content: "",
actionRows: [],
embeds: [
{
fields: [
{
name: "field name",
value: "field value",
},
{
inline: true,
name: "field name",
value: "field value",
},
{
inline: true,
name: "field name",
value: "field value",
},
{
name: "field name",
value: "",
},
],
},
],
},
],
)
expect(message.embeds.map((e) => e.toJSON())).toEqual([
expect.objectContaining({
fields: [
{
name: "field name",
value: "field value",
inline: false,
},
{
name: "field name",
value: "field value",
inline: true,
},
{
name: "field name",
value: "field value",
inline: true,
},
{
name: "field name",
value: "_ _",
inline: false,
},
],
}),
])
})
test("footer variants", async () => {
const now = new Date()
await testing.assertRender(
const { message } = await tester.render(
"footer variants",
<>
<Embed>
<EmbedFooter text="footer text" />
@@ -176,45 +176,37 @@ test("footer variants", async () => {
<EmbedFooter iconUrl="https://example.com/footer.png" timestamp={now} />
</Embed>
</>,
[
{
content: "",
actionRows: [],
embeds: [
{
footer: {
text: "footer text",
},
},
{
footer: {
icon_url: "https://example.com/footer.png",
text: "footer text",
},
},
{
footer: {
text: "footer text",
},
timestamp: now.toISOString(),
},
{
footer: {
icon_url: "https://example.com/footer.png",
text: "",
},
timestamp: now.toISOString(),
},
],
},
],
)
expect(message.embeds.map((e) => e.toJSON())).toEqual([
expect.objectContaining({
footer: {
text: "footer text",
},
}),
expect.objectContaining({
footer: expect.objectContaining({
icon_url: "https://example.com/footer.png",
text: "footer text",
}),
}),
expect.objectContaining({
timestamp: expect.stringContaining(""),
}),
expect.objectContaining({
timestamp: expect.stringContaining(""),
}),
])
expect(new Date(message.embeds[2]!.timestamp!)).toEqual(now)
expect(new Date(message.embeds[3]!.timestamp!)).toEqual(now)
})
test("embed props", async () => {
test.only("embed props", async () => {
const now = new Date()
await testing.assertRender(
const { message } = await tester.render(
"embed props",
<Embed
title="title text"
description="description text"
@@ -241,35 +233,33 @@ test("embed props", async () => {
{ name: "block field", value: "block field value" },
]}
/>,
[
{
content: "",
actionRows: [],
embeds: [
{
title: "title text",
description: "description text",
url: "https://example.com/",
color: 0xfe_ee_ef,
timestamp: now.toISOString(),
author: {
name: "author name",
url: "https://example.com/author",
icon_url: "https://example.com/author.png",
},
thumbnail: { url: "https://example.com/thumbnail.png" },
image: { url: "https://example.com/image.png" },
footer: {
text: "footer text",
icon_url: "https://example.com/footer.png",
},
fields: [
{ name: "field name", value: "field value", inline: true },
{ name: "block field", value: "block field value" },
],
},
],
},
],
)
expect(message.embeds.map((e) => e.toJSON())).toEqual([
expect.objectContaining({
title: "title text",
description: "description text",
url: "https://example.com/",
color: 0xfe_ee_ef,
author: expect.objectContaining({
name: "author name",
url: "https://example.com/author",
icon_url: "https://example.com/author.png",
}),
thumbnail: expect.objectContaining({
url: "https://example.com/thumbnail.png",
}),
image: expect.objectContaining({ url: "https://example.com/image.png" }),
footer: expect.objectContaining({
text: "footer text",
icon_url: "https://example.com/footer.png",
}),
fields: [
{ name: "field name", value: "field value", inline: true },
{ name: "block field", value: "block field value", inline: false },
],
}),
])
expect(new Date(message.embeds[0]!.timestamp!)).toEqual(now)
})

View File

@@ -0,0 +1,5 @@
import { ReacordTester } from "./tester"
export async function setup() {
await ReacordTester.removeChannels()
}

View File

@@ -0,0 +1,85 @@
import { raise } from "@reacord/helpers/raise"
import type { Client } from "discord.js"
import { CategoryChannel, ChannelType, GatewayIntentBits } from "discord.js"
import { kebabCase } from "lodash-es"
import { randomBytes } from "node:crypto"
import type { ReactNode } from "react"
import { createDiscordClient } from "../library/create-discord-client"
import { ReacordClient } from "../library/reacord-client"
import { testEnv } from "./test-env"
export class ReacordTester {
private static client?: Client
static async removeChannels() {
const client = await ReacordTester.getClient()
const category = await ReacordTester.getCategory(client)
for (const [, channel] of category.children.cache) {
await channel.delete()
}
}
static async create() {
const client = await ReacordTester.getClient()
const category = await ReacordTester.getCategory(client)
return new ReacordTester(client, category)
}
private static async getClient() {
return (this.client ??= await createDiscordClient(testEnv.TEST_BOT_TOKEN, {
intents: GatewayIntentBits.Guilds | GatewayIntentBits.GuildMessages,
}))
}
private static async getCategory(client: Client<true>) {
const category =
client.channels.cache.get(testEnv.TEST_CATEGORY_ID) ??
(await client.channels.fetch(testEnv.TEST_CATEGORY_ID))
if (!(category instanceof CategoryChannel)) {
throw new TypeError("Category channel not found")
}
return category
}
private reacord?: ReacordClient
constructor(readonly client: Client, readonly category: CategoryChannel) {}
private async getTestChannel(testName: string) {
const hash = randomBytes(16).toString("hex").slice(0, 6)
const channelName = `${kebabCase(testName)}-${hash}`
let channel = this.category.children.cache.find(
(the) => the.name === channelName,
)
if (!channel || !channel.isTextBased()) {
channel = await this.category.children.create({
type: ChannelType.GuildText,
name: channelName,
})
}
for (const [, message] of await channel.messages.fetch()) {
await message.delete()
}
return channel
}
async render(testName: string, content?: ReactNode) {
this.reacord ??= new ReacordClient({
token: testEnv.TEST_BOT_TOKEN,
})
const channel = await this.getTestChannel(testName)
await channel.sendTyping()
const instance = this.reacord.send(channel.id, content)
const result = await channel.awaitMessages({ max: 1 })
const message = result.first() ?? raise("failed to send message")
return { message, instance }
}
}