implement select
This commit is contained in:
7
helpers/is-instance-of.ts
Normal file
7
helpers/is-instance-of.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* for narrowing instance types with array.filter
|
||||||
|
*/
|
||||||
|
export const isInstanceOf =
|
||||||
|
<T>(Constructor: new (...args: any[]) => T) =>
|
||||||
|
(value: unknown): value is T =>
|
||||||
|
value instanceof Constructor
|
||||||
@@ -18,7 +18,7 @@ export class DiscordJsAdapter implements Adapter<Discord.CommandInteraction> {
|
|||||||
listener: (interaction: ComponentInteraction) => void,
|
listener: (interaction: ComponentInteraction) => void,
|
||||||
) {
|
) {
|
||||||
this.client.on("interactionCreate", (interaction) => {
|
this.client.on("interactionCreate", (interaction) => {
|
||||||
if (interaction.isButton()) {
|
if (interaction.isMessageComponent()) {
|
||||||
listener(createReacordComponentInteraction(interaction))
|
listener(createReacordComponentInteraction(interaction))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -56,6 +56,7 @@ export class DiscordJsAdapter implements Adapter<Discord.CommandInteraction> {
|
|||||||
function createReacordComponentInteraction(
|
function createReacordComponentInteraction(
|
||||||
interaction: Discord.MessageComponentInteraction,
|
interaction: Discord.MessageComponentInteraction,
|
||||||
): ComponentInteraction {
|
): ComponentInteraction {
|
||||||
|
if (interaction.isButton()) {
|
||||||
return {
|
return {
|
||||||
type: "button",
|
type: "button",
|
||||||
id: interaction.id,
|
id: interaction.id,
|
||||||
@@ -65,6 +66,22 @@ function createReacordComponentInteraction(
|
|||||||
await interaction.update(getDiscordMessageOptions(options))
|
await interaction.update(getDiscordMessageOptions(options))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interaction.isSelectMenu()) {
|
||||||
|
return {
|
||||||
|
type: "select",
|
||||||
|
id: interaction.id,
|
||||||
|
channelId: interaction.channelId,
|
||||||
|
customId: interaction.customId,
|
||||||
|
values: interaction.values,
|
||||||
|
update: async (options) => {
|
||||||
|
await interaction.update(getDiscordMessageOptions(options))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raise(`Unsupported component interaction type: ${interaction.type}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createReacordMessage(message: Discord.Message): Message {
|
function createReacordMessage(message: Discord.Message): Message {
|
||||||
@@ -94,7 +111,8 @@ function getDiscordMessageOptions(
|
|||||||
embeds: options.embeds,
|
embeds: options.embeds,
|
||||||
components: options.actionRows.map((row) => ({
|
components: options.actionRows.map((row) => ({
|
||||||
type: "ACTION_ROW",
|
type: "ACTION_ROW",
|
||||||
components: row.map((component) => {
|
components: row.map(
|
||||||
|
(component): Discord.MessageActionRowComponentOptions => {
|
||||||
if (component.type === "button") {
|
if (component.type === "button") {
|
||||||
return {
|
return {
|
||||||
type: "BUTTON",
|
type: "BUTTON",
|
||||||
@@ -105,8 +123,21 @@ function getDiscordMessageOptions(
|
|||||||
emoji: component.emoji,
|
emoji: component.emoji,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (component.type === "select") {
|
||||||
|
return {
|
||||||
|
...component,
|
||||||
|
type: "SELECT_MENU",
|
||||||
|
options: component.options.map((option) => ({
|
||||||
|
...option,
|
||||||
|
default: component.values?.includes(option.value),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
raise(`Unsupported component type: ${component.type}`)
|
raise(`Unsupported component type: ${component.type}`)
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
library/core/components/option-node.ts
Normal file
14
library/core/components/option-node.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { MessageSelectOptionOptions } from "../../internal/message"
|
||||||
|
import { Node } from "../../internal/node"
|
||||||
|
import type { OptionProps } from "./option"
|
||||||
|
|
||||||
|
export class OptionNode extends Node<OptionProps> {
|
||||||
|
get options(): MessageSelectOptionOptions {
|
||||||
|
return {
|
||||||
|
label: this.props.children || this.props.label || this.props.value || "",
|
||||||
|
value: this.props.value,
|
||||||
|
description: this.props.description,
|
||||||
|
emoji: this.props.emoji,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
library/core/components/option.tsx
Normal file
17
library/core/components/option.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react"
|
||||||
|
import { ReacordElement } from "../../internal/element"
|
||||||
|
import { OptionNode } from "./option-node"
|
||||||
|
|
||||||
|
export type OptionProps = {
|
||||||
|
label?: string
|
||||||
|
children?: string
|
||||||
|
value: string
|
||||||
|
description?: string
|
||||||
|
emoji?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Option(props: OptionProps) {
|
||||||
|
return (
|
||||||
|
<ReacordElement props={props} createNode={() => new OptionNode(props)} />
|
||||||
|
)
|
||||||
|
}
|
||||||
89
library/core/components/select.tsx
Normal file
89
library/core/components/select.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { nanoid } from "nanoid"
|
||||||
|
import type { ReactNode } from "react"
|
||||||
|
import React from "react"
|
||||||
|
import { isInstanceOf } from "../../../helpers/is-instance-of"
|
||||||
|
import { ReacordElement } from "../../internal/element.js"
|
||||||
|
import type { ComponentInteraction } from "../../internal/interaction"
|
||||||
|
import type { MessageOptions } from "../../internal/message"
|
||||||
|
import { getNextActionRow } from "../../internal/message"
|
||||||
|
import { Node } from "../../internal/node.js"
|
||||||
|
import { OptionNode } from "./option-node"
|
||||||
|
|
||||||
|
export type SelectProps = {
|
||||||
|
children?: ReactNode
|
||||||
|
value?: string
|
||||||
|
values?: string[]
|
||||||
|
placeholder?: string
|
||||||
|
multiple?: boolean
|
||||||
|
minValues?: number
|
||||||
|
maxValues?: number
|
||||||
|
disabled?: boolean
|
||||||
|
onSelect?: (event: SelectEvent) => void
|
||||||
|
onSelectValue?: (value: string) => void
|
||||||
|
onSelectMultiple?: (values: string[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectEvent = {
|
||||||
|
values: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Select(props: SelectProps) {
|
||||||
|
return (
|
||||||
|
<ReacordElement props={props} createNode={() => new SelectNode(props)}>
|
||||||
|
{props.children}
|
||||||
|
</ReacordElement>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectNode extends Node<SelectProps> {
|
||||||
|
readonly customId = nanoid()
|
||||||
|
|
||||||
|
override modifyMessageOptions(message: MessageOptions): void {
|
||||||
|
const actionRow = getNextActionRow(message)
|
||||||
|
|
||||||
|
const options = [...this.children]
|
||||||
|
.filter(isInstanceOf(OptionNode))
|
||||||
|
.map((node) => node.options)
|
||||||
|
|
||||||
|
const {
|
||||||
|
multiple,
|
||||||
|
value,
|
||||||
|
values,
|
||||||
|
minValues = 0,
|
||||||
|
maxValues = 25,
|
||||||
|
children,
|
||||||
|
onSelect,
|
||||||
|
onSelectValue,
|
||||||
|
onSelectMultiple,
|
||||||
|
...props
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
actionRow.push({
|
||||||
|
...props,
|
||||||
|
type: "select",
|
||||||
|
customId: this.customId,
|
||||||
|
options,
|
||||||
|
values: [...(values || []), ...(value ? [value] : [])],
|
||||||
|
minValues: multiple ? minValues : undefined,
|
||||||
|
maxValues: multiple ? Math.max(minValues, maxValues) : undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override handleComponentInteraction(
|
||||||
|
interaction: ComponentInteraction,
|
||||||
|
): boolean {
|
||||||
|
if (
|
||||||
|
interaction.type === "select" &&
|
||||||
|
interaction.customId === this.customId &&
|
||||||
|
!this.props.disabled
|
||||||
|
) {
|
||||||
|
this.props.onSelect?.({ values: interaction.values })
|
||||||
|
this.props.onSelectMultiple?.(interaction.values)
|
||||||
|
if (interaction.values[0]) {
|
||||||
|
this.props.onSelectValue?.(interaction.values[0])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,5 +25,6 @@ export type SelectInteraction = {
|
|||||||
id: string
|
id: string
|
||||||
channelId: string
|
channelId: string
|
||||||
customId: string
|
customId: string
|
||||||
|
values: string[]
|
||||||
update(options: MessageOptions): Promise<void>
|
update(options: MessageOptions): Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
import type { Except } from "type-fest"
|
||||||
|
import { last } from "../../helpers/last"
|
||||||
import type { EmbedOptions } from "../core/components/embed-options"
|
import type { EmbedOptions } from "../core/components/embed-options"
|
||||||
|
import type { SelectProps } from "../core/components/select"
|
||||||
|
|
||||||
export type MessageOptions = {
|
export type MessageOptions = {
|
||||||
content: string
|
content: string
|
||||||
embeds: EmbedOptions[]
|
embeds: EmbedOptions[]
|
||||||
actionRows: Array<
|
actionRows: ActionRow[]
|
||||||
Array<MessageButtonOptions | MessageLinkOptions | MessageSelectOptions>
|
|
||||||
>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActionRow = Array<
|
||||||
|
MessageButtonOptions | MessageLinkOptions | MessageSelectOptions
|
||||||
|
>
|
||||||
|
|
||||||
export type MessageButtonOptions = {
|
export type MessageButtonOptions = {
|
||||||
type: "button"
|
type: "button"
|
||||||
customId: string
|
customId: string
|
||||||
@@ -25,12 +30,33 @@ export type MessageLinkOptions = {
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MessageSelectOptions = {
|
export type MessageSelectOptions = Except<SelectProps, "children" | "value"> & {
|
||||||
type: "select"
|
type: "select"
|
||||||
customId: string
|
customId: string
|
||||||
|
options: MessageSelectOptionOptions[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MessageSelectOptionOptions = {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
description?: string
|
||||||
|
emoji?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Message = {
|
export type Message = {
|
||||||
edit(options: MessageOptions): Promise<void>
|
edit(options: MessageOptions): Promise<void>
|
||||||
disableComponents(): Promise<void>
|
disableComponents(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNextActionRow(options: MessageOptions): ActionRow {
|
||||||
|
let actionRow = last(options.actionRows)
|
||||||
|
if (
|
||||||
|
actionRow == undefined ||
|
||||||
|
actionRow.length >= 5 ||
|
||||||
|
actionRow[0]?.type === "select"
|
||||||
|
) {
|
||||||
|
actionRow = []
|
||||||
|
options.actionRows.push(actionRow)
|
||||||
|
}
|
||||||
|
return actionRow
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ export * from "./core/components/embed-image"
|
|||||||
export * from "./core/components/embed-thumbnail"
|
export * from "./core/components/embed-thumbnail"
|
||||||
export * from "./core/components/embed-title"
|
export * from "./core/components/embed-title"
|
||||||
export * from "./core/components/link"
|
export * from "./core/components/link"
|
||||||
|
export * from "./core/components/option"
|
||||||
|
export * from "./core/components/select"
|
||||||
export * from "./core/reacord"
|
export * from "./core/reacord"
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import type {
|
|||||||
ButtonInteraction,
|
ButtonInteraction,
|
||||||
CommandInteraction,
|
CommandInteraction,
|
||||||
ComponentInteraction,
|
ComponentInteraction,
|
||||||
|
SelectInteraction,
|
||||||
} from "./internal/interaction"
|
} from "./internal/interaction"
|
||||||
import type {
|
import type {
|
||||||
Message,
|
Message,
|
||||||
MessageButtonOptions,
|
MessageButtonOptions,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
|
MessageSelectOptions,
|
||||||
} from "./internal/message"
|
} from "./internal/message"
|
||||||
|
|
||||||
export class TestAdapter implements Adapter<TestCommandInteraction> {
|
export class TestAdapter implements Adapter<TestCommandInteraction> {
|
||||||
@@ -44,6 +46,20 @@ export class TestAdapter implements Adapter<TestCommandInteraction> {
|
|||||||
raise(`Couldn't find button with label "${label}"`)
|
raise(`Couldn't find button with label "${label}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findSelectByPlaceholder(placeholder: string) {
|
||||||
|
for (const message of this.messages) {
|
||||||
|
for (const component of message.options.actionRows.flat()) {
|
||||||
|
if (
|
||||||
|
component.type === "select" &&
|
||||||
|
component.placeholder === placeholder
|
||||||
|
) {
|
||||||
|
return this.createSelectActions(component, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raise(`Couldn't find select with placeholder "${placeholder}"`)
|
||||||
|
}
|
||||||
|
|
||||||
private createButtonActions(
|
private createButtonActions(
|
||||||
button: MessageButtonOptions,
|
button: MessageButtonOptions,
|
||||||
message: TestMessage,
|
message: TestMessage,
|
||||||
@@ -56,6 +72,19 @@ export class TestAdapter implements Adapter<TestCommandInteraction> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createSelectActions(
|
||||||
|
component: MessageSelectOptions,
|
||||||
|
message: TestMessage,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
select: (...values: string[]) => {
|
||||||
|
this.componentInteractionListener(
|
||||||
|
new TestSelectInteraction(component.customId, message, values),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestMessage implements Message {
|
export class TestMessage implements Message {
|
||||||
@@ -109,3 +138,19 @@ export class TestButtonInteraction implements ButtonInteraction {
|
|||||||
this.message.options = options
|
this.message.options = options
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TestSelectInteraction implements SelectInteraction {
|
||||||
|
readonly type = "select"
|
||||||
|
readonly id = nanoid()
|
||||||
|
readonly channelId = "test-channel-id"
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly customId: string,
|
||||||
|
readonly message: TestMessage,
|
||||||
|
readonly values: string[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async update(options: MessageOptions): Promise<void> {
|
||||||
|
this.message.options = options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
6
notes.md
6
notes.md
@@ -17,10 +17,10 @@
|
|||||||
- message components
|
- message components
|
||||||
- [x] buttons
|
- [x] buttons
|
||||||
- [x] links
|
- [x] links
|
||||||
- [ ] select
|
- [x] select
|
||||||
|
- [x] select onChange
|
||||||
- [ ] action row
|
- [ ] action row
|
||||||
- [x] button onClick
|
- [x] button onClick
|
||||||
- [ ] select onChange
|
|
||||||
- [x] deactivate
|
- [x] deactivate
|
||||||
- [ ] destroy
|
- [ ] destroy
|
||||||
- [ ] docs
|
- [ ] docs
|
||||||
@@ -37,3 +37,5 @@
|
|||||||
- [ ] `useReactions`
|
- [ ] `useReactions`
|
||||||
- [ ] max instance count per guild
|
- [ ] max instance count per guild
|
||||||
- [ ] max instance count per channel
|
- [ ] max instance count per channel
|
||||||
|
- [ ] uncontrolled select
|
||||||
|
- [ ] single class/helper function for testing `ReacordTester`
|
||||||
|
|||||||
31
playground/fruit-select.tsx
Normal file
31
playground/fruit-select.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import { Button, Option, Select } from "../library/main"
|
||||||
|
|
||||||
|
export function FruitSelect() {
|
||||||
|
const [value, setValue] = useState<string>()
|
||||||
|
const [finalChoice, setFinalChoice] = useState<string>()
|
||||||
|
|
||||||
|
if (finalChoice) {
|
||||||
|
return <>you chose {finalChoice}</>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{"_ _"}
|
||||||
|
<Select
|
||||||
|
placeholder="choose a fruit"
|
||||||
|
value={value}
|
||||||
|
onSelectValue={setValue}
|
||||||
|
>
|
||||||
|
<Option value="🍎" />
|
||||||
|
<Option value="🍌" />
|
||||||
|
<Option value="🍒" />
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
label="confirm"
|
||||||
|
disabled={value == undefined}
|
||||||
|
onClick={() => setFinalChoice(value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Client } from "discord.js"
|
import { Client } from "discord.js"
|
||||||
import "dotenv/config"
|
import "dotenv/config"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { DiscordJsAdapter, Reacord } from "../library/main.js"
|
import { DiscordJsAdapter, Reacord } from "../library/main"
|
||||||
import { createCommandHandler } from "./command-handler.js"
|
import { createCommandHandler } from "./command-handler"
|
||||||
import { Counter } from "./counter.js"
|
import { Counter } from "./counter"
|
||||||
|
import { FruitSelect } from "./fruit-select"
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
intents: ["GUILDS"],
|
intents: ["GUILDS"],
|
||||||
@@ -23,6 +24,13 @@ createCommandHandler(client, [
|
|||||||
reply.render(<Counter onDeactivate={() => reply.deactivate()} />)
|
reply.render(<Counter onDeactivate={() => reply.deactivate()} />)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "select",
|
||||||
|
description: "shows a select",
|
||||||
|
run: (interaction) => {
|
||||||
|
reacord.createCommandReply(interaction).render(<FruitSelect />)
|
||||||
|
},
|
||||||
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
await client.login(process.env.TEST_BOT_TOKEN)
|
await client.login(process.env.TEST_BOT_TOKEN)
|
||||||
|
|||||||
247
test/select.test.tsx
Normal file
247
test/select.test.tsx
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import { Button, Option, Select } from "../library/main"
|
||||||
|
import { setupReacordTesting } from "./setup-testing"
|
||||||
|
|
||||||
|
const { adapter, reply, assertMessages } = setupReacordTesting()
|
||||||
|
|
||||||
|
test("single select", async () => {
|
||||||
|
function TestSelect() {
|
||||||
|
const [value, setValue] = useState<string>()
|
||||||
|
const [disabled, setDisabled] = useState(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Select
|
||||||
|
placeholder="choose one"
|
||||||
|
value={value}
|
||||||
|
onSelectValue={setValue}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Option value="1">one</Option>
|
||||||
|
<Option value="2">two</Option>
|
||||||
|
<Option value="3">three</Option>
|
||||||
|
</Select>
|
||||||
|
<Button label="disable" onClick={() => setDisabled(true)} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.render(<TestSelect />)
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "choose one",
|
||||||
|
values: [],
|
||||||
|
disabled: false,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[{ type: "button", style: "secondary", label: "disable" }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findSelectByPlaceholder("choose one").select("2")
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "choose one",
|
||||||
|
values: ["2"],
|
||||||
|
disabled: false,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[{ type: "button", style: "secondary", label: "disable" }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findButtonByLabel("disable").click()
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "choose one",
|
||||||
|
values: ["2"],
|
||||||
|
disabled: true,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[{ type: "button", style: "secondary", label: "disable" }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findSelectByPlaceholder("choose one").select("1")
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "choose one",
|
||||||
|
values: ["2"],
|
||||||
|
disabled: true,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[{ type: "button", style: "secondary", label: "disable" }],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test("multiple select", async () => {
|
||||||
|
function TestSelect() {
|
||||||
|
const [values, setValues] = useState<string[]>([])
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
placeholder="select"
|
||||||
|
multiple
|
||||||
|
values={values}
|
||||||
|
onSelectMultiple={setValues}
|
||||||
|
>
|
||||||
|
<Option value="1">one</Option>
|
||||||
|
<Option value="2">two</Option>
|
||||||
|
<Option value="3">three</Option>
|
||||||
|
</Select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.render(<TestSelect />)
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "select",
|
||||||
|
values: [],
|
||||||
|
minValues: 0,
|
||||||
|
maxValues: 25,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findSelectByPlaceholder("select").select("1", "3")
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "select",
|
||||||
|
values: expect.arrayContaining(["1", "3"]),
|
||||||
|
minValues: 0,
|
||||||
|
maxValues: 25,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findSelectByPlaceholder("select").select("2")
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "select",
|
||||||
|
values: ["2"],
|
||||||
|
minValues: 0,
|
||||||
|
maxValues: 25,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
adapter.findSelectByPlaceholder("select").select()
|
||||||
|
|
||||||
|
await assertMessages([
|
||||||
|
{
|
||||||
|
content: "",
|
||||||
|
embeds: [],
|
||||||
|
actionRows: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
placeholder: "select",
|
||||||
|
values: [],
|
||||||
|
minValues: 0,
|
||||||
|
maxValues: 25,
|
||||||
|
options: [
|
||||||
|
{ label: "one", value: "1" },
|
||||||
|
{ label: "two", value: "2" },
|
||||||
|
{ label: "three", value: "3" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
test.todo("select minValues and maxValues")
|
||||||
@@ -38,7 +38,9 @@ function sampleMessages(adapter: TestAdapter) {
|
|||||||
return adapter.messages.map((message) => ({
|
return adapter.messages.map((message) => ({
|
||||||
...message.options,
|
...message.options,
|
||||||
actionRows: message.options.actionRows.map((row) =>
|
actionRows: message.options.actionRows.map((row) =>
|
||||||
row.map((component) => omit(component, ["customId"])),
|
row.map((component) =>
|
||||||
|
omit(component, ["customId", "onClick", "onSelect", "onSelectValue"]),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user