@kodelyth/line 2026.5.42 → 2026.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/klaw.plugin.json +329 -2
- package/package.json +18 -6
- package/api.ts +0 -11
- package/channel-plugin-api.ts +0 -1
- package/contract-api.ts +0 -5
- package/index.ts +0 -53
- package/runtime-api.ts +0 -179
- package/secret-contract-api.ts +0 -4
- package/setup-api.ts +0 -2
- package/setup-entry.ts +0 -9
- package/src/account-helpers.ts +0 -16
- package/src/accounts.test.ts +0 -288
- package/src/accounts.ts +0 -187
- package/src/actions.ts +0 -61
- package/src/auto-reply-delivery.test.ts +0 -253
- package/src/auto-reply-delivery.ts +0 -200
- package/src/bindings.ts +0 -65
- package/src/bot-access.ts +0 -30
- package/src/bot-handlers.test.ts +0 -1094
- package/src/bot-handlers.ts +0 -620
- package/src/bot-message-context.test.ts +0 -420
- package/src/bot-message-context.ts +0 -586
- package/src/bot.ts +0 -66
- package/src/card-command.ts +0 -347
- package/src/channel-access-token.ts +0 -14
- package/src/channel-api.ts +0 -17
- package/src/channel-setup-status.contract.test.ts +0 -70
- package/src/channel-shared.ts +0 -48
- package/src/channel.logout.test.ts +0 -145
- package/src/channel.runtime.ts +0 -3
- package/src/channel.sendPayload.test.ts +0 -659
- package/src/channel.setup.ts +0 -11
- package/src/channel.status.test.ts +0 -63
- package/src/channel.ts +0 -155
- package/src/config-adapter.ts +0 -29
- package/src/config-schema.test.ts +0 -53
- package/src/config-schema.ts +0 -81
- package/src/download.test.ts +0 -164
- package/src/download.ts +0 -34
- package/src/flex-templates/basic-cards.ts +0 -395
- package/src/flex-templates/common.ts +0 -20
- package/src/flex-templates/media-control-cards.ts +0 -555
- package/src/flex-templates/message.ts +0 -13
- package/src/flex-templates/schedule-cards.ts +0 -467
- package/src/flex-templates/types.ts +0 -22
- package/src/flex-templates.ts +0 -32
- package/src/gateway.ts +0 -129
- package/src/group-keys.test.ts +0 -123
- package/src/group-keys.ts +0 -65
- package/src/group-policy.ts +0 -22
- package/src/markdown-to-line.test.ts +0 -348
- package/src/markdown-to-line.ts +0 -416
- package/src/message-cards.test.ts +0 -204
- package/src/monitor-durable.test.ts +0 -57
- package/src/monitor-durable.ts +0 -37
- package/src/monitor.lifecycle.test.ts +0 -499
- package/src/monitor.runtime.ts +0 -1
- package/src/monitor.ts +0 -507
- package/src/outbound-media.test.ts +0 -194
- package/src/outbound-media.ts +0 -120
- package/src/outbound.runtime.ts +0 -12
- package/src/outbound.ts +0 -427
- package/src/probe.contract.test.ts +0 -9
- package/src/probe.runtime.ts +0 -1
- package/src/probe.ts +0 -34
- package/src/quick-reply-fallback.ts +0 -10
- package/src/reply-chunks.test.ts +0 -180
- package/src/reply-chunks.ts +0 -110
- package/src/reply-payload-transform.test.ts +0 -392
- package/src/reply-payload-transform.ts +0 -317
- package/src/rich-menu.test.ts +0 -315
- package/src/rich-menu.ts +0 -326
- package/src/runtime.ts +0 -32
- package/src/send-receipt.ts +0 -32
- package/src/send.test.ts +0 -453
- package/src/send.ts +0 -531
- package/src/setup-core.ts +0 -149
- package/src/setup-runtime-api.ts +0 -9
- package/src/setup-surface.test.ts +0 -481
- package/src/setup-surface.ts +0 -229
- package/src/signature.test.ts +0 -34
- package/src/signature.ts +0 -24
- package/src/status.ts +0 -37
- package/src/template-messages.ts +0 -333
- package/src/types.ts +0 -130
- package/src/webhook-node.test.ts +0 -598
- package/src/webhook-node.ts +0 -155
- package/src/webhook-utils.ts +0 -10
- package/src/webhook.ts +0 -135
- package/tsconfig.json +0 -16
package/src/group-keys.test.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
resolveExactLineGroupConfigKey,
|
|
4
|
-
resolveLineGroupConfigEntry,
|
|
5
|
-
resolveLineGroupLookupIds,
|
|
6
|
-
resolveLineGroupsConfig,
|
|
7
|
-
} from "./group-keys.js";
|
|
8
|
-
import { resolveLineGroupRequireMention } from "./group-policy.js";
|
|
9
|
-
|
|
10
|
-
describe("resolveLineGroupLookupIds", () => {
|
|
11
|
-
it("expands raw ids to both prefixed candidates", () => {
|
|
12
|
-
expect(resolveLineGroupLookupIds("abc123")).toEqual(["abc123", "group:abc123", "room:abc123"]);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("preserves prefixed ids while also checking the raw id", () => {
|
|
16
|
-
expect(resolveLineGroupLookupIds("room:abc123")).toEqual(["abc123", "room:abc123"]);
|
|
17
|
-
expect(resolveLineGroupLookupIds("group:abc123")).toEqual(["abc123", "group:abc123"]);
|
|
18
|
-
});
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe("resolveLineGroupConfigEntry", () => {
|
|
22
|
-
it("matches raw, prefixed, and wildcard group config entries", () => {
|
|
23
|
-
const groups = {
|
|
24
|
-
"group:g1": { requireMention: false },
|
|
25
|
-
"room:r1": { systemPrompt: "Room prompt" },
|
|
26
|
-
"*": { requireMention: true },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
expect(resolveLineGroupConfigEntry(groups, { groupId: "g1" })).toEqual({
|
|
30
|
-
requireMention: false,
|
|
31
|
-
});
|
|
32
|
-
expect(resolveLineGroupConfigEntry(groups, { roomId: "r1" })).toEqual({
|
|
33
|
-
systemPrompt: "Room prompt",
|
|
34
|
-
});
|
|
35
|
-
expect(resolveLineGroupConfigEntry(groups, { groupId: "missing" })).toEqual({
|
|
36
|
-
requireMention: true,
|
|
37
|
-
});
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("account-scoped LINE groups", () => {
|
|
42
|
-
it("resolves the effective account-scoped groups map", () => {
|
|
43
|
-
const cfg = {
|
|
44
|
-
channels: {
|
|
45
|
-
line: {
|
|
46
|
-
groups: {
|
|
47
|
-
"*": { requireMention: true },
|
|
48
|
-
},
|
|
49
|
-
accounts: {
|
|
50
|
-
work: {
|
|
51
|
-
groups: {
|
|
52
|
-
"group:g1": { requireMention: false },
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
} as any;
|
|
59
|
-
|
|
60
|
-
expect(resolveLineGroupsConfig(cfg, "work")).toEqual({
|
|
61
|
-
"group:g1": { requireMention: false },
|
|
62
|
-
});
|
|
63
|
-
expect(resolveExactLineGroupConfigKey({ cfg, accountId: "work", groupId: "g1" })).toBe(
|
|
64
|
-
"group:g1",
|
|
65
|
-
);
|
|
66
|
-
expect(resolveExactLineGroupConfigKey({ cfg, accountId: "default", groupId: "g1" })).toBe(
|
|
67
|
-
undefined,
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe("line group policy", () => {
|
|
73
|
-
it("matches raw and prefixed LINE group keys for requireMention", () => {
|
|
74
|
-
const cfg = {
|
|
75
|
-
channels: {
|
|
76
|
-
line: {
|
|
77
|
-
groups: {
|
|
78
|
-
"room:r123": {
|
|
79
|
-
requireMention: false,
|
|
80
|
-
},
|
|
81
|
-
"group:g123": {
|
|
82
|
-
requireMention: false,
|
|
83
|
-
},
|
|
84
|
-
"*": {
|
|
85
|
-
requireMention: true,
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
} as any;
|
|
91
|
-
|
|
92
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "r123" })).toBe(false);
|
|
93
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "room:r123" })).toBe(false);
|
|
94
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "g123" })).toBe(false);
|
|
95
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "group:g123" })).toBe(false);
|
|
96
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "other" })).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("uses account-scoped prefixed LINE group config for requireMention", () => {
|
|
100
|
-
const cfg = {
|
|
101
|
-
channels: {
|
|
102
|
-
line: {
|
|
103
|
-
groups: {
|
|
104
|
-
"*": {
|
|
105
|
-
requireMention: true,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
accounts: {
|
|
109
|
-
work: {
|
|
110
|
-
groups: {
|
|
111
|
-
"group:g123": {
|
|
112
|
-
requireMention: false,
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
} as any;
|
|
120
|
-
|
|
121
|
-
expect(resolveLineGroupRequireMention({ cfg, groupId: "g123", accountId: "work" })).toBe(false);
|
|
122
|
-
});
|
|
123
|
-
});
|
package/src/group-keys.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { normalizeAccountId } from "klaw/plugin-sdk/account-id";
|
|
2
|
-
import type { KlawConfig } from "klaw/plugin-sdk/account-resolution";
|
|
3
|
-
import { resolveAccountEntry } from "klaw/plugin-sdk/account-resolution";
|
|
4
|
-
import type { LineConfig, LineGroupConfig } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export function resolveLineGroupLookupIds(groupId?: string | null): string[] {
|
|
7
|
-
const normalized = groupId?.trim();
|
|
8
|
-
if (!normalized) {
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
if (normalized.startsWith("group:") || normalized.startsWith("room:")) {
|
|
12
|
-
const rawId = normalized.split(":").slice(1).join(":");
|
|
13
|
-
return rawId ? [rawId, normalized] : [normalized];
|
|
14
|
-
}
|
|
15
|
-
return [normalized, `group:${normalized}`, `room:${normalized}`];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function resolveLineGroupConfigEntry<T>(
|
|
19
|
-
groups: Record<string, T | undefined> | undefined,
|
|
20
|
-
params: { groupId?: string | null; roomId?: string | null },
|
|
21
|
-
): T | undefined {
|
|
22
|
-
if (!groups) {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
for (const candidate of resolveLineGroupLookupIds(params.groupId)) {
|
|
26
|
-
const hit = groups[candidate];
|
|
27
|
-
if (hit) {
|
|
28
|
-
return hit;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
for (const candidate of resolveLineGroupLookupIds(params.roomId)) {
|
|
32
|
-
const hit = groups[candidate];
|
|
33
|
-
if (hit) {
|
|
34
|
-
return hit;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return groups["*"];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function resolveLineGroupsConfig(
|
|
41
|
-
cfg: KlawConfig,
|
|
42
|
-
accountId?: string | null,
|
|
43
|
-
): Record<string, LineGroupConfig | undefined> | undefined {
|
|
44
|
-
const lineConfig = cfg.channels?.line as LineConfig | undefined;
|
|
45
|
-
if (!lineConfig) {
|
|
46
|
-
return undefined;
|
|
47
|
-
}
|
|
48
|
-
const normalizedAccountId = normalizeAccountId(accountId);
|
|
49
|
-
const accountGroups = resolveAccountEntry(lineConfig.accounts, normalizedAccountId)?.groups;
|
|
50
|
-
return accountGroups ?? lineConfig.groups;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function resolveExactLineGroupConfigKey(params: {
|
|
54
|
-
cfg: KlawConfig;
|
|
55
|
-
accountId?: string | null;
|
|
56
|
-
groupId?: string | null;
|
|
57
|
-
}): string | undefined {
|
|
58
|
-
const groups = resolveLineGroupsConfig(params.cfg, params.accountId);
|
|
59
|
-
if (!groups) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
return resolveLineGroupLookupIds(params.groupId).find((candidate) =>
|
|
63
|
-
Object.hasOwn(groups, candidate),
|
|
64
|
-
);
|
|
65
|
-
}
|
package/src/group-policy.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { resolveChannelGroupRequireMention } from "klaw/plugin-sdk/channel-policy";
|
|
2
|
-
import { resolveExactLineGroupConfigKey, type KlawConfig } from "./channel-api.js";
|
|
3
|
-
|
|
4
|
-
type LineGroupContext = {
|
|
5
|
-
cfg: KlawConfig;
|
|
6
|
-
accountId?: string | null;
|
|
7
|
-
groupId?: string | null;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export function resolveLineGroupRequireMention(params: LineGroupContext): boolean {
|
|
11
|
-
const exactGroupId = resolveExactLineGroupConfigKey({
|
|
12
|
-
cfg: params.cfg,
|
|
13
|
-
accountId: params.accountId,
|
|
14
|
-
groupId: params.groupId,
|
|
15
|
-
});
|
|
16
|
-
return resolveChannelGroupRequireMention({
|
|
17
|
-
cfg: params.cfg,
|
|
18
|
-
channel: "line",
|
|
19
|
-
groupId: exactGroupId ?? params.groupId,
|
|
20
|
-
accountId: params.accountId,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
extractMarkdownTables,
|
|
4
|
-
extractCodeBlocks,
|
|
5
|
-
extractLinks,
|
|
6
|
-
stripMarkdown,
|
|
7
|
-
processLineMessage,
|
|
8
|
-
convertTableToFlexBubble,
|
|
9
|
-
convertCodeBlockToFlexBubble,
|
|
10
|
-
hasMarkdownToConvert,
|
|
11
|
-
} from "./markdown-to-line.js";
|
|
12
|
-
|
|
13
|
-
describe("extractMarkdownTables", () => {
|
|
14
|
-
it("extracts a simple 2-column table", () => {
|
|
15
|
-
const text = `Here is a table:
|
|
16
|
-
|
|
17
|
-
| Name | Value |
|
|
18
|
-
|------|-------|
|
|
19
|
-
| foo | 123 |
|
|
20
|
-
| bar | 456 |
|
|
21
|
-
|
|
22
|
-
And some more text.`;
|
|
23
|
-
|
|
24
|
-
const { tables, textWithoutTables } = extractMarkdownTables(text);
|
|
25
|
-
|
|
26
|
-
expect(tables).toHaveLength(1);
|
|
27
|
-
expect(tables[0].headers).toEqual(["Name", "Value"]);
|
|
28
|
-
expect(tables[0].rows).toEqual([
|
|
29
|
-
["foo", "123"],
|
|
30
|
-
["bar", "456"],
|
|
31
|
-
]);
|
|
32
|
-
expect(textWithoutTables).toContain("Here is a table:");
|
|
33
|
-
expect(textWithoutTables).toContain("And some more text.");
|
|
34
|
-
expect(textWithoutTables).not.toContain("|");
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("extracts multiple tables", () => {
|
|
38
|
-
const text = `Table 1:
|
|
39
|
-
|
|
40
|
-
| A | B |
|
|
41
|
-
|---|---|
|
|
42
|
-
| 1 | 2 |
|
|
43
|
-
|
|
44
|
-
Table 2:
|
|
45
|
-
|
|
46
|
-
| X | Y |
|
|
47
|
-
|---|---|
|
|
48
|
-
| 3 | 4 |`;
|
|
49
|
-
|
|
50
|
-
const { tables } = extractMarkdownTables(text);
|
|
51
|
-
|
|
52
|
-
expect(tables).toHaveLength(2);
|
|
53
|
-
expect(tables[0].headers).toEqual(["A", "B"]);
|
|
54
|
-
expect(tables[1].headers).toEqual(["X", "Y"]);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("handles tables with alignment markers", () => {
|
|
58
|
-
const text = `| Left | Center | Right |
|
|
59
|
-
|:-----|:------:|------:|
|
|
60
|
-
| a | b | c |`;
|
|
61
|
-
|
|
62
|
-
const { tables } = extractMarkdownTables(text);
|
|
63
|
-
|
|
64
|
-
expect(tables).toHaveLength(1);
|
|
65
|
-
expect(tables[0].headers).toEqual(["Left", "Center", "Right"]);
|
|
66
|
-
expect(tables[0].rows).toEqual([["a", "b", "c"]]);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("returns empty when no tables present", () => {
|
|
70
|
-
const text = "Just some plain text without tables.";
|
|
71
|
-
|
|
72
|
-
const { tables, textWithoutTables } = extractMarkdownTables(text);
|
|
73
|
-
|
|
74
|
-
expect(tables).toHaveLength(0);
|
|
75
|
-
expect(textWithoutTables).toBe(text);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
describe("extractCodeBlocks", () => {
|
|
80
|
-
it("extracts code blocks across language/no-language/multiple variants", () => {
|
|
81
|
-
const withLanguage = `Here is some code:
|
|
82
|
-
|
|
83
|
-
\`\`\`javascript
|
|
84
|
-
const x = 1;
|
|
85
|
-
console.log(x);
|
|
86
|
-
\`\`\`
|
|
87
|
-
|
|
88
|
-
And more text.`;
|
|
89
|
-
const withLanguageResult = extractCodeBlocks(withLanguage);
|
|
90
|
-
expect(withLanguageResult.codeBlocks).toHaveLength(1);
|
|
91
|
-
expect(withLanguageResult.codeBlocks[0].language).toBe("javascript");
|
|
92
|
-
expect(withLanguageResult.codeBlocks[0].code).toBe("const x = 1;\nconsole.log(x);");
|
|
93
|
-
expect(withLanguageResult.textWithoutCode).toContain("Here is some code:");
|
|
94
|
-
expect(withLanguageResult.textWithoutCode).toContain("And more text.");
|
|
95
|
-
expect(withLanguageResult.textWithoutCode).not.toContain("```");
|
|
96
|
-
|
|
97
|
-
const withoutLanguage = `\`\`\`
|
|
98
|
-
plain code
|
|
99
|
-
\`\`\``;
|
|
100
|
-
const withoutLanguageResult = extractCodeBlocks(withoutLanguage);
|
|
101
|
-
expect(withoutLanguageResult.codeBlocks).toHaveLength(1);
|
|
102
|
-
expect(withoutLanguageResult.codeBlocks[0].language).toBeUndefined();
|
|
103
|
-
expect(withoutLanguageResult.codeBlocks[0].code).toBe("plain code");
|
|
104
|
-
|
|
105
|
-
const multiple = `\`\`\`python
|
|
106
|
-
print("hello")
|
|
107
|
-
\`\`\`
|
|
108
|
-
|
|
109
|
-
Some text
|
|
110
|
-
|
|
111
|
-
\`\`\`bash
|
|
112
|
-
echo "world"
|
|
113
|
-
\`\`\``;
|
|
114
|
-
const multipleResult = extractCodeBlocks(multiple);
|
|
115
|
-
expect(multipleResult.codeBlocks).toHaveLength(2);
|
|
116
|
-
expect(multipleResult.codeBlocks[0].language).toBe("python");
|
|
117
|
-
expect(multipleResult.codeBlocks[1].language).toBe("bash");
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe("extractLinks", () => {
|
|
122
|
-
it("extracts markdown links", () => {
|
|
123
|
-
const text = "Check out [Google](https://google.com) and [GitHub](https://github.com).";
|
|
124
|
-
|
|
125
|
-
const { links, textWithLinks } = extractLinks(text);
|
|
126
|
-
|
|
127
|
-
expect(links).toHaveLength(2);
|
|
128
|
-
expect(links[0]).toEqual({ text: "Google", url: "https://google.com" });
|
|
129
|
-
expect(links[1]).toEqual({ text: "GitHub", url: "https://github.com" });
|
|
130
|
-
expect(textWithLinks).toBe("Check out Google and GitHub.");
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("stripMarkdown", () => {
|
|
135
|
-
it("strips inline markdown marker variants", () => {
|
|
136
|
-
const cases = [
|
|
137
|
-
["strips bold **", "This is **bold** text", "This is bold text"],
|
|
138
|
-
["strips bold __", "This is __bold__ text", "This is bold text"],
|
|
139
|
-
["strips italic *", "This is *italic* text", "This is italic text"],
|
|
140
|
-
["strips italic _", "This is _italic_ text", "This is italic text"],
|
|
141
|
-
["strips strikethrough", "This is ~~deleted~~ text", "This is deleted text"],
|
|
142
|
-
["removes hr ---", "Above\n---\nBelow", "Above\n\nBelow"],
|
|
143
|
-
["removes hr ***", "Above\n***\nBelow", "Above\n\nBelow"],
|
|
144
|
-
["strips inline code markers", "Use `const` keyword", "Use const keyword"],
|
|
145
|
-
] as const;
|
|
146
|
-
for (const [name, input, expected] of cases) {
|
|
147
|
-
expect(stripMarkdown(input), name).toBe(expected);
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("preserves underscores inside words", () => {
|
|
152
|
-
expect(stripMarkdown("here_is_a_message")).toBe("here_is_a_message");
|
|
153
|
-
expect(stripMarkdown("snake_case_var")).toBe("snake_case_var");
|
|
154
|
-
expect(stripMarkdown("use foo_bar_baz in code")).toBe("use foo_bar_baz in code");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("still strips proper italic _text_", () => {
|
|
158
|
-
expect(stripMarkdown("This is _italic_ text")).toBe("This is italic text");
|
|
159
|
-
expect(stripMarkdown("_italic_ at start")).toBe("italic at start");
|
|
160
|
-
expect(stripMarkdown("end _italic_")).toBe("end italic");
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("strips italic between underscored words", () => {
|
|
164
|
-
expect(stripMarkdown("foo_bar _italic_ baz_qux")).toBe("foo_bar italic baz_qux");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("preserves underscores inside non-Latin words", () => {
|
|
168
|
-
expect(stripMarkdown("привет_мир_тест")).toBe("привет_мир_тест");
|
|
169
|
-
expect(stripMarkdown("東京_駅_前")).toBe("東京_駅_前");
|
|
170
|
-
expect(stripMarkdown("var_123_end")).toBe("var_123_end");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("strips standalone italic between non-Latin words", () => {
|
|
174
|
-
expect(stripMarkdown("こんにちは _italic_ テスト")).toBe("こんにちは italic テスト");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("handles complex markdown", () => {
|
|
178
|
-
const input = `# Title
|
|
179
|
-
|
|
180
|
-
This is **bold** and *italic* text.
|
|
181
|
-
|
|
182
|
-
> A quote
|
|
183
|
-
|
|
184
|
-
Some ~~deleted~~ content.`;
|
|
185
|
-
|
|
186
|
-
const result = stripMarkdown(input);
|
|
187
|
-
|
|
188
|
-
expect(result).toContain("Title");
|
|
189
|
-
expect(result).toContain("This is bold and italic text.");
|
|
190
|
-
expect(result).toContain("A quote");
|
|
191
|
-
expect(result).toContain("Some deleted content.");
|
|
192
|
-
expect(result).not.toContain("#");
|
|
193
|
-
expect(result).not.toContain("**");
|
|
194
|
-
expect(result).not.toContain("~~");
|
|
195
|
-
expect(result).not.toContain(">");
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe("convertTableToFlexBubble", () => {
|
|
200
|
-
it("replaces empty cells with placeholders", () => {
|
|
201
|
-
const table = {
|
|
202
|
-
headers: ["A", "B"],
|
|
203
|
-
rows: [["", ""]],
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const bubble = convertTableToFlexBubble(table);
|
|
207
|
-
const body = bubble.body as {
|
|
208
|
-
contents: Array<{ contents?: Array<{ contents?: Array<{ text: string }> }> }>;
|
|
209
|
-
};
|
|
210
|
-
const rowsBox = body.contents[2] as { contents: Array<{ contents: Array<{ text: string }> }> };
|
|
211
|
-
|
|
212
|
-
expect(rowsBox.contents[0].contents[0].text).toBe("-");
|
|
213
|
-
expect(rowsBox.contents[0].contents[1].text).toBe("-");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("strips bold markers and applies weight for fully bold cells", () => {
|
|
217
|
-
const table = {
|
|
218
|
-
headers: ["**Name**", "Status"],
|
|
219
|
-
rows: [["**Alpha**", "OK"]],
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
const bubble = convertTableToFlexBubble(table);
|
|
223
|
-
const body = bubble.body as {
|
|
224
|
-
contents: Array<{ contents?: Array<{ text: string; weight?: string }> }>;
|
|
225
|
-
};
|
|
226
|
-
const headerRow = body.contents[0] as { contents: Array<{ text: string; weight?: string }> };
|
|
227
|
-
const dataRow = body.contents[2] as { contents: Array<{ text: string; weight?: string }> };
|
|
228
|
-
|
|
229
|
-
expect(headerRow.contents[0].text).toBe("Name");
|
|
230
|
-
expect(headerRow.contents[0].weight).toBe("bold");
|
|
231
|
-
expect(dataRow.contents[0].text).toBe("Alpha");
|
|
232
|
-
expect(dataRow.contents[0].weight).toBe("bold");
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe("convertCodeBlockToFlexBubble", () => {
|
|
237
|
-
it("creates a code card with language label", () => {
|
|
238
|
-
const block = { language: "typescript", code: "const x = 1;" };
|
|
239
|
-
|
|
240
|
-
const bubble = convertCodeBlockToFlexBubble(block);
|
|
241
|
-
|
|
242
|
-
const body = bubble.body as { contents: Array<{ text: string }> };
|
|
243
|
-
expect(body.contents[0].text).toBe("Code (typescript)");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("creates a code card without language", () => {
|
|
247
|
-
const block = { code: "plain code" };
|
|
248
|
-
|
|
249
|
-
const bubble = convertCodeBlockToFlexBubble(block);
|
|
250
|
-
|
|
251
|
-
const body = bubble.body as { contents: Array<{ text: string }> };
|
|
252
|
-
expect(body.contents[0].text).toBe("Code");
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
it("truncates very long code", () => {
|
|
256
|
-
const longCode = "x".repeat(3000);
|
|
257
|
-
const block = { code: longCode };
|
|
258
|
-
|
|
259
|
-
const bubble = convertCodeBlockToFlexBubble(block);
|
|
260
|
-
|
|
261
|
-
const body = bubble.body as { contents: Array<{ contents: Array<{ text: string }> }> };
|
|
262
|
-
const codeText = body.contents[1].contents[0].text;
|
|
263
|
-
expect(codeText.length).toBeLessThan(longCode.length);
|
|
264
|
-
expect(codeText).toContain("...");
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
describe("processLineMessage", () => {
|
|
269
|
-
it("processes text with code blocks", () => {
|
|
270
|
-
const text = `Check this code:
|
|
271
|
-
|
|
272
|
-
\`\`\`js
|
|
273
|
-
console.log("hi");
|
|
274
|
-
\`\`\`
|
|
275
|
-
|
|
276
|
-
That's it.`;
|
|
277
|
-
|
|
278
|
-
const result = processLineMessage(text);
|
|
279
|
-
|
|
280
|
-
expect(result.flexMessages).toHaveLength(1);
|
|
281
|
-
expect(result.text).toContain("Check this code:");
|
|
282
|
-
expect(result.text).toContain("That's it.");
|
|
283
|
-
expect(result.text).not.toContain("```");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("handles mixed content", () => {
|
|
287
|
-
const text = `# Summary
|
|
288
|
-
|
|
289
|
-
Here's **important** info:
|
|
290
|
-
|
|
291
|
-
| Item | Count |
|
|
292
|
-
|------|-------|
|
|
293
|
-
| A | 5 |
|
|
294
|
-
|
|
295
|
-
\`\`\`python
|
|
296
|
-
print("done")
|
|
297
|
-
\`\`\`
|
|
298
|
-
|
|
299
|
-
> Note: Check the link [here](https://example.com).`;
|
|
300
|
-
|
|
301
|
-
const result = processLineMessage(text);
|
|
302
|
-
|
|
303
|
-
// Should have 2 flex messages (table + code)
|
|
304
|
-
expect(result.flexMessages).toHaveLength(2);
|
|
305
|
-
|
|
306
|
-
// Text should be cleaned
|
|
307
|
-
expect(result.text).toContain("Summary");
|
|
308
|
-
expect(result.text).toContain("important");
|
|
309
|
-
expect(result.text).toContain("Note: Check the link here.");
|
|
310
|
-
expect(result.text).not.toContain("#");
|
|
311
|
-
expect(result.text).not.toContain("**");
|
|
312
|
-
expect(result.text).not.toContain("|");
|
|
313
|
-
expect(result.text).not.toContain("```");
|
|
314
|
-
expect(result.text).not.toContain("[here]");
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it("handles plain text unchanged", () => {
|
|
318
|
-
const text = "Just plain text with no markdown.";
|
|
319
|
-
|
|
320
|
-
const result = processLineMessage(text);
|
|
321
|
-
|
|
322
|
-
expect(result.text).toBe(text);
|
|
323
|
-
expect(result.flexMessages).toHaveLength(0);
|
|
324
|
-
});
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
describe("hasMarkdownToConvert", () => {
|
|
328
|
-
it("detects supported markdown patterns", () => {
|
|
329
|
-
const cases = [
|
|
330
|
-
`| A | B |
|
|
331
|
-
|---|---|
|
|
332
|
-
| 1 | 2 |`,
|
|
333
|
-
"```js\ncode\n```",
|
|
334
|
-
"**bold**",
|
|
335
|
-
"~~deleted~~",
|
|
336
|
-
"# Title",
|
|
337
|
-
"> quote",
|
|
338
|
-
];
|
|
339
|
-
|
|
340
|
-
for (const text of cases) {
|
|
341
|
-
expect(hasMarkdownToConvert(text)).toBe(true);
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it("returns false for plain text", () => {
|
|
346
|
-
expect(hasMarkdownToConvert("Just plain text.")).toBe(false);
|
|
347
|
-
});
|
|
348
|
-
});
|