test improvements
This commit is contained in:
21
packages/reacord/helpers/wait-for.ts
Normal file
21
packages/reacord/helpers/wait-for.ts
Normal 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")
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user