From 7781765c7a32c8b0874080d382b5317725365294 Mon Sep 17 00:00:00 2001 From: Parker Bartlett Date: Sun, 17 Sep 2023 09:57:57 -0400 Subject: [PATCH] test installer (#36) * test vox.new task - add flag support for installer version - print mix help info when `mix vox.new` is ran without args - add test for vox.new task * test cleanup * run installer tests in ci * loosen installer's elixir version * line wrap pipe * fix deps for CI --- .github/workflows/elixir.yml | 5 ++ installer/lib/mix/tasks/vox.new.ex | 37 +++++++++----- installer/mix.exs | 4 +- installer/test/mix_helper.exs | 53 ++++++++++++++++++++ installer/test/vox_new_test.exs | 78 ++++++++++++++++++++++++++++-- 5 files changed, 159 insertions(+), 18 deletions(-) create mode 100644 installer/test/mix_helper.exs diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index 442d43d..e6bb70d 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -35,3 +35,8 @@ jobs: run: mix deps.get - name: Run tests run: mix test + - name: Run installer tests + run: | + cd installer + mix deps.get + mix test diff --git a/installer/lib/mix/tasks/vox.new.ex b/installer/lib/mix/tasks/vox.new.ex index ca3f585..ce75858 100644 --- a/installer/lib/mix/tasks/vox.new.ex +++ b/installer/lib/mix/tasks/vox.new.ex @@ -8,9 +8,12 @@ defmodule Mix.Tasks.Vox.New do ## Command line options * `--esbuild` - include a simple esbuild system for asset compliation + + * `-v`, `--version` - prints the Vox installer version """ @template_string_to_replace "APP" + @version Mix.Project.config()[:version] use Mix.Task use VoxNew.Templater @@ -32,20 +35,28 @@ defmodule Mix.Tasks.Vox.New do template("test/#{@template_string_to_replace}_test.exs") @impl Mix.Task + def run([version]) when version in ~w(-v --version) do + Mix.shell().info("Vox installer v#{@version}") + end + def run(argv) do - {flags, [path | _rest]} = OptionParser.parse!(argv, strict: [esbuild: :boolean]) - - # [TODO] I think these could result in incorrect formatting - module_name = Macro.camelize(path) - app_name = Macro.underscore(path) - esbuild = Keyword.get(flags, :esbuild, false) - - generate(%Project{ - app_name: app_name, - base_path: path, - esbuild: esbuild, - module_name: module_name - }) + case OptionParser.parse!(argv, strict: [esbuild: :boolean]) do + {_, []} -> + Mix.Tasks.Help.run(["vox.new"]) + + {flags, [path | _rest]} -> + # [TODO] I think these could result in incorrect formatting + module_name = Macro.camelize(path) + app_name = Macro.underscore(path) + esbuild = Keyword.get(flags, :esbuild, false) + + generate(%Project{ + app_name: app_name, + base_path: path, + esbuild: esbuild, + module_name: module_name + }) + end end defp generate(project) do diff --git a/installer/mix.exs b/installer/mix.exs index c2ccdf7..7d520d1 100644 --- a/installer/mix.exs +++ b/installer/mix.exs @@ -8,7 +8,7 @@ defmodule VoxNew.MixProject do [ app: :vox_new, version: @version, - elixir: "~> 1.15", + elixir: "~> 1.14", start_permanent: Mix.env() == :prod, source_url: @source_url, package: package(), @@ -43,7 +43,7 @@ defmodule VoxNew.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:ex_doc, "~> 0.30.6"} + {:ex_doc, "~> 0.30.6", only: :dev, runtime: false} ] end diff --git a/installer/test/mix_helper.exs b/installer/test/mix_helper.exs new file mode 100644 index 0000000..a5839ba --- /dev/null +++ b/installer/test/mix_helper.exs @@ -0,0 +1,53 @@ +Mix.shell(Mix.Shell.Process) + +defmodule MixHelper do + import ExUnit.Assertions + + def tmp_path do + Path.expand("../../tmp", __DIR__) + end + + defp random_string(len) do + len + |> :crypto.strong_rand_bytes() + |> Base.encode64() + |> binary_part(0, len) + end + + def in_tmp(which, function) do + path = Path.join([tmp_path(), random_string(10), to_string(which)]) + + try do + File.rm_rf!(path) + File.mkdir_p!(path) + File.cd!(path, function) + after + File.rm_rf!(path) + end + end + + def assert_file(file) do + assert File.regular?(file), "Expected #{file} to exist, but does not" + end + + def assert_file(file, match) do + cond do + is_list(match) -> + assert_file(file, &Enum.each(match, fn m -> assert &1 =~ m end)) + + is_binary(match) or is_struct(match, Regex) -> + assert_file(file, &assert(&1 =~ match)) + + is_function(match, 1) -> + assert_file(file) + match.(File.read!(file)) + + true -> + raise inspect({file, match}) + end + end + + def refute_file(file) do + refute File.regular?(file), "Expected #{file} to not exist, but it does" + end +end diff --git a/installer/test/vox_new_test.exs b/installer/test/vox_new_test.exs index 8b62d35..a158c49 100644 --- a/installer/test/vox_new_test.exs +++ b/installer/test/vox_new_test.exs @@ -1,8 +1,80 @@ +Code.require_file("mix_helper.exs", __DIR__) + defmodule VoxNewTest do use ExUnit.Case - doctest VoxNew + import MixHelper + import ExUnit.CaptureIO + + @app_name "blog" + + test "returns the version" do + Mix.Tasks.Vox.New.run(["-v"]) + assert_received {:mix_shell, :info, ["Vox installer v" <> _]} + end + + test "new with defaults" do + in_tmp("new with defaults", fn -> + Mix.Tasks.Vox.New.run([@app_name]) + + assert_file("#{@app_name}/mix.exs", fn file -> + assert file =~ "defmodule Blog.MixProject" + assert file =~ "app: :blog" + refute file =~ "mod: {Blog.Application, []}" + refute file =~ ":esbuild" + end) + + assert_file("#{@app_name}/config/config.exs", fn file -> + assert file =~ "src_dir = \"site\"" + assert file =~ "output_dir = \"_html\"" + refute file =~ "config :esbuild" + end) + + refute_file("#{@app_name}/assets/app.js") + refute_file("#{@app_name}/lib/application.ex") + refute_file("#{@app_name}/lib/#{@app_name}/esbuild.ex") + end) + end + + test "new with --esbuild" do + in_tmp("new with --esbuild", fn -> + Mix.Tasks.Vox.New.run([@app_name, "--esbuild"]) + + shared_file_assertions() + + assert_file("#{@app_name}/mix.exs", fn file -> + assert file =~ "defmodule Blog.MixProject" + assert file =~ "app: :blog" + assert file =~ "mod: {Blog.Application, []}" + assert file =~ ":esbuild" + end) + + assert_file("#{@app_name}/config/config.exs", fn file -> + assert file =~ "src_dir = \"site\"" + assert file =~ "output_dir = \"_html\"" + assert file =~ "config :esbuild" + end) + + assert_file("#{@app_name}/assets/app.js") + assert_file("#{@app_name}/lib/application.ex") + assert_file("#{@app_name}/lib/#{@app_name}/esbuild.ex") + end) + end + + test "new without args" do + in_tmp("new without args", fn -> + assert capture_io(fn -> Mix.Tasks.Vox.New.run([]) end) =~ + "Generate a new Vox application." + end) + end - test "greets the world" do - assert VoxNew.hello() == :world + def shared_file_assertions() do + assert_file("#{@app_name}/README.md") + assert_file("#{@app_name}/lib/#{@app_name}.ex") + assert_file("#{@app_name}/site/_root.html.eex") + assert_file("#{@app_name}/site/_template.html.eex") + assert_file("#{@app_name}/site/index.html.eex") + assert_file("#{@app_name}/site/posts/hello-world.html.eex") + assert_file("#{@app_name}/test/test_helper.exs") + assert_file("#{@app_name}/test/#{@app_name}_test.exs") end end