Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions packages/core/src/api/parsers/markdown/detectMarkdown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import { describe, expect, it } from "vitest";
import { isMarkdown } from "./detectMarkdown.js";

describe("isMarkdown", () => {
describe("Headings (H1-H6)", () => {
it("should detect H1 headings", () => {
expect(isMarkdown("# Heading 1\n\nContent")).toBe(true);
expect(isMarkdown(" # Heading 1\n\nContent")).toBe(true);
expect(isMarkdown(" # Heading 1\n\nContent")).toBe(true);
});

it("should detect H2-H6 headings", () => {
expect(isMarkdown("## Heading 2\n\nContent")).toBe(true);
expect(isMarkdown("### Heading 3\n\nContent")).toBe(true);
expect(isMarkdown("#### Heading 4\n\nContent")).toBe(true);
expect(isMarkdown("##### Heading 5\n\nContent")).toBe(true);
expect(isMarkdown("###### Heading 6\n\nContent")).toBe(true);
});

it("should not detect invalid headings", () => {
expect(isMarkdown("####### Heading 7\n\nContent")).toBe(false);
expect(isMarkdown("#Heading without space\n\nContent")).toBe(false);
expect(isMarkdown("# \n\nContent")).toBe(false);
expect(
isMarkdown(
"# Very long heading that exceeds the character limit and should not be detected as a valid markdown heading\n\nContent",
),
).toBe(false);
});
});

describe("Bold, italic, underline, strikethrough, highlight", () => {
it("should detect bold text", () => {
expect(isMarkdown("**bold text**")).toBe(true);
expect(isMarkdown("__bold text__")).toBe(true);
expect(isMarkdown(" *bold text* ")).toBe(true);
expect(isMarkdown(" _bold text_ ")).toBe(true);
});

it("should detect italic text", () => {
expect(isMarkdown("*italic text*")).toBe(true);
expect(isMarkdown("_italic text_")).toBe(true);
});

it("should detect strikethrough text", () => {
expect(isMarkdown("~~strikethrough text~~")).toBe(true);
});

it("should detect highlighted text", () => {
expect(isMarkdown("==highlighted text==")).toBe(true);
expect(isMarkdown("++highlighted text++")).toBe(true);
});
});

describe("Links", () => {
it("should detect basic links", () => {
expect(isMarkdown("[Link text](https://example.com)")).toBe(true);
expect(isMarkdown("[Link text](http://example.com)")).toBe(true);
expect(isMarkdown("[Short](https://ex.com)")).toBe(true);
});

it("should detect image links", () => {
expect(isMarkdown("![Alt text](https://example.com/image.jpg)")).toBe(
true,
);
});
});

describe("Inline code", () => {
it("should detect inline code", () => {
expect(isMarkdown("`code`")).toBe(true);
expect(isMarkdown(" `code` ")).toBe(true);
expect(isMarkdown("`const x = 1;`")).toBe(true);
});

it("should not detect invalid inline code", () => {
expect(isMarkdown("` code `")).toBe(false); // spaces around content
expect(isMarkdown("``")).toBe(false); // empty
expect(isMarkdown("` `")).toBe(false); // only space
});
});

describe("Unordered lists", () => {
it("should detect unordered lists", () => {
expect(isMarkdown("- Item 1\n- Item 2")).toBe(true);
expect(isMarkdown(" - Item 1\n - Item 2")).toBe(true);
expect(isMarkdown(" - Item 1\n - Item 2")).toBe(true);
expect(isMarkdown(" - Item 1\n - Item 2")).toBe(true);
expect(isMarkdown(" - Item 1\n - Item 2")).toBe(true);
expect(isMarkdown(" - Item 1\n - Item 2")).toBe(true);
});

it("should not detect invalid unordered lists", () => {
expect(isMarkdown("- Item 1")).toBe(false); // single item
expect(isMarkdown("-- Item 1\n-- Item 2")).toBe(false); // wrong marker
expect(isMarkdown("-Item 1\n-Item 2")).toBe(false); // no space after marker
});
});

describe("Ordered lists", () => {
it("should detect ordered lists", () => {
expect(isMarkdown("1. Item 1\n2. Item 2")).toBe(true);
expect(isMarkdown(" 1. Item 1\n 2. Item 2")).toBe(true);
expect(isMarkdown(" 1. Item 1\n 2. Item 2")).toBe(true);
expect(isMarkdown(" 1. Item 1\n 2. Item 2")).toBe(true);
expect(isMarkdown(" 1. Item 1\n 2. Item 2")).toBe(true);
expect(isMarkdown(" 1. Item 1\n 2. Item 2")).toBe(true);
});

it("should not detect invalid ordered lists", () => {
expect(isMarkdown("1. Item 1")).toBe(false); // single item
expect(isMarkdown("1 Item 1\n2 Item 2")).toBe(false); // no dot
expect(isMarkdown("1.Item 1\n2.Item 2")).toBe(false); // no space after dot
});
});

describe("Horizontal rules", () => {
it("should detect horizontal rules", () => {
expect(isMarkdown("\n\n ---\n\n")).toBe(true);
expect(isMarkdown("\n\n ----\n\n")).toBe(true);
expect(isMarkdown("\n\n ---\n\n")).toBe(true);
expect(isMarkdown("\n\n ---\n\n")).toBe(true);
});
});

describe("Fenced code blocks", () => {
it("should detect fenced code blocks", () => {
expect(isMarkdown("```\ncode block\n```")).toBe(true);
expect(isMarkdown("~~~\ncode block\n~~~")).toBe(true);
expect(isMarkdown("```javascript\nconst x = 1;\n```")).toBe(true);
expect(isMarkdown("```js\nconst x = 1;\n```")).toBe(true);
});
});

describe("Classical underlined headings", () => {
it("should detect H1 with equals", () => {
expect(isMarkdown("Heading\n===\n\nContent")).toBe(true);
expect(isMarkdown("Heading\n====\n\nContent")).toBe(true);
});

it("should detect H2 with dashes", () => {
expect(isMarkdown("Heading\n---\n\nContent")).toBe(true);
expect(isMarkdown("Heading\n----\n\nContent")).toBe(true);
});
});

describe("Blockquotes", () => {
it("should detect blockquotes", () => {
expect(isMarkdown("> This is a blockquote\n\nContent")).toBe(true);
expect(isMarkdown(" > This is a blockquote\n\nContent")).toBe(true);
expect(isMarkdown(" > This is a blockquote\n\nContent")).toBe(true);
expect(isMarkdown(" > This is a blockquote\n\nContent")).toBe(true);
});

it("should detect multi-line blockquotes", () => {
expect(isMarkdown("> Line 1\n> Line 2\n\nContent")).toBe(true);
expect(isMarkdown("> Line 1\n> Line 2\n> Line 3\n\nContent")).toBe(true);
});
});

describe("Tables", () => {
it("should detect table headers", () => {
expect(isMarkdown("| Header 1 | Header 2 |\n")).toBe(true);
expect(isMarkdown("| Header 1 | Header 2 | Header 3 |\n")).toBe(true);
});

it("should detect table dividers", () => {
expect(isMarkdown("| --- | --- |\n")).toBe(true);
expect(isMarkdown("| :--- | ---: |\n")).toBe(true);
expect(isMarkdown("| :---: | --- |\n")).toBe(true);
});

it("should detect table rows", () => {
expect(isMarkdown("| Cell 1 | Cell 2 |\n")).toBe(true);
expect(isMarkdown("| Cell 1 | Cell 2 | Cell 3 |\n")).toBe(true);
});

it("should detect complete tables", () => {
const table =
"| Header 1 | Header 2 |\n| --- | --- |\n| Cell 1 | Cell 2 |\n";
expect(isMarkdown(table)).toBe(true);
});

it("should not detect invalid tables", () => {
expect(isMarkdown("| Header 1 | Header 2\n")).toBe(false); // missing closing pipe
expect(isMarkdown("Header 1 | Header 2 |\n")).toBe(false); // missing opening pipe
});
});

describe("Edge cases and combinations", () => {
it("should detect mixed markdown content", () => {
const mixedContent =
"# Heading\n\nThis is **bold** and *italic* text with a [link](https://example.com).\n\n- List item 1\n- List item 2\n\n> Blockquote\n\n```\ncode block\n```";
expect(isMarkdown(mixedContent)).toBe(true);
});

it("should not detect plain text", () => {
expect(
isMarkdown("This is just plain text without any markdown formatting."),
).toBe(false);
expect(isMarkdown("")).toBe(false);
expect(isMarkdown(" \n \n ")).toBe(false); // only whitespace
});

it("should handle special characters", () => {
expect(isMarkdown("**text with `backticks`**")).toBe(true);
expect(isMarkdown("**text with [brackets]**")).toBe(true);
expect(isMarkdown("**text with (parentheses)**")).toBe(true);
});
});
});
5 changes: 3 additions & 2 deletions packages/core/src/api/parsers/markdown/detectMarkdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
const h1 = /(^|\n) {0,3}#{1,6} {1,8}[^\n]{1,64}\r?\n\r?\n\s{0,32}\S/;

// Bold, italic, underline, strikethrough, highlight.
const bold = /(?:\s|^)(_|__|\*|\*\*|~~|==|\+\+)(?!\s).{1,64}(?<!\s)(?=\1)/;
const bold =
/(_|__|\*|\*\*|~~|==|\+\+)(?!\s)(?:[^\s](?:.{0,62}[^\s])?|\S)(?=\1)/;

// Basic inline link (also captures images).
const link = /\[[^\]]{1,128}\]\(https?:\/\/\S{1,999}\)/;

// Inline code.
const code = /(?:\s|^)`(?!\s)[^`]{1,48}(?<!\s)`([^\w]|$)/;
const code = /(?:\s|^)`(?!\s)(?:[^\s`](?:[^`]{0,46}[^\s`])?|[^\s`])`([^\w]|$)/;

// Unordered list.
const ul = /(?:^|\n)\s{0,5}-\s{1}[^\n]+\n\s{0,15}-\s/;
Expand Down
Loading