Files
supabase__supabase/apps/kb/scripts/generate-markdown.test.mjs

178 lines
6.1 KiB
JavaScript

import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import {
absolutizeLinks,
getInternalLinkBaseUrl,
parseFrontmatter,
renderMarkdown,
renderTopicMarkdown,
} from './generate-markdown.mjs'
describe('parseFrontmatter', () => {
it('parses title/description out of the YAML frontmatter and returns the rest as body', () => {
const raw = `---\ntitle: 'Sample guide'\ndescription: 'A short summary.'\n---\n\nBody text here.\n`
const { data, body } = parseFrontmatter(raw)
expect(data.title).toBe('Sample guide')
expect(data.description).toBe('A short summary.')
// The blank line separating the closing `---` from the body is kept as-is
// here — renderMarkdown() is what trims it before composing the output.
expect(body).toBe('\nBody text here.\n')
})
it('parses values that would trip up a naive regex (colons and quotes in the text)', () => {
const raw = `---\ntitle: "Note: this has a colon"\ndescription: "Quoted 'inner' text: still one value"\n---\nBody\n`
const { data } = parseFrontmatter(raw)
expect(data.title).toBe('Note: this has a colon')
expect(data.description).toBe("Quoted 'inner' text: still one value")
})
it('returns an empty data object and the raw text untouched when there is no frontmatter', () => {
const raw = 'Just a plain paragraph, no frontmatter.\n'
const { data, body } = parseFrontmatter(raw)
expect(data).toEqual({})
expect(body).toBe(raw)
})
})
describe('getInternalLinkBaseUrl', () => {
const ORIGINAL_ENV = process.env
beforeEach(() => {
process.env = { ...ORIGINAL_ENV }
delete process.env.VERCEL_ENV
delete process.env.VERCEL_URL
})
afterEach(() => {
process.env = ORIGINAL_ENV
})
it('returns the production origin when VERCEL_ENV=production', () => {
process.env.VERCEL_ENV = 'production'
expect(getInternalLinkBaseUrl()).toBe('https://supabase.com')
})
it('returns the deployment URL when VERCEL_ENV=preview', () => {
process.env.VERCEL_ENV = 'preview'
process.env.VERCEL_URL = 'kb-git-fork-supabase.vercel.app'
expect(getInternalLinkBaseUrl()).toBe('https://kb-git-fork-supabase.vercel.app')
})
it('returns empty when preview is set but VERCEL_URL is missing', () => {
process.env.VERCEL_ENV = 'preview'
expect(getInternalLinkBaseUrl()).toBe('')
})
it('returns empty when VERCEL_ENV is not set (local dev/CI)', () => {
expect(getInternalLinkBaseUrl()).toBe('')
})
})
describe('absolutizeLinks', () => {
const ORIGINAL_ENV = process.env
beforeEach(() => {
process.env = { ...ORIGINAL_ENV, VERCEL_ENV: 'production' }
})
afterEach(() => {
process.env = ORIGINAL_ENV
})
it('leaves already-absolute links untouched', () => {
expect(absolutizeLinks('See [the docs](https://example.com/guide) for more.')).toBe(
'See [the docs](https://example.com/guide) for more.\n'
)
})
it('rewrites a root-relative link into a full URL under the kb base path', () => {
expect(absolutizeLinks('See [another guide](/guides/other-guide) for more.')).toBe(
'See [another guide](https://supabase.com/kb/guides/other-guide) for more.\n'
)
})
it('does not double up the base path when a link already includes it', () => {
expect(absolutizeLinks('[another guide](/kb/guides/other-guide)')).toBe(
'[another guide](https://supabase.com/kb/guides/other-guide)\n'
)
})
it('leaves the link relative (base path only, no origin) outside of Vercel', () => {
process.env.VERCEL_ENV = undefined
expect(absolutizeLinks('[another guide](/guides/other-guide)')).toBe(
'[another guide](/kb/guides/other-guide)\n'
)
})
it('does not rewrite image URLs', () => {
expect(absolutizeLinks('![alt](/img.png)')).toBe('![alt](/img.png)\n')
})
it('skips link-like text inside fenced code blocks', () => {
expect(absolutizeLinks('```\n[x](/x)\n```\n\n[y](/y)')).toBe(
'```\n[x](/x)\n```\n\n[y](https://supabase.com/kb/y)\n'
)
})
it('leaves a root-relative URL inside a fenced code block untouched, even outside link syntax', () => {
const body = '```bash\ncurl -X GET /guides/foo\n```'
expect(absolutizeLinks(body)).toBe('```bash\ncurl -X GET /guides/foo\n```\n')
})
it('leaves a root-relative URL inside a fenced markdown code block untouched', () => {
const body = '```md\n[Guides](/guides/foo)\n```'
expect(absolutizeLinks(body)).toBe('```md\n[Guides](/guides/foo)\n```\n')
})
it('leaves a root-relative URL inside an inline code span untouched', () => {
expect(absolutizeLinks('Run `GET /guides/foo` to fetch it.')).toBe(
'Run `GET /guides/foo` to fetch it.\n'
)
})
it('round-trips GFM tables and strikethrough without mangling them', () => {
const body = '| a | b |\n| - | - |\n| 1 | 2 |\n\n~~gone~~'
expect(absolutizeLinks(body)).toBe('| a | b |\n| - | - |\n| 1 | 2 |\n\n~~gone~~\n')
})
})
describe('renderMarkdown', () => {
it('turns the title into an h1 and the description into the paragraph beneath it', () => {
const data = { title: 'Sample guide', description: 'A short summary.' }
expect(renderMarkdown(data, 'Body text.')).toBe(
'# Sample guide\n\nA short summary.\n\nBody text.\n'
)
})
it('skips the heading/lead lines gracefully when title or description are missing', () => {
expect(renderMarkdown({}, 'Body text.')).toBe('Body text.\n')
})
})
describe('renderTopicMarkdown', () => {
const topic = { name: 'Tutorial', description: 'Step-by-step walkthroughs.' }
it('renders the topic as an h1/description followed by a bullet list of its guides', () => {
const guides = [
{ title: 'Sample guide', url: 'https://supabase.com/kb/guides/sample-guide.md' },
]
expect(renderTopicMarkdown(topic, guides)).toBe(
'# Tutorial\n\nStep-by-step walkthroughs.\n\n- [Sample guide](https://supabase.com/kb/guides/sample-guide.md)\n'
)
})
it('falls back to "No guides for this topic" when the list is empty', () => {
expect(renderTopicMarkdown(topic, [])).toBe(
'# Tutorial\n\nStep-by-step walkthroughs.\n\nNo guides for this topic\n'
)
})
})