defmodule Mix.Tasks.PhxTest.New do
@readme "README.md"
|> File.read!()
|> String.split("<!-- ModuleDoc -->")
|> Enum.at(1)
@moduledoc """
Creates and embeds a new Phoenix project for your dev enviroment.
#{@readme}
"""
use Mix.Task
alias Mix.{PhxTest.Context, Tasks.Phx}
@impl true
def run(argv) do
Application.ensure_all_started(:phx_test)
validate_phx_new!()
raise_if_umbrella!(argv)
{context, argv} = parse_opts(argv)
Phx.New.run(argv)
Mix.shell().info([:yellow, "***** From phx_test *****", :reset])
phx_path = "#{context.sub_directory}/#{context.app_name}/"
phx_config_path = "../#{phx_path}config/config.exs"
if File.exists?("config/config.exs") do
inject_into_existing_config(phx_config_path)
else
write_config_file(phx_config_path)
end
inject_test_requirements(context)
deps = inject_deps(context)
maybe_prompt_to_install_deps(context, deps)
if context.ecto?, do: ecto_message(phx_path)
end
defp inject_into_existing_config(phx_config_path) do
path = "config/config.exs"
Mix.shell().info([:green, "* injecting ", :reset, path])
path
|> File.read!()
|> Kernel.<>("\n")
|> Kernel.<>(import_config(phx_config_path))
|> then(&File.write!(path, &1))
end
defp write_config_file(phx_config_path) do
File.mkdir("config")
Mix.Generator.create_file("config/config.exs", """
# This file is responsible for configuring your application
# and its dependencies with the aid of the Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
# General application configuration
import Config
#{String.trim_trailing(import_config(phx_config_path), "\n")}
""")
end
defp import_config(phx_config_path) do
"""
# If you are developing a hex package, this config will not be included
# with your published library.
#
# If your config ships with your library or
# production application, you may want to move this line to the desired
# environment config file. e.g. config/dev.exs, config/test.exs
import_config "#{phx_config_path}"
"""
end
defp parse_opts(argv) do
case OptionParser.parse(argv, strict: [sub_directory: :string]) do
{[{:sub_directory, dir}], [path | _], _} ->
context = Context.new(dir, path, ecto?(argv), install?(argv))
argv = inject_sub_dir(dir, path, argv)
{context, argv}
{[], [_ | _], _} ->
parse_opts(argv ++ ["--sub-directory", "priv"])
{_, [], _} ->
parse_opts(["phx_test_app" | argv])
end
end
defp inject_sub_dir(dir, path, argv) do
argv = argv -- ["--sub-directory", dir, path]
["#{dir}/#{path}" | argv]
end
defp ecto?(argv) do
case OptionParser.parse(argv, strict: [ecto: :boolean]) do
{[ecto: false], _, _} -> false
_ -> true
end
end
defp install?(argv) do
case OptionParser.parse(argv, strict: [install: :boolean]) do
{[install: true], _, _} -> true
_ -> false
end
end
defp raise_if_umbrella!(argv) do
case OptionParser.parse(argv, strict: [umbrella: :boolean]) do
{[umbrella: true], _, _} -> raise_umbrella_error!()
_ -> :ok
end
end
defp raise_umbrella_error! do
Mix.raise("""
`mix phx_test.new` does not support the --umbrella option
""")
end
defp validate_phx_new! do
with {:module, Phx.New} <- Code.ensure_loaded(Phx.New),
true <- function_exported?(Phx.New, :run, 1),
:ok <- Application.ensure_started(:phx_new),
{:ok, chars} <- :application.get_key(:phx_new, :vsn),
phx_new_vsn = List.to_string(chars),
comp when comp in [:gt, :eq] <- Version.compare(phx_new_vsn, "1.6.0") do
:ok
else
_ -> raise_phx_new_dependency_error!()
end
end
defp raise_phx_new_dependency_error! do
Mix.raise("""
phx_test requires phx_new >= 1.6. Please take one of the following steps:
* Install the latest phx_new archive with `mix archive.install hex phx_new`
* Add `{:phx_new, "~> 1.6", only: :dev, runtime: false}` to your deps in mix.exs
""")
end
defp inject_deps(%{app_name: app_name, sub_directory: sub_directory}) do
path = "mix.exs"
deps =
" {:#{app_name}, path: \"./#{sub_directory}/#{app_name}\", only: [:test, :dev]}," <>
"\n {:phoenix_live_reload, \"~> 1.2\", only: :dev}," <>
"\n {:floki, \">= 0.30.0\", only: :test},\n"
a =
path
|> File.read!()
|> String.split("defp deps")
b =
a
|> Enum.at(1)
|> String.split("[\n")
c =
b
|> List.replace_at(1, deps <> Enum.at(b, 1))
|> Enum.join("[\n")
d =
a
|> List.replace_at(1, c)
|> Enum.join("defp deps")
Mix.shell().info([:green, "* injecting ", :reset, path])
File.write!(path, d)
deps
end
defp inject_test_requirements(context) do
%{app_name: app_name, sub_directory: sub_directory, ecto?: ecto?} = context
test_dir_path = "#{sub_directory}/#{app_name}/test"
comment = """
# Your Phoenix test app's test cases must be explicitly
# required by your test helper in order to use them in your
# root project's tests.
"""
injection =
comment
|> maybe_add_ecto_paths(test_dir_path, ecto?)
|> add_conn_case_path(test_dir_path)
path = "test/test_helper.exs"
test_helper_contents = File.read!(path)
Mix.shell().info([:green, "* injecting ", :reset, path])
File.write!(path, test_helper_contents <> injection)
end
defp maybe_add_ecto_paths(injection, test_dir_path, ecto?) do
if ecto? do
injection <>
"Code.require_file(\"#{test_dir_path}/test_helper.exs\")\n" <>
"Code.require_file(\"#{test_dir_path}/support/data_case.ex\")\n"
else
injection
end
end
defp add_conn_case_path(injection, test_dir_path) do
injection <> "Code.require_file(\"#{test_dir_path}/support/conn_case.ex\")\n"
end
defp maybe_prompt_to_install_deps(context, deps) do
install? =
context.install? or
Mix.shell().yes?(
"\nAdded new dev and test dependencies to your root project's mix.exs:\n\n" <>
String.trim_trailing(deps, ",\n") <>
"\n\nDo you want to install them now?"
)
if install? do
Mix.shell().info([:green, "* running", :reset, " mix deps.get"])
System.cmd("mix", ["deps.get"])
Mix.shell().info([:green, "* running", :reset, " mix deps.compile"])
System.cmd("mix", ["deps.compile"])
else
Mix.shell().info("\nRemember to install your dependencies later\n")
end
end
defp ecto_message(phx_path) do
Mix.shell().info("""
You must manually create your test database before running your tests:
$ cd #{phx_path}
$ MIX_ENV=test mix ecto.create
Remember to run any future database commands in the test environment. e.g.
$ mix ecto.migrate
$ MIX_ENV=test mix ecto.migrate
These will not be done for you automatically when running `mix test` from your root project directory.
""")
end
end