test container instead of render
allows awaiting renders
This commit is contained in:
81
src/container.test.ts
Normal file
81
src/container.test.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import test from "ava"
|
||||||
|
import { Client, TextChannel } from "discord.js"
|
||||||
|
import { nanoid } from "nanoid"
|
||||||
|
import { ReacordContainer } from "./container.js"
|
||||||
|
import { raise } from "./helpers/raise.js"
|
||||||
|
import { testBotToken, testChannelId } from "./test-environment.js"
|
||||||
|
|
||||||
|
const client = new Client({
|
||||||
|
intents: ["GUILDS"],
|
||||||
|
})
|
||||||
|
|
||||||
|
let channel: TextChannel
|
||||||
|
|
||||||
|
test.before(async () => {
|
||||||
|
await client.login(testBotToken)
|
||||||
|
|
||||||
|
const result =
|
||||||
|
client.channels.cache.get(testChannelId) ??
|
||||||
|
(await client.channels.fetch(testChannelId)) ??
|
||||||
|
raise("Channel not found")
|
||||||
|
|
||||||
|
if (!(result instanceof TextChannel)) {
|
||||||
|
throw new TypeError("Channel must be a text channel")
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = result
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after(() => {
|
||||||
|
client.destroy()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rendering text", async (t) => {
|
||||||
|
const container = new ReacordContainer(channel)
|
||||||
|
|
||||||
|
const content = nanoid()
|
||||||
|
await container.render([content])
|
||||||
|
|
||||||
|
{
|
||||||
|
const messages = await channel.messages.fetch()
|
||||||
|
t.true(messages.some((m) => m.content === content))
|
||||||
|
}
|
||||||
|
|
||||||
|
const newContent = nanoid()
|
||||||
|
await container.render([newContent])
|
||||||
|
|
||||||
|
{
|
||||||
|
const messages = await channel.messages.fetch()
|
||||||
|
t.true(messages.some((m) => m.content === newContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
await container.render([])
|
||||||
|
|
||||||
|
{
|
||||||
|
const messages = await channel.messages.fetch()
|
||||||
|
t.false(messages.some((m) => m.content === newContent))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("rapid updates", async (t) => {
|
||||||
|
const container = new ReacordContainer(channel)
|
||||||
|
|
||||||
|
const content = nanoid()
|
||||||
|
const newContent = nanoid()
|
||||||
|
|
||||||
|
void container.render([content])
|
||||||
|
await container.render([newContent])
|
||||||
|
|
||||||
|
{
|
||||||
|
const messages = await channel.messages.fetch()
|
||||||
|
t.true(messages.some((m) => m.content === newContent))
|
||||||
|
}
|
||||||
|
|
||||||
|
void container.render([content])
|
||||||
|
await container.render([])
|
||||||
|
|
||||||
|
{
|
||||||
|
const messages = await channel.messages.fetch()
|
||||||
|
t.false(messages.some((m) => m.content === newContent))
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Message, MessageOptions, TextBasedChannels } from "discord.js"
|
import type { Message, MessageOptions, TextBasedChannels } from "discord.js"
|
||||||
|
import { createDeferred } from "./helpers/deferred.js"
|
||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| { type: "updateMessage"; options: MessageOptions }
|
| { type: "updateMessage"; options: MessageOptions }
|
||||||
@@ -8,38 +9,42 @@ export class ReacordContainer {
|
|||||||
channel: TextBasedChannels
|
channel: TextBasedChannels
|
||||||
message?: Message
|
message?: Message
|
||||||
actions: Action[] = []
|
actions: Action[] = []
|
||||||
runningActions = false
|
runningPromise?: PromiseLike<void>
|
||||||
|
|
||||||
constructor(channel: TextBasedChannels) {
|
constructor(channel: TextBasedChannels) {
|
||||||
this.channel = channel
|
this.channel = channel
|
||||||
}
|
}
|
||||||
|
|
||||||
render(instances: string[]) {
|
async render(instances: string[]) {
|
||||||
const messageOptions: MessageOptions = {
|
const messageOptions: MessageOptions = {
|
||||||
content: instances.join("") || undefined, // empty strings are not allowed
|
content: instances.join("") || undefined, // empty strings are not allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasContent = messageOptions.content !== undefined
|
const hasContent = messageOptions.content !== undefined
|
||||||
if (hasContent) {
|
|
||||||
this.addAction({ type: "updateMessage", options: messageOptions })
|
await this.addAction(
|
||||||
} else {
|
hasContent
|
||||||
this.addAction({ type: "deleteMessage" })
|
? { type: "updateMessage", options: messageOptions }
|
||||||
}
|
: { type: "deleteMessage" },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private addAction(action: Action) {
|
private async addAction(action: Action) {
|
||||||
const lastAction = this.actions[this.actions.length - 1]
|
const lastAction = this.actions[this.actions.length - 1]
|
||||||
if (lastAction?.type === action.type) {
|
if (lastAction?.type === action.type) {
|
||||||
this.actions[this.actions.length - 1] = action
|
this.actions[this.actions.length - 1] = action
|
||||||
} else {
|
} else {
|
||||||
this.actions.push(action)
|
this.actions.push(action)
|
||||||
}
|
}
|
||||||
void this.runActions()
|
await this.runActions()
|
||||||
}
|
}
|
||||||
|
|
||||||
private runActions() {
|
private async runActions() {
|
||||||
if (this.runningActions) return
|
if (this.runningPromise) {
|
||||||
this.runningActions = true
|
return this.runningPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
const promise = (this.runningPromise = createDeferred())
|
||||||
|
|
||||||
queueMicrotask(async () => {
|
queueMicrotask(async () => {
|
||||||
let action: Action | undefined
|
let action: Action | undefined
|
||||||
@@ -47,11 +52,9 @@ export class ReacordContainer {
|
|||||||
try {
|
try {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case "updateMessage":
|
case "updateMessage":
|
||||||
if (this.message) {
|
this.message = await (this.message
|
||||||
await this.message.edit(action.options)
|
? this.message.edit(action.options)
|
||||||
} else {
|
: this.channel.send(action.options))
|
||||||
this.message = await this.channel.send(action.options)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
case "deleteMessage":
|
case "deleteMessage":
|
||||||
if (this.message) {
|
if (this.message) {
|
||||||
@@ -66,7 +69,10 @@ export class ReacordContainer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.runningActions = false
|
promise.resolve()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await promise
|
||||||
|
this.runningPromise = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
import test from "ava"
|
|
||||||
import { Client, TextChannel } from "discord.js"
|
|
||||||
import { nanoid } from "nanoid"
|
|
||||||
import { raise } from "./helpers/raise.js"
|
|
||||||
import { waitForWithTimeout } from "./helpers/wait-for-with-timeout.js"
|
|
||||||
import { render } from "./render.js"
|
|
||||||
import { testBotToken, testChannelId } from "./test-environment.js"
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
intents: ["GUILDS"],
|
|
||||||
})
|
|
||||||
|
|
||||||
let channel: TextChannel
|
|
||||||
|
|
||||||
test.before(async () => {
|
|
||||||
await client.login(testBotToken)
|
|
||||||
|
|
||||||
const result =
|
|
||||||
client.channels.cache.get(testChannelId) ??
|
|
||||||
(await client.channels.fetch(testChannelId)) ??
|
|
||||||
raise("Channel not found")
|
|
||||||
|
|
||||||
if (!(result instanceof TextChannel)) {
|
|
||||||
throw new TypeError("Channel must be a text channel")
|
|
||||||
}
|
|
||||||
|
|
||||||
channel = result
|
|
||||||
})
|
|
||||||
|
|
||||||
test.after(() => {
|
|
||||||
client.destroy()
|
|
||||||
})
|
|
||||||
|
|
||||||
test.serial("rendering text", async (t) => {
|
|
||||||
const content = nanoid()
|
|
||||||
const root = render(content, channel)
|
|
||||||
|
|
||||||
await waitForWithTimeout(
|
|
||||||
async () => {
|
|
||||||
const messages = await channel.messages.fetch()
|
|
||||||
return messages.some((m) => m.content === content)
|
|
||||||
},
|
|
||||||
10_000,
|
|
||||||
"Message not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
const newContent = nanoid()
|
|
||||||
root.rerender(newContent)
|
|
||||||
|
|
||||||
await waitForWithTimeout(
|
|
||||||
async () => {
|
|
||||||
const messages = await channel.messages.fetch()
|
|
||||||
return messages.some((m) => m.content === newContent)
|
|
||||||
},
|
|
||||||
10_000,
|
|
||||||
"Message not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
root.destroy()
|
|
||||||
|
|
||||||
await waitForWithTimeout(
|
|
||||||
async () => {
|
|
||||||
const messages = await channel.messages.fetch()
|
|
||||||
return messages.size === 0
|
|
||||||
},
|
|
||||||
10_000,
|
|
||||||
"Message was not deleted",
|
|
||||||
)
|
|
||||||
|
|
||||||
t.pass()
|
|
||||||
})
|
|
||||||
|
|
||||||
test.serial("rapid updates", async (t) => {
|
|
||||||
const content = nanoid()
|
|
||||||
const newContent = nanoid()
|
|
||||||
|
|
||||||
const root = render(content, channel)
|
|
||||||
root.rerender(newContent)
|
|
||||||
|
|
||||||
await waitForWithTimeout(
|
|
||||||
async () => {
|
|
||||||
const messages = await channel.messages.fetch()
|
|
||||||
return messages.some((m) => m.content === newContent)
|
|
||||||
},
|
|
||||||
10_000,
|
|
||||||
"Message not found",
|
|
||||||
)
|
|
||||||
|
|
||||||
root.rerender(content)
|
|
||||||
root.destroy()
|
|
||||||
|
|
||||||
await waitForWithTimeout(
|
|
||||||
async () => {
|
|
||||||
const messages = await channel.messages.fetch()
|
|
||||||
return messages.size === 0
|
|
||||||
},
|
|
||||||
10_000,
|
|
||||||
"Message was not deleted",
|
|
||||||
)
|
|
||||||
|
|
||||||
t.pass()
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user