← all posts

A 25-Step TDD Kata: Building a Markdown Renderer in Rust

Gordon Beeming
Gordon Beeming
On this page2 sections ▾

There's a gap between knowing a language's syntax and actually feeling comfortable in it. For me, that gap is currently Rust. I've been looking for challenges that push me to write code in a structured way and get faster with my IDE shortcuts - not just toy examples, but something with enough surface area to feel real.

That search brought me to code katas. Inspired by classics like the String Calculator by Roy Osherove, I put together a more substantial challenge: a 25-step kata to build a Markdown-to-HTML renderer from scratch.

I'm working through this in Rust, but the kata is completely language-agnostic. TDD works the same way whether you're in C#, Python, TypeScript, or anything else. So here's the challenge: pick your language and leave a comment below letting us know which one you used. I'd love to see solutions in different languages stack up.

The structure throughout is the TDD cycle of Red-Green-Refactor.

A diagram showing a cycle with three parts: Red, Green, and Refactor.
The Red-Green-Refactor cycle will be our guide for this journey.

The goal is to embrace the TDD rhythm: write a failing test (Red), make it pass with the simplest code possible (Green), then clean it up (Refactor). Resist the urge to skip ahead.

#The Challenge

Your task is to create a function that takes a string of Markdown and returns the correctly formatted HTML string. Build up the functionality one feature at a time, keeping the test suite green before moving to the next step.

#The 25 Progressions

Here is the step-by-step path to build your renderer. Each step represents a new test (or set of tests) you should write first.

#Phase 1: The Document Structure

  1. Handle Empty Input

    • Input: ""
    • Output: ""
  2. A Single Line of Text

    • Input: Hello, World!
    • Output: <p>Hello, World!</p>
  3. Multiple Lines in One Paragraph

    • Input: Hello,\nWorld!
    • Output: <p>Hello, World!</p>
  4. Separate Paragraphs

    • Input: Line 1\n\nLine 2
    • Output: <p>Line 1</p><p>Line 2</p>

#Phase 2: Headers

  1. Header 1

    • Input: # Header 1
    • Output: <h1>Header 1</h1>
  2. Header 2

    • Input: ## Header 2
    • Output: <h2>Header 2</h2>
  3. Support all Headers (H3-H6)

    • Test inputs from ### H3 through ###### H6.
  4. Refactoring Break

    • This is a perfect time to refactor. Instead of multiple if conditions, can you create a single, elegant piece of logic that counts the # characters to determine the header level?

#Phase 3: Inline Styling

  1. Emphasis (Italics)

    • Input: This is *italic* text.
    • Output: <p>This is <em>italic</em> text.</p>
  2. Alternative Emphasis

    • Input: This is _italic_ text.
    • Output: <p>This is <em>italic</em> text.</p>
  3. Strong (Bold)

    • Input: This is **bold** text.
    • Output: <p>This is <strong>bold</strong> text.</p>
  4. Alternative Strong

    • Input: This is __bold__ text.
    • Output: <p>This is <strong>bold</strong> text.</p>
  5. Strikethrough

    • Input: This is ~~struck~~ text.
    • Output: <p>This is <del>struck</del> text.</p>
  6. Nested Inline Styles

    • Input: This is **bold and *italic*** text.
    • Output: <p>This is <strong>bold and <em>italic</em></strong> text.</p>
  1. Links

    • Input: [Gordon's Blog](https://gordonbeeming.com)
    • Output: <p><a href="https://gordonbeeming.com">Gordon's Blog</a></p>
  2. Images

    • Input: ![An alt text](image.jpg)
    • Output: <p><img src="image.jpg" alt="An alt text"></p>

#Phase 5: Lists and Blocks

  1. Unordered List (Single Item)

    • Input: * List item
    • Output: <ul><li>List item</li></ul>
  2. Unordered List (Multiple Items)

    • Input: * Item 1\n* Item 2
    • Output: <ul><li>Item 1</li><li>Item 2</li></ul>
  3. Ordered List

    • Input: 1. First item\n2. Second item
    • Output: <ol><li>First item</li><li>Second item</li></ol>
  4. Blockquotes

    • Input: > This is a quote.
    • Output: <blockquote><p>This is a quote.</p></blockquote>
  5. Horizontal Rule

    • Input: ---
    • Output: <hr>

#Phase 6: Advanced Elements & Final Polish

  1. Inline Code

    • Input: Use the `let` keyword.
    • Output: <p>Use the <code>let</code> keyword.</p>
  2. Security Refactor (HTML Escaping)

    • Input: <script>
    • Output: <p>&lt;script&gt;</p>
  3. Fenced Code Blocks

    • Input: "```\nlet x = 5;\n```"
    • Output: <pre><code>let x = 5;\n</code></pre>
  4. Links with Titles

    • Input: [Tiani's Site](https://tianibeeming.com \"Tiani Beeming\")
    • Output: <p><a href="https://tianibeeming.com" title="Tiani Beeming">Tiani's Site</a></p>

After 25 steps, you'll have a renderer capable of turning complex Markdown into clean HTML.

For example, a piece of Markdown like this:

Sample Markdown input
# My Document

This paragraph contains **bold text** and a link to [my wife Tiani's blog](https://tianibeeming.com).

- List Item 1
- List Item 2

...will produce the following HTML output.

Expected HTML output
<h1>My Document</h1>
<p>This paragraph contains <strong>bold text</strong> and a link to <a href="https://tianibeeming.com">my wife Tiani's blog</a>.</p>
<ul>
  <li>List Item 1</li>
  <li>List Item 2</li>
</ul>

That's the full 25-step progression. By the time you're done, you'll have a functional Markdown renderer and dozens of reps through the TDD cycle. The process starts to feel natural somewhere around step 10 - that's when it clicks.

Drop a comment below with the language you used and how it went.

Gordon Beeming
Gordon Beeming

Father • Husband • Triathlete • SSW Solution Architect

Related posts