import { mount } from "@vue/test-utils";
import { describe, it, expect, vi } from "vitest";
import ConversationMessageEntry from "@/components/messages/ConversationMessageEntry.vue";
function makeCv(overrides = {}) {
return {
hasMessageBubble: () => true,
hasFileAttachments: () => false,
getParsedItems: () => null,
bubbleViewModel: (item) => ({
kind: "html",
textForRender: item?.lxmf_message?.content || "",
singleEmoji: false,
showFooter: false,
}),
renderMarkdown: (text) => text,
bubbleMessageBodyFontSizePx: () => 14,
shouldHideAutoImageCaption: () => false,
isMessageBodyTooLargeForDisplay: () => false,
messageBodyCharCount: () => 0,
formatTimeAgo: () => "now",
getMessageInfoLines: () => [],
outboundBubbleSurfaceClass: () => "bubble",
outboundBubbleFooterTimeClass: () => "",
outboundEmbeddedCardClass: () => "",
outboundEmbeddedSecondaryTextClass: () => "",
outboundReplySnippetTitleClass: () => "",
outboundExpandedActionsShellClass: () => "",
outboundMessageMenuButtonClass: () => "",
outboundMessageMenuButtonHoverClass: () => "",
outboundBubbleDeliveredIconClass: () => "",
outboundBubbleSentCheckIconClass: () => "",
outboundSendingStatusIconClass: () => "",
outboundBubblePendingCheckIconClass: () => "",
outboundSentStatusTitle: () => "",
outboundBubbleFailedTitle: () => "",
outboundBubbleStatusHoverTitle: () => "",
isOutboundPendingForUi: (item) => item?.lxmf_message?.state === "sending",
isOutboundWaitingBubble: () => false,
isOpportunisticDeferredDelivery: () => false,
isThemeOutboundBubble: () => false,
showRichOutboundPendingUi: () => false,
showOutboundTransferProgress: () => false,
canCancelOutboundSend: (item) =>
item?.is_outbound && ["sending", "outbound", "generating"].includes(item?.lxmf_message?.state),
onChatItemClick: vi.fn((item) => {
item.is_actions_expanded = !item.is_actions_expanded;
}),
onMessageContextMenu: vi.fn(),
replyToMessage: vi.fn(),
deleteChatItem: vi.fn(),
showRawMessage: vi.fn(),
cancelSendingMessage: vi.fn(),
downloadLxmfFileAttachment: vi.fn(),
scrollToMessage: vi.fn(),
copyOversizedMessageBody: vi.fn(),
formatAttachmentSize: () => "1 B",
bubbleStyles: () => ({}),
isImageOnlyMessage: () => false,
pendingOutboundImageSrc: () => "",
onOutboundImageClick: vi.fn(),
openImage: vi.fn(),
imageGroupSortedChron: (items) => items,
imageGroupGalleryUrls: () => [],
lxmfImageUrl: () => "",
expandedMessageInfo: null,
...overrides,
};
}
describe("ConversationMessageEntry wiring", () => {
it("shows Cancel send in expanded actions while outbound message is sending", async () => {
const chatItem = {
type: "lxmf_message",
is_outbound: true,
is_actions_expanded: true,
lxmf_message: {
hash: "aa".repeat(16),
state: "sending",
progress: 10,
content: "hello",
destination_hash: "bb".repeat(16),
source_hash: "cc".repeat(16),
fields: {},
},
};
const cv = makeCv();
const wrapper = mount(ConversationMessageEntry, {
props: {
entry: { type: "message", key: "m1", chatItem, showTimestamp: true },
cv,
},
global: {
mocks: { $t: (key) => key },
stubs: {
MaterialDesignIcon: { template: "" },
MessageReactionsOverlay: true,
OutboundTransferProgressFooter: true,
},
},
});
expect(wrapper.text()).toContain("messages.cancel_send");
});
it("clicking Cancel send calls cv.cancelSendingMessage", async () => {
const chatItem = {
type: "lxmf_message",
is_outbound: true,
is_actions_expanded: true,
lxmf_message: {
hash: "aa".repeat(16),
state: "sending",
content: "cancel me",
destination_hash: "bb".repeat(16),
source_hash: "cc".repeat(16),
fields: {},
},
};
const cv = makeCv();
const wrapper = mount(ConversationMessageEntry, {
props: {
entry: { type: "message", key: "m2", chatItem, showTimestamp: true },
cv,
},
global: {
mocks: { $t: (key) => key },
stubs: {
MaterialDesignIcon: { template: "" },
MessageReactionsOverlay: true,
OutboundTransferProgressFooter: true,
},
},
});
const cancelBtn = wrapper.findAll("button").find((b) => b.text().includes("messages.cancel_send"));
expect(cancelBtn).toBeDefined();
await cancelBtn.trigger("click");
expect(cv.cancelSendingMessage).toHaveBeenCalledWith(chatItem);
});
it("file attachment row calls downloadLxmfFileAttachment instead of navigating", async () => {
const chatItem = {
type: "lxmf_message",
is_outbound: false,
lxmf_message: {
hash: "dd".repeat(16),
state: "delivered",
content: "",
destination_hash: "bb".repeat(16),
source_hash: "cc".repeat(16),
fields: {
file_attachments: [{ file_name: "photo.jpg", file_size: 100 }],
},
},
};
const cv = makeCv({
hasFileAttachments: () => true,
});
const wrapper = mount(ConversationMessageEntry, {
props: {
entry: { type: "message", key: "m3", chatItem, showTimestamp: true },
cv,
},
global: {
mocks: { $t: (key) => key },
stubs: {
MaterialDesignIcon: { template: "" },
MessageReactionsOverlay: true,
OutboundTransferProgressFooter: true,
},
},
});
const fileBtn = wrapper.findAll("button").find((b) => b.text().includes("photo.jpg"));
expect(fileBtn).toBeDefined();
expect(fileBtn.attributes("href")).toBeUndefined();
await fileBtn.trigger("click");
expect(cv.downloadLxmfFileAttachment).toHaveBeenCalledWith(chatItem, 0);
});
});