@kodelyth/line 2026.5.42 → 2026.6.1

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.
Files changed (90) hide show
  1. package/klaw.plugin.json +329 -2
  2. package/package.json +16 -4
  3. package/api.ts +0 -11
  4. package/channel-plugin-api.ts +0 -1
  5. package/contract-api.ts +0 -5
  6. package/index.ts +0 -53
  7. package/runtime-api.ts +0 -179
  8. package/secret-contract-api.ts +0 -4
  9. package/setup-api.ts +0 -2
  10. package/setup-entry.ts +0 -9
  11. package/src/account-helpers.ts +0 -16
  12. package/src/accounts.test.ts +0 -288
  13. package/src/accounts.ts +0 -187
  14. package/src/actions.ts +0 -61
  15. package/src/auto-reply-delivery.test.ts +0 -253
  16. package/src/auto-reply-delivery.ts +0 -200
  17. package/src/bindings.ts +0 -65
  18. package/src/bot-access.ts +0 -30
  19. package/src/bot-handlers.test.ts +0 -1094
  20. package/src/bot-handlers.ts +0 -620
  21. package/src/bot-message-context.test.ts +0 -420
  22. package/src/bot-message-context.ts +0 -586
  23. package/src/bot.ts +0 -66
  24. package/src/card-command.ts +0 -347
  25. package/src/channel-access-token.ts +0 -14
  26. package/src/channel-api.ts +0 -17
  27. package/src/channel-setup-status.contract.test.ts +0 -70
  28. package/src/channel-shared.ts +0 -48
  29. package/src/channel.logout.test.ts +0 -145
  30. package/src/channel.runtime.ts +0 -3
  31. package/src/channel.sendPayload.test.ts +0 -659
  32. package/src/channel.setup.ts +0 -11
  33. package/src/channel.status.test.ts +0 -63
  34. package/src/channel.ts +0 -155
  35. package/src/config-adapter.ts +0 -29
  36. package/src/config-schema.test.ts +0 -53
  37. package/src/config-schema.ts +0 -81
  38. package/src/download.test.ts +0 -164
  39. package/src/download.ts +0 -34
  40. package/src/flex-templates/basic-cards.ts +0 -395
  41. package/src/flex-templates/common.ts +0 -20
  42. package/src/flex-templates/media-control-cards.ts +0 -555
  43. package/src/flex-templates/message.ts +0 -13
  44. package/src/flex-templates/schedule-cards.ts +0 -467
  45. package/src/flex-templates/types.ts +0 -22
  46. package/src/flex-templates.ts +0 -32
  47. package/src/gateway.ts +0 -129
  48. package/src/group-keys.test.ts +0 -123
  49. package/src/group-keys.ts +0 -65
  50. package/src/group-policy.ts +0 -22
  51. package/src/markdown-to-line.test.ts +0 -348
  52. package/src/markdown-to-line.ts +0 -416
  53. package/src/message-cards.test.ts +0 -204
  54. package/src/monitor-durable.test.ts +0 -57
  55. package/src/monitor-durable.ts +0 -37
  56. package/src/monitor.lifecycle.test.ts +0 -499
  57. package/src/monitor.runtime.ts +0 -1
  58. package/src/monitor.ts +0 -507
  59. package/src/outbound-media.test.ts +0 -194
  60. package/src/outbound-media.ts +0 -120
  61. package/src/outbound.runtime.ts +0 -12
  62. package/src/outbound.ts +0 -427
  63. package/src/probe.contract.test.ts +0 -9
  64. package/src/probe.runtime.ts +0 -1
  65. package/src/probe.ts +0 -34
  66. package/src/quick-reply-fallback.ts +0 -10
  67. package/src/reply-chunks.test.ts +0 -180
  68. package/src/reply-chunks.ts +0 -110
  69. package/src/reply-payload-transform.test.ts +0 -392
  70. package/src/reply-payload-transform.ts +0 -317
  71. package/src/rich-menu.test.ts +0 -315
  72. package/src/rich-menu.ts +0 -326
  73. package/src/runtime.ts +0 -32
  74. package/src/send-receipt.ts +0 -32
  75. package/src/send.test.ts +0 -453
  76. package/src/send.ts +0 -531
  77. package/src/setup-core.ts +0 -149
  78. package/src/setup-runtime-api.ts +0 -9
  79. package/src/setup-surface.test.ts +0 -481
  80. package/src/setup-surface.ts +0 -229
  81. package/src/signature.test.ts +0 -34
  82. package/src/signature.ts +0 -24
  83. package/src/status.ts +0 -37
  84. package/src/template-messages.ts +0 -333
  85. package/src/types.ts +0 -130
  86. package/src/webhook-node.test.ts +0 -598
  87. package/src/webhook-node.ts +0 -155
  88. package/src/webhook-utils.ts +0 -10
  89. package/src/webhook.ts +0 -135
  90. package/tsconfig.json +0 -16
@@ -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
- }
@@ -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
- });