mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-17 08:02:12 -06:00
0212188cb6
Preserve `reusable` for portable message presentation buttons and carry it through Discord component registration so repeatable callbacks stay available after a successful interaction. Also keeps `reusable` through legacy presentation-to-interactive conversion and documents the user-visible change in the changelog. Verification: - `pnpm test src/interactive/payload.test.ts extensions/discord/src/shared-interactive.test.ts extensions/discord/src/components.test.ts -- --reporter=verbose` - `git diff --check` - `AUTOREVIEW_AUTO_TESTS=0 .agents/skills/autoreview/scripts/autoreview --mode local` - PR CI at `52f25221b3e01f3255d8df37df73d0357ab7410b`: all completed checks green/skipped/neutral except pending CodeQL `Security High (mcp-process-tool-boundary)` at time auto-merge was armed. Co-authored-by: OpenClaw Contributor <100menotu001@users.noreply.github.com>
212 lines
5.6 KiB
TypeScript
212 lines
5.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
buildDiscordInteractiveComponents,
|
|
buildDiscordPresentationComponents,
|
|
} from "./shared-interactive.js";
|
|
|
|
describe("buildDiscordInteractiveComponents", () => {
|
|
it("maps shared buttons and selects into Discord component blocks", () => {
|
|
expect(
|
|
buildDiscordInteractiveComponents({
|
|
blocks: [
|
|
{
|
|
type: "buttons",
|
|
buttons: [
|
|
{ label: "Approve", value: "approve", style: "success" },
|
|
{ label: "Reject", value: "reject", style: "danger" },
|
|
],
|
|
},
|
|
{
|
|
type: "select",
|
|
placeholder: "Pick one",
|
|
options: [{ label: "Alpha", value: "alpha" }],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [
|
|
{ label: "Approve", style: "success", callbackData: "approve" },
|
|
{ label: "Reject", style: "danger", callbackData: "reject" },
|
|
],
|
|
},
|
|
{
|
|
type: "actions",
|
|
select: {
|
|
type: "string",
|
|
placeholder: "Pick one",
|
|
options: [{ label: "Alpha", value: "alpha" }],
|
|
},
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("preserves authored shared text blocks around controls", () => {
|
|
expect(
|
|
buildDiscordInteractiveComponents({
|
|
blocks: [
|
|
{ type: "text", text: "First" },
|
|
{
|
|
type: "buttons",
|
|
buttons: [{ label: "Approve", value: "approve", style: "success" }],
|
|
},
|
|
{ type: "text", text: "Last" },
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{ type: "text", text: "First" },
|
|
{
|
|
type: "actions",
|
|
buttons: [{ label: "Approve", style: "success", callbackData: "approve" }],
|
|
},
|
|
{ type: "text", text: "Last" },
|
|
],
|
|
});
|
|
});
|
|
|
|
it("preserves URL-only buttons as Discord link buttons", () => {
|
|
expect(
|
|
buildDiscordInteractiveComponents({
|
|
blocks: [
|
|
{
|
|
type: "buttons",
|
|
buttons: [{ label: "Docs", url: "https://example.com/docs" }],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [{ label: "Docs", style: "link", url: "https://example.com/docs" }],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("splits long shared button rows to stay within Discord action limits", () => {
|
|
expect(
|
|
buildDiscordInteractiveComponents({
|
|
blocks: [
|
|
{
|
|
type: "buttons",
|
|
buttons: [
|
|
{ label: "One", value: "1" },
|
|
{ label: "Two", value: "2" },
|
|
{ label: "Three", value: "3" },
|
|
{ label: "Four", value: "4" },
|
|
{ label: "Five", value: "5" },
|
|
{ label: "Six", value: "6" },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [
|
|
{ label: "One", style: "secondary", callbackData: "1" },
|
|
{ label: "Two", style: "secondary", callbackData: "2" },
|
|
{ label: "Three", style: "secondary", callbackData: "3" },
|
|
{ label: "Four", style: "secondary", callbackData: "4" },
|
|
{ label: "Five", style: "secondary", callbackData: "5" },
|
|
],
|
|
},
|
|
{
|
|
type: "actions",
|
|
buttons: [{ label: "Six", style: "secondary", callbackData: "6" }],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("does not duplicate presentation text when appending controls", () => {
|
|
expect(
|
|
buildDiscordPresentationComponents({
|
|
title: "Status",
|
|
blocks: [
|
|
{ type: "text", text: "Build completed" },
|
|
{ type: "context", text: "main branch" },
|
|
{
|
|
type: "buttons",
|
|
buttons: [{ label: "Open", value: "open" }],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{ type: "text", text: "Status" },
|
|
{ type: "text", text: "Build completed" },
|
|
{ type: "text", text: "-# main branch" },
|
|
{
|
|
type: "actions",
|
|
buttons: [{ label: "Open", style: "secondary", callbackData: "open" }],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("preserves disabled presentation buttons for Discord components", () => {
|
|
expect(
|
|
buildDiscordPresentationComponents({
|
|
blocks: [
|
|
{
|
|
type: "buttons",
|
|
buttons: [
|
|
{ label: "Already handled", value: "done", disabled: true },
|
|
{ label: "Open docs", url: "https://example.com/docs", disabled: true },
|
|
],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [
|
|
{
|
|
label: "Already handled",
|
|
style: "secondary",
|
|
callbackData: "done",
|
|
disabled: true,
|
|
},
|
|
{
|
|
label: "Open docs",
|
|
style: "link",
|
|
url: "https://example.com/docs",
|
|
disabled: true,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("preserves reusable presentation buttons for Discord action entries", () => {
|
|
expect(
|
|
buildDiscordPresentationComponents({
|
|
blocks: [
|
|
{
|
|
type: "buttons",
|
|
buttons: [{ label: "Refresh", value: "refresh", reusable: true }],
|
|
},
|
|
],
|
|
}),
|
|
).toEqual({
|
|
blocks: [
|
|
{
|
|
type: "actions",
|
|
buttons: [
|
|
{ label: "Refresh", style: "secondary", callbackData: "refresh", reusable: true },
|
|
],
|
|
},
|
|
],
|
|
});
|
|
});
|
|
});
|