test: dedupe channel and transport adapters

This commit is contained in:
Peter Steinberger
2026-02-21 21:43:18 +00:00
parent 52ddb6ae18
commit 58254b3b57
19 changed files with 2187 additions and 2545 deletions

View File

@@ -77,8 +77,8 @@ Table 2:
});
describe("extractCodeBlocks", () => {
it("extracts a code block with language", () => {
const text = `Here is some code:
it("extracts code blocks across language/no-language/multiple variants", () => {
const withLanguage = `Here is some code:
\`\`\`javascript
const x = 1;
@@ -86,31 +86,23 @@ console.log(x);
\`\`\`
And more text.`;
const withLanguageResult = extractCodeBlocks(withLanguage);
expect(withLanguageResult.codeBlocks).toHaveLength(1);
expect(withLanguageResult.codeBlocks[0].language).toBe("javascript");
expect(withLanguageResult.codeBlocks[0].code).toBe("const x = 1;\nconsole.log(x);");
expect(withLanguageResult.textWithoutCode).toContain("Here is some code:");
expect(withLanguageResult.textWithoutCode).toContain("And more text.");
expect(withLanguageResult.textWithoutCode).not.toContain("```");
const { codeBlocks, textWithoutCode } = extractCodeBlocks(text);
expect(codeBlocks).toHaveLength(1);
expect(codeBlocks[0].language).toBe("javascript");
expect(codeBlocks[0].code).toBe("const x = 1;\nconsole.log(x);");
expect(textWithoutCode).toContain("Here is some code:");
expect(textWithoutCode).toContain("And more text.");
expect(textWithoutCode).not.toContain("```");
});
it("extracts a code block without language", () => {
const text = `\`\`\`
const withoutLanguage = `\`\`\`
plain code
\`\`\``;
const withoutLanguageResult = extractCodeBlocks(withoutLanguage);
expect(withoutLanguageResult.codeBlocks).toHaveLength(1);
expect(withoutLanguageResult.codeBlocks[0].language).toBeUndefined();
expect(withoutLanguageResult.codeBlocks[0].code).toBe("plain code");
const { codeBlocks } = extractCodeBlocks(text);
expect(codeBlocks).toHaveLength(1);
expect(codeBlocks[0].language).toBeUndefined();
expect(codeBlocks[0].code).toBe("plain code");
});
it("extracts multiple code blocks", () => {
const text = `\`\`\`python
const multiple = `\`\`\`python
print("hello")
\`\`\`
@@ -119,12 +111,10 @@ Some text
\`\`\`bash
echo "world"
\`\`\``;
const { codeBlocks } = extractCodeBlocks(text);
expect(codeBlocks).toHaveLength(2);
expect(codeBlocks[0].language).toBe("python");
expect(codeBlocks[1].language).toBe("bash");
const multipleResult = extractCodeBlocks(multiple);
expect(multipleResult.codeBlocks).toHaveLength(2);
expect(multipleResult.codeBlocks[0].language).toBe("python");
expect(multipleResult.codeBlocks[1].language).toBe("bash");
});
});
@@ -142,27 +132,20 @@ describe("extractLinks", () => {
});
describe("stripMarkdown", () => {
it("strips bold markers", () => {
expect(stripMarkdown("This is **bold** text")).toBe("This is bold text");
expect(stripMarkdown("This is __bold__ text")).toBe("This is bold text");
});
it("strips italic markers", () => {
expect(stripMarkdown("This is *italic* text")).toBe("This is italic text");
expect(stripMarkdown("This is _italic_ text")).toBe("This is italic text");
});
it("strips strikethrough markers", () => {
expect(stripMarkdown("This is ~~deleted~~ text")).toBe("This is deleted text");
});
it("removes horizontal rules", () => {
expect(stripMarkdown("Above\n---\nBelow")).toBe("Above\n\nBelow");
expect(stripMarkdown("Above\n***\nBelow")).toBe("Above\n\nBelow");
});
it("strips inline code markers", () => {
expect(stripMarkdown("Use `const` keyword")).toBe("Use const keyword");
it("strips inline markdown marker variants", () => {
const cases = [
["strips bold **", "This is **bold** text", "This is bold text"],
["strips bold __", "This is __bold__ text", "This is bold text"],
["strips italic *", "This is *italic* text", "This is italic text"],
["strips italic _", "This is _italic_ text", "This is italic text"],
["strips strikethrough", "This is ~~deleted~~ text", "This is deleted text"],
["removes hr ---", "Above\n---\nBelow", "Above\n\nBelow"],
["removes hr ***", "Above\n***\nBelow", "Above\n\nBelow"],
["strips inline code markers", "Use `const` keyword", "Use const keyword"],
] as const;
for (const [name, input, expected] of cases) {
expect(stripMarkdown(input), name).toBe(expected);
}
});
it("handles complex markdown", () => {

View File

@@ -9,18 +9,19 @@ import {
} from "./rich-menu.js";
describe("messageAction", () => {
it("creates a message action", () => {
const action = messageAction("Help", "/help");
expect(action.type).toBe("message");
expect(action.label).toBe("Help");
expect((action as { text: string }).text).toBe("/help");
});
it("uses label as text when text not provided", () => {
const action = messageAction("Click");
expect((action as { text: string }).text).toBe("Click");
it("creates message actions with explicit or default text", () => {
const cases = [
{ name: "explicit text", label: "Help", text: "/help", expectedText: "/help" },
{ name: "defaults to label", label: "Click", text: undefined, expectedText: "Click" },
] as const;
for (const testCase of cases) {
const action = testCase.text
? messageAction(testCase.label, testCase.text)
: messageAction(testCase.label);
expect(action.type, testCase.name).toBe("message");
expect(action.label, testCase.name).toBe(testCase.label);
expect((action as { text: string }).text, testCase.name).toBe(testCase.expectedText);
}
});
});
@@ -61,47 +62,32 @@ describe("postbackAction", () => {
expect((action as { displayText: string }).displayText).toBe("Selected item 1");
});
it("truncates data to 300 characters", () => {
const longData = "x".repeat(400);
const action = postbackAction("Test", longData);
it("applies postback payload truncation and displayText behavior", () => {
const truncatedData = postbackAction("Test", "x".repeat(400));
expect((truncatedData as { data: string }).data.length).toBe(300);
expect((action as { data: string }).data.length).toBe(300);
});
const truncatedDisplay = postbackAction("Test", "data", "y".repeat(400));
expect((truncatedDisplay as { displayText: string }).displayText?.length).toBe(300);
it("truncates displayText to 300 characters", () => {
const longText = "y".repeat(400);
const action = postbackAction("Test", "data", longText);
expect((action as { displayText: string }).displayText?.length).toBe(300);
});
it("omits displayText when not provided", () => {
const action = postbackAction("Test", "data");
expect((action as { displayText?: string }).displayText).toBeUndefined();
const noDisplayText = postbackAction("Test", "data");
expect((noDisplayText as { displayText?: string }).displayText).toBeUndefined();
});
});
describe("datetimePickerAction", () => {
it("creates a date picker action", () => {
const action = datetimePickerAction("Pick date", "date_picked", "date");
expect(action.type).toBe("datetimepicker");
expect(action.label).toBe("Pick date");
expect((action as { mode: string }).mode).toBe("date");
expect((action as { data: string }).data).toBe("date_picked");
});
it("creates a time picker action", () => {
const action = datetimePickerAction("Pick time", "time_picked", "time");
expect((action as { mode: string }).mode).toBe("time");
});
it("creates a datetime picker action", () => {
const action = datetimePickerAction("Pick datetime", "datetime_picked", "datetime");
expect((action as { mode: string }).mode).toBe("datetime");
it("creates picker actions for all supported modes", () => {
const cases = [
{ label: "Pick date", data: "date_picked", mode: "date" as const },
{ label: "Pick time", data: "time_picked", mode: "time" as const },
{ label: "Pick datetime", data: "datetime_picked", mode: "datetime" as const },
];
for (const testCase of cases) {
const action = datetimePickerAction(testCase.label, testCase.data, testCase.mode);
expect(action.type).toBe("datetimepicker");
expect(action.label).toBe(testCase.label);
expect((action as { mode: string }).mode).toBe(testCase.mode);
expect((action as { data: string }).data).toBe(testCase.data);
}
});
it("includes initial/min/max when provided", () => {
@@ -136,37 +122,22 @@ describe("createGridLayout", () => {
];
}
it("creates a 2x3 grid layout for tall menu", () => {
it("computes expected 2x3 layout for supported menu heights", () => {
const actions = createSixSimpleActions();
const areas = createGridLayout(1686, actions);
expect(areas.length).toBe(6);
// Check first row positions
expect(areas[0].bounds.x).toBe(0);
expect(areas[0].bounds.y).toBe(0);
expect(areas[1].bounds.x).toBe(833);
expect(areas[1].bounds.y).toBe(0);
expect(areas[2].bounds.x).toBe(1666);
expect(areas[2].bounds.y).toBe(0);
// Check second row positions
expect(areas[3].bounds.y).toBe(843);
expect(areas[4].bounds.y).toBe(843);
expect(areas[5].bounds.y).toBe(843);
});
it("creates a 2x3 grid layout for short menu", () => {
const actions = createSixSimpleActions();
const areas = createGridLayout(843, actions);
expect(areas.length).toBe(6);
// Row height should be half of 843
expect(areas[0].bounds.height).toBe(421);
expect(areas[3].bounds.y).toBe(421);
const cases = [
{ height: 1686, firstRowY: 0, secondRowY: 843, rowHeight: 843 },
{ height: 843, firstRowY: 0, secondRowY: 421, rowHeight: 421 },
] as const;
for (const testCase of cases) {
const areas = createGridLayout(testCase.height, actions);
expect(areas.length).toBe(6);
expect(areas[0]?.bounds.y).toBe(testCase.firstRowY);
expect(areas[0]?.bounds.height).toBe(testCase.rowHeight);
expect(areas[3]?.bounds.y).toBe(testCase.secondRowY);
expect(areas[0]?.bounds.x).toBe(0);
expect(areas[1]?.bounds.x).toBe(833);
expect(areas[2]?.bounds.x).toBe(1666);
}
});
it("assigns correct actions to areas", () => {
@@ -222,17 +193,12 @@ describe("createDefaultMenuConfig", () => {
}
});
it("has message actions for all areas", () => {
it("uses message actions with expected default commands", () => {
const config = createDefaultMenuConfig();
for (const area of config.areas) {
expect(area.action.type).toBe("message");
}
});
it("has expected default commands", () => {
const config = createDefaultMenuConfig();
const commands = config.areas.map((a) => (a.action as { text: string }).text);
expect(commands).toContain("/help");
expect(commands).toContain("/status");