* feat: add LINE plugin (#1630) (thanks @plum-dawg) * feat: complete LINE plugin (#1630) (thanks @plum-dawg) * chore: drop line plugin node_modules (#1630) (thanks @plum-dawg) * test: mock /context report in commands test (#1630) (thanks @plum-dawg) * test: limit macOS CI workers to avoid OOM (#1630) (thanks @plum-dawg) * test: reduce macOS CI vitest workers (#1630) (thanks @plum-dawg) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
392 lines
12 KiB
TypeScript
392 lines
12 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
createConfirmTemplate,
|
|
createButtonTemplate,
|
|
createTemplateCarousel,
|
|
createCarouselColumn,
|
|
createImageCarousel,
|
|
createImageCarouselColumn,
|
|
createYesNoConfirm,
|
|
createButtonMenu,
|
|
createLinkMenu,
|
|
createProductCarousel,
|
|
messageAction,
|
|
uriAction,
|
|
postbackAction,
|
|
datetimePickerAction,
|
|
} from "./template-messages.js";
|
|
|
|
describe("messageAction", () => {
|
|
it("creates a message action", () => {
|
|
const action = messageAction("Click me", "clicked");
|
|
|
|
expect(action.type).toBe("message");
|
|
expect(action.label).toBe("Click me");
|
|
expect((action as { text: string }).text).toBe("clicked");
|
|
});
|
|
|
|
it("uses label as text when text not provided", () => {
|
|
const action = messageAction("Click");
|
|
|
|
expect((action as { text: string }).text).toBe("Click");
|
|
});
|
|
|
|
it("truncates label to 20 characters", () => {
|
|
const action = messageAction("This is a very long label that exceeds the limit");
|
|
|
|
expect(action.label).toBe("This is a very long ");
|
|
});
|
|
});
|
|
|
|
describe("uriAction", () => {
|
|
it("creates a URI action", () => {
|
|
const action = uriAction("Visit", "https://example.com");
|
|
|
|
expect(action.type).toBe("uri");
|
|
expect(action.label).toBe("Visit");
|
|
expect((action as { uri: string }).uri).toBe("https://example.com");
|
|
});
|
|
});
|
|
|
|
describe("postbackAction", () => {
|
|
it("creates a postback action", () => {
|
|
const action = postbackAction("Select", "action=select&id=1");
|
|
|
|
expect(action.type).toBe("postback");
|
|
expect(action.label).toBe("Select");
|
|
expect((action as { data: string }).data).toBe("action=select&id=1");
|
|
});
|
|
|
|
it("includes displayText when provided", () => {
|
|
const action = postbackAction("Select", "data", "Selected!");
|
|
|
|
expect((action as { displayText: string }).displayText).toBe("Selected!");
|
|
});
|
|
|
|
it("truncates data to 300 characters", () => {
|
|
const longData = "x".repeat(400);
|
|
const action = postbackAction("Test", longData);
|
|
|
|
expect((action as { data: string }).data.length).toBe(300);
|
|
});
|
|
});
|
|
|
|
describe("datetimePickerAction", () => {
|
|
it("creates a datetime picker action", () => {
|
|
const action = datetimePickerAction("Pick date", "date_selected", "date");
|
|
|
|
expect(action.type).toBe("datetimepicker");
|
|
expect(action.label).toBe("Pick date");
|
|
expect((action as { mode: string }).mode).toBe("date");
|
|
});
|
|
|
|
it("includes min/max/initial when provided", () => {
|
|
const action = datetimePickerAction("Pick", "data", "datetime", {
|
|
initial: "2024-01-01T12:00",
|
|
min: "2024-01-01T00:00",
|
|
max: "2024-12-31T23:59",
|
|
});
|
|
|
|
expect((action as { initial: string }).initial).toBe("2024-01-01T12:00");
|
|
expect((action as { min: string }).min).toBe("2024-01-01T00:00");
|
|
expect((action as { max: string }).max).toBe("2024-12-31T23:59");
|
|
});
|
|
});
|
|
|
|
describe("createConfirmTemplate", () => {
|
|
it("creates a confirm template", () => {
|
|
const confirm = messageAction("Yes");
|
|
const cancel = messageAction("No");
|
|
const template = createConfirmTemplate("Are you sure?", confirm, cancel);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("confirm");
|
|
expect((template.template as { text: string }).text).toBe("Are you sure?");
|
|
});
|
|
|
|
it("truncates text to 240 characters", () => {
|
|
const longText = "x".repeat(300);
|
|
const template = createConfirmTemplate(longText, messageAction("Yes"), messageAction("No"));
|
|
|
|
expect((template.template as { text: string }).text.length).toBe(240);
|
|
});
|
|
|
|
it("uses custom altText when provided", () => {
|
|
const template = createConfirmTemplate(
|
|
"Question?",
|
|
messageAction("Yes"),
|
|
messageAction("No"),
|
|
"Custom alt",
|
|
);
|
|
|
|
expect(template.altText).toBe("Custom alt");
|
|
});
|
|
});
|
|
|
|
describe("createButtonTemplate", () => {
|
|
it("creates a button template", () => {
|
|
const actions = [messageAction("Button 1"), messageAction("Button 2")];
|
|
const template = createButtonTemplate("Title", "Description", actions);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("buttons");
|
|
expect((template.template as { title: string }).title).toBe("Title");
|
|
expect((template.template as { text: string }).text).toBe("Description");
|
|
});
|
|
|
|
it("limits actions to 4", () => {
|
|
const actions = Array.from({ length: 6 }, (_, i) => messageAction(`Button ${i}`));
|
|
const template = createButtonTemplate("Title", "Text", actions);
|
|
|
|
expect((template.template as { actions: unknown[] }).actions.length).toBe(4);
|
|
});
|
|
|
|
it("truncates title to 40 characters", () => {
|
|
const longTitle = "x".repeat(50);
|
|
const template = createButtonTemplate(longTitle, "Text", [messageAction("OK")]);
|
|
|
|
expect((template.template as { title: string }).title.length).toBe(40);
|
|
});
|
|
|
|
it("includes thumbnail when provided", () => {
|
|
const template = createButtonTemplate("Title", "Text", [messageAction("OK")], {
|
|
thumbnailImageUrl: "https://example.com/thumb.jpg",
|
|
});
|
|
|
|
expect((template.template as { thumbnailImageUrl: string }).thumbnailImageUrl).toBe(
|
|
"https://example.com/thumb.jpg",
|
|
);
|
|
});
|
|
|
|
it("truncates text to 60 chars when no thumbnail is provided", () => {
|
|
const longText = "x".repeat(100);
|
|
const template = createButtonTemplate("Title", longText, [messageAction("OK")]);
|
|
|
|
expect((template.template as { text: string }).text.length).toBe(60);
|
|
});
|
|
|
|
it("keeps longer text when thumbnail is provided", () => {
|
|
const longText = "x".repeat(100);
|
|
const template = createButtonTemplate("Title", longText, [messageAction("OK")], {
|
|
thumbnailImageUrl: "https://example.com/thumb.jpg",
|
|
});
|
|
|
|
expect((template.template as { text: string }).text.length).toBe(100);
|
|
});
|
|
});
|
|
|
|
describe("createTemplateCarousel", () => {
|
|
it("creates a carousel template", () => {
|
|
const columns = [
|
|
createCarouselColumn({ text: "Column 1", actions: [messageAction("Select")] }),
|
|
createCarouselColumn({ text: "Column 2", actions: [messageAction("Select")] }),
|
|
];
|
|
const template = createTemplateCarousel(columns);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("carousel");
|
|
expect((template.template as { columns: unknown[] }).columns.length).toBe(2);
|
|
});
|
|
|
|
it("limits columns to 10", () => {
|
|
const columns = Array.from({ length: 15 }, () =>
|
|
createCarouselColumn({ text: "Text", actions: [messageAction("OK")] }),
|
|
);
|
|
const template = createTemplateCarousel(columns);
|
|
|
|
expect((template.template as { columns: unknown[] }).columns.length).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("createCarouselColumn", () => {
|
|
it("creates a carousel column", () => {
|
|
const column = createCarouselColumn({
|
|
title: "Item",
|
|
text: "Description",
|
|
actions: [messageAction("View")],
|
|
thumbnailImageUrl: "https://example.com/img.jpg",
|
|
});
|
|
|
|
expect(column.title).toBe("Item");
|
|
expect(column.text).toBe("Description");
|
|
expect(column.thumbnailImageUrl).toBe("https://example.com/img.jpg");
|
|
expect(column.actions.length).toBe(1);
|
|
});
|
|
|
|
it("limits actions to 3", () => {
|
|
const column = createCarouselColumn({
|
|
text: "Text",
|
|
actions: [
|
|
messageAction("A1"),
|
|
messageAction("A2"),
|
|
messageAction("A3"),
|
|
messageAction("A4"),
|
|
messageAction("A5"),
|
|
],
|
|
});
|
|
|
|
expect(column.actions.length).toBe(3);
|
|
});
|
|
|
|
it("truncates text to 120 characters", () => {
|
|
const longText = "x".repeat(150);
|
|
const column = createCarouselColumn({ text: longText, actions: [messageAction("OK")] });
|
|
|
|
expect(column.text.length).toBe(120);
|
|
});
|
|
});
|
|
|
|
describe("createImageCarousel", () => {
|
|
it("creates an image carousel", () => {
|
|
const columns = [
|
|
createImageCarouselColumn("https://example.com/1.jpg", messageAction("View 1")),
|
|
createImageCarouselColumn("https://example.com/2.jpg", messageAction("View 2")),
|
|
];
|
|
const template = createImageCarousel(columns);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("image_carousel");
|
|
});
|
|
|
|
it("limits columns to 10", () => {
|
|
const columns = Array.from({ length: 15 }, (_, i) =>
|
|
createImageCarouselColumn(`https://example.com/${i}.jpg`, messageAction("View")),
|
|
);
|
|
const template = createImageCarousel(columns);
|
|
|
|
expect((template.template as { columns: unknown[] }).columns.length).toBe(10);
|
|
});
|
|
});
|
|
|
|
describe("createImageCarouselColumn", () => {
|
|
it("creates an image carousel column", () => {
|
|
const action = uriAction("Visit", "https://example.com");
|
|
const column = createImageCarouselColumn("https://example.com/img.jpg", action);
|
|
|
|
expect(column.imageUrl).toBe("https://example.com/img.jpg");
|
|
expect(column.action).toBe(action);
|
|
});
|
|
});
|
|
|
|
describe("createYesNoConfirm", () => {
|
|
it("creates a yes/no confirmation with defaults", () => {
|
|
const template = createYesNoConfirm("Continue?");
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("confirm");
|
|
|
|
const actions = (template.template as { actions: Array<{ label: string }> }).actions;
|
|
expect(actions[0].label).toBe("Yes");
|
|
expect(actions[1].label).toBe("No");
|
|
});
|
|
|
|
it("allows custom button text", () => {
|
|
const template = createYesNoConfirm("Delete?", {
|
|
yesText: "Delete",
|
|
noText: "Cancel",
|
|
});
|
|
|
|
const actions = (template.template as { actions: Array<{ label: string }> }).actions;
|
|
expect(actions[0].label).toBe("Delete");
|
|
expect(actions[1].label).toBe("Cancel");
|
|
});
|
|
|
|
it("uses postback actions when data provided", () => {
|
|
const template = createYesNoConfirm("Confirm?", {
|
|
yesData: "action=confirm",
|
|
noData: "action=cancel",
|
|
});
|
|
|
|
const actions = (template.template as { actions: Array<{ type: string }> }).actions;
|
|
expect(actions[0].type).toBe("postback");
|
|
expect(actions[1].type).toBe("postback");
|
|
});
|
|
});
|
|
|
|
describe("createButtonMenu", () => {
|
|
it("creates a button menu with text buttons", () => {
|
|
const template = createButtonMenu("Menu", "Choose an option", [
|
|
{ label: "Option 1" },
|
|
{ label: "Option 2", text: "selected option 2" },
|
|
]);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("buttons");
|
|
|
|
const actions = (template.template as { actions: Array<{ type: string }> }).actions;
|
|
expect(actions.length).toBe(2);
|
|
expect(actions[0].type).toBe("message");
|
|
});
|
|
});
|
|
|
|
describe("createLinkMenu", () => {
|
|
it("creates a button menu with URL links", () => {
|
|
const template = createLinkMenu("Links", "Visit our sites", [
|
|
{ label: "Site 1", url: "https://site1.com" },
|
|
{ label: "Site 2", url: "https://site2.com" },
|
|
]);
|
|
|
|
expect(template.type).toBe("template");
|
|
|
|
const actions = (template.template as { actions: Array<{ type: string }> }).actions;
|
|
expect(actions[0].type).toBe("uri");
|
|
expect(actions[1].type).toBe("uri");
|
|
});
|
|
});
|
|
|
|
describe("createProductCarousel", () => {
|
|
it("creates a product carousel", () => {
|
|
const template = createProductCarousel([
|
|
{ title: "Product 1", description: "Desc 1", price: "$10" },
|
|
{ title: "Product 2", description: "Desc 2", imageUrl: "https://example.com/p2.jpg" },
|
|
]);
|
|
|
|
expect(template.type).toBe("template");
|
|
expect(template.template.type).toBe("carousel");
|
|
|
|
const columns = (template.template as { columns: unknown[] }).columns;
|
|
expect(columns.length).toBe(2);
|
|
});
|
|
|
|
it("uses URI action when actionUrl provided", () => {
|
|
const template = createProductCarousel([
|
|
{
|
|
title: "Product",
|
|
description: "Desc",
|
|
actionLabel: "Buy",
|
|
actionUrl: "https://shop.com/buy",
|
|
},
|
|
]);
|
|
|
|
const columns = (template.template as { columns: Array<{ actions: Array<{ type: string }> }> })
|
|
.columns;
|
|
expect(columns[0].actions[0].type).toBe("uri");
|
|
});
|
|
|
|
it("uses postback action when actionData provided", () => {
|
|
const template = createProductCarousel([
|
|
{
|
|
title: "Product",
|
|
description: "Desc",
|
|
actionLabel: "Select",
|
|
actionData: "product_id=123",
|
|
},
|
|
]);
|
|
|
|
const columns = (template.template as { columns: Array<{ actions: Array<{ type: string }> }> })
|
|
.columns;
|
|
expect(columns[0].actions[0].type).toBe("postback");
|
|
});
|
|
|
|
it("limits to 10 products", () => {
|
|
const products = Array.from({ length: 15 }, (_, i) => ({
|
|
title: `Product ${i}`,
|
|
description: `Desc ${i}`,
|
|
}));
|
|
const template = createProductCarousel(products);
|
|
|
|
const columns = (template.template as { columns: unknown[] }).columns;
|
|
expect(columns.length).toBe(10);
|
|
});
|
|
});
|