From 2f2d833c02ca578a2478d6bfdaf49ea53e3344be Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 11:46:34 -0400 Subject: [PATCH 1/7] Add mix_project/1 and phoenix_project/1 - Deprecate test_project/1 in favor of mix_project/1 - Format test files Missing in this PR: - Umbrella - Other opts such as --module, --no-ecto, etc --- lib/igniter.ex | 3 +- lib/igniter/test.ex | 333 ++++++++++++++++++++++++++----------- mix.exs | 3 +- mix.lock | 1 + test/igniter/test_test.exs | 43 +++++ 5 files changed, 288 insertions(+), 95 deletions(-) create mode 100644 test/igniter/test_test.exs diff --git a/lib/igniter.ex b/lib/igniter.ex index 9506e09..fca4d61 100644 --- a/lib/igniter.ex +++ b/lib/igniter.ex @@ -671,7 +671,8 @@ defmodule Igniter do end end - defp source_handler(path, opts \\ []) do + @doc false + def source_handler(path, opts \\ []) do Keyword.get_lazy(opts, :source_handler, fn -> if Path.extname(path) in Rewrite.Source.Ex.extensions() do Rewrite.Source.Ex diff --git a/lib/igniter/test.ex b/lib/igniter/test.ex index e07832c..95c0cd8 100644 --- a/lib/igniter/test.ex +++ b/lib/igniter/test.ex @@ -1,8 +1,18 @@ defmodule Igniter.Test do @moduledoc "Tools for testing with igniter." + @deprecated "Use mix_project/1 instead" + @spec test_project(opts :: Keyword.t()) :: Igniter.t() + def test_project(opts \\ []) do + Igniter.new() + |> Igniter.assign(:test_mode?, true) + |> Igniter.assign(:test_files, add_mix_new(opts)) + |> Igniter.Project.IgniterConfig.setup() + |> apply_igniter!() + end + @doc """ - Sets up a test igniter that has only the files passed to it. + Sets up a test Igniter that mimics a new mix project. ## Starting point @@ -23,7 +33,7 @@ defmodule Igniter.Test do ## Examples - test_project(files: %{ + mix_project(files: %{ "lib/foo.ex" => \"\"\" defmodule MyApp.Foo do use Ash.Resource @@ -31,8 +41,8 @@ defmodule Igniter.Test do \"\"\" }) """ - @spec test_project(opts :: Keyword.t()) :: Igniter.t() - def test_project(opts \\ []) do + @spec mix_project(opts :: Keyword.t()) :: Igniter.t() + def mix_project(opts \\ []) do Igniter.new() |> Igniter.assign(:test_mode?, true) |> Igniter.assign(:test_files, add_mix_new(opts)) @@ -40,6 +50,45 @@ defmodule Igniter.Test do |> apply_igniter!() end + @doc """ + Sets up a test Igniter that mimics a new Phoenix project. + + ## Starting point + + All of the files of an empty mix project are added by default. + You can specify more or overwrite the default files by passing a map of + file paths to their contents. + + ## Limitations + + You cannot install new dependencies, or use dependencies your own project does not have. + If you need to do that kind of thing, you will have to do a test that uses tools like + `System.cmd` in a temporary directory. + + ## Options + + * `files` - A map of file paths to file contents. The file paths should be relative to the project root. + * `app_name` - The name of the application. Defaults to `:test`. + + ## Examples + + phoenix_project(files: %{ + "lib/foo.ex" => \"\"\" + defmodule MyApp.Foo do + use Ash.Resource + end + \"\"\" + }) + """ + @spec phoenix_project(opts :: Keyword.t()) :: Igniter.t() + def phoenix_project(opts \\ []) do + Igniter.new() + |> Igniter.assign(:test_mode?, true) + |> Igniter.assign(:test_files, add_phoenix_new(opts)) + |> Igniter.Project.IgniterConfig.setup() + |> apply_igniter!() + end + @doc """ IO.puts the current igniter diff, and returns the igniter @@ -344,128 +393,226 @@ defmodule Igniter.Test do end) end + # https://github.com/elixir-lang/elixir/blob/51289220afe991d252a65e828d85184193b52ad3/lib/mix/lib/mix/tasks/new.ex + # TODO: opts[:umbrella] + # TODO: opts[:sup] defp add_mix_new(opts) do app_name = opts[:app_name] || :test - module_name = Module.concat([Macro.camelize(to_string(app_name))]) + mod = app_name |> to_string() |> Macro.camelize() + mod_filename = Macro.underscore(mod) + + assigns = [ + app: app_name, + mod: mod, + sup_app: "", + version: get_version(System.version()) + ] opts[:files] |> Kernel.||(%{}) - |> Map.put_new("test/test_helper.exs", "ExUnit.start()") - |> Map.put_new("test/#{app_name}_test.exs", """ - defmodule #{module_name}Test do - use ExUnit.Case - doctest #{module_name} - - test "greets the world" do - assert #{module_name}.hello() == :world - end - end - """) - |> Map.put_new("lib/#{app_name}.ex", """ - defmodule #{module_name} do - @moduledoc \"\"\" - Documentation for `#{module_name}`. - \"\"\" - - @doc \"\"\" - Hello world. - - ## Examples - - iex> #{module_name}.hello() - :world - - \"\"\" - def hello do - :world - end - end - """) - |> Map.put_new("README.md", """ - # #{module_name} + |> put_file( + "README.md", + """ + # <%= @mod %> **TODO: Add description** - + <%= if @app do %> ## Installation If [available in Hex](https://hex.pm/docs/publish), the package can be installed - by adding `thing` to your list of dependencies in `mix.exs`: + by adding `<%= @app %>` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:#{app_name}, "~> 0.1.0"} + {:<%= @app %>, "~> 0.1.0"} ] end ``` Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can - be found at . - """) - |> Map.put_new(".formatter.exs", """ - # Used by "mix format" - [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] - ] - """) - |> Map.put_new(".gitignore", """ - # The directory Mix will write compiled artifacts to. - /_build/ + be found at >. + <% end %> + """, + assigns + ) + |> put_file( + ".formatter.exs", + """ + # Used by "mix format" + [ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] + ] + """, + assigns + ) + |> put_file( + ".gitignore", + """ + # The directory Mix will write compiled artifacts to. + /_build/ + + # If you run "mix test --cover", coverage assets end up here. + /cover/ + + # The directory Mix downloads your dependencies sources to. + /deps/ + + # Where third-party dependencies like ExDoc output generated docs. + /doc/ + + # If the VM crashes, it generates a dump, let's ignore it too. + erl_crash.dump + + # Also ignore archive artifacts (built via "mix archive.build"). + *.ez + <%= if @app do %> + # Ignore package tarball (built via "mix hex.build"). + <%= @app %>-*.tar + <% end %> + # Temporary files, for example, from tests. + /tmp/ + """, + assigns + ) + |> put_file( + "mix.exs", + """ + defmodule <%= @mod %>.MixProject do + use Mix.Project + + def project do + [ + app: :<%= @app %>, + version: "0.1.0", + elixir: "~> <%= @version %>", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end - # If you run "mix test --cover", coverage assets end up here. - /cover/ + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger]<%= @sup_app %> + ] + end - # The directory Mix downloads your dependencies sources to. - /deps/ + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end + end + """, + assigns + ) + |> put_file( + "lib/#{mod_filename}.ex", + """ + defmodule <%= @mod %> do + @moduledoc \""" + Documentation for `<%= @mod %>`. + \""" - # Where third-party dependencies like ExDoc output generated docs. - /doc/ + @doc \""" + Hello world. - # Ignore .fetch files in case you like to edit your project deps locally. - /.fetch + ## Examples - # If the VM crashes, it generates a dump, let's ignore it too. - erl_crash.dump + iex> <%= @mod %>.hello() + :world - # Also ignore archive artifacts (built via "mix archive.build"). - *.ez + \""" + def hello do + :world + end + end + """, + assigns + ) + |> put_file( + "test/test_helper.exs", + """ + ExUnit.start() + """, + assigns + ) + |> put_file( + "test/#{mod_filename}_test.exs", + """ + defmodule <%= @mod %>Test do + use ExUnit.Case + doctest <%= @mod %> - # Ignore package tarball (built via "mix hex.build"). - #{app_name}-*.tar + test "greets the world" do + assert <%= @mod %>.hello() == :world + end + end + """, + assigns + ) + end - # Temporary files, for example, from tests. - /tmp/ - """) - |> Map.put_new("mix.exs", """ - defmodule #{module_name}.MixProject do - use Mix.Project + # https://github.com/phoenixframework/phoenix/tree/ed1331ea71bf458cc7909bca023564905a44fa90/installer + # TODO: opts[:umbrella] + # TODO: maybe support other options such as --module, --no-ecto, etc + defp add_phoenix_new(opts) do + # FIXME: base path + app_name = to_string(opts[:app_name] || :test) + source_base_path = Path.expand("../../deps/phx_new/templates", __DIR__) + + project = + app_name + |> Phx.New.Project.new([]) + |> Phx.New.Single.prepare_project() + |> Phx.New.Generator.put_binding() + + templates = + for {_, _, files} <- Phx.New.Single.template_files(:new), + {source, target} <- files, + source = to_string(source) do + {Path.expand(source, source_base_path), expand_path_with_bindings(target, project)} + end - def project do - [ - app: :#{app_name}, - version: "0.1.0", - elixir: "~> 1.17", - start_permanent: Mix.env() == :prod, - deps: deps() - ] + Enum.reduce(templates, opts[:files] || %{}, fn {source, target}, files -> + if File.dir?(source) do + files + else + put_file(files, target, File.read!(source), project.binding) end + end) + end - # Run "mix help compile.app" to learn about applications. - def application do - [ - extra_applications: [:logger] - ] + defp put_file(files, path, contents, assigns) do + contents = + if Igniter.source_handler(path) == Rewrite.Source.Ex do + contents + |> EEx.eval_string(assigns: assigns) + |> Rewrite.Source.Ex.format() + else + EEx.eval_string(contents, assigns: assigns) end - # Run "mix help deps" to learn about dependencies. - defp deps do - [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} - ] + Map.put_new(files, path, contents) + end + + defp get_version(version) do + {:ok, version} = Version.parse(version) + + "#{version.major}.#{version.minor}" <> + case version.pre do + [h | _] -> "-#{h}" + [] -> "" end - end - """) + end + + defp expand_path_with_bindings(path, %Phx.New.Project{} = project) do + Regex.replace(Regex.recompile!(~r/:[a-zA-Z0-9_]+/), path, fn ":" <> key, _ -> + project |> Map.fetch!(:"#{key}") |> to_string() + end) end end diff --git a/mix.exs b/mix.exs index c89cd67..9de34fe 100644 --- a/mix.exs +++ b/mix.exs @@ -103,7 +103,8 @@ defmodule Igniter.MixProject do {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}, {:benchee, "~> 1.1", only: [:dev, :test]}, - {:doctor, "~> 0.21", only: [:dev, :test]} + {:doctor, "~> 0.21", only: [:dev, :test]}, + {:phx_new, "~> 1.7", only: :test, runtime: false} ] end diff --git a/mix.lock b/mix.lock index 82c840e..b7e4404 100644 --- a/mix.lock +++ b/mix.lock @@ -24,6 +24,7 @@ "mix_audit": {:hex, :mix_audit, "2.1.4", "0a23d5b07350cdd69001c13882a4f5fb9f90fbd4cbf2ebc190a2ee0d187ea3e9", [:make, :mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "fd807653cc8c1cada2911129c7eb9e985e3cc76ebf26f4dd628bb25bbcaa7099"}, "mix_test_watch": {:hex, :mix_test_watch, "1.2.0", "1f9acd9e1104f62f280e30fc2243ae5e6d8ddc2f7f4dc9bceb454b9a41c82b42", [:mix], [{:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "278dc955c20b3fb9a3168b5c2493c2e5cffad133548d307e0a50c7f2cfbf34f6"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, + "phx_new": {:hex, :phx_new, "1.7.14", "30d2d38b78bb762452595fe2e32f3a4a838f26e87713024840059884204ff141", [:mix], [], "hexpm", "e1a8b3839a9a2d94bceb95d96ca1f175264c751405fee8f39a4e67b379314a39"}, "rewrite": {:hex, :rewrite, "0.10.5", "6afadeae0b9d843b27ac6225e88e165884875e0aed333ef4ad3bf36f9c101bed", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "51cc347a4269ad3a1e7a2c4122dbac9198302b082f5615964358b4635ebf3d4f"}, "sourceror": {:hex, :sourceror, "1.6.0", "9907884e1449a4bd7dbaabe95088ed4d9a09c3c791fb0103964e6316bc9448a7", [:mix], [], "hexpm", "e90aef8c82dacf32c89c8ef83d1416fc343cd3e5556773eeffd2c1e3f991f699"}, "spitfire": {:hex, :spitfire, "0.1.3", "7ea0f544005dfbe48e615ed90250c9a271bfe126914012023fd5e4b6b82b7ec7", [:mix], [], "hexpm", "d53b5107bcff526a05c5bb54c95e77b36834550affd5830c9f58760e8c543657"}, diff --git a/test/igniter/test_test.exs b/test/igniter/test_test.exs new file mode 100644 index 0000000..054f51e --- /dev/null +++ b/test/igniter/test_test.exs @@ -0,0 +1,43 @@ +defmodule Igniter.TestTest do + use ExUnit.Case, async: true + + import Igniter.Test + + test "mix_project" do + assert Map.keys(mix_project().assigns.test_files) |> Enum.sort() == [ + ".formatter.exs", + ".gitignore", + ".igniter.exs", + "README.md", + "lib/test.ex", + "mix.exs", + "test/test_helper.exs", + "test/test_test.exs" + ] + end + + test "phoenix_project(" do + assert Map.keys(phoenix_project().assigns.test_files) |> Enum.sort() == [ + ".formatter.exs", + ".gitignore", + ".igniter.exs", + "README.md", + "config/config.exs", + "config/dev.exs", + "config/prod.exs", + "config/runtime.exs", + "config/test.exs", + "lib/test.ex", + "lib/test/application.ex", + "lib/test_web.ex", + "lib/test_web/controllers/error_json.ex", + "lib/test_web/endpoint.ex", + "lib/test_web/router.ex", + "lib/test_web/telemetry.ex", + "mix.exs", + "test/support/conn_case.ex", + "test/test_helper.exs", + "test/test_web/controllers/error_json_test.exs" + ] + end +end From 51760a52a962cc82e0457fe2733192867197e21b Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 13:04:44 -0400 Subject: [PATCH 2/7] test_project() -> mix_project() --- test/igniter/code/module_test.exs | 2 +- test/igniter/extensions/phoenix_test.exs | 10 ++--- test/igniter/libs/ecto_test.exs | 10 ++--- test/igniter/libs/phoenix_test.exs | 2 +- test/igniter/project/application_test.exs | 20 ++++----- test/igniter/project/config_test.exs | 46 ++++++++++---------- test/igniter/project/deps_test.exs | 4 +- test/igniter/project/igniter_config_test.exs | 6 +-- test/igniter/project/task_aliases_test.exs | 8 ++-- test/igniter/refactors/elixir_test.exs | 2 +- test/igniter/refactors/renames_test.exs | 14 +++--- test/igniter_test.exs | 4 +- test/mix/tasks/igniter.gen.task_test.exs | 4 +- 13 files changed, 66 insertions(+), 66 deletions(-) diff --git a/test/igniter/code/module_test.exs b/test/igniter/code/module_test.exs index 3e25c58..33b4a3c 100644 --- a/test/igniter/code/module_test.exs +++ b/test/igniter/code/module_test.exs @@ -123,7 +123,7 @@ defmodule Igniter.Code.ModuleTest do describe inspect(&Igniter.Code.Module.find_all_matching_modules/1) do test "finds all elixir files but ignores all other files" do igniter = - test_project() + mix_project() |> Igniter.Project.Module.create_module(Foo, """ defmodule Foo do end diff --git a/test/igniter/extensions/phoenix_test.exs b/test/igniter/extensions/phoenix_test.exs index 17d2e96..469c355 100644 --- a/test/igniter/extensions/phoenix_test.exs +++ b/test/igniter/extensions/phoenix_test.exs @@ -4,7 +4,7 @@ defmodule Igniter.Extensions.PhoenixTest do describe "proper_location/2" do test "extensions are honored even if the extension is added in the same check" do - test_project() + mix_project() |> Igniter.Project.IgniterConfig.add_extension(Igniter.Extensions.Phoenix) |> Igniter.Project.Module.create_module(TestWeb.FooController, """ use TestWeb, :controller @@ -14,7 +14,7 @@ defmodule Igniter.Extensions.PhoenixTest do test "returns a controller location" do igniter = - test_project() + mix_project() |> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """ defmodule TestWeb.FooController do use TestWeb, :controller @@ -28,7 +28,7 @@ defmodule Igniter.Extensions.PhoenixTest do test "when belonging to a controller, it returns an html location" do igniter = - test_project() + mix_project() |> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """ defmodule TestWeb.FooController do use TestWeb, :controller @@ -47,7 +47,7 @@ defmodule Igniter.Extensions.PhoenixTest do test "when not belonging to a controller, we say we don't know where it goes" do igniter = - test_project() + mix_project() |> Igniter.create_new_file("lib/test_web/controllers/foo_html.ex", """ defmodule TestWeb.FooHTML do use TestWeb, :html @@ -60,7 +60,7 @@ defmodule Igniter.Extensions.PhoenixTest do test "returns a json location" do igniter = - test_project() + mix_project() |> Igniter.create_new_file("test_web/controllers/foo_controller.ex", """ defmodule TestWeb.FooController do use TestWeb, :controller diff --git a/test/igniter/libs/ecto_test.exs b/test/igniter/libs/ecto_test.exs index d03c0bf..8c0abf2 100644 --- a/test/igniter/libs/ecto_test.exs +++ b/test/igniter/libs/ecto_test.exs @@ -15,7 +15,7 @@ defmodule Igniter.Libs.EctoTest do describe "list_repos" do test "returns the list of repos" do {_igniter, repos} = - test_project() + mix_project() |> Igniter.Project.Module.create_module(Example.Repo, "use Ecto.Repo") |> Igniter.Project.Module.create_module(Example.Repo2, "use AshPostgres.Repo") |> Igniter.Libs.Ecto.list_repos() @@ -29,7 +29,7 @@ defmodule Igniter.Libs.EctoTest do send(self(), {:mix_shell_input, :prompt, "0"}) assert {_igniter, Example.Repo} = - test_project() + mix_project() |> Igniter.Project.Module.create_module(Example.Repo, "use Ecto.Repo") |> Igniter.Project.Module.create_module(Example.Repo2, "use AshPostgres.Repo") |> Igniter.Libs.Ecto.select_repo(label: "Which repo would you like to use?") @@ -38,7 +38,7 @@ defmodule Igniter.Libs.EctoTest do describe "gen_migration" do test "it generates a migration file" do - test_project() + mix_project() |> Igniter.Libs.Ecto.gen_migration(Example.Repo, "create_users", body: """ def up do @@ -67,7 +67,7 @@ defmodule Igniter.Libs.EctoTest do end test "it increments duplicates" do - test_project() + mix_project() |> Igniter.Libs.Ecto.gen_migration(Example.Repo, "create_users", body: """ def up do @@ -110,7 +110,7 @@ defmodule Igniter.Libs.EctoTest do end test "it overwrites existing file" do - test_project() + mix_project() |> Igniter.Libs.Ecto.gen_migration(Example.Repo, "create_users", body: """ def up, do: "up old" diff --git a/test/igniter/libs/phoenix_test.exs b/test/igniter/libs/phoenix_test.exs index 3b5bebc..b373975 100644 --- a/test/igniter/libs/phoenix_test.exs +++ b/test/igniter/libs/phoenix_test.exs @@ -5,7 +5,7 @@ defmodule Igniter.Libs.PhoenixTest do describe "controller?/2" do test "detects a phoenix controller" do igniter = - assert test_project() + assert mix_project() |> Igniter.create_new_file("lib/test_web/controllers/foo_controller.ex", """ defmodule TestWeb.FooController do use TestWeb, :controller diff --git a/test/igniter/project/application_test.exs b/test/igniter/project/application_test.exs index acbb2d1..79d297b 100644 --- a/test/igniter/project/application_test.exs +++ b/test/igniter/project/application_test.exs @@ -4,7 +4,7 @@ defmodule Igniter.Project.ApplicationTest do describe "add_new_child/1" do test "adds an application if one doesn't exist" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child(Foo) |> assert_creates("lib/test/application.ex", """ defmodule Test.Application do @@ -29,7 +29,7 @@ defmodule Igniter.Project.ApplicationTest do end test "doesnt add a module if its already supervised" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child(Foo) |> apply_igniter!() |> Igniter.Project.Application.add_new_child(Foo) @@ -37,7 +37,7 @@ defmodule Igniter.Project.ApplicationTest do end test "doesnt add a module if its already supervised as a tuple" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child({Foo, a: 1}) |> apply_igniter!() |> Igniter.Project.Application.add_new_child(Foo) @@ -45,7 +45,7 @@ defmodule Igniter.Project.ApplicationTest do end test "doesnt add a module if its already supervised as an atom and we're adding a tuple" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child(Foo) |> apply_igniter!() |> Igniter.Project.Application.add_new_child({Foo, a: 1}) @@ -53,7 +53,7 @@ defmodule Igniter.Project.ApplicationTest do end test "supports taking options as the second argument" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child({Foo, a: :b}) |> assert_creates("lib/test/application.ex", """ defmodule Test.Application do @@ -78,7 +78,7 @@ defmodule Igniter.Project.ApplicationTest do end test "supports updating options using `opts_updater`" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child({Foo, a: :b}) |> apply_igniter!() |> Igniter.Project.Application.add_new_child(Foo, @@ -93,7 +93,7 @@ defmodule Igniter.Project.ApplicationTest do end test "will set opts to an empty list if using `opts_updater`" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child(Foo) |> apply_igniter!() |> Igniter.Project.Application.add_new_child(Foo, @@ -108,7 +108,7 @@ defmodule Igniter.Project.ApplicationTest do end test "using `after: fn _ -> true end` with tuples in the list" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child({Foo, a: :b}) |> Igniter.Project.Application.add_new_child(Something) |> Igniter.Project.Application.add_new_child(SomethingAtTheEnd, after: fn _ -> true end) @@ -130,7 +130,7 @@ defmodule Igniter.Project.ApplicationTest do end test "supports taking code as the second argument" do - test_project() + mix_project() |> Igniter.Project.Application.add_new_child( {Foo, {:code, @@ -163,7 +163,7 @@ defmodule Igniter.Project.ApplicationTest do test "supports expressing " do :erlang.system_flag(:backtrace_depth, 1000) - test_project() + mix_project() |> Igniter.Project.Application.add_new_child(Foo) |> apply_igniter!() |> Igniter.Project.Application.add_new_child(Bar) diff --git a/test/igniter/project/config_test.exs b/test/igniter/project/config_test.exs index 0ef5134..4b010d9 100644 --- a/test/igniter/project/config_test.exs +++ b/test/igniter/project/config_test.exs @@ -5,7 +5,7 @@ defmodule Igniter.Project.ConfigTest do describe "configure/6" do test "it creates the config file if it does not exist" do - test_project() + mix_project() |> Igniter.Project.Config.configure("fake.exs", :fake, [:foo, :bar], "baz") |> assert_creates("config/fake.exs", """ import Config @@ -14,7 +14,7 @@ defmodule Igniter.Project.ConfigTest do end test "it merges with 2 arg version of existing config" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -30,7 +30,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "it sets the spark formatter plugins" do - test_project() + mix_project() |> Igniter.Project.Config.configure( "fake.exs", :spark, @@ -53,7 +53,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "it merges the spark formatter plugins" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config config :spark, formatter: ["Ash.Resource": []] @@ -78,7 +78,7 @@ defmodule Igniter.Project.ConfigTest do end test "it merges with 2 arg version of existing config with a single path item" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -96,7 +96,7 @@ defmodule Igniter.Project.ConfigTest do end test "it chooses the 3 arg version when first item in path is not pretty" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config """) @@ -108,7 +108,7 @@ defmodule Igniter.Project.ConfigTest do end test "it doesn't add non-pretty keys to existing config" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config config :fake, foo: 10 @@ -121,7 +121,7 @@ defmodule Igniter.Project.ConfigTest do end test "it chooses the 3 arg version when first item in path is not pretty, and merges that way" do - test_project() + mix_project() |> Igniter.Project.Config.configure("fake.exs", :fake, [Foo.Bar, :bar], "baz") |> apply_igniter!() |> Igniter.Project.Config.configure("fake.exs", :fake, [Foo.Bar, :buz], "biz") @@ -132,7 +132,7 @@ defmodule Igniter.Project.ConfigTest do end test "it merges with 3 arg version of existing config" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -148,7 +148,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "it merges with 3 arg version of existing config with the config set to []" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -169,7 +169,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "it merges with 3 arg version of existing config with the config set to [] and the path is one level deeper than existing" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -190,7 +190,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "it merges with 2 arg version of existing config with the config set to [] and the path is one level deeper than existing" do - test_project() + mix_project() |> Igniter.create_new_file( "config/fake.exs", """ @@ -220,7 +220,7 @@ defmodule Igniter.Project.ConfigTest do end test "it merges with 3 arg version of existing config with a single path item" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -235,7 +235,7 @@ defmodule Igniter.Project.ConfigTest do end test "present values can be updated" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -254,7 +254,7 @@ defmodule Igniter.Project.ConfigTest do end test "we merge configs even in large config files" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ # this is too @@ -283,7 +283,7 @@ defmodule Igniter.Project.ConfigTest do end test "integers can be used as values" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -299,7 +299,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "arbitrary data structures can be used as values" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config config :level1, :level2, level3: [{"hello", "world"}] @@ -321,7 +321,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "quoted code can be used as values" do - test_project() + mix_project() |> Igniter.create_new_file( "config/fake.exs", """ @@ -363,7 +363,7 @@ defmodule Igniter.Project.ConfigTest do end test "present values can be updated by updating map keys" do - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -398,7 +398,7 @@ defmodule Igniter.Project.ConfigTest do """ ] }} = - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ config :fake, foo: %{"a" => ["a", "b"]} """) @@ -417,7 +417,7 @@ defmodule Igniter.Project.ConfigTest do @tag :regression test "works with conditional import present in config file" do # this test just asserts no error is raised doing this - test_project() + mix_project() |> Igniter.create_new_file("config/config.exs", """ import Config config :foo, :bar, 10 @@ -433,7 +433,7 @@ defmodule Igniter.Project.ConfigTest do test "configures_root_key?/3" do igniter = - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config @@ -452,7 +452,7 @@ defmodule Igniter.Project.ConfigTest do setup do %{ igniter: - test_project() + mix_project() |> Igniter.create_new_file("config/fake.exs", """ import Config diff --git a/test/igniter/project/deps_test.exs b/test/igniter/project/deps_test.exs index f6c8808..7ca003f 100644 --- a/test/igniter/project/deps_test.exs +++ b/test/igniter/project/deps_test.exs @@ -14,7 +14,7 @@ defmodule Igniter.Project.DepsTest do end test "adds the provided dependency in a tuple format" do - test_project() + mix_project() |> Igniter.Project.Deps.add_dep({:foobar, "~> 2.0"}) |> assert_has_patch("mix.exs", "+ | {:foobar, \"~> 2.0\"}") |> Igniter.Project.Deps.add_dep({:barfoo, "~> 1.0"}) @@ -33,7 +33,7 @@ defmodule Igniter.Project.DepsTest do describe "set_dep_option" do test "sets the option when no options exist" do - test_project() + mix_project() |> Igniter.Project.Deps.add_dep({:foobar, "~> 2.0"}) |> apply_igniter!() |> Igniter.Project.Deps.set_dep_option(:foobar, :only, :test) diff --git a/test/igniter/project/igniter_config_test.exs b/test/igniter/project/igniter_config_test.exs index 77b3fa4..a45c411 100644 --- a/test/igniter/project/igniter_config_test.exs +++ b/test/igniter/project/igniter_config_test.exs @@ -4,7 +4,7 @@ defmodule Igniter.Project.IgniterConfigTest do describe "add_extension/2" do test "adds an extension to the list" do - test_project() + mix_project() |> Igniter.Project.IgniterConfig.add_extension(Foobar) |> assert_has_patch(".igniter.exs", """ 9 - | extensions: [] @@ -15,7 +15,7 @@ defmodule Igniter.Project.IgniterConfigTest do describe "dont_move_file_pattern/2" do test "adds a pattern to the list" do - test_project() + mix_project() |> Igniter.Project.IgniterConfig.dont_move_file_pattern(~r"abc") |> assert_has_patch(".igniter.exs", """ 8 - | dont_move_files: [~r"lib/mix"], @@ -24,7 +24,7 @@ defmodule Igniter.Project.IgniterConfigTest do end test "doesnt add a duplicate pattern to the list" do - test_project() + mix_project() |> Igniter.Project.IgniterConfig.dont_move_file_pattern(~r"lib/mix") |> assert_unchanged(".igniter.exs") end diff --git a/test/igniter/project/task_aliases_test.exs b/test/igniter/project/task_aliases_test.exs index 2467064..60d7a64 100644 --- a/test/igniter/project/task_aliases_test.exs +++ b/test/igniter/project/task_aliases_test.exs @@ -4,7 +4,7 @@ defmodule Igniter.Project.TaskAliasesTest do describe "add_alias/3-4" do test "adds a task alias to the `mix.exs` file" do - test_project() + mix_project() |> Igniter.Project.TaskAliases.add_alias("test", "test --special") |> assert_has_patch("mix.exs", """ 10 + | deps: deps(), @@ -18,7 +18,7 @@ defmodule Igniter.Project.TaskAliasesTest do end test "by default, it ignores existing aliases" do - test_project() + mix_project() |> Igniter.Project.TaskAliases.add_alias("test", "test --special") |> apply_igniter!() |> Igniter.Project.TaskAliases.add_alias("test", "my_thing.setup_tests") @@ -26,7 +26,7 @@ defmodule Igniter.Project.TaskAliasesTest do end test "the alter option can be used to modify existing aliases" do - test_project() + mix_project() |> Igniter.Project.TaskAliases.add_alias("test", "test --special") |> apply_igniter!() |> Igniter.Project.TaskAliases.add_alias("test", "my_thing.setup_tests", @@ -39,7 +39,7 @@ defmodule Igniter.Project.TaskAliasesTest do end test "the alter option won't add steps that are already present" do - test_project() + mix_project() |> Igniter.Project.TaskAliases.add_alias("test", ["my_thing.setup_tests", "test --special"]) |> apply_igniter!() |> Igniter.Project.TaskAliases.add_alias("test", "my_thing.setup_tests", diff --git a/test/igniter/refactors/elixir_test.exs b/test/igniter/refactors/elixir_test.exs index b42c0cb..4f42d5f 100644 --- a/test/igniter/refactors/elixir_test.exs +++ b/test/igniter/refactors/elixir_test.exs @@ -70,7 +70,7 @@ defmodule Igniter.Refactors.ElixirTest do end defp assert_format(code, expectation) do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do #{code} diff --git a/test/igniter/refactors/renames_test.exs b/test/igniter/refactors/renames_test.exs index 6d21c57..fa06027 100644 --- a/test/igniter/refactors/renames_test.exs +++ b/test/igniter/refactors/renames_test.exs @@ -7,7 +7,7 @@ defmodule Igniter.Refactors.RenameTest do end test "performs a simple rename on zero arity functions" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do SomeModule.some_function() @@ -26,7 +26,7 @@ defmodule Igniter.Refactors.RenameTest do end test "performs a simple rename on two arity functions" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do SomeModule.some_function(1, 2) @@ -45,7 +45,7 @@ defmodule Igniter.Refactors.RenameTest do end test "performs a simple rename on piped module call functions" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do 1 @@ -65,7 +65,7 @@ defmodule Igniter.Refactors.RenameTest do end test "can detect aliases" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do alias SomeModule, as: SomethingElse @@ -86,7 +86,7 @@ defmodule Igniter.Refactors.RenameTest do end test "can detect imports" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do import SomeModule @@ -107,7 +107,7 @@ defmodule Igniter.Refactors.RenameTest do end test "will rewrite the definitions within the module" do - test_project() + mix_project() |> Igniter.create_new_file("lib/some_module.ex", """ defmodule SomeModule do def some_function(a, b), do: a + b @@ -137,7 +137,7 @@ defmodule Igniter.Refactors.RenameTest do end test "will rewrite function captures" do - test_project() + mix_project() |> Igniter.create_new_file("lib/example.ex", """ defmodule Example do import SomeModule diff --git a/test/igniter_test.exs b/test/igniter_test.exs index 536c985..5d9ab72 100644 --- a/test/igniter_test.exs +++ b/test/igniter_test.exs @@ -6,7 +6,7 @@ defmodule IgniterTest do describe "Igniter.copy_template/4" do test "it evaluates and writes the template" do - test_project() + mix_project() |> Igniter.copy_template("test/templates/template.css.eex", "lib/foobar.css", class: "hello" ) @@ -18,7 +18,7 @@ defmodule IgniterTest do end test "it overwrites an existing file" do - test_project() + mix_project() |> Igniter.copy_template("test/templates/template.css.eex", "lib/foobar.css", class: "hello" ) diff --git a/test/mix/tasks/igniter.gen.task_test.exs b/test/mix/tasks/igniter.gen.task_test.exs index 5d6e9d0..2991adb 100644 --- a/test/mix/tasks/igniter.gen.task_test.exs +++ b/test/mix/tasks/igniter.gen.task_test.exs @@ -4,7 +4,7 @@ defmodule Mix.Tasks.Igniter.Gen.TaskTest do describe "igniter.gen.task" do test "generates a mix task" do - test_project() + mix_project() |> Igniter.compose_task("igniter.gen.task", ["foo.bar"]) |> assert_creates( "lib/mix/tasks/foo.bar.ex", @@ -74,7 +74,7 @@ defmodule Mix.Tasks.Igniter.Gen.TaskTest do end test "generates a mix task that switches on igniter being compiled with `--optional`" do - test_project() + mix_project() |> Igniter.compose_task("igniter.gen.task", ["foo.bar", "--optional"]) |> assert_creates( "lib/mix/tasks/foo.bar.ex", From 250c820139c754d674db8892e3d980d0002248ce Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 13:07:11 -0400 Subject: [PATCH 3/7] Add :phx_new in :dev to respect CI rules --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 9de34fe..18ea55e 100644 --- a/mix.exs +++ b/mix.exs @@ -104,7 +104,7 @@ defmodule Igniter.MixProject do {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:doctor, "~> 0.21", only: [:dev, :test]}, - {:phx_new, "~> 1.7", only: :test, runtime: false} + {:phx_new, "~> 1.7", only: [:dev, :test], runtime: false} ] end From e92bfec6d70a52981ce51430c5dc55369ae094f6 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 13:16:43 -0400 Subject: [PATCH 4/7] Remove reference to Phx.New.Project struct --- lib/igniter/test.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/igniter/test.ex b/lib/igniter/test.ex index 95c0cd8..6f853a5 100644 --- a/lib/igniter/test.ex +++ b/lib/igniter/test.ex @@ -610,7 +610,7 @@ defmodule Igniter.Test do end end - defp expand_path_with_bindings(path, %Phx.New.Project{} = project) do + defp expand_path_with_bindings(path, project) do Regex.replace(Regex.recompile!(~r/:[a-zA-Z0-9_]+/), path, fn ":" <> key, _ -> project |> Map.fetch!(:"#{key}") |> to_string() end) From e6d9f3b75850f4f13f81f5460554baa52840b33f Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 14:06:22 -0400 Subject: [PATCH 5/7] Start :phx_new --- lib/igniter/test.ex | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/igniter/test.ex b/lib/igniter/test.ex index 6f853a5..d7a9582 100644 --- a/lib/igniter/test.ex +++ b/lib/igniter/test.ex @@ -610,7 +610,7 @@ defmodule Igniter.Test do end end - defp expand_path_with_bindings(path, project) do + defp expand_path_with_bindings(path, %Phx.New.Project{} = project) do Regex.replace(Regex.recompile!(~r/:[a-zA-Z0-9_]+/), path, fn ":" <> key, _ -> project |> Map.fetch!(:"#{key}") |> to_string() end) diff --git a/mix.exs b/mix.exs index 18ea55e..437bc88 100644 --- a/mix.exs +++ b/mix.exs @@ -104,7 +104,7 @@ defmodule Igniter.MixProject do {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:doctor, "~> 0.21", only: [:dev, :test]}, - {:phx_new, "~> 1.7", only: [:dev, :test], runtime: false} + {:phx_new, "~> 1.7", only: [:dev, :test]} ] end From 045f520a55a0a1a7dd5bcd25084ed7b7507183cf Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 14:46:10 -0400 Subject: [PATCH 6/7] Make phx_new optional --- lib/igniter/test.ex | 20 +++++++++++++------- mix.exs | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/igniter/test.ex b/lib/igniter/test.ex index d7a9582..6f98785 100644 --- a/lib/igniter/test.ex +++ b/lib/igniter/test.ex @@ -81,12 +81,18 @@ defmodule Igniter.Test do }) """ @spec phoenix_project(opts :: Keyword.t()) :: Igniter.t() - def phoenix_project(opts \\ []) do - Igniter.new() - |> Igniter.assign(:test_mode?, true) - |> Igniter.assign(:test_files, add_phoenix_new(opts)) - |> Igniter.Project.IgniterConfig.setup() - |> apply_igniter!() + if Code.ensure_loaded(Phx.New) do + def phoenix_project(opts \\ []) do + Igniter.new() + |> Igniter.assign(:test_mode?, true) + |> Igniter.assign(:test_files, add_phoenix_new(opts)) + |> Igniter.Project.IgniterConfig.setup() + |> apply_igniter!() + end + else + def phoenix_project(_opts \\ []) do + raise "You must include the `phx_new` dependency to use `#{inspect(__MODULE__)}.phoenix_project/1`" + end end @doc """ @@ -610,7 +616,7 @@ defmodule Igniter.Test do end end - defp expand_path_with_bindings(path, %Phx.New.Project{} = project) do + defp expand_path_with_bindings(path, %Phx.New.Project{} = project) do Regex.replace(Regex.recompile!(~r/:[a-zA-Z0-9_]+/), path, fn ":" <> key, _ -> project |> Map.fetch!(:"#{key}") |> to_string() end) diff --git a/mix.exs b/mix.exs index 437bc88..b68ddf0 100644 --- a/mix.exs +++ b/mix.exs @@ -92,6 +92,7 @@ defmodule Igniter.MixProject do {:spitfire, "~> 0.1 and >= 0.1.3"}, {:sourceror, "~> 1.4"}, {:jason, "~> 1.4"}, + {:phx_new, "~> 1.7", optional: true}, # Dev/Test dependencies {:eflame, "~> 1.0", only: [:dev, :test]}, {:ex_doc, "~> 0.32", only: [:dev, :test], runtime: false}, @@ -104,7 +105,6 @@ defmodule Igniter.MixProject do {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}, {:benchee, "~> 1.1", only: [:dev, :test]}, {:doctor, "~> 0.21", only: [:dev, :test]}, - {:phx_new, "~> 1.7", only: [:dev, :test]} ] end From 8f5f45dae73e04196838d79d2bd9f9f3fcfc9683 Mon Sep 17 00:00:00 2001 From: Leandro Pereira Date: Fri, 25 Oct 2024 14:46:30 -0400 Subject: [PATCH 7/7] mix format --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index b68ddf0..eec6613 100644 --- a/mix.exs +++ b/mix.exs @@ -104,7 +104,7 @@ defmodule Igniter.MixProject do {:mix_audit, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:mix_test_watch, "~> 1.0", only: [:dev, :test], runtime: false}, {:benchee, "~> 1.1", only: [:dev, :test]}, - {:doctor, "~> 0.21", only: [:dev, :test]}, + {:doctor, "~> 0.21", only: [:dev, :test]} ] end