Skip to content

Commit

Permalink
Initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MainShayne233 committed Nov 13, 2019
1 parent 8b95275 commit bce6ae0
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 19 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

Test the Elixir code in your markdown!

<!--- TESTME -->
<!--- MARKDOWN_TEST_START -->
```elixir
iex> 1 + 2
3
```
<!--- MARKDOWN_TEST_END -->

<!--- MARKDOWN_TEST_START -->
```elixir
defmodule MyModule do
def add(x, y), do: x + y
Expand All @@ -13,7 +20,8 @@ iex> x = 1
...> MyModule.add(x, y)
3

iex> MyModule.add(x, y)
3
iex> MyModule.add(1, 2)
4
```
<!--- MARKDOWN_TEST_END -->

126 changes: 115 additions & 11 deletions lib/markdown_test.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,122 @@
defmodule MarkdownTest do
@moduledoc """
Documentation for MarkdownTest.
"""
defmodule TestBlock do
defstruct [:preface_code, cases: []]
end

@test_block_start "<!--- MARKDOWN_TEST_START -->"
@test_block_end "<!--- MARKDOWN_TEST_END -->"

@markdown_code_delimiter_pattern ~r/```(elixir)?/

defmacro __using__([]) do
quote do
import MarkdownTest, only: [test_markdown: 1]
end
end

defmacro test_markdown(path) do
file = fetch_file!(path)

test_blocks = parse_test_blocks!(file)

@doc """
Hello world.
describes =
test_blocks
|> Enum.with_index()
|> Enum.map(fn {test_block, index} ->
tests =
test_block.cases
|> Enum.with_index()
|> Enum.map(fn {{expression, expected}, index} ->
quote do
test "case: #{unquote(index)}" do
assert unquote(expression) === unquote(expected)
end
end
end)

## Examples
quote do
describe "block #{unquote(index)}" do
unquote(test_block.preface_code)

unquote_splicing(tests)
end
end
end)

quote do
(unquote_splicing(describes))
end
end

defp parse_test_blocks!(file) do
file
|> parse_raw_code_blocks!()
|> Enum.map(&parse_code_block!/1)
end

defp parse_raw_code_blocks!(file) do
file
|> String.split(@test_block_start)
|> Enum.drop(1)
|> Enum.map(&(String.split(&1, @test_block_end) |> hd()))
|> Enum.map(&String.replace(&1, @markdown_code_delimiter_pattern, ""))
end

iex> MarkdownTest.hello()
:world
defp parse_code_block!(raw_code_block) do
preface_code = parse_preface_code!(raw_code_block)
test_cases = parse_test_cases!(raw_code_block)

"""
def hello do
:world
%TestBlock{
preface_code: preface_code,
cases: test_cases
}
end

defp parse_preface_code!(raw_code_block) do
raw_code_block
|> String.split("iex>")
|> hd()
|> String.trim()
|> Code.string_to_quoted!()
end

defp parse_test_cases!(raw_code_block) do
{raw_test_cases, _} =
raw_code_block
|> String.split("\n")
|> Enum.map(&String.trim/1)
|> Enum.reduce({[], :init}, fn
"iex> " <> expression_start, {cases, :init} ->
{[{expression_start, nil} | cases], :expression}

_, {cases, :init} ->
{cases, :init}

"...> " <> expression_chunk, {[{current_expression, nil} | cases], :expression} ->
{[{current_expression <> "\n" <> expression_chunk, nil} | cases], :expression}

expected_start, {[{current_expression, nil} | cases], :expression} ->
{[{current_expression, expected_start} | cases], :expected}

"" <> _, {cases, :expected} ->
{cases, :init}

expected_chunk, {[{current_expression, current_expected} | cases], :expected} ->
{[{current_expression, current_expected <> "\n" <> expected_chunk} | cases]}
end)

raw_test_cases
|> Enum.map(fn {raw_expression, raw_expected} ->
{Code.string_to_quoted!(raw_expression), Code.string_to_quoted!(raw_expected)}
end)
|> Enum.reverse()
end

defp fetch_file!(path) do
File.read(path)
|> unwrap_or_raise("No file at path: #{path}")
end

defp unwrap_or_raise({:ok, term}, _message), do: term
defp unwrap_or_raise(_, message), do: raise(%RuntimeError{message: message})
end
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmodule MarkdownTest.MixProject do

defp deps do
[
{:mix_test_watch, "~> 0.9.0", only: :dev, runtime: false},
{:mix_test_watch, "~> 0.9.0", only: :dev, runtime: false}
]
end
end
6 changes: 2 additions & 4 deletions test/markdown_test_test.exs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
defmodule MarkdownTestTest do
use ExUnit.Case
doctest MarkdownTest
use MarkdownTest

test "greets the world" do
assert MarkdownTest.hello() == :world
end
test_markdown("README.md")
end

0 comments on commit bce6ae0

Please sign in to comment.