From 5dd501dd2667b077284179ff4420ef5aba399b40 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 19:39:22 -0300 Subject: [PATCH 1/9] fix(notion): preserve manual line breaks --- scripts/notion-fetch/exportDatabase.ts | 36 +++++++++++++++++++------- scripts/notion-fetch/generateBlocks.ts | 32 +++++++++++++++++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/scripts/notion-fetch/exportDatabase.ts b/scripts/notion-fetch/exportDatabase.ts index dc8969f5..69d4a68e 100644 --- a/scripts/notion-fetch/exportDatabase.ts +++ b/scripts/notion-fetch/exportDatabase.ts @@ -14,6 +14,9 @@ dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const LINE_BREAK_REGEX = /(?:\r\n|[\r\n\u2028\u2029])/g; +const HTML_LINE_BREAK = "
\n"; + // CLI Options Interface interface ExportOptions { verbose: boolean; @@ -213,17 +216,21 @@ function extractTextFromBlock(block: Record): string { const blockContent = block[blockType]; // Handle rich text arrays (most common case) + // Preserve manual line breaks (including Windows \r\n) by converting them to HTML
tags if (blockContent.rich_text && Array.isArray(blockContent.rich_text)) { return blockContent.rich_text .map((textObj: any) => textObj.plain_text || textObj.text?.content || "") - .join(""); + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK); } // Handle title blocks + // Preserve manual line breaks (including Windows \r\n) by converting them to HTML
tags if (blockContent.title && Array.isArray(blockContent.title)) { return blockContent.title .map((textObj: any) => textObj.plain_text || textObj.text?.content || "") - .join(""); + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK); } // Handle text property directly @@ -237,21 +244,28 @@ function extractTextFromBlock(block: Record): string { } // Handle specific block types + // Preserve manual line breaks in captions and code blocks switch (blockType) { case "image": return ( - blockContent.caption?.map((c: any) => c.plain_text || "").join("") || - "[Image]" + blockContent.caption + ?.map((c: any) => c.plain_text || "") + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK) || "[Image]" ); case "video": return ( - blockContent.caption?.map((c: any) => c.plain_text || "").join("") || - "[Video]" + blockContent.caption + ?.map((c: any) => c.plain_text || "") + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK) || "[Video]" ); case "file": return ( - blockContent.caption?.map((c: any) => c.plain_text || "").join("") || - "[File]" + blockContent.caption + ?.map((c: any) => c.plain_text || "") + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK) || "[File]" ); case "bookmark": return blockContent.url || "[Bookmark]"; @@ -259,8 +273,10 @@ function extractTextFromBlock(block: Record): string { return blockContent.expression || "[Equation]"; case "code": return ( - blockContent.rich_text?.map((t: any) => t.plain_text || "").join("") || - "[Code]" + blockContent.rich_text + ?.map((t: any) => t.plain_text || "") + .join("") + .replace(LINE_BREAK_REGEX, HTML_LINE_BREAK) || "[Code]" ); case "divider": return "[Divider]"; diff --git a/scripts/notion-fetch/generateBlocks.ts b/scripts/notion-fetch/generateBlocks.ts index 0fa9456c..d6ac7aaf 100644 --- a/scripts/notion-fetch/generateBlocks.ts +++ b/scripts/notion-fetch/generateBlocks.ts @@ -286,6 +286,34 @@ function sanitizeMarkdownImages(content: string): string { return sanitized; } +/** + * Ensure standalone bold lines (`**Heading**`) are treated as their own paragraphs + * by inserting a blank line when missing. This preserves Notion formatting where + * bold text represents a section title followed by descriptive copy. + */ +export function ensureBlankLineAfterStandaloneBold(content: string): string { + if (!content) return content; + + const lines = content.split("\n"); + const result: string[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + result.push(line); + + const nextLine = lines[i + 1]; + const isStandaloneBold = /^\s*\*\*[^*].*\*\*\s*$/.test(line.trim()); + const nextLineHasContent = + nextLine !== undefined && nextLine.trim().length > 0; + + if (isStandaloneBold && nextLineHasContent) { + result.push(""); + } + } + + return result.join("\n"); +} + /** * Image cache system to prevent re-downloading and provide recovery options */ @@ -1804,6 +1832,10 @@ export async function generateBlocks(pages, progressCallback) { markdownString.parent = sanitizeMarkdownContent( markdownString.parent ); + + markdownString.parent = ensureBlankLineAfterStandaloneBold( + markdownString.parent + ); // Remove duplicate title heading if it exists // The first H1 heading often duplicates the title in Notion exports let contentBody = markdownString.parent; From 665fb3b1d4d85fb9d86a3c5183150726b483a6b6 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 19:39:48 -0300 Subject: [PATCH 2/9] test(notion): add regression coverage for line breaks --- scripts/notion-fetch/exportDatabase.test.ts | 296 ++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/scripts/notion-fetch/exportDatabase.test.ts b/scripts/notion-fetch/exportDatabase.test.ts index c434c918..96bee8b5 100644 --- a/scripts/notion-fetch/exportDatabase.test.ts +++ b/scripts/notion-fetch/exportDatabase.test.ts @@ -208,4 +208,300 @@ describe("exportNotionDatabase", () => { expect.stringContaining("Failed to fetch blocks for page page-2") ); }); + + it("preserves line breaks in paragraph rich_text", async () => { + const blocksWithLineBreaks = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "First line\nSecond line", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithLineBreaks); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe("First line
\nSecond line"); + }); + + it("preserves Unicode line separators (U+2028)", async () => { + const blocksWithUnicodeSeparator = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "First line\u2028Second line", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithUnicodeSeparator); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe("First line
\nSecond line"); + }); + + it("preserves Unicode paragraph separators (U+2029)", async () => { + const blocksWithUnicodeParagraph = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "First line\u2029Second line", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithUnicodeParagraph); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe("First line
\nSecond line"); + }); + + it("preserves Windows-style line breaks (\\r\\n) in rich_text", async () => { + const blocksWithWindowsBreaks = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "First line\r\nSecond line", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithWindowsBreaks); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe("First line
\nSecond line"); + }); + + it("preserves standalone carriage returns (\\r) in rich_text", async () => { + const blocksWithCarriageReturns = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "First line\rSecond line", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithCarriageReturns); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe("First line
\nSecond line"); + }); + + it("preserves line breaks in image captions", async () => { + const imageBlockWithLineBreaks = [ + { + id: "block-1", + type: "image", + image: { + caption: [ + { + plain_text: "Caption line 1\nCaption line 2", + }, + ], + external: { url: "https://example.com/image.png" }, + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(imageBlockWithLineBreaks); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe( + "Caption line 1
\nCaption line 2" + ); + }); + + it("preserves line breaks in code blocks", async () => { + const codeBlockWithLineBreaks = [ + { + id: "block-1", + type: "code", + code: { + rich_text: [ + { + plain_text: "function test() {\n return true;\n}", + }, + ], + language: "javascript", + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(codeBlockWithLineBreaks); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe( + "function test() {
\n return true;
\n}" + ); + }); + + it("preserves multiple consecutive line breaks", async () => { + const blocksWithMultipleLineBreaks = [ + { + id: "block-1", + type: "paragraph", + paragraph: { + rich_text: [ + { + plain_text: "Line 1\n\nLine 2\n\n\nLine 3", + }, + ], + }, + has_children: false, + archived: false, + created_time: "2024-01-02T12:34:56.000Z", + last_edited_time: "2024-01-02T12:34:56.000Z", + }, + ]; + + fetchNotionBlocksMock.mockResolvedValue(blocksWithMultipleLineBreaks); + + const { exportNotionDatabase } = await import("./exportDatabase"); + + await exportNotionDatabase({ + verbose: false, + quick: false, + includeRawData: true, + }); + + const completePayload = writeFileMock.mock.calls[0][1] as string; + const completeJson = JSON.parse(completePayload); + + const blockAnalysis = completeJson.pages[0].blocks[0]; + expect(blockAnalysis.textContent).toBe( + "Line 1
\n
\nLine 2
\n
\n
\nLine 3" + ); + }); }); From cc881508d0be2f5038d2a6c0beefe640e13c2d7d Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 19:40:43 -0300 Subject: [PATCH 3/9] test(notion): add introduction markdown inspection --- .../__tests__/introductionMarkdown.test.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 scripts/notion-fetch/__tests__/introductionMarkdown.test.ts diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts new file mode 100644 index 00000000..362bc9e9 --- /dev/null +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from "vitest"; +import { n2m } from "../../notionClient"; +import { ensureBlankLineAfterStandaloneBold } from "../generateBlocks"; + +describe("Notion introduction markdown inspection", () => { + it("prints the generated markdown for the Introduction page", async () => { + if (!process.env.NOTION_API_KEY || !process.env.DATABASE_ID) { + console.warn( + "Skipping markdown inspection: Notion credentials are not configured." + ); + return; + } + + const INTRODUCTION_PAGE_ID = "21f1b081-62d5-8008-9ca5-fad63c1a30ac"; + + const markdownBlocks = await n2m.pageToMarkdown(INTRODUCTION_PAGE_ID); + const rawMarkdown = n2m.toMarkdownString(markdownBlocks).parent ?? ""; + const markdown = ensureBlankLineAfterStandaloneBold(rawMarkdown); + + // Output to the console so we can compare against the published site + // when running the test manually. + console.log("\n--- Intro Markdown Snapshot ---\n"); + console.log(markdown); + console.log("\n--- End Snapshot ---\n"); + + expect(markdown).toContain( + "**Collected Data**\n\nThis section provides overviews and walkthroughs all features related to gathering reviewing the GIS data and media that can be collected with CoMapeo." + ); + }, 60_000); +}); From d63c75932e7a014dac72e9813a050d0842c426b5 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 19:41:14 -0300 Subject: [PATCH 4/9] style(css): match paragraph and list spacing with Notion --- src/css/custom.css | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/css/custom.css b/src/css/custom.css index 4e0ec226..03fa97ac 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -297,6 +297,38 @@ aside[class*="sidebar"] .menu__list-item-collapsible .menu__link { display: none !important; } +/* + * Paragraph and list spacing adjustments + * Issue #48: Match spacing with Notion layout + * + * Reduces vertical spacing between paragraphs and lists to align with + * Notion's visual rhythm. Scoped to .theme-doc-markdown to avoid affecting + * other components like landing pages, admonitions, and tables. + */ + +/* Reduce paragraph spacing from default ~1rem to ~0.5rem */ +.theme-doc-markdown p { + margin-bottom: 0.5rem; +} + +/* Reduce list spacing for better visual rhythm */ +.theme-doc-markdown ul, +.theme-doc-markdown ol { + margin-bottom: 0.75rem; +} + +/* Reduce spacing between list items */ +.theme-doc-markdown li { + margin-bottom: 0.25rem; +} + +/* Ensure nested lists maintain proper spacing */ +.theme-doc-markdown li > ul, +.theme-doc-markdown li > ol { + margin-top: 0.25rem; + margin-bottom: 0.25rem; +} + /* * Details/Toggle Styling * Issue #56: Match toggle background to warm light gray (same as callouts) From 79b34e7145b923c5b091fa2ff7d2e9ca75ee2f12 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 19:55:15 -0300 Subject: [PATCH 5/9] test(notion): avoid requiring Notion env at import time --- .../notion-fetch/__tests__/introductionMarkdown.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts index 362bc9e9..ecf50202 100644 --- a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -1,10 +1,12 @@ import { describe, it, expect } from "vitest"; -import { n2m } from "../../notionClient"; import { ensureBlankLineAfterStandaloneBold } from "../generateBlocks"; describe("Notion introduction markdown inspection", () => { it("prints the generated markdown for the Introduction page", async () => { - if (!process.env.NOTION_API_KEY || !process.env.DATABASE_ID) { + const hasCredentials = + Boolean(process.env.NOTION_API_KEY) && Boolean(process.env.DATABASE_ID); + + if (!hasCredentials) { console.warn( "Skipping markdown inspection: Notion credentials are not configured." ); @@ -13,6 +15,8 @@ describe("Notion introduction markdown inspection", () => { const INTRODUCTION_PAGE_ID = "21f1b081-62d5-8008-9ca5-fad63c1a30ac"; + const { n2m } = await import("../../notionClient"); + const markdownBlocks = await n2m.pageToMarkdown(INTRODUCTION_PAGE_ID); const rawMarkdown = n2m.toMarkdownString(markdownBlocks).parent ?? ""; const markdown = ensureBlankLineAfterStandaloneBold(rawMarkdown); From d67081c841140e8099562b0a5966d0aa85cacb52 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 20:09:02 -0300 Subject: [PATCH 6/9] test(notion): lazily import markdown helpers --- scripts/notion-fetch/__tests__/introductionMarkdown.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts index ecf50202..36b76d7f 100644 --- a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -1,5 +1,4 @@ import { describe, it, expect } from "vitest"; -import { ensureBlankLineAfterStandaloneBold } from "../generateBlocks"; describe("Notion introduction markdown inspection", () => { it("prints the generated markdown for the Introduction page", async () => { @@ -15,6 +14,10 @@ describe("Notion introduction markdown inspection", () => { const INTRODUCTION_PAGE_ID = "21f1b081-62d5-8008-9ca5-fad63c1a30ac"; + const { ensureBlankLineAfterStandaloneBold } = + (await import("../generateBlocks")) as { + ensureBlankLineAfterStandaloneBold: (content: string) => string; + }; const { n2m } = await import("../../notionClient"); const markdownBlocks = await n2m.pageToMarkdown(INTRODUCTION_PAGE_ID); From 3b55ee133125bf1dd4889dcb86bc478fd908f586 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 20:26:42 -0300 Subject: [PATCH 7/9] test(notion-fetch): decouple introduction markdown check --- .../__tests__/introductionMarkdown.test.ts | 59 +++++++++---------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts index 36b76d7f..1da5950b 100644 --- a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -1,37 +1,36 @@ import { describe, it, expect } from "vitest"; +import { ensureBlankLineAfterStandaloneBold } from "../generateBlocks"; -describe("Notion introduction markdown inspection", () => { - it("prints the generated markdown for the Introduction page", async () => { - const hasCredentials = - Boolean(process.env.NOTION_API_KEY) && Boolean(process.env.DATABASE_ID); +describe("ensureBlankLineAfterStandaloneBold", () => { + it("inserts a blank line after standalone bold headings", () => { + const input = [ + "**Collected Data**", + "This section provides overviews and walkthroughs of features.", + "", + "**Another Section**", + "", + "Already spaced content.", + ].join("\n"); - if (!hasCredentials) { - console.warn( - "Skipping markdown inspection: Notion credentials are not configured." - ); - return; - } + const output = ensureBlankLineAfterStandaloneBold(input); - const INTRODUCTION_PAGE_ID = "21f1b081-62d5-8008-9ca5-fad63c1a30ac"; - - const { ensureBlankLineAfterStandaloneBold } = - (await import("../generateBlocks")) as { - ensureBlankLineAfterStandaloneBold: (content: string) => string; - }; - const { n2m } = await import("../../notionClient"); - - const markdownBlocks = await n2m.pageToMarkdown(INTRODUCTION_PAGE_ID); - const rawMarkdown = n2m.toMarkdownString(markdownBlocks).parent ?? ""; - const markdown = ensureBlankLineAfterStandaloneBold(rawMarkdown); + expect(output).toBe( + [ + "**Collected Data**", + "", + "This section provides overviews and walkthroughs of features.", + "", + "**Another Section**", + "", + "Already spaced content.", + ].join("\n") + ); + }); - // Output to the console so we can compare against the published site - // when running the test manually. - console.log("\n--- Intro Markdown Snapshot ---\n"); - console.log(markdown); - console.log("\n--- End Snapshot ---\n"); + it("ignores bold text that is not standalone", () => { + const input = "Some **inline bold** content."; + const output = ensureBlankLineAfterStandaloneBold(input); - expect(markdown).toContain( - "**Collected Data**\n\nThis section provides overviews and walkthroughs all features related to gathering reviewing the GIS data and media that can be collected with CoMapeo." - ); - }, 60_000); + expect(output).toBe(input); + }); }); From f2f9d0c8dd933566790009534ede087c7b4be7d0 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 20:26:42 -0300 Subject: [PATCH 8/9] test(notion-fetch): decouple introduction markdown check --- .../__tests__/introductionMarkdown.test.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts index 1da5950b..1cabda76 100644 --- a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -1,5 +1,14 @@ -import { describe, it, expect } from "vitest"; -import { ensureBlankLineAfterStandaloneBold } from "../generateBlocks"; +import { describe, it, expect, beforeAll, vi } from "vitest"; + +vi.mock("../notionClient", () => ({ + n2m: {}, +})); + +let ensureBlankLineAfterStandaloneBold: (content: string) => string; + +beforeAll(async () => { + ({ ensureBlankLineAfterStandaloneBold } = await import("../generateBlocks")); +}); describe("ensureBlankLineAfterStandaloneBold", () => { it("inserts a blank line after standalone bold headings", () => { From 4c90b1b0774c0ae2cfc7b190b73d83094f2f01a3 Mon Sep 17 00:00:00 2001 From: luandro Date: Wed, 5 Nov 2025 20:26:42 -0300 Subject: [PATCH 9/9] test(notion-fetch): decouple introduction markdown check --- scripts/notion-fetch/__tests__/introductionMarkdown.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts index 1cabda76..ff293a1a 100644 --- a/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts +++ b/scripts/notion-fetch/__tests__/introductionMarkdown.test.ts @@ -1,6 +1,9 @@ import { describe, it, expect, beforeAll, vi } from "vitest"; -vi.mock("../notionClient", () => ({ +process.env.NOTION_API_KEY ??= "test-notion-api-key"; +process.env.DATABASE_ID ??= "test-database-id"; + +vi.mock("../../notionClient", () => ({ n2m: {}, }));