import { describe, expect, it, afterAll } from "@jest/globals";
import { promises as fs } from "fs";
import path from "path";
import { setActiveTheme } from "../../themes/index.js";
import {
getAvailableStyleNames,
getStyle,
setActiveStyle,
} from "../index.js";
import type { StyleName } from "../../themes/types.js";
const examplesDir = path.resolve(process.cwd(), "assets/examples");
const examplesMarkdownDir = path.resolve(examplesDir, "md");
const SAMPLE_IMAGE = "https://picsum.photos/1280/720";
/**
* Sample params for rich style layouts (complete set β no merge with theme).
*/
const richLayoutParams: Record<string, Record<string, unknown>> = {
title: {
heading: "Welcome to the Future",
content: "A bold vision for modern presentations",
},
section: {
title: "Part Two",
subtitle: "Diving deeper into the details",
},
list: {
heading: "Key Points",
list: ["Background context", "Insights discovered", "Next steps"],
citations: "Source: Sample Dataset",
},
table: {
heading: "Data Overview",
tableMarkdown: [
"| Item | Value |",
"| ---- | ----- |",
"| Alpha | 42 |",
"| Beta | 37 |",
"| Gamma | 58 |",
"| Delta | 21 |",
].join("\n"),
description: "Higher is better.",
citations: "Source: Sample Stats",
},
"image-right": {
heading: "Architecture Diagram",
list: ["Ingest", "Process", "Serve"],
imagePath: SAMPLE_IMAGE,
citations: "Diagram credit: picsum.photos",
},
"image-center": {
heading: "Workflow Snapshot",
imagePath: SAMPLE_IMAGE,
description: "Step-by-step overview.",
citations: "Figure 1",
},
"image-split": {
heading: "Visual Overview",
imageUrl: SAMPLE_IMAGE,
items: ["Clear structure", "Engaging visuals", "Concise messaging"],
},
timeline: {
heading: "Project Milestones",
items: [
"Q1 2025: Research & Discovery",
"Q2 2025: Prototype Development",
"Q3 2025: Beta Launch",
"Q4 2025: General Availability",
],
},
"card-grid": {
heading: "Core Capabilities",
cards: [
"π|Performance|Blazing fast response times",
"π|Security|Enterprise-grade protection",
"π|Analytics|Real-time insights dashboard",
"π|Integration|Connect with any platform",
],
},
statistics: {
heading: "Impact at a Glance",
stats: ["99.9%|Uptime", "2.5M|Users", "150ms|Avg Latency", "4.8β
|Rating"],
caption: "Data as of Q4 2025",
},
"highlight-box": {
heading: "Key Takeaway",
content:
"Simplicity and clarity are the foundations of effective communication.",
},
"two-column-panel": {
heading: "Plan Comparison",
panel1Title: "Free Tier",
panel1List: ["5 projects", "Community support", "Basic analytics"],
panel2Title: "Pro Tier",
panel2List: ["Unlimited projects", "Priority support", "Advanced analytics"],
accentPanel: "right",
},
"three-column-panel": {
heading: "Our Process",
panels: [
"Discover|We research your needs and goals",
"Design|We craft a tailored solution",
"Deliver|We ship and iterate together",
],
},
"image-comparison": {
heading: "Before & After",
image1Url: SAMPLE_IMAGE,
image1Label: "Before",
image2Url: SAMPLE_IMAGE,
image2Label: "After",
},
content: {
heading: "Summary",
body: "This is a **free-form** markdown slide.\n\n- Point one\n- Point two\n- Point three",
},
quote: {
quote: "The best way to predict the future is to invent it.",
attribution: "Alan Kay",
content: "A guiding principle for innovation.",
},
process: {
heading: "Development Workflow",
steps: ["Plan", "Develop", "Test", "Deploy"],
},
};
describe("style example generator", () => {
afterAll(() => {
setActiveTheme("default");
setActiveStyle("default");
});
it("writes markdown examples for every style", async () => {
await fs.mkdir(examplesMarkdownDir, { recursive: true });
const styleNames = getAvailableStyleNames();
for (const styleName of styleNames) {
const style = getStyle(styleName);
if (!style) {
throw new Error(`Style "${styleName}" not found`);
}
// Skip styles with no layouts (e.g. default)
const layoutNames = Object.keys(style.layouts);
if (layoutNames.length === 0) {
continue;
}
const markdown = buildExampleMarkdown(styleName, style.layouts, layoutNames);
const filePath = path.join(
examplesMarkdownDir,
`example-default-${styleName}-style.md`,
);
await fs.writeFile(filePath, markdown, "utf-8");
const savedMarkdown = await fs.readFile(filePath, "utf-8");
expect(savedMarkdown).toBe(markdown);
}
}, 30000);
});
function buildExampleMarkdown(
styleName: StyleName,
layouts: Record<string, { template: (params: Record<string, unknown>) => string }>,
layoutNames: string[],
): string {
setActiveTheme("default");
setActiveStyle(styleName);
const style = getStyle(styleName);
if (!style) {
throw new Error(`Style "${styleName}" not found`);
}
const slides = layoutNames.map((layoutName) => {
const layout = layouts[layoutName];
if (!layout) {
throw new Error(`Layout "${layoutName}" not found in style "${styleName}"`);
}
const params = richLayoutParams[layoutName];
if (!params) {
throw new Error(
`Missing sample params for layout "${layoutName}" in style "${styleName}"`,
);
}
const content = layout.template(params);
return `<!-- layout: ${layoutName} -->\n${content.trim()}`;
});
const frontmatterLines = [
"---",
"marp: true",
"theme: default",
`header: Example Labs | default theme, ${styleName} style`,
"paginate: true",
];
if (style.css) {
frontmatterLines.push(`style: |`);
for (const line of style.css.split("\n")) {
frontmatterLines.push(` ${line}`);
}
}
frontmatterLines.push("---");
return [
...frontmatterLines,
"",
slides.join("\n\n---\n\n"),
"",
].join("\n");
}