support embed singleton fields via props

This commit is contained in:
MapleLeaf
2021-12-20 19:11:07 -06:00
parent d1935d283a
commit 628c4b23d7
9 changed files with 128 additions and 78 deletions

View File

@@ -1,12 +1,13 @@
/* eslint-disable unicorn/no-null */
import type { Message } from "discord.js"
import { Client, TextChannel } from "discord.js"
import { deepEqual } from "node:assert"
import type { ReacordRoot } from "reacord"
import { createRoot, Embed, EmbedAuthor, Text } from "reacord"
import { createRoot, Embed, Text } from "reacord"
import { pick } from "reacord-helpers/pick.js"
import { raise } from "reacord-helpers/raise.js"
import React from "react"
import { afterAll, beforeAll, expect, test } from "vitest"
import { afterAll, beforeAll, test } from "vitest"
import { testBotToken, testChannelId } from "./test-environment.js"
const client = new Client({
@@ -61,28 +62,20 @@ test("nested text", async () => {
await assertMessages([{ content: "hi world hi moon hi sun" }])
})
test("empty embed fallback", async () => {
test.only("empty embed fallback", async () => {
await root.render(<Embed />)
await assertMessages([{ embeds: [{ description: "_ _" }] }])
})
test("embed with only author", async () => {
await root.render(
<Embed>
<EmbedAuthor>only author</EmbedAuthor>
</Embed>,
)
test.only("embed with only author", async () => {
await root.render(<Embed author={{ name: "only author" }} />)
await assertMessages([
{ embeds: [{ description: "_ _", author: { name: "only author" } }] },
])
})
test("empty embed author", async () => {
await root.render(
<Embed>
<EmbedAuthor />
</Embed>,
)
await root.render(<Embed author={{}} />)
await assertMessages([{ embeds: [{ description: "_ _" }] }])
})
@@ -91,14 +84,25 @@ test("kitchen sink", async () => {
<>
message <Text>content</Text>
no space
<Embed color="#feeeef">
<Embed
color="#feeeef"
title="the embed"
url="https://example.com"
timestamp={new Date().toISOString()}
thumbnailUrl="https://cdn.discordapp.com/avatars/109677308410875904/3e53fcb70760a08fa63f73376ede5d1f.png?size=1024"
author={{
name: "hi craw",
url: "https://example.com",
iconUrl:
"https://cdn.discordapp.com/avatars/109677308410875904/3e53fcb70760a08fa63f73376ede5d1f.png?size=1024",
}}
footer={{
text: "the footer",
iconUrl:
"https://cdn.discordapp.com/avatars/109677308410875904/3e53fcb70760a08fa63f73376ede5d1f.png?size=1024",
}}
>
description <Text>more description</Text>
<EmbedAuthor
url="https://example.com"
iconUrl="https://cdn.discordapp.com/avatars/109677308410875904/3e53fcb70760a08fa63f73376ede5d1f.png?size=1024"
>
hi craw
</EmbedAuthor>
</Embed>
<Embed>
another <Text>hi</Text>
@@ -146,7 +150,8 @@ function extractMessageData(message: Message) {
async function assertMessages(expected: Array<DeepPartial<MessageData>>) {
const messages = await channel.messages.fetch()
expect(messages.map((message) => extractMessageData(message))).toEqual(
deepEqual(
messages.map((message) => extractMessageData(message)),
expected.map((message) => ({
content: "",
...message,

View File

@@ -1,35 +0,0 @@
import type { MessageEmbedOptions } from "discord.js"
import type { ReactNode } from "react"
import React from "react"
import { ContainerInstance } from "./container-instance.js"
export type EmbedAuthorProps = {
url?: string
iconUrl?: string
children?: ReactNode
}
export function EmbedAuthor({ children, ...options }: EmbedAuthorProps) {
return (
<reacord-element createInstance={() => new EmbedAuthorInstance(options)}>
{children}
</reacord-element>
)
}
type EmbedAuthorOptions = Omit<EmbedAuthorProps, "children">
class EmbedAuthorInstance extends ContainerInstance {
readonly name = "EmbedAuthor"
constructor(private readonly props: EmbedAuthorOptions) {
super({ warnOnNonTextChildren: true })
}
override renderToEmbed(options: MessageEmbedOptions) {
options.author ??= {}
options.author.name = this.getChildrenText()
options.author.url = this.props.url
options.author.iconURL = this.props.iconUrl
}
}

View File

@@ -8,13 +8,26 @@ import React from "react"
import { ContainerInstance } from "./container-instance.js"
export type EmbedProps = {
title?: string
color?: ColorResolvable
url?: string
timestamp?: Date | number | string
thumbnailUrl?: string
author?: {
name?: string
url?: string
iconUrl?: string
}
footer?: {
text?: string
iconUrl?: string
}
children?: ReactNode
}
export function Embed(props: EmbedProps) {
return (
<reacord-element createInstance={() => new EmbedInstance(props.color)}>
<reacord-element createInstance={() => new EmbedInstance(props)}>
{props.children}
</reacord-element>
)
@@ -23,22 +36,31 @@ export function Embed(props: EmbedProps) {
class EmbedInstance extends ContainerInstance {
readonly name = "Embed"
constructor(readonly color?: ColorResolvable) {
constructor(readonly props: EmbedProps) {
super({ warnOnNonTextChildren: false })
}
override renderToMessage(message: MessageOptions) {
message.embeds ??= []
message.embeds.push(this.embedOptions)
message.embeds.push(this.getEmbedOptions())
}
get embedOptions(): MessageEmbedOptions {
/* eslint-disable unicorn/no-null */
getEmbedOptions(): MessageEmbedOptions {
const options: MessageEmbedOptions = {
color: this.color,
description: null as unknown as undefined,
...this.props,
author: {
...this.props.author,
iconURL: this.props.author?.iconUrl,
},
footer: {
text: "",
...this.props.footer,
iconURL: this.props.footer?.iconUrl,
},
timestamp: this.props.timestamp
? new Date(this.props.timestamp) // this _may_ need date-fns to parse this
: undefined,
}
/* eslint-enable unicorn/no-null */
for (const child of this.children) {
if (!child.renderToEmbed) {

View File

@@ -1,4 +1,3 @@
export * from "./embed-author.js"
export * from "./embed.js"
export * from "./root.js"
export * from "./text.js"