Files
openclaw/extensions/slack/src/question-finalization.test.ts
T
Peter Steinberger d7de67ae02 feat: ask_user follow-ups — harness convergence, channel finalization + reactions, native cards, docked web panel (#110372)
* feat(ask-user): follow-up harness slice

* feat(ask-user): follow-up channels slice

* feat(ask-user): follow-up native slice

* feat(ui): dock question panel above composer with stepper and compact stream summaries

* docs: refresh follow-up integration maps

* test(ui): align terminal summary proof

* fix(infra): echo declared option answers in terminal status when free-text is allowed

* fix(infra): keep reaction answering when display labels are formatter-adjusted

* fix(agents): settle plain-text claims only after question registration commits

* fix(agents,apps): commit-ordered claim persistence, claim-aware prompt delivery, non-blocking question refresh

* fix(harness,infra): reaction-appropriate question copy, caller presentations honored

* fix(native,infra,agents): local-expiry eviction, value-addressed reactions, reserve-before-request

* fix(infra,android): dual-mode question resolver for compact callbacks; reset terminal retention on replayed pending

* fix(harness,discord): claim-aware prompt delivery in run helper; escape finalization labels

* fix(macos): merge transient-content visibility with question cards after main sync

* fix: repair ask user follow-up CI

* test: update limited bootstrap scope expectation

* fix: retain shared question card API

* chore: refresh native i18n inventory
2026-07-18 03:32:39 -04:00

87 lines
2.9 KiB
TypeScript

// Covers Slack question delivery capture and Block Kit final edit.
import { describe, expect, it, vi } from "vitest";
const hoisted = vi.hoisted(() => ({
update: vi.fn(),
registration: undefined as
| { finalize: (statusLine: string) => void | Promise<void>; deliveryId: string }
| undefined,
}));
vi.mock("openclaw/plugin-sdk/question-gateway-runtime", async (importOriginal) => {
const original =
await importOriginal<typeof import("openclaw/plugin-sdk/question-gateway-runtime")>();
return {
...original,
questionGatewayRuntime: {
...original.questionGatewayRuntime,
registerChannelDelivery: (registration: typeof hoisted.registration) => {
hoisted.registration = registration;
},
},
};
});
vi.mock("./send.js", () => ({ updateMessageSlack: hoisted.update }));
import { slackOutbound } from "./outbound-adapter.js";
describe("Slack question finalization", () => {
it("removes action blocks and appends terminal context", async () => {
const questionId = "ask_0123456789abcdef0123456789abcdef";
const payload = {
channelData: { askUser: { questionId } },
presentation: {
blocks: [
{ type: "text" as const, text: "Pick one" },
{
type: "buttons" as const,
buttons: ["One", "Two"].map((label) => ({
label,
action: { type: "question" as const, questionId, optionValue: label },
})),
},
],
},
};
const rendered = await slackOutbound.renderPresentation?.({
payload,
presentation: payload.presentation,
ctx: { cfg: {}, to: "C123", text: "Pick one", payload },
});
expect(rendered).not.toBeNull();
const slackData = rendered!.channelData?.slack as {
renderedPresentationSegments: unknown[];
};
slackData.renderedPresentationSegments.unshift({
kind: "text",
text: "Preface",
mrkdwn: false,
});
await slackOutbound.afterDeliverPayload?.({
cfg: {},
target: { channel: "slack", to: "C123", accountId: "default" },
payload: rendered!,
results: [
{ channel: "slack", messageId: "44", channelId: "C123" },
{ channel: "slack", messageId: "55", channelId: "C123" },
],
});
await hoisted.registration?.finalize("Answered: <!channel>");
expect(hoisted.update).toHaveBeenCalledWith(
expect.objectContaining({
channelId: "C123",
messageTs: "55",
text: expect.stringContaining("Answered: &lt;!channel&gt;"),
blocks: expect.arrayContaining([
{
type: "context",
elements: [{ type: "mrkdwn", text: "Answered: &lt;!channel&gt;" }],
},
]),
}),
);
const blocks = hoisted.update.mock.calls[0]?.[0]?.blocks as Array<{ type?: string }>;
expect(blocks.some((block) => block.type === "actions")).toBe(false);
});
});