lib/test_iex.ex

defmodule TestIex do
  @moduledoc """
  Allows running test files by substring of their names
  """

  alias TestIex.Matcher
  alias TestIex.Log

  @doc """
  Run all matching test files

  Examples:

    iex> TestIex.run("user")
    iex> TestIex.run("user_test.exs")
    iex> TestIex.run("some_file:12")
  """
  def run(matcher \\ "") do
    result = Matcher.match(matcher, test_files())

    case result do
      {files, nil} -> do_run(files)
      {file, line} -> do_run_line(file, line)
    end
  end

  @doc """
  Run the first matching test file with a `line` selector

  Examples:
    # run first matching file for "user" on line 40
    iex> TestIex.run("user", 40)

    # same as
    iex> TestIex.run("user:40")
  """
  def run(matcher, line) do
    run("#{matcher}:#{line}")
  end

  defp do_run(files) do
    Log.log_files(files)
    core_module().start()
    core_module().test(files)
  end

  defp do_run_line(file, line) do
    Log.log_file_and_line(file, line)
    core_module().start()
    core_module().test(file, line)
  end

  ##
  ## WATCHING
  ##

  @doc """
  Set which test files / lines should run on each file change
  `TestIex.Config.test_file_globs()` defines which files would trigger a re-run
  """
  def watch(matcher \\ "") do
    cmd = fn -> TestIex.run(matcher) end
    TestIex.Watcher.set_command(cmd)
  end

  def watch(matcher, line) do
    cmd = fn -> TestIex.run("#{matcher}:#{line}") end
    TestIex.Watcher.set_command(cmd)
  end

  @doc """
  Stop watching changes on files
  """
  def unwatch() do
    TestIex.Watcher.set_command(nil)
  end

  ##
  ## Public just for introspection, internal functions
  ##
  def test_files do
    Enum.reduce(TestIex.Config.test_file_globs(), [], fn path, acc ->
      acc ++ Path.wildcard(path)
    end)
  end

  def core_module(), do: TestIex.Config.core_module()
end