test improvements

This commit is contained in:
itsMapleLeaf
2022-04-23 01:54:52 -05:00
parent 1ba75492e5
commit 6851c5419a
3 changed files with 78 additions and 64 deletions

View File

@@ -0,0 +1,21 @@
import { setTimeout } from "timers/promises"
const maxTime = 1000
export async function waitFor<Result>(
predicate: () => Result,
): Promise<Awaited<Result>> {
const startTime = Date.now()
let lastError: unknown
while (Date.now() - startTime < maxTime) {
try {
return await predicate()
} catch (error) {
lastError = error
await setTimeout(50)
}
}
throw lastError ?? new Error("Timeout")
}

View File

@@ -1,14 +1,14 @@
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
/* eslint-disable require-await */ /* eslint-disable require-await */
import { nanoid } from "nanoid" import { nanoid } from "nanoid"
import { nextTick } from "node:process" import { setTimeout } from "node:timers/promises"
import { promisify } from "node:util"
import type { ReactNode } from "react" import type { ReactNode } from "react"
import { expect } from "vitest" import { expect } from "vitest"
import { logPretty } from "../helpers/log-pretty" import { logPretty } from "../helpers/log-pretty"
import { omit } from "../helpers/omit" import { omit } from "../helpers/omit"
import { pruneNullishValues } from "../helpers/prune-nullish-values" import { pruneNullishValues } from "../helpers/prune-nullish-values"
import { raise } from "../helpers/raise" import { raise } from "../helpers/raise"
import { waitFor } from "../helpers/wait-for"
import type { import type {
ChannelInfo, ChannelInfo,
GuildInfo, GuildInfo,
@@ -26,17 +26,10 @@ import type {
CommandInteraction, CommandInteraction,
SelectInteraction, SelectInteraction,
} from "../library/internal/interaction" } from "../library/internal/interaction"
import type { import type { Message, MessageOptions } from "../library/internal/message"
Message,
MessageButtonOptions,
MessageOptions,
MessageSelectOptions,
} from "../library/internal/message"
import { ChannelMessageRenderer } from "../library/internal/renderers/channel-message-renderer" import { ChannelMessageRenderer } from "../library/internal/renderers/channel-message-renderer"
import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer" import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer"
const nextTickPromise = promisify(nextTick)
export type MessageSample = ReturnType<ReacordTester["sampleMessages"]>[0] export type MessageSample = ReturnType<ReacordTester["sampleMessages"]>[0]
/** /**
@@ -73,9 +66,10 @@ export class ReacordTester extends Reacord {
return this.reply(initialContent) return this.reply(initialContent)
} }
async assertMessages(expected: MessageSample[]) { assertMessages(expected: MessageSample[]) {
await nextTickPromise() return waitFor(() => {
expect(this.sampleMessages()).toEqual(expected) expect(this.sampleMessages()).toEqual(expected)
})
} }
async assertRender(content: ReactNode, expected: MessageSample[]) { async assertRender(content: ReactNode, expected: MessageSample[]) {
@@ -108,57 +102,58 @@ export class ReacordTester extends Reacord {
} }
findButtonByLabel(label: string) { findButtonByLabel(label: string) {
for (const message of this.messageContainer) { return {
for (const component of message.options.actionRows.flat()) { click: () => {
if (component.type === "button" && component.label === label) { return waitFor(() => {
return this.createButtonActions(component, message) for (const [component, message] of this.eachComponent()) {
} if (component.type === "button" && component.label === label) {
} this.handleComponentInteraction(
new TestButtonInteraction(component.customId, message, this),
)
return
}
}
raise(`Couldn't find button with label "${label}"`)
})
},
} }
raise(`Couldn't find button with label "${label}"`)
} }
findSelectByPlaceholder(placeholder: string) { findSelectByPlaceholder(placeholder: string) {
for (const message of this.messageContainer) { return {
for (const component of message.options.actionRows.flat()) { select: (...values: string[]) => {
if ( return waitFor(() => {
component.type === "select" && for (const [component, message] of this.eachComponent()) {
component.placeholder === placeholder if (
) { component.type === "select" &&
return this.createSelectActions(component, message) component.placeholder === placeholder
} ) {
} this.handleComponentInteraction(
new TestSelectInteraction(
component.customId,
message,
values,
this,
),
)
return
}
}
raise(`Couldn't find select with placeholder "${placeholder}"`)
})
},
} }
raise(`Couldn't find select with placeholder "${placeholder}"`)
} }
createMessage(options: MessageOptions) { createMessage(options: MessageOptions) {
return new TestMessage(options, this.messageContainer) return new TestMessage(options, this.messageContainer)
} }
private createButtonActions( private *eachComponent() {
button: MessageButtonOptions, for (const message of this.messageContainer) {
message: TestMessage, for (const component of message.options.actionRows.flat()) {
) { yield [component, message] as const
return { }
click: () => {
this.handleComponentInteraction(
new TestButtonInteraction(button.customId, message, this),
)
},
}
}
private createSelectActions(
component: MessageSelectOptions,
message: TestMessage,
) {
return {
select: (...values: string[]) => {
this.handleComponentInteraction(
new TestSelectInteraction(component.customId, message, values, this),
)
},
} }
} }
} }
@@ -197,16 +192,14 @@ class TestCommandInteraction implements CommandInteraction {
constructor(private messageContainer: Container<TestMessage>) {} constructor(private messageContainer: Container<TestMessage>) {}
reply(messageOptions: MessageOptions): Promise<Message> { async reply(messageOptions: MessageOptions): Promise<Message> {
return Promise.resolve( await setTimeout()
new TestMessage(messageOptions, this.messageContainer), return new TestMessage(messageOptions, this.messageContainer)
)
} }
followUp(messageOptions: MessageOptions): Promise<Message> { async followUp(messageOptions: MessageOptions): Promise<Message> {
return Promise.resolve( await setTimeout()
new TestMessage(messageOptions, this.messageContainer), return new TestMessage(messageOptions, this.messageContainer)
)
} }
} }

View File

@@ -55,7 +55,7 @@ describe("useInstance", () => {
await tester.assertMessages([messageOutput("parent")]) await tester.assertMessages([messageOutput("parent")])
expect(instanceFromHook).toBe(instance) expect(instanceFromHook).toBe(instance)
tester.findButtonByLabel("create parent").click() await tester.findButtonByLabel("create parent").click()
await tester.assertMessages([ await tester.assertMessages([
messageOutput("parent"), messageOutput("parent"),
messageOutput("child"), messageOutput("child"),
@@ -63,10 +63,10 @@ describe("useInstance", () => {
// this test ensures that the only the child instance is destroyed, // this test ensures that the only the child instance is destroyed,
// and not the parent instance // and not the parent instance
tester.findButtonByLabel("destroy child").click() await tester.findButtonByLabel("destroy child").click()
await tester.assertMessages([messageOutput("parent")]) await tester.assertMessages([messageOutput("parent")])
tester.findButtonByLabel("destroy parent").click() await tester.findButtonByLabel("destroy parent").click()
await tester.assertMessages([]) await tester.assertMessages([])
}) })
}) })