refactor: unify reply content checks

This commit is contained in:
Peter Steinberger
2026-03-16 05:32:41 +00:00
parent 3963408871
commit 7bea559166
8 changed files with 195 additions and 50 deletions

View File

@@ -0,0 +1,66 @@
import { describe, expect, it } from "vitest";
import {
hasReplyChannelData,
hasReplyContent,
normalizeInteractiveReply,
resolveInteractiveTextFallback,
} from "./payload.js";
describe("hasReplyChannelData", () => {
it("accepts non-empty objects only", () => {
expect(hasReplyChannelData(undefined)).toBe(false);
expect(hasReplyChannelData({})).toBe(false);
expect(hasReplyChannelData([])).toBe(false);
expect(hasReplyChannelData({ slack: { blocks: [] } })).toBe(true);
});
});
describe("hasReplyContent", () => {
it("treats whitespace-only text and empty structured payloads as empty", () => {
expect(
hasReplyContent({
text: " ",
mediaUrls: ["", " "],
interactive: { blocks: [] },
hasChannelData: false,
}),
).toBe(false);
});
it("accepts shared interactive blocks and explicit extra content", () => {
expect(
hasReplyContent({
interactive: {
blocks: [{ type: "buttons", buttons: [{ label: "Retry", value: "retry" }] }],
},
}),
).toBe(true);
expect(
hasReplyContent({
text: " ",
extraContent: true,
}),
).toBe(true);
});
});
describe("interactive payload helpers", () => {
it("normalizes interactive replies and resolves text fallbacks", () => {
const interactive = normalizeInteractiveReply({
blocks: [
{ type: "text", text: "First" },
{ type: "buttons", buttons: [{ label: "Retry", value: "retry" }] },
{ type: "text", text: "Second" },
],
});
expect(interactive).toEqual({
blocks: [
{ type: "text", text: "First" },
{ type: "buttons", buttons: [{ label: "Retry", value: "retry" }] },
{ type: "text", text: "Second" },
],
});
expect(resolveInteractiveTextFallback({ interactive })).toBe("First\n\nSecond");
});
});

View File

@@ -136,6 +136,30 @@ export function hasInteractiveReplyBlocks(value: unknown): value is InteractiveR
return Boolean(normalizeInteractiveReply(value));
}
export function hasReplyChannelData(value: unknown): value is Record<string, unknown> {
return Boolean(
value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0,
);
}
export function hasReplyContent(params: {
text?: string | null;
mediaUrl?: string | null;
mediaUrls?: ReadonlyArray<string | null | undefined>;
interactive?: unknown;
hasChannelData?: boolean;
extraContent?: boolean;
}): boolean {
return Boolean(
params.text?.trim() ||
params.mediaUrl?.trim() ||
params.mediaUrls?.some((entry) => Boolean(entry?.trim())) ||
hasInteractiveReplyBlocks(params.interactive) ||
params.hasChannelData ||
params.extraContent,
);
}
export function resolveInteractiveTextFallback(params: {
text?: string;
interactive?: InteractiveReply;