diff --git a/lib/mix/tasks/mneme.install.ex b/lib/mix/tasks/mneme.install.ex index 08ab3ea..63b98d9 100644 --- a/lib/mix/tasks/mneme.install.ex +++ b/lib/mix/tasks/mneme.install.ex @@ -7,7 +7,7 @@ defmodule Mix.Tasks.Mneme.Install do Running this command will automatically patch the following: - * `mix.exs` - Adds `"mneme.watch": :test` to `:preferred_cli_env` + * `mix.exs` - Adds Mneme's tasks to `:preferred_cli_env` * `.formatter.exs` - Adds `:mneme` to `:import_deps` * `test/test_helper.exs` - Adds `Mneme.start()` after `ExUnit.start()` @@ -16,7 +16,7 @@ defmodule Mix.Tasks.Mneme.Install do Since your `:mneme` dependency is usually specified with `only: :test`, this task should be run with `MIX_ENV=test`. - ```bash + ```shell $ #{example} Igniter: @@ -37,7 +37,7 @@ defmodule Mix.Tasks.Mneme.Install do 9 9 | start_permanent: Mix.env() == :prod, 10 - | deps: deps() 10 + | deps: deps(), - 11 + | preferred_cli_env: ["mneme.watch": :test] + 11 + | preferred_cli_env: ["mneme.test": :test, "mneme.watch": :test] 11 12 | ] 12 13 | end ...| @@ -98,6 +98,12 @@ defmodule Mix.Tasks.Mneme.Install do Igniter.update_elixir_file(igniter, "mix.exs", fn zipper -> # First, try to update a keyword literal in the project with {:ok, zipper} <- Function.move_to_def(zipper, :project, 0), + {:ok, zipper} <- + Igniter.Code.Keyword.put_in_keyword( + zipper, + [:preferred_cli_env, :"mneme.test"], + :test + ), {:ok, zipper} <- Igniter.Code.Keyword.put_in_keyword( zipper, diff --git a/lib/mix/tasks/mneme.test.ex b/lib/mix/tasks/mneme.test.ex new file mode 100644 index 0000000..15417c0 --- /dev/null +++ b/lib/mix/tasks/mneme.test.ex @@ -0,0 +1,73 @@ +defmodule Mix.Tasks.Mneme.Test do + @shortdoc "Run tests with support for Mneme's options at the command line" + @moduledoc """ + #{@shortdoc} + + This task is like `mix test`, except that it accepts some command line + options specific to Mneme. See ["Command line options"](#module-command-line-options) + below. + + ## Setup + + To ensure `mix mneme.test` runs in the test environment, add a + `:preferred_cli_env` entry in `mix.exs`: + + def project do + [ + ... + preferred_cli_env: [ + "mneme.test": :test, + "mneme.watch": :test + ], + ... + ] + end + + ## Command line options + + In addition to the options supported by `mix test`, which runs under + the hood, the following CLI options are available: + + * `--action [prompt,accept,reject]` + * `--default-pattern [infer,first,last]` + * `--diff [text,semantic]` + * `--diff-style [side_by_side,stacked]` + * `--force-update` + * `--target [mneme,ex_unit]` + + Option documentation is found here: [Mneme – Options](`Mneme#module-options`) + """ + + use Mix.Task + + @switches [ + action: :string, + default_pattern: :string, + diff: :string, + diff_style: :string, + force_update: :boolean, + target: :string, + dry_run: :boolean + ] + + # Ensure that these switches are collected instead of all but last + # discarded so that they can be passed to `mix test`. + @mix_test_keep [ + include: :keep, + exclude: :keep, + only: :keep, + formatter: :keep + ] + + @impl Mix.Task + def run(argv) do + {opts, argv} = OptionParser.parse!(argv, switches: @switches ++ @mix_test_keep) + {opts, mix_test_opts} = Keyword.split(opts, Keyword.keys(@switches)) + mix_test_argv = OptionParser.to_argv(mix_test_opts) ++ argv + + Application.put_env(:mneme, :cli_opts, opts) + + Mix.Task.reenable("test") + Mix.Task.run("test", mix_test_argv) + end +end diff --git a/lib/mix/tasks/mneme.watch.ex b/lib/mix/tasks/mneme.watch.ex index af04271..8d14dc9 100644 --- a/lib/mix/tasks/mneme.watch.ex +++ b/lib/mix/tasks/mneme.watch.ex @@ -1,7 +1,7 @@ defmodule Mix.Tasks.Mneme.Watch do - @shortdoc "Run tests when files change" + @shortdoc "Runs the tests for a project when source files change." @moduledoc """ - Runs the tests for a project when source files change. + #{@shortdoc} This task is similar to [`mix test.watch`](https://hex.pm/packages/mix_test_watch), but updated to work with Mneme: @@ -18,6 +18,7 @@ defmodule Mix.Tasks.Mneme.Watch do [ ... preferred_cli_env: [ + "mneme.test": :test, "mneme.watch": :test ], ... @@ -26,6 +27,10 @@ defmodule Mix.Tasks.Mneme.Watch do ## Command line options + In addition to the options supported by `mix mneme.test` and `mix test`, + which this task runs under the hood, the following CLI options are + available: + * `--exit-on-success` - stops the test watcher the first time the test suite passes. diff --git a/lib/mneme/options.ex b/lib/mneme/options.ex index 3ceadec..65352bf 100644 --- a/lib/mneme/options.ex +++ b/lib/mneme/options.ex @@ -211,6 +211,7 @@ defmodule Mneme.Options do |> collect_attributes(Map.get(attrs, @test_attr, [])) |> collect_attributes(Map.get(attrs, @describe_attr, [])) |> collect_attributes(Map.get(attrs, @module_attr, [])) + |> collect_attributes(Application.get_env(:mneme, :cli_opts, [])) |> collect_attributes([persistent_term_get(@config_cache, [])]) end @@ -218,7 +219,9 @@ defmodule Mneme.Options do defp collect_attributes(acc, lower_priority) do for_result = - for attrs <- lower_priority, kv <- List.wrap(attrs), reduce: %{} do + for attrs <- lower_priority, + kv <- List.wrap(attrs), + reduce: %{} do acc -> {k, v} = case kv do @@ -226,6 +229,13 @@ defmodule Mneme.Options do k when is_atom(k) -> {k, true} end + v = + if is_binary(v) do + String.to_atom(v) + else + v + end + Map.update(acc, k, [v], &[v | &1]) end diff --git a/lib/mneme/watch/test_runner.ex b/lib/mneme/watch/test_runner.ex index 3e65c79..8a2c023 100644 --- a/lib/mneme/watch/test_runner.ex +++ b/lib/mneme/watch/test_runner.ex @@ -237,8 +237,8 @@ defmodule Mneme.Watch.TestRunner do def run_tests(cli_args, system_restart_marker) do Code.unrequire_files(Code.required_files()) recompile() - Mix.Task.reenable(:test) - Mix.Task.run(:test, cli_args) + Mix.Task.reenable("mneme.test") + Mix.Task.run("mneme.test", cli_args) catch :exit, _ -> write_system_restart_marker!(system_restart_marker) diff --git a/mix.exs b/mix.exs index e5c6146..cb5b38c 100644 --- a/mix.exs +++ b/mix.exs @@ -88,6 +88,7 @@ defmodule Mneme.MixProject do coveralls: :test, "coveralls.html": :test, "test.mneme_not_started": :test, + "mneme.test": :test, "mneme.watch": :test ] end diff --git a/test/mix/tasks/mneme.install_test.exs b/test/mix/tasks/mneme.install_test.exs index 80bbf55..369171a 100644 --- a/test/mix/tasks/mneme.install_test.exs +++ b/test/mix/tasks/mneme.install_test.exs @@ -37,7 +37,7 @@ defmodule Mix.Tasks.Mneme.InstallTest do 9 9 | start_permanent: Mix.env() == :prod, 10 - | deps: deps() 10 + | deps: deps(), - 11 + | preferred_cli_env: ["mneme.watch": :test] + 11 + | preferred_cli_env: ["mneme.test": :test, "mneme.watch": :test] 11 12 | ] 12 13 | end ...| @@ -81,9 +81,10 @@ defmodule Mix.Tasks.Mneme.InstallTest do 7 7 | preferred_cli_env: [ 8 - | "existing.task": :dev 8 + | "existing.task": :dev, - 9 + | "mneme.watch": :test - 9 10 | ] - 10 11 | ] + 9 + | "mneme.test": :test, + 10 + | "mneme.watch": :test + 9 11 | ] + 10 12 | ] ...| """ <- @@ -92,7 +93,7 @@ defmodule Mix.Tasks.Mneme.InstallTest do |> igniter_diff(only: "mix.exs") end - test "when :preferred_cli_env already contains mneme.watch" do + test "when :preferred_cli_env already contains mneme tasks" do test_project = test_project( files: %{ @@ -104,6 +105,7 @@ defmodule Mix.Tasks.Mneme.InstallTest do [ app: :test, preferred_cli_env: [ + "mneme.test": :test, "mneme.watch": :test ] ] @@ -119,6 +121,46 @@ defmodule Mix.Tasks.Mneme.InstallTest do |> igniter_diff(only: "mix.exs") end + test "when :preferred_cli_env already contains only mneme.watch" do + test_project = + test_project( + files: %{ + "mix.exs" => """ + defmodule Test.MixProject do + use Mix.Project + + def project do + [ + app: :test, + preferred_cli_env: [ + "mneme.watch": :test + ] + ] + end + end + """ + } + ) + + auto_assert """ + Update: mix.exs + + ...| + 6 6 | app: :test, + 7 7 | preferred_cli_env: [ + 8 - | "mneme.watch": :test + 8 + | "mneme.watch": :test, + 9 + | "mneme.test": :test + 9 10 | ] + 10 11 | ] + ...| + + """ <- + test_project + |> Igniter.compose_task("mneme.install") + |> igniter_diff(only: "mix.exs") + end + test "when :preferred_cli_env is a call to a local function" do test_project = test_project(